我在这里看到两个问题:
第一个问题——“游戏逻辑循环”和“动画循环”的分离:
在您粘贴在问题中的代码 sn-p 中,您的游戏逻辑(“state()”调用)与渲染逻辑/动画相关联。这样游戏逻辑更新(状态)需要等待渲染,渲染需要等待游戏逻辑更新。另外,如果:“游戏逻辑更新”非常繁重,应该每 0.2 秒调用一次 - 所以不像渲染那样频繁,每秒发生约 60 次(60 FPS)?
有两个独立的循环可以帮助解决这个问题——我们称之为:“游戏逻辑循环”和“动画循环”。
function gameLogicLoop()
updateState();
setTimeout(function () { gameLogicLoop(); }, 200); // ms
}
function animationLoop() {
prepareStageFromState();
requestAnimationFrame(gameLoop);
renderer.render(stage);
}
// start both loops:
gameLogicLoop();
animationLoop();
这样,animationLoop() 每秒被调用约 60 次,gameLogicLoop() 每秒被调用 5 次(每 0.2 秒)。
当你使用这种方法时,你应该遵守以下规则:
-
gameLogicLoop() 中发生的任何事情都不应修改渲染/动画/图形。例如,您可以在这里:
- 改变玩家的金币数量
- 改变玩家在游戏地图中的位置(例如:“从 D3 块移动到 D4 块” - 而不是屏幕上的“像素 x/y”)
- 在地图等上创建新怪物。
-
animationLoop() 中发生的任何事情都不应修改游戏逻辑。例如,您可以在这里:
- 在屏幕上绘制金币计数器(您从游戏状态数据中获取金币数量)
- 动画玩家从 D3 到 D4 的移动(您可以根据游戏状态数据中的图块计算像素 x/y)
- 在地图上显示新怪物(可能带有一些动画)。
例如,请检查这个简单的脚本:https://jsfiddle.net/urvdhw8b/
- notice
gameLogicLoop 函数 - 处理游戏状态数据的更新(每 1 秒)。
- 注意
animationLoop 和 prepareStageFromState 函数 - 处理渲染 (~60 FPS)。
- 另外游戏状态数据由以下人员修改:
// Click on circle should increase number of lumberjack huts:
circle.on('click', function(event) {
lumberjackHutCount++;
});
当点击圆圈时增加伐木工人小屋数量的“点击”事件发生在两个循环的“外部” - 因为是独立的异步事件(鼠标点击)。我们也可以将其归类为游戏逻辑。
第二个问题——客户端/服务器通信:
用户按下按钮,向服务器发出请求,服务器接收它并发送一些数据作为响应,客户端接收该数据,游戏继续。但是socket.io通信是一个异步过程。
您可以使用事件(类似于上面的circle.on('click'...)和gameLogicLoop() 来处理这个问题。 animationLoop() 不应处理任何客户端/服务器通信或调用“socket.io”等中的任何内容 - 它应仅根据游戏状态数据在屏幕上呈现内容。
我们可以为上面的“lumberjack”脚本创建以下事件:
// on client side:
socket.on("lumberjack_huts_updated", (data) => {
lumberjackHutCount = data.newLumberjackHutCount;
});
// on server side:
hutCount++;
...
socket.emit("lumberjack_huts_updated", { newLumberjackHutCount: hutCount });
这样,当客户端从服务器接收到“lumberjack_huts_updated”消息时,它将更新游戏状态数据(lumberjackHutCount 变量)。这将异步发生 - 独立于 animationLoop()。
当然,这只是一个简单的示例,随着您的进步,您肯定会找到改进和优化它的新方法。例如:在客户端中,您可以考虑将来自服务器的传入消息排队(将它们存储在某个数组中),然后在gameLogicLoop() 中处理它们。
这里有一些与此主题和“多人游戏”相关的教程/文档/项目 - 这是利用客户端/服务器通信的游戏最流行的用例: