import { _decorator, CCInteger, clamp, Component, lerp, Sprite, Node, tween, Prefab, Tween, Vec3, 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'; 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: Prefab, visible: true }) private _scoreObjectPrefab: Prefab; @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 _starPool: ObjectPool; 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._starPool = 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._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._starPool.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._starPool.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) { SoundManager.instance.playSfx(this._goalSound, 3); return; } SoundManager.instance.playSfx(this._cheeseModeGoalSound, 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._starPool.get(this._scoreUI); Vec3.random(offset, 30); offset.y = 0; obj.setWorldPosition(this.node.getWorldPosition().add(offset)); this._soundFx.playSound(0.2); tween(obj) .to(randomRange(0.3, 0.4), { worldPosition: this._scoreUI.worldPosition }, { easing: 'sineIn' }) .call(() => this._starPool.release(obj)) .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 Utilities.delay(time); } await Utilities.waitUntil(() => this._starPool.countActive == 0, 0.1); this._comboComplete = true; this._soundFx.playSound(); await Utilities.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); fx.node.setWorldPosition(this.node.worldPosition); SoundManager.instance.playSfx(selectReward.sound); await Utilities.waitUntil(() => fx.isStopped, 0.1); selectReward.pool.release(fx); } } private calcPositionOnCircleLine(angle: number) { const rad = math.toRadian(angle); this._currentValuePosition.x = this._center.x + this._radius * -Math.cos(rad); this._currentValuePosition.y = this._center.y + this._radius * Math.sin(rad); } 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; } } }