Reputation: 30256
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 Player
for each socket that connects.
game.ts exports the classes Game
and Player
. The Game
is actually only a container for all important data. It stores a list of all players and a list of all gameObjects. Game
implements the method requestSpawn(...)
which checks if a new gameObject may be spawned. The class Player
is 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 the Player
instance. The Player
instance then calls requestSpawn
to try to spawn the desired GameObject
.
GameObjects.ts exports the interface GameObject
and various implementations of this interface.
The game runs like this:
Player
instance receives the message and calls requestSpawn
on its instance of Game
Game
instance creates a new GameObject
of the correct type at the correct position and adds it to the list of GameObjects
GameObject
should 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 ?
Upvotes: 0
Views: 4278
Reputation: 11284
You've posted a lot of code, and there may be other issues, but at least one is that you are missing the break
in your switch statement...
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());
}
...and therefore always creating a Robot
, which traces nothing in its update()
method. (If you try to create a Factory
, you succeed, but then immediately overwrite it by assigning a new Robot
to the same var o
).
Consider this simplified example:
interface GameObject {
update():void;
}
class Factory implements GameObject {
update():void {
console.log("Factory");
}
}
class Robot implements GameObject {
update():void {
console.log("Robot");
}
}
class Test {
private o:GameObject;
constructor(index:number){
switch(index){
case 1:
this.o = new Factory();
case 2:
this.o = new Robot();
}
this.o.update();
}
}
class BreakTest {
private o:GameObject;
constructor(index:number){
switch(index){
case 1:
this.o = new Factory();
break;
case 2:
this.o = new Robot();
break; // Not necessary in final case, but good form IMO.
}
this.o.update();
}
}
var t = new Test(1); // Traces 'Robot'
var tt = new Test(2); // Traces 'Robot'
var b = new BreakTest(1); // Traces 'Factory'
var bt = new BreakTest(2); // Traces 'Robot'
(Ref)
Upvotes: 2