GameMode.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import { Label, dragonBones, instantiate } from 'cc';
  2. import { find } from 'cc';
  3. import { _decorator, Component, Node } from 'cc';
  4. import { v3 } from 'cc';
  5. import { Charactor } from './Charactor/Charactor';
  6. import { GameEventEnum, GameSystemInputType } from './GameStruct';
  7. import { CharactorAI } from './Charactor/CharactorAI';
  8. import { JCMGO } from '../ThirdParty/JCMGO';
  9. import { BuiltinMatchInfos } from './View/GameMatchView';
  10. const { ccclass, property } = _decorator;
  11. @ccclass('GameMode')
  12. export class GameMode extends Component {
  13. protected onLoad(): void {
  14. //监听处理游戏开始
  15. window.gm.node.on(GameEventEnum.GameStart, this.handleGameStart.bind(this));
  16. //监听处理游戏结束
  17. window.gm.node.on(GameEventEnum.GameEnd, this.handleGameEnd.bind(this));
  18. //监听匹配结束
  19. window.gm.node.on(GameEventEnum.MatchEnd, () => {
  20. //显示游戏角色
  21. find("Canvas/CharactorGroup").active = true;
  22. });
  23. }
  24. protected start(): void {
  25. this.initLines();
  26. //显示匹配界面
  27. find("Canvas-001/GameMatchView").active = true;
  28. }
  29. protected lateUpdate(): void {
  30. this.updateFollowCamera();
  31. this.uploadInputs();
  32. }
  33. handleGameStart() {
  34. this.displayReadyGo();
  35. //打开游戏UI界面
  36. find("Canvas-001/GameModeView").active = true;
  37. //ai判断
  38. for (let i = 0; i < window.gm.state.aiFlags.length; i++) {
  39. if ( window.gm.state.aiFlags[i]) {
  40. this.getCharactorNode(i).addComponent(CharactorAI);
  41. }
  42. }
  43. }
  44. handleGameEnd() {
  45. //关闭游戏UI界面
  46. find("Canvas-001/GameModeView").active = false;
  47. //打开结算界面
  48. find("Canvas-001/GameSettleView").active = true;
  49. //关闭socket
  50. window.gm.socketPlayer.close();
  51. }
  52. /**初始化-起跑线/终点 */
  53. private initLines() {
  54. let startLine = find("Canvas/BandGroup/Start");
  55. let endLine = instantiate(startLine);
  56. endLine.name = "End";
  57. endLine.position = endLine.position.add(v3(window.gm.config.endPointDistance));
  58. find("Canvas/BandGroup").addChild(endLine);
  59. }
  60. /**更新相机跟随 */
  61. private updateFollowCamera() {
  62. let bgGroupNode = this.getBGGroupNode();
  63. let cameraNode = this.getCameraNode();
  64. let charactorNode = this.getCharactorNode(window.gm.state.myPlayerIndex);
  65. //相机跟随我控制的角色
  66. cameraNode.position = cameraNode.position.set(charactorNode.position.x, cameraNode.position.y)
  67. //根据相机位置,动态补充背景
  68. while (cameraNode.position.x >= bgGroupNode.children[1].position.x) {
  69. bgGroupNode.children[0].setPosition(
  70. bgGroupNode.children[0].position.add(v3(720))
  71. )
  72. bgGroupNode.children[0].setSiblingIndex(1);
  73. }
  74. }
  75. /**显示ReadyGo动画 */
  76. private displayReadyGo() {
  77. let dp = find("Canvas-001/ReadyGoDisplay").getComponent(dragonBones.ArmatureDisplay);
  78. dp.node.active = true;
  79. dp.addEventListener(dragonBones.EventObject.COMPLETE, () => {
  80. dp.node.destroy();
  81. }, this);
  82. window.gm.node.emit(GameEventEnum.ReadyGo);
  83. }
  84. //========================获取节点========================
  85. private _cameraNode: Node;
  86. private getCameraNode() {
  87. if (!this._cameraNode) this._cameraNode = find("Canvas/Camera");
  88. return this._cameraNode;
  89. }
  90. private _bgGroupNode: Node;
  91. private getBGGroupNode() {
  92. if (!this._bgGroupNode) {
  93. this._bgGroupNode = find("Canvas/BGGroup");
  94. if (this._bgGroupNode.children.length == 1) {
  95. this._bgGroupNode.addChild(instantiate(this._bgGroupNode.children[0]))
  96. }
  97. }
  98. return this._bgGroupNode;
  99. }
  100. private _charactorGroup: Node;
  101. public getCharactor(index: number) {
  102. if (!this._charactorGroup) this._charactorGroup = find("Canvas/CharactorGroup")
  103. return this._charactorGroup.children[index].getComponent(Charactor);
  104. }
  105. public getCharactorNode(index: number) {
  106. if (!this._charactorGroup) this._charactorGroup = find("Canvas/CharactorGroup")
  107. return this._charactorGroup.children[index];
  108. }
  109. //========================游戏逻辑处理(逻辑/渲染已分离)========================
  110. /**处理进入房间 */
  111. handleEnterRoom(data: JCMGO.RoomAddPlayerBst) {
  112. if (data.isSelf) window.gm.state.myPlayerIndex = data.playerIndex;
  113. }
  114. /**处理匹配完成 */
  115. handleMatchComplete(data: JCMGO.RoomEndMatchingBst) {
  116. JCMGO.ExactMath.setSeed(data.timestamp);
  117. let aiInfoIndexes = [];
  118. for (let i = 0; i < BuiltinMatchInfos.aiPlayerInfos.length; i++) aiInfoIndexes.push(i);
  119. aiInfoIndexes.sort(() => JCMGO.ExactMath.sub(0.5, JCMGO.ExactMath.random()))
  120. window.gm.state.matchPlayerInfos = new Array(window.gm.config.maxMembers);
  121. for (let i = 0; i < window.gm.state.matchPlayerInfos.length; i++) {
  122. let playerInfo = data.roomInfo.playerInfos[i];
  123. if (!playerInfo) { //这个索引位没有真人,就分配个ai
  124. playerInfo = BuiltinMatchInfos.aiPlayerInfos[aiInfoIndexes.shift()];
  125. window.gm.state.aiFlags[i] = true;
  126. } else {
  127. window.gm.state.aiFlags[i] = false;
  128. }
  129. window.gm.state.matchPlayerInfos[i] = JCMGO.ObjectUtils.merge({}, playerInfo) as MatchPlayerInfo;
  130. }
  131. window.gm.node.emit(GameEventEnum.MatchEnd);
  132. }
  133. handleFrameSyncStart() {
  134. window.gm.state.isFrameSyncStarted = true;
  135. window.gm.node.emit(GameEventEnum.GameStart);
  136. }
  137. /**上传帧输入 */
  138. public uploadInputs() {
  139. if (!window.gm.state.isFrameSyncStarted || window.gm.state.authGameSystemState.gameOver) return;
  140. //本地预测时间间隔(最终还是以服务器下发的为准)
  141. let nowTime = Date.now();
  142. if (window.gm.state.lastUploadTime === 0) window.gm.state.lastUploadTime = nowTime;
  143. //模拟本地预测帧并更新状态
  144. let moniFrame = window.gm.socketPlayer.moniServerFrame({sign: window.gm.state.lastSign.toString(), dt: nowTime - window.gm.state.lastUploadTime});
  145. window.gm.state.lastUploadTime = nowTime;
  146. window.gm.state.localMoniFrames.push(moniFrame);
  147. this.handleInputs(window.gm.state.gameSystemState, moniFrame.inputs as GameSystemInput[], moniFrame.dt);
  148. //上传帧输入到服务器
  149. window.gm.socketPlayer.uploadInputs(window.gm.state.lastSign.toString());
  150. window.gm.state.lastSign++;
  151. //清除缓存的帧输入
  152. window.gm.socketPlayer.clearInputs();
  153. }
  154. /**处理服务器下发的帧数据 */
  155. public handleRecvFrame(frame: JCMGO.Frame) {
  156. //更新权威状态
  157. this.handleInputs(window.gm.state.authGameSystemState, frame.inputs as GameSystemInput[], frame.dt);
  158. //移除已经被服务器认证的本地帧
  159. for (let i = window.gm.state.localMoniFrames.length - 1; i >= 0; i--) {
  160. let localFrame = window.gm.state.localMoniFrames[i];
  161. if (parseInt(localFrame.sign) <= parseInt(frame.sign)) {
  162. window.gm.state.localMoniFrames.splice(i, 1);
  163. }
  164. }
  165. //当前状态回滚到权威状态
  166. window.gm.state.gameSystemState = JCMGO.ObjectUtils.merge({}, window.gm.state.authGameSystemState) as GameSystemState;
  167. //预测并更新当前状态
  168. window.gm.state.localMoniFrames.forEach((frame: JCMGO.Frame) => {
  169. this.handleInputs(window.gm.state.gameSystemState, frame.inputs as GameSystemInput[], frame.dt)
  170. });
  171. // find("Canvas-001/DebugUI/Label").getComponent(Label).string = JSON.stringify(
  172. // window.gm.state.authGameSystemState.gameOver ? window.gm.state.authGameSystemState : window.gm.state.gameSystemState,
  173. // null, "\t");
  174. }
  175. /**处理帧输入 */
  176. private handleInputs(state: GameSystemState, inputs: GameSystemInput[], dt: number) {
  177. if (state.gameOver) return;
  178. //计时
  179. state.time = JCMGO.ExactMath.add(state.time, dt);
  180. //判断readygo是否完成
  181. if (!state.readyComplete) {
  182. if (state.time > 1600) {
  183. state.readyComplete = true;
  184. } else {
  185. return;
  186. }
  187. }
  188. //处理玩家输入
  189. if (inputs instanceof Array) {
  190. inputs.forEach((input: GameSystemInput) => {
  191. if (input.type === GameSystemInputType.PlayerAcc) {
  192. let ps = state.playerStates.find(v => v.index === input.playerIndex);
  193. if (ps) {
  194. ps.speedX = JCMGO.ExactMath.add(ps.speedX, window.gm.config.acc);
  195. ps.lastAccTime = state.time;
  196. ps.accCount++;
  197. }
  198. return;
  199. }
  200. });
  201. }
  202. //===========================处理游戏逻辑======================
  203. //更新玩家状态
  204. state.playerStates.forEach(ps => {
  205. //如果超过1秒没加速,就开始减速
  206. if (JCMGO.ExactMath.sub(state.time, ps.lastAccTime) > window.gm.config.decGap) {
  207. ps.speedX = JCMGO.ExactMath.add(
  208. ps.speedX,
  209. JCMGO.ExactMath.mul(window.gm.config.dec, JCMGO.ExactMath.div(dt, 1000))
  210. );
  211. if (ps.speedX < 0) ps.speedX = 0;
  212. }
  213. //更新位置
  214. ps.moveX = JCMGO.ExactMath.add(
  215. ps.moveX,
  216. JCMGO.ExactMath.mul(ps.speedX, JCMGO.ExactMath.div(dt, 1000))
  217. );
  218. });
  219. //判断是否有玩家获胜,游戏结束
  220. let winners: PlayerState[] = [];
  221. for (const ps of state.playerStates) {
  222. if (ps.moveX >= window.gm.config.endPointDistance) {
  223. winners.push(ps);
  224. }
  225. }
  226. if (winners.length > 0) {
  227. winners.sort((a, b) => b.moveX - a.moveX);
  228. state.winPlayerIndex = winners[0].index;
  229. state.gameOver = true;
  230. this.emitGameEnd();
  231. }
  232. }
  233. private _emitedGameEnd = false;
  234. private emitGameEnd() {
  235. if (this._emitedGameEnd) return;
  236. if (window.gm.state.authGameSystemState.gameOver) {
  237. this._emitedGameEnd = true;
  238. window.gm.node.emit(GameEventEnum.GameEnd);
  239. }
  240. }
  241. }