我目前的项目是一个简单的多人游戏。客户端和服务器都是用 Typescript 编写的。客户端只处理输入并渲染游戏,所有逻辑都在服务器端实现。
服务器代码使用 nodejs 执行,结构如下
main.ts 包含一个快速服务器并使用客户端脚本提供 html 文件。此外,它设置了 socket.io,创建了一个新的 Game 实例,并
Player
为每个连接的套接字创建了一个新的实例。game.ts 导出类
Game
和Player
.Game
实际上只是所有重要数据的容器。它存储所有玩家的列表和所有游戏对象的列表。Game
实现requestSpawn(...)
检查是否可以生成新游戏对象的方法。该类Player
只是 socket.io 套接字的包装器。它处理传入和传出的消息。如果客户端尝试生成 GameObject,则会将消息发送到服务器并到达存储在Player
实例中的套接字。然后Player
实例调用requestSpawn
以尝试生成所需的GameObject
.GameObjects.ts 导出接口
GameObject
和该接口的各种实现。
游戏运行如下:
- 一个人在 html-canvas 内点击
- 一个新的“REQUESTSPAWN”消息被打包并发送到服务器
Player
实例接收消息并调用其requestSpawn
实例Game
- 该
Game
实例在正确的位置创建一个GameObject
正确类型的 new 并将其添加到GameObjects
GameObject
现在应该更新这个新的。
它不是。以下是相关代码:
游戏.ts
import go = module("GameObjects");
import util = module("Utilities");
//...
export class Game {
private players: Player[];
public gameObjects:go.GameObject[];
private gameObjectCounter: number;
constructor() {
this.players = [];
this.gameObjectCounter = 0;
this.gameObjects = [];
var prev = Date.now();
var deltaTime = Date.now() - prev;
setInterval(() =>{ deltaTime = Date.now() - prev; prev = Date.now(); this.update(deltaTime); }, 200);
}
broadcast(msg: Message) {
this.players.forEach((player) => { player.send(msg); });
}
requestSpawn(msg:RequestSpawnMessage, clientID:number): bool {
var pos = new util.Vector2(msg.x, msg.y);
var o: go.GameObject;
switch (msg.tag) {
case UID.FACTORY:
o = new go.Factory(pos.clone(), this.players[clientID], this.newGameObject());
case UID.ROBOT:
o = new go.Robot(pos.clone(), this.players[clientID], this.newGameObject());
}
this.broadcast(new SpawnMessage(msg.tag, o.id, clientID, pos.x, pos.y));
this.gameObjects.push(o);
console.log(this.gameObjects);
o.update(1);
console.log("tried to update the factory");
return true;
}
update(deltaTime){
this.gameObjects.forEach((object) =>{object.update(deltaTime); });
}
addPlayer(socket: Socket) {
var player = new Player(this, socket, this.players.length);
this.players.push(player);
}
newGameObject() : number {
return this.gameObjectCounter++;
}
}
游戏对象.ts
export import util = module("Utilities");
export import s = module("server");
export import g = module("game");
export interface GameObject{
tag: g.UID;
id:number;
player: g.Player;
clientId: number;
pos:util.Vector2;
getPos():util.Vector2;
setPos(newPos:util.Vector2);
// !TODO how to make that const?
boundingBox: util.Rectangle;
update(deltaTime:number);
}
export class Factory implements GameObject {
tag: g.UID;
id: number;
player: g.Player;
clientId: number;
server: s.Server;
//variables for handling the delay between spawning robots
private current_time: number;
public delay: number;
boundingBox: util.Rectangle;
public static dimensions = new util.Vector2(30, 30);
constructor(pos: util.Vector2, player:g.Player, id: number) {
this.pos = pos;
this.tag = g.UID.FACTORY;
this.player = player;
this.clientId = this.player.getID();
this.current_time = 0;
this.delay = 1;
this.id = id;
this.boundingBox = new util.Rectangle(pos, Factory.dimensions.x, Factory.dimensions.y);
console.log("just created a factory");
//this.update(1);
}
pos: util.Vector2;
getPos() { return this.pos; }
setPos(pos: util.Vector2) { this.pos = pos; }
public once = true;
//check if it's time to create a new robot
public update(deltaTime: number) {
console.log("updating a factory");
//this code will produce a robot just once, this is handy for development, since there's not so much waiting time
if (this.once) { this.player.requestSpawn(g.UID.ROBOT, this.pos.x, this.pos.y); console.log("just spawned a robot"); }
this.once = false;
/*this.current_time += deltaTime/1000;
if (this.current_time > this.delay*(Factory.count+1)/(Mine.count+1)) {
this.current_time = 0;
this.spawnRobot();
}*/
}
}
//this will be the fighting robot meant to destroy enemy factories
export class Robot implements GameObject{
tag: g.UID;
id: number;
player:g.Player;
clientId: number;
game: g.Game;
boundingBox: util.Rectangle;
// ! TODO constants should have capital letters.
public static radius = 15;
constructor(pos:util.Vector2,player:g.Player,id:number){
this.tag = g.UID.ROBOT;
this.player=player;
this.clientId = this.player.getID();
this.boundingBox = new util.Rectangle(pos, Robot.radius, Robot.radius);
}
pos:util.Vector2;
getPos(){return this.pos;}
setPos(pos:util.Vector2){this.pos=pos;}
//now the robot is moved by keyboard input but soon it will check the gameObjects array and search for the closest enemy,
//in order to attack it
public update(deltaTime: number) {
}
}
现在第一次调用工厂实例的更新方法会产生一个机器人,然后工厂“休眠”。机器人在他们的更新方法中什么都不做。
我想引导您关注两行代码: - 在requestSpawn
方法中,GameObject
立即使用 deltaTime=1 更新
这意味着在生成工厂之后,也应该直接生成机器人。但这不会发生。我console.log
在方法中添加了一个调用requestSpawn
。它成功打印了“刚刚尝试更新工厂”,但没有任何反应。所以我认为更新方法无法正常工作并在console.log
那里添加了一个调用。这是方法的第一行,Factory's
update
应该打印“更新工厂”。但这永远不会发生。
我真的很困惑。该方法应该被调用,但它不是。虽然它被宣布为公开,但我认为这个问题可能与访问权限有关。所以这是第二行代码,我想指出: - 在工厂的构造函数中,我已经注释掉了对this.update(1)
.
我认为至少自己的构造函数应该能够调用更新方法。它的确是。当这一行没有被注释掉时,更新被调用一次。然后工厂尝试生成一个新机器人并调用requestSpawn()
其实例Game
。结果,一个新的机器人被创建SpawnMessage
出来,并提供给所有的客户。机器人甚至出现在客户端的浏览器选项卡中。
因此很明显该方法没有被调用。一切正常,消息解析、工厂更新和机器人创建都是正确的。唯一的问题是所有从内部更新的调用Game
都没有执行。什么地方出了错 ?