import { _decorator, Component, Node, Prefab, Vec2, Vec3, randomRangeInt, CCInteger, AudioClip, Quat, game, EPhysics2DDrawFlags, PhysicsSystem2D, } from 'cc'; import ObjectPool from '../Pool/ObjectPool'; import { Ball } from '../GamePlay/Ball'; import Utilities from '../Utilities'; import GameState from '../Enum/GameState'; import { EventManger } from './EventManger'; import GameEvent from '../Events/GameEvent'; import ScoreType from '../Enum/ScoreType'; import { FloatingText } from '../Environments/FloatingText'; import { SoundManager } from './SoundManager'; import TimeConfig from '../Enum/TimeConfig'; import BEConnector from '../API/BEConnector'; import BoosterType from '../Enum/BoosterType'; 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.instance.numberTicket += value; GameManager.instance.gameRelive(); } } }); ccclass('Booster'); 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 Component { //singleton private static _instance: GameManager = null; public static get instance(): GameManager { return GameManager._instance; } @property({ type: Prefab, visible: true }) private _ballPrefab: Prefab; @property({ type: Prefab, visible: true }) private _floatingScoreText: Prefab; @property({ type: Node, visible: true }) private _floatingTextContainer: Node; @property({ type: Node, visible: true }) private _ballHolder: Node; @property({ visible: true }) private _ballSpawnPosition: Vec3; @property({ type: CCInteger, visible: true }) private readonly _timePlay = 3; @property({ type: AudioClip, visible: true }) private _startSound: AudioClip; @property({ type: AudioClip, visible: true }) private _backgroundMusic: AudioClip; private _ballPool: ObjectPool; private _FloatingScorePool: ObjectPool; private _gameState: GameState; private _timer: number; @property({ type: Booster, visible: true, readonly: true }) private _boostersActive: Booster[] = []; private _score = 0; private isReplayed = false; private _isMultiBall = false; private _currentBallInGame = 0; public get score() { return this._score; } protected onLoad(): void { GameManager._instance = this; this._ballPool = new ObjectPool(this._ballPrefab, 10, true, Ball); this._FloatingScorePool = new ObjectPool(this._floatingScoreText, 10, true); // PhysicsSystem2D.instance.debugDrawFlags = EPhysics2DDrawFlags.Shape; } protected start(): void { this.changeGameState(GameState.Init); } protected update(dt: number): void { if (this._gameState != GameState.Playing) return; this._timer -= dt; if (this._timer <= 0) { this._timer = 0; this.gameOver(); } 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); EventManger.instance.emit(GameEvent.BoosterDisable, booster.type); } } EventManger.instance.emit(GameEvent.TimeUpdate, this._timer); } private async changeGameState(state: GameState) { this._gameState = state; EventManger.instance.emit(GameEvent.GameStateChange, this._gameState); switch (state) { case GameState.Init: BEConnector.instance.authenticate(); break; case GameState.Playing: BEConnector.instance.ticketMinus('auth'); break; case GameState.GameOver: break; case GameState.End: await Utilities.delay(3); BEConnector.instance.postScoreToServer(this._score); break; case GameState.Relive: BEConnector.instance.ticketMinus('revive'); 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(this._floatingTextContainer, FloatingText); floatingScore.show(`+${score}`, position, score >= 100 ? opts.scaleMax : opts.scaleMin, opts.duration); EventManger.instance.emit(GameEvent.Score, [this._score, score, type]); } private setCurrentBallInGame(value: number) { this._currentBallInGame += value; if (this._currentBallInGame >= 2) { if (!this._isMultiBall) { this._isMultiBall = true; EventManger.instance.emit(GameEvent.MultiBall, true); } } if (this._currentBallInGame <= 0) { if (this._isMultiBall) { this._isMultiBall = false; EventManger.instance.emit(GameEvent.MultiBall, false); } } } public spawnBall(throwBall: boolean): Ball { if (this._gameState != GameState.Playing) return; SoundManager.instance.playSfx(this._startSound); this.setCurrentBallInGame(1); const ball = this._ballPool.get(this._ballHolder, Ball); 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); 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(this._floatingTextContainer, FloatingText); floatingScore.show(`+${bonusTime}⏰`, position, 1.5); } } public addTime(time: number) { this._timer += time; } public gameOver() { if (this.isReplayed) { this.changeGameState(GameState.End); return; } this.isReplayed = true; this._ballPool.releaseAll(); this.changeGameState(GameState.GameOver); } public async play() { this._timer = this._timePlay + TimeConfig.DelayPLay; this._score = 0; this._currentBallInGame = 0; this._isMultiBall = false; SoundManager.instance.playBGM(this._backgroundMusic, 0.5); this.changeGameState(GameState.Playing); await Utilities.delay(TimeConfig.DelayPLay); this.spawnBall(true); } public async gameRelive() { this.changeGameState(GameState.Relive); this._timer = 60 + TimeConfig.DelayPLay; this._currentBallInGame = 0; this._isMultiBall = false; SoundManager.instance.playBGM(this._backgroundMusic, 0.5); this.changeGameState(GameState.Playing); await Utilities.delay(TimeConfig.DelayPLay); this.spawnBall(true); } public ActiveBooster(type: BoosterType, time: number) { //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); } }