My current project is a simple multiplayer game. Both client and server are written in Typescript. The client only processes the input and renders the game, all logic is implemented on the serverside.
The server code is executed with nodejs and structured like this
main.ts contains an express server and serves the html file with the client-side script. Moreover it sets up socket.io, creates a new instance of Game and creates a new instance of
Playerfor each socket that connects.game.ts exports the classes
GameandPlayer. TheGameis actually only a container for all important data. It stores a list of all players and a list of all gameObjects.Gameimplements the methodrequestSpawn(...)which checks if a new gameObject may be spawned. The classPlayeris just a wrapper for a socket.io socket. It processes incoming and outgoing messages. If the client tries to spawn a GameObject a message is sent to the server and arrives at the socket stored in thePlayerinstance. ThePlayerinstance then callsrequestSpawnto try to spawn the desiredGameObject.GameObjects.ts exports the interface
GameObjectand various implementations of this interface.
The game runs like this:
- A person clicks inside the html-canvas
- A new "REQUESTSPAWN" message is packaged and sent to the server
- A
Playerinstance receives the message and callsrequestSpawnon its instance ofGame - The
Gameinstance creates a newGameObjectof the correct type at the correct position and adds it to the list ofGameObjects - This new
GameObjectshould now be updated.
Which it isn't. Here's the relevant code:
game.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++;
}
}
GameObjects.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) {
}
}
Right now the first call of the update method of a factory instance spawns a robot, after that the factory "sleeps". Robots do nothing in their update method.
I would like to lead your eyes on two lines of the code:
- In the requestSpawn method the GameObject is updated immediately with deltaTime=1
That means that directly after spawning a factory, a robot should be spawned, too. But that doesn't happen. I added a console.log call inside the requestSpawn method. It successfully prints "just tried to update a factory" but nevertheless, nothing happens.
So I supposed that the update method was not working properly and added a console.log call there, too. It's the first line of the Factory's update method and should print "updating a factory". But that never happens.
I was really confused. The method should be called but it isn't. Although it is declared public, I thought that the problem might have something to do with access rights. So this here's the second line of code, that I would like to point out:
- in the constructor of the Factory, I've commented out a call to this.update(1).
I figured that at least the very own constructor should be able to call the update method. Indeed, it is. When this line is not commented out, update is called once. The factory then tries to spawn a new robot and calls requestSpawn() on its instance of Game. As a consequence a new robot is created and a SpawnMessage goes out to all clients. The robot even appears in the client's browser tab.
Therefore it's obvious that the method is not called. Everything works fine, the message parsing, factory updating and robot creating are correct. The only problem is that all calls to update from within Game are not executed. What went wrong ?