import { _decorator, AudioClip, CCInteger, Color, EPhysics2DDrawFlags, Node, PhysicsSystem2D, Prefab, Quat, randomRangeInt, SpriteFrame, Vec2, Vec3, } from 'cc'; import BEConnector from '../API/BEConnector'; import Timer, { TimerType } from '../Base/Timer'; import BoosterType from '../Enum/BoosterType'; import GameState from '../Enum/GameState'; import ScoreType from '../Enum/ScoreType'; import TimeConfig from '../Enum/TimeConfig'; import { FloatingText } from '../Environments/FloatingText'; import GameEvent from '../Events/GameEvent'; import { Ball } from '../GamePlay/Ball'; import ObjectPool from '../Pool/ObjectPool'; import Singleton from '../Singleton'; import Utilities from '../Utilities'; import AudioManager from './AudioManager'; import { EventManger } from './EventManger'; import { StickerManager } from './StickerManager'; const { ccclass, property } = _decorator; window.addEventListener('message', (data) => { const { data: res } = data; const objectRes = Utilities.getJson(res); if (objectRes) { const { type, value } = objectRes; if (type === 'newTicket') { BEConnector.numberTicket += value; GameManager.instance.gameRelive(); } } }); class Booster { public type: BoosterType; public time: number; public runningTime: number = 0; constructor(type: BoosterType, time: number) { this.type = type; this.time = time; } } @ccclass('GameManager') export class GameManager extends Singleton() { @property({ visible: true }) private _colliderDebug: boolean = false; @property({ type: Prefab, visible: true }) private _ballPrefab: Prefab; @property({ type: Prefab, visible: true }) private _floatingScoreText: Prefab; @property({ type: Node, visible: true }) private _topContainer: Node; @property({ type: Node, visible: true }) private _ballHolder: Node; @property({ visible: true }) private _ballSpawnPosition: Vec3; @property({ type: CCInteger, visible: true }) private readonly _timePlay = 120; @property({ type: SpriteFrame, visible: true }) private _clockIcon: SpriteFrame; @property({ type: AudioClip, visible: true }) private _boosterActiveSound: AudioClip; @property({ type: AudioClip, visible: true }) private _startSound: AudioClip; @property({ type: AudioClip, visible: true }) private _ballOutSound: AudioClip; @property({ type: AudioClip, visible: true }) private _backgroundMusic: AudioClip; @property({ type: AudioClip, visible: true }) private _gameOverMusic: AudioClip; private _ballPool: ObjectPool; private _FloatingScorePool: ObjectPool; private _gameState: GameState; private _timer: Timer = new Timer(TimerType.countDown); private _boostersActive: Booster[] = []; private _score = 0; private isReplayed = false; private _isMultiBall = false; private _warningTime = false; private _currentBallInGame = 0; private _isWaitingUpdateScore = false; public get isWaitingUpdateScore() { return this._isWaitingUpdateScore; } public get topContainer() { return this._topContainer; } public get score() { return this._score; } public get gameTime() { return this._timePlay; } public get gameState() { return this._gameState; } protected onLoad(): void { super.onLoad(); this._ballPool = new ObjectPool(this._ballPrefab, 10, true, Ball); this._FloatingScorePool = new ObjectPool(this._floatingScoreText, 10, true); BEConnector.getGameData(); if (this._colliderDebug) PhysicsSystem2D.instance.debugDrawFlags = EPhysics2DDrawFlags.Shape; } protected start(): void { this.changeGameState(GameState.Init); } protected update(dt: number): void { if (this._gameState != GameState.Playing) return; for (let i = 0; i < this._boostersActive.length; i++) { const booster = this._boostersActive[i]; booster.runningTime += dt; if (booster.runningTime >= booster.time) { this._boostersActive.splice(i, 1); if (this._boostersActive.length == 0) { AudioManager.setPlayRateBGM(1); } EventManger.instance.emit(GameEvent.BoosterDisable, booster.type); } } } private async changeGameState(state: GameState) { this._gameState = state; EventManger.instance.emit(GameEvent.GameStateChange, this._gameState); let ticket = 0; switch (state) { case GameState.Init: BEConnector.authenticate(); break; case GameState.Ready: break; case GameState.Playing: this.countTime(); ticket = await BEConnector.ticketMinus('auth'); EventManger.instance.emit(GameEvent.TicketUpdate, ticket); break; case GameState.GameOver: break; case GameState.End: break; case GameState.Relive: ticket = await BEConnector.ticketMinus('revive'); EventManger.instance.emit(GameEvent.TicketUpdate, ticket); break; default: throw new Error(`Argument Out Of Range Exception: ${GameState[state]}`); } } public addScore( score: number, type: ScoreType, position: Vec3, opts?: { scaleMin?: number; scaleMax?: number; duration?: number }, ) { this._score += score; const floatingScore = this._FloatingScorePool.get(FloatingText, this._topContainer); floatingScore.show( `+${score}`, position, score >= 100 ? opts?.scaleMax || 1 : opts?.scaleMin || 1, opts?.duration || 1, ); EventManger.instance.emit(GameEvent.Score, [this._score, score, type, position]); } public async addScoreWithWaiting( score: number, type: ScoreType, position: Vec3, predicate: () => boolean, opts: { scaleMin: number; scaleMax: number; duration: number }, ) { this._isWaitingUpdateScore = true; await Utilities.waitUntil(predicate); this._score += score; const floatingScore = this._FloatingScorePool.get(FloatingText, this._topContainer); floatingScore.show(`+${score}`, position, score >= 100 ? opts.scaleMax : opts.scaleMin, opts.duration); EventManger.instance.emit(GameEvent.Score, [this._score, score, type, position]); this._isWaitingUpdateScore = false; } private async countTime() { while (this._gameState == GameState.Playing) { if (this._timer.time <= 0) { this._timer.time = 0; this._timer.stopCount(); this.gameOver(); } if (!this._warningTime && this._timer.time <= 10) { this._warningTime = true; EventManger.instance.emit(GameEvent.WarningTime, true); } EventManger.instance.emit(GameEvent.TimeUpdate, this._timer.timeRound); await Utilities.delay(1); } } private setCurrentBallInGame(value: number) { this._currentBallInGame += value; if (value > 0 && this._currentBallInGame >= 2) { this._isMultiBall = true; EventManger.instance.emit(GameEvent.MultiBall, true); this._ballPool.listActive.forEach((ball) => ball.getComponent(Ball).playMultiBallEffect()); } if (this._currentBallInGame <= 0) { if (this._isMultiBall) { this._isMultiBall = false; EventManger.instance.emit(GameEvent.MultiBall, false); } } } public spawnBall(throwBall: boolean, playStartSound: boolean = true): Ball { if (this._gameState != GameState.Playing) return; if (playStartSound) AudioManager.playSfx(this._startSound); this.setCurrentBallInGame(1); const ball = this._ballPool.get(Ball, this._ballHolder); ball.init(this._boostersActive.length > 0); ball.node.setRotation(Quat.IDENTITY); ball.node.setPosition(this._ballSpawnPosition); if (!throwBall) return ball; let dir = randomRangeInt(-1, 2); while (dir == 0) { dir = randomRangeInt(-1, 2); } const force = new Vec2(dir, 1); ball.throwBall(force.multiply2f(1, 40)); return ball; } public async ballOut() { this.setCurrentBallInGame(-1); if (this._currentBallInGame <= 0) { EventManger.instance.emit(GameEvent.BallOut, null); AudioManager.playSfx(this._ballOutSound); this.DisableAllBooster(); await Utilities.delay(TimeConfig.DelayPLay); this.spawnBall(true); } } public async goal(bonusScore: number, position: Vec3) { this.addScore(this._isMultiBall ? bonusScore * 2 : bonusScore, ScoreType.Goal, position, { scaleMin: 2, scaleMax: 3, duration: 1, }); this.setCurrentBallInGame(-1); if (this._currentBallInGame <= 0) { await Utilities.delay(TimeConfig.DelayGoal); this.spawnBall(true); } } public async destroyEnvironmentObject(bonusScore: number, position: Vec3, bonusTime?: number) { if (bonusScore) { this.addScore(bonusScore, ScoreType.DestroyObject, position, { scaleMin: 1.5, scaleMax: 2, duration: 0.7, }); await Utilities.delay(0.3); } if (bonusTime) { this.addTime(bonusTime); const floatingScore = this._FloatingScorePool.get(FloatingText, this._topContainer); floatingScore.show(`+${bonusTime}`, position, 1.5, 1, this._clockIcon); } } public addTime(time: number) { this._timer.time += time; if (this._warningTime && this._timer.time > 10) { this._warningTime = false; EventManger.instance.emit(GameEvent.WarningTime, false); } EventManger.instance.emit(GameEvent.TimeUpdate, this._timer.timeRound); } public async gameOver() { this._ballPool.releaseAll(); this.DisableAllBooster(); AudioManager.playBGM(this._gameOverMusic); StickerManager.instance.showLabel('TIME UP!!!', { color: new Color('#ed3a18'), outLineColor: Color.WHITE }); BEConnector.gameScore = this.score; if (this.isReplayed) { this.changeGameState(GameState.End); return; } this.isReplayed = true; this.changeGameState(GameState.GameOver); } public Ready() { AudioManager.playBGM(this._backgroundMusic); this.changeGameState(GameState.Ready); } public async play() { this._timer.time = this._timePlay; this._score = 0; this._currentBallInGame = 0; this._isMultiBall = false; this.changeGameState(GameState.Playing); await Utilities.delay(TimeConfig.DelayPLay); this._timer.startCount(); this.spawnBall(true); } public async gameRelive() { this.changeGameState(GameState.Relive); this._timer.time = this._timePlay; this._currentBallInGame = 0; this._isMultiBall = false; AudioManager.playBGM(this._backgroundMusic); this.changeGameState(GameState.Playing); await Utilities.delay(TimeConfig.DelayPLay); this._timer.startCount(); this.spawnBall(true); } private DisableAllBooster() { for (let i = 0; i < this._boostersActive.length; i++) { const booster = this._boostersActive[i]; EventManger.instance.emit(GameEvent.BoosterDisable, booster.type); } this._boostersActive = []; AudioManager.setPlayRateBGM(1); } public async ActiveBooster(type: BoosterType, time: number, displayName: string) { //check booster already active for (let i = 0; i < this._boostersActive.length; i++) { const booster = this._boostersActive[i]; if (booster.type == type) return; } this._boostersActive.push(new Booster(type, time)); EventManger.instance.emit(GameEvent.BoosterActive, [type, displayName]); AudioManager.playSfx(this._boosterActiveSound); AudioManager.setPlayRateBGM(1.25); } }