import { _decorator, AudioClip, CCFloat, CCInteger, clamp, clamp01, Component, lerp, Node, ParticleSystem, Prefab, randomRange, Sprite, tween, Tween, Vec3, } from 'cc'; import BoosterType from '../Enum/BoosterType'; import ScoreType from '../Enum/ScoreType'; import GameEvent from '../Events/GameEvent'; import SpriteFloatingFactory from '../Factory/SpriteFloatingFactory'; import AudioManager from '../Manager/AudioManager'; import { EventManger } from '../Manager/EventManger'; import { GameManager } from '../Manager/GameManager'; import ObjectPool from '../Pool/ObjectPool'; import Utils from '../Utilities'; import { SequenceSound } from './SequenceSound'; const { ccclass, property } = _decorator; @ccclass('Reward') class Reward { @property(CCInteger) public scoreMin: number = 0; @property(CCInteger) public scoreMax: number = 0; @property(AudioClip) public sound: AudioClip; @property(Prefab) public rewardEffect: Prefab; public pool: ObjectPool; Init() { this.pool = new ObjectPool(this.rewardEffect, 5, true); } } @ccclass('CumulativeBar') export class CumulativeBar extends Component { @property({ type: Sprite, visible: true }) private _fillBar: Sprite; @property({ type: CCInteger, visible: true }) private _maxValue = 1000; @property({ type: Node, visible: true }) private _scoreUI: Node; @property({ type: Prefab, visible: true }) private _starFxObjectPrefab: Prefab; @property({ type: SpriteFloatingFactory, visible: true }) private _floatingStartFactory: SpriteFloatingFactory; @property({ visible: true }) private _centerOffset: Vec3 = new Vec3(); @property({ type: CCFloat, visible: true }) private _radius: number = 0; @property({ type: CCFloat, visible: true }) private _minAngle: number = 0; @property({ type: CCFloat, visible: true }) private _maxAngle: number = 0; @property({ type: SequenceSound, visible: true }) private _soundFx: SequenceSound; @property({ type: AudioClip, visible: true }) private _goalSound: AudioClip; @property({ type: AudioClip, visible: true }) private _cheeseModeGoalSound: AudioClip; @property({ type: AudioClip, visible: true }) private _collectStartSoundFx; @property({ type: Reward, visible: true }) private _rewards: Reward[] = []; private _fxPool: ObjectPool; private _currentValue = 0; private _fillValue = 0; private _active = false; private _goal = false; private _timer = 0; private _multiplier = 1; private _comboComplete = false; private _currentValuePosition = new Vec3(); private _center = new Vec3(); protected onLoad(): void { this._fillBar.fillRange = 0; this._fxPool = new ObjectPool(this._starFxObjectPrefab, 50, true); EventManger.instance.on(GameEvent.Score, this.onScore, this); EventManger.instance.on(GameEvent.BoosterActive, this.onBoosterActive, this); EventManger.instance.on(GameEvent.BoosterDisable, this.onBoosterDisable, this); this._center = this._fillBar.node.getWorldPosition(); this._center.add(this._centerOffset); this.calcPositionOnCircleLine(this._minAngle); this._rewards.forEach((reward) => reward.Init()); } protected update(dt: number): void { if (!this._goal && !this._active && this._currentValue > 0) { this._timer += dt; if (this._timer >= 0.1) { this._timer = 0; this._currentValue -= 2; if (this._currentValue < 0) { this._currentValue = 0; } this._fillValue = -clamp(this._currentValue / 2 / this._maxValue, 0, 0.5); } } if (Math.abs(this._fillValue - this._fillBar.fillRange) >= 0.001) { this._fillBar.fillRange = lerp(this._fillBar.fillRange, this._fillValue, dt * 3); const progress = clamp01(this._fillBar.fillRange / -0.5); const angle = lerp(this._minAngle, this._maxAngle, progress); this.calcPositionOnCircleLine(angle); } } private async onScore(score: number, points: number, type: ScoreType, position: Vec3) { switch (type) { case ScoreType.DestroyObject: if (!this._active) return; const star = this._floatingStartFactory.create(GameManager.instance.topContainer); star.node.setWorldPosition(position); tween(star.node) .to( 1, { worldPosition: this._currentValuePosition }, { onUpdate: (target: Node, ratio: number) => { target.worldPosition = target.worldPosition.lerp(this._currentValuePosition, ratio); }, }, ) .call(async () => { const fx = this._fxPool.get(ParticleSystem, GameManager.instance.topContainer); const pos = star.node.getWorldPosition(); pos.z = 10; fx.node.setWorldPosition(pos); star.node.releaseToPool(); AudioManager.playSfx(this._collectStartSoundFx); await Utils.waitUntil(() => { return fx.isStopped; }, 0.1); this._fxPool.release(fx); }) .start(); this._multiplier++; this._currentValue += points * this._multiplier; if (this._currentValue > this._maxValue) this._currentValue = this._maxValue; break; case ScoreType.Goal: if (this._currentValue == 0) { AudioManager.playSfx(this._goalSound, { volume: 3 }); return; } AudioManager.playSfx(this._cheeseModeGoalSound, { volume: 3 }); this._multiplier = 0; this._goal = true; let items = Math.ceil(this._currentValue / 10); this.playCollectEffect(items, this._currentValue); this._goal = false; this._currentValue = 0; break; } this._fillValue = -clamp(this._currentValue / 2 / this._maxValue, 0, 0.5); } private async playCollectEffect(items: number, score: number) { const time = 0.04; const offset = new Vec3(); this._comboComplete = false; GameManager.instance.addScoreWithWaiting( Math.round(score), ScoreType.Combo, this.node.getWorldPosition(), () => this._comboComplete, { scaleMin: 2, scaleMax: 4, duration: 0.8, }, ); while (items > 0) { items--; const obj = this._floatingStartFactory.create(GameManager.instance.topContainer); Vec3.random(offset, 30); offset.y = 0; obj.node.setWorldPosition(this.node.getWorldPosition().add(offset)); this._soundFx.playSound(0.2); tween(obj.node) .to(randomRange(0.3, 0.4), { worldPosition: this._scoreUI.worldPosition }, { easing: 'sineIn' }) .call(() => obj.node.releaseToPool()) .call(() => { Tween.stopAllByTarget(this._scoreUI); tween(this._scoreUI) .set({ scale: Vec3.ONE }) .to(0.1, { scale: new Vec3(1.3, 1.3, 1.3) }) .to(0.1, { scale: Vec3.ONE }) .start(); }) .start(); await Utils.delay(time); } await Utils.waitUntil(() => this._floatingStartFactory.countActive == 0, 0.1); this._comboComplete = true; this._soundFx.playSound(); await Utils.delay(0.8); this.calcReward(score); } private async calcReward(score: number) { let selectReward: Reward; for (let i = 0; i < this._rewards.length; i++) { const reward = this._rewards[i]; if (score >= reward.scoreMin && score <= reward.scoreMax) { selectReward = reward; break; } } if (selectReward) { const fx = selectReward.pool.get(ParticleSystem, GameManager.instance.topContainer); const pos = this.node.getWorldPosition(); pos.z = 10; fx.node.setWorldPosition(pos); AudioManager.playSfx(selectReward.sound); await Utils.waitUntil(() => fx.isStopped, 0.1); selectReward.pool.release(fx); } } private calcPositionOnCircleLine(angle: number) { this._currentValuePosition = this._center.getPositionOnCircle(angle, this._radius); } private onBoosterActive(type: BoosterType) { if (type == BoosterType.CumulativeBar) this._active = true; } private onBoosterDisable(type: BoosterType) { if (type == BoosterType.CumulativeBar) { this._multiplier = 0; this._active = false; } } }