import { _decorator, AudioClip, CCInteger, Color, EPhysics2DDrawFlags, Node, PhysicsSystem2D, Quat, randomRangeInt, SpriteFrame, tween, 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 GameEvent from '../Events/GameEvent'; import BallFactory from '../Factory/BallFactory'; import FloatingTextFactory from '../Factory/FloatingTextFactory'; import { Ball } from '../GamePlay/Ball'; import Singleton from '../Singleton'; import Utils 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 = Utils.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: FloatingTextFactory, visible: true }) private _floatingScoreFactory: FloatingTextFactory; @property({ type: Node, visible: true }) private _topContainer: Node; @property({ type: Node, visible: true }) private _ballHolder: Node; @property({ visible: true }) private _ballSpawnPosition: Vec3 = new Vec3(); @property({ type: CCInteger, visible: true }) private readonly _timePlay = 120; @property({ type: SpriteFrame, visible: true }) private _clockIcon: SpriteFrame; @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 _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(); BEConnector.getGameData(); if (this._colliderDebug) PhysicsSystem2D.instance.debugDrawFlags = EPhysics2DDrawFlags.Shape; } protected start(): void { this.changeGameState(GameState.Init); } protected update(dt: number): void { this._timer.update(dt); 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._floatingScoreFactory.create(this._topContainer); if (position) { 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 Utils.waitUntil(predicate); this._score += score; const floatingScore = this._floatingScoreFactory.create(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 Utils.delay(1); } } private setCurrentBallInGame(value: number) { this._currentBallInGame += value; if (value > 0 && this._currentBallInGame >= 2) { this._isMultiBall = true; EventManger.instance.emit(GameEvent.MultiBall, true); BallFactory.instance.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 = BallFactory.instance.create(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 Utils.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 Utils.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 Utils.delay(0.3); } if (bonusTime) { this.addTime(bonusTime, position); } } public addTime(time: number, position?: Vec3) { this._timer.time += time; if (this._warningTime && this._timer.time > 10) { this._warningTime = false; EventManger.instance.emit(GameEvent.WarningTime, false); } if (position) { const floatingScore = this._floatingScoreFactory.create(this._topContainer); floatingScore.show(`+${time}`, position, 1.5, 1, this._clockIcon); } EventManger.instance.emit(GameEvent.TimeUpdate, this._timer.timeRound); } public async gameOver() { BallFactory.instance.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 Utils.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 Utils.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.setPlayRateBGM(1.25); } }