diff --git a/examples/cocos-creator-airplane/backend/src/index.ts b/examples/cocos-creator-airplane/backend/src/index.ts index b7c765f..2199909 100644 --- a/examples/cocos-creator-airplane/backend/src/index.ts +++ b/examples/cocos-creator-airplane/backend/src/index.ts @@ -1,3 +1,4 @@ +import 'k8w-extend-native'; import * as path from "path"; import { WsServer } from "tsrpc"; import { serviceProto } from './shared/protocols/serviceProto'; diff --git a/examples/cocos-creator-airplane/backend/src/shared/game/GameSystem.ts b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystem.ts new file mode 100644 index 0000000..dda311f --- /dev/null +++ b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystem.ts @@ -0,0 +1,153 @@ +import { gameConfig } from "./gameConfig"; +import { GameSystemEvent } from "./GameSystemEvent"; +import { GameSystemInput } from "./GameSystemInput"; +import { GameSystemState } from "./GameSystemState"; + +/** + * 前后端复用的状态计算模块 + */ +export class GameSystem { + + // 当前状态 + state: GameSystemState; + + constructor(state: GameSystemState) { + this.state = state; + } + + // 应用输入,计算状态变更 + applyInput(input: GameSystemInput) { + switch (input.type) { + // 击中敌机 + case 'PlayerHitEnemy': { + let enemyIndex = this.state.enemies.findIndex(v => v.id === input.enemyId); + let player = this.state.players.find(v => v.id === input.playerId); + if (player && enemyIndex > -1) { + this.state.enemies.splice(enemyIndex, 1); + // 得 1 分 + player.score += 1; + this.emit('enemyDie', { enemyId: input.enemyId }) + } + break; + } + // 吃到子弹道具 + case 'PlayerHitFightIcon': { + let fightIconIndex = this.state.fightIcons.findIndex(v => v.id === input.fightIconId); + let player = this.state.players.find(v => v.id === input.playerId); + if (player && fightIconIndex > -1) { + let fightIcon = this.state.fightIcons[fightIconIndex]; + this.state.fightIcons.splice(fightIconIndex, 1); + player.currentBulletType = fightIcon.type; + } + break; + } + // 玩家受到伤害 + case 'PlayerHurt': { + let player = this.state.players.find(v => v.id === input.playerId); + if (player) { + player.life = Math.max(0, player.life - input.hurtLife); + if (player.life === 0) { + this.state.players.removeOne(v => v.id === input.playerId); + this.emit('playerDie', { playerId: player.id }) + } + } + break; + } + // 移动并攻击 + case 'PlayerMove': { + let player = this.state.players.find(v => v.id === input.playerId); + if (player) { + // Update Pos + player.pos.x += input.offset.x; + player.pos.y += input.offset.y; + // Update Bullets + player.bullets.push(...input.createBullets); + } + break; + } + // 子弹碰撞,抵消 + case 'BulletHit': { + let player = this.state.players.find(v => v.id === input.player.id); + let enemy = this.state.enemies.find(v => v.id === input.enemy.id); + if (player && enemy) { + player.bullets.removeOne(v => v.id === input.player.bulletId); + enemy.bullets.removeOne(v => v.id === input.enemy.bulletId); + } + break; + } + // 时间流逝 + case 'TimePast': { + // 更新己方子弹位置 + this.state.players.forEach(player => { + player.bullets.forEach(bullet => { + bullet.pos.x = bullet.init.pos.x + bullet.init.direction.x * gameConfig.player.bulletSpeed * (this.state.now - bullet.init.time); + bullet.pos.y = bullet.init.pos.y + bullet.init.direction.y * gameConfig.player.bulletSpeed * (this.state.now - bullet.init.time); + }) + }) + + // 更新敌机 + this.state.enemies.forEach(enemy => { + // 更新敌机位置 + const enemyTime = this.state.now - enemy.init.time; + enemy.pos.y = enemy.init.pos.y - gameConfig.enemy.speed * enemyTime; + + // 更新敌机子弹位置 + enemy.bullets.forEach(bullet => { + bullet.pos.y = bullet.init.pos.y - gameConfig.enemy.bulletSpeed * (this.state.now - bullet.init.time); + }) + + // 敌机发射子弹 + if ((this.state.now - enemy.lastBulletTime) >= gameConfig.enemy.bulletGapTime) { + enemy.bullets.push({ + id: enemy.nextId.bullet++, + pos: { ...enemy.pos }, + init: { + time: this.state.now, + pos: { ...enemy.pos }, + } + }) + enemy.lastBulletTime = this.state.now; + } + }); + + // 创建新敌机 + this._createEnemies(); + + break; + } + } + } + + private _createEnemies() { + // 前 10 秒 + // 每 1 秒创建一个敌机 + + // 10~20秒 + // 每3秒一个组合,然后每秒一个敌机 + + // 20秒以后 + // 每 2 秒一个组合(组合1 / 组合2 / xxx) + } + + // 简易的事件侦听器 + private _eventHandlers: { [key: string]: Function[] } = {}; + on(eventName: T, handler: (e: GameSystemEvent[T]) => void): (e: GameSystemEvent[T]) => void { + if (!this._eventHandlers[eventName]) { + this._eventHandlers[eventName] = []; + } + this._eventHandlers[eventName].push(handler); + return handler; + } + off(eventName: T, handler?: (e: GameSystemEvent[T]) => void): void { + if (!handler) { + this._eventHandlers[eventName] = []; + return; + } + + this._eventHandlers[eventName]?.removeOne(v => v === handler); + } + emit(eventName: T, eventData: GameSystemEvent[T]) { + this._eventHandlers[eventName]?.forEach(v => v(eventData)); + } + +} \ No newline at end of file diff --git a/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemEvent.ts b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemEvent.ts new file mode 100644 index 0000000..eb76ec4 --- /dev/null +++ b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemEvent.ts @@ -0,0 +1,10 @@ +import { uint } from "tsrpc"; + +export interface GameSystemEvent { + playerDie: { + playerId: uint + }, + enemyDie: { + enemyId: uint + } +} \ No newline at end of file diff --git a/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemInput.ts b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemInput.ts new file mode 100644 index 0000000..c6fd08f --- /dev/null +++ b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemInput.ts @@ -0,0 +1,54 @@ +import { uint } from "tsrpc"; +import { PlayerState } from "./GameSystemState"; + +// 移动并攻击 +export interface PlayerMove { + type: 'PlayerMove', + playerId: uint, + offset: { x: number, y: number }, + createBullets: PlayerState['bullets'] +} + +// 命中敌机 +export interface PlayerHitEnemy { + type: 'PlayerHitEnemy', + playerId: uint, + enemyId: uint +} + +// 吃到 FightIcon +export interface PlayerHitFightIcon { + type: 'PlayerHitFightIcon', + playerId: uint, + fightIconId: uint +} + +// 被敌机击中 +export interface PlayerHurt { + type: 'PlayerHurt', + playerId: uint, + // 伤害血量 + hurtLife: number, +} + +// 子弹互相碰撞,双双消失 +export interface BulletHit { + type: 'BulletHit', + player: { + id: uint, + bulletId: uint + }, + enemy: { + id: uint, + bulletId: uint + }, +} + +// 时间流逝 +export interface TimePast { + type: 'TimePast', + dt: number +} + +// 输入定义 +export type GameSystemInput = PlayerMove | PlayerHitEnemy | PlayerHitFightIcon | PlayerHurt | BulletHit | TimePast; \ No newline at end of file diff --git a/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemState.ts b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemState.ts new file mode 100644 index 0000000..a8783a9 --- /dev/null +++ b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemState.ts @@ -0,0 +1,87 @@ +import { uint } from "tsrpc"; + +// 坐标系:X-Y 原点在左下角 + +export interface GameSystemState { + // 游戏时间 + now: number, + + players: PlayerState[], + enemies: EnemyState[], + fightIcons: FightIconState[], + + // ID 生成器 + nextId: { + enemy: uint, + fightIcon: uint + }, + // 上次创建敌机的时间 + lastCreateEnemyTime: number, + randomSeed: number, +} + +// 玩家 +export interface PlayerState { + id: uint, + // 得分 + score: number, + // 生命值 + life: number, + // 当前子弹 + currentBulletType: PlayerBulletType, + // 位置 + pos: { x: number, y: number }, + bullets: { + id: uint, + type: PlayerBulletType, + pos: { x: number, y: number }, + // 初始状态 + init: { + time: number, + pos: { x: number, y: number }, + direction: { x: number, y: number } + } + }[], + nextId: { + bullet: uint + } +} + +export type PlayerBulletType = 'M' | 'H' | 'S'; + +// 敌机 +export interface EnemyState { + id: uint, + type: uint, + pos: { x: number, y: number }, + init: { + time: number, + pos: { x: number, y: number }, + speed: { y: number } + } + bullets: { + id: uint, + pos: { x: number, y: number }, + // 初始状态 + init: { + time: number, + // 目前只能向下飞 + pos: { x: number, y: number }, + } + }[], + // 上次发射子弹的时间 + lastBulletTime: number, + nextId: { + bullet: uint + } +} + +// 子弹图标 +export interface FightIconState { + id: uint, + type: PlayerBulletType, + init: { + time: number, + pos: { x: number, y: number } + } +} \ No newline at end of file diff --git a/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemUtil.ts b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemUtil.ts new file mode 100644 index 0000000..48f9e31 --- /dev/null +++ b/examples/cocos-creator-airplane/backend/src/shared/game/GameSystemUtil.ts @@ -0,0 +1,7 @@ +export class GameSystemUtil { + + static getPlayerBulletPos() { } + + static getEnemyBulletPos() { } + +} \ No newline at end of file diff --git a/examples/cocos-creator-airplane/backend/src/shared/game/gameConfig.ts b/examples/cocos-creator-airplane/backend/src/shared/game/gameConfig.ts new file mode 100644 index 0000000..48d8801 --- /dev/null +++ b/examples/cocos-creator-airplane/backend/src/shared/game/gameConfig.ts @@ -0,0 +1,16 @@ +export const gameConfig = { + syncRate: 10, + + enemy: { + speed: 10, + bulletSpeed: 20, + // 第一次发射子弹的延迟时间 + bulletDelayTime: 1500, + // 每次发射子弹的间隔 + bulletGapTime: 500 + }, + + player: { + bulletSpeed: 10, + }, +} \ No newline at end of file diff --git a/examples/cocos-creator-airplane/frontend/assets/res/model/plane01/plane01.mtl b/examples/cocos-creator-airplane/frontend/assets/res/model/plane01/plane01.mtl index 1f4962c..1a4e6b4 100644 --- a/examples/cocos-creator-airplane/frontend/assets/res/model/plane01/plane01.mtl +++ b/examples/cocos-creator-airplane/frontend/assets/res/model/plane01/plane01.mtl @@ -4,7 +4,8 @@ "_objFlags": 0, "_native": "", "_effectAsset": { - "__uuid__": "a3cd009f-0ab0-420d-9278-b9fdab939bbc" + "__uuid__": "a3cd009f-0ab0-420d-9278-b9fdab939bbc", + "__expectedType__": "cc.EffectAsset" }, "_techIdx": 0, "_defines": [ @@ -26,7 +27,8 @@ "_props": [ { "mainTexture": { - "__uuid__": "e163c875-c8dd-4512-b96a-e6fd360a1b99@6c48a" + "__uuid__": "e163c875-c8dd-4512-b96a-e6fd360a1b99@6c48a", + "__expectedType__": "cc.Texture2D" } } ]