私の現在のプロジェクトはシンプルなマルチプレイヤーゲームです。クライアントとサーバーの両方がTypescriptで書かれています。クライアントは入力を処理してゲームをレンダリングするだけで、すべてのロジックはサーバー側に実装されます。
サーバーコードはnodejsで実行され、次のように構成されます
main.tsにはエクスプレスサーバーが含まれており、クライアント側のスクリプトでhtmlファイルを提供します。さらに、socket.ioをセットアップし、Gameの新しいインスタンスを作成し、
Player
接続するソケットごとにの新しいインスタンスを作成します。game.tsはクラス
Game
とをエクスポートしますPlayer
。これGame
は、実際にはすべての重要なデータの単なるコンテナです。すべてのプレーヤーのリストとすべてのgameObjectのリストが格納されます。新しいgameObjectが生成される可能性があるかどうかをチェックGame
するメソッドを実装します。requestSpawn(...)
このクラスPlayer
は、socket.ioソケットの単なるラッパーです。着信メッセージと発信メッセージを処理します。クライアントがGameObjectを生成しようとすると、メッセージがサーバーに送信され、Player
インスタンスに格納されているソケットに到着します。次に、Player
インスタンスは呼び出しrequestSpawn
て、目的のを生成しようとしますGameObject
。GameObjects.tsは、インターフェイス
GameObject
とこのインターフェイスのさまざまな実装をエクスポートします。
ゲームは次のように実行されます。
- 人がhtml-canvas内をクリックします
- 新しい「REQUESTSPAWN」メッセージがパッケージ化され、サーバーに送信されます
Player
インスタンスはメッセージを受信し、そのインスタンスを呼び出しますrequestSpawn
。Game
Game
インスタンスはGameObject
、正しいタイプの新しいものを正しい位置に作成し、それを次のリストに追加します。GameObjects
- この新しい
GameObject
ものを更新する必要があります。
そうではありません。関連するコードは次のとおりです。
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) {
}
}
現在、ファクトリインスタンスのupdateメソッドの最初の呼び出しでロボットが生成され、その後、ファクトリが「スリープ」します。ロボットは更新方法で何もしません。
コードの2行に注目したいと思います。-requestSpawn
メソッドでGameObject
は、はdeltaTime=1ですぐに更新されます。
つまり、ファクトリをスポーンした直後に、ロボットもスポーンする必要があります。しかし、それは起こりません。メソッドconsole.log
内に呼び出しを追加しました。requestSpawn
「ファクトリを更新しようとしました」と正常に印刷されますが、それでも何も起こりません。そのため、updateメソッドが正しく機能していないと想定し、console.log
そこにも呼び出しを追加しました。これはメソッドの最初の行であり、Factory's
update
「ファクトリの更新」と出力する必要があります。しかし、それは決して起こりません。
本当に戸惑いました。メソッドを呼び出す必要がありますが、そうではありません。公開宣言ですが、アクセス権に関係があるのではないかと思いました。これが2行目のコードです。指摘したいのは次のとおりです。-Factoryのコンストラクターで、への呼び出しをコメントアウトしましたthis.update(1)
。
少なくとも、非常に独自のコンストラクターがupdateメソッドを呼び出すことができるはずだと思いました。確かにそうです。この行がコメントアウトされていない場合、updateが1回呼び出されます。次に、ファクトリは新しいロボットを生成しようとしrequestSpawn()
、そのインスタンスを呼び出しますGame
。その結果、新しいロボットが作成されSpawnMessage
、すべてのクライアントに送信されます。ロボットはクライアントのブラウザタブにも表示されます。
したがって、メソッドが呼び出されないことは明らかです。すべてが正常に機能し、メッセージの解析、ファクトリの更新、ロボットの作成は正しく行われます。唯一の問題は、内部から更新するためのすべての呼び出しがGame
実行されないことです。何が悪かったのか ?