import { _decorator, CCInteger, clamp, Component, lerp, Sprite, Node, tween, Prefab, Tween, Vec3, Label, AudioClip, randomRange, math, CCFloat, clamp01, ParticleSystem, } from 'cc'; import { EventManger } from '../Manager/EventManger'; import GameEvent from '../Events/GameEvent'; import ScoreType from '../Enum/ScoreType'; import Utilities from '../Utilities'; import { GameManager } from '../Manager/GameManager'; import BoosterType from '../Enum/BoosterType'; import ObjectPool from '../Pool/ObjectPool'; import { SoundManager } from '../Manager/SoundManager'; const { ccclass, property } = _decorator; @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: Prefab, visible: true }) private _scoreObjectPrefab: Prefab; @property({ type: CCFloat, visible: true }) private _radius: number = 0; @property({ type: AudioClip, visible: true }) private _soundFx; @property({ type: AudioClip, visible: true }) private _collectStartSoundFx; private _pool: ObjectPool; private _fxPool: ObjectPool; private _currentValue = 0; private _fillValue = 0; private _active = false; private _goal = false; private _timer = 0; private _multiplier = 1; private _currentValuePosition = new Vec3(); private _center = new Vec3(); protected onLoad(): void { this._fillBar.fillRange = 0; this._pool = new ObjectPool(this._scoreObjectPrefab, 50, true); 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.calcPositionOnCircleLine(0); } 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(0, 180, 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._pool.get(this.node); star.setWorldPosition(position); tween(star) .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, this.node); fx.node.setWorldPosition(star.worldPosition); this._pool.release(star); SoundManager.instance.playSfx(this._collectStartSoundFx); await Utilities.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) return; this._multiplier = 0; this._goal = true; await Utilities.delay(1); GameManager.instance.addScore( Math.round(this._currentValue), ScoreType.Combo, this.node.getWorldPosition(), { scaleMin: 2, scaleMax: 4, duration: 1, }, ); let items = Math.ceil(this._currentValue / 10); this.playCollectEffect(items); this._goal = false; this._currentValue = 0; break; } this._fillValue = -clamp(this._currentValue / 2 / this._maxValue, 0, 0.5); } private async playCollectEffect(items: number) { const time = 0.04; const offset = new Vec3(); while (items > 0) { const obj = this._pool.get(this._scoreUI); Vec3.random(offset, 30); offset.y = 0; obj.setWorldPosition(this.node.getWorldPosition().add(offset)); tween(obj) .to(randomRange(0.3, 0.4), { worldPosition: this._scoreUI.worldPosition }, { easing: 'sineIn' }) .call(() => { Tween.stopAllByTarget(this._scoreUI); tween(this._scoreUI) .set({ scale: Vec3.ONE }) .to(0.1, { scale: new Vec3(1.2, 1.2, 1.2) }) .set({ scale: Vec3.ONE }) .start(); }) .call(() => this._pool.release(obj)) .start(); items--; SoundManager.instance.playSfx(this._soundFx, 0.5); await Utilities.delay(time); } } private calcPositionOnCircleLine(angle: number) { this._currentValuePosition.x = this._center.x + this._radius * -Math.cos(math.toRadian(angle)); this._currentValuePosition.y = this._center.y + this._radius * Math.sin(math.toRadian(angle)); } 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; } } }