我正在尝试运行一个在服务器上运行物理模拟的服务器,并让客户端通过 websockets/socket.io 连接到该服务器。我知道我可以使用 Matter.js 与渲染分开计算引擎。所以我的问题是,如何将引擎数据发送给客户端?
我有一个
Game
类,在套接字连接上,我想将 World
发送给客户端进行渲染。
var g = new Game()
g.initEngine()
io.sockets.on('connection', function(socket) {
io.emit('gamestate', g.getGameState())
});
在游戏状态下,我不太确定要传递什么:
var Game = function(Player1, Player2) {
var self = this
this.gameID = 22
this.engine = null
this.world = null
// Get game state - WHAT DO I SEND HERE!??
this.getGameState = function() {
return self.engine
}
// Create the engine
this.initEngine = function() {
self.engine = M.Engine.create();
self.world = self.engine.world;
self.world.gravity.x = 0;
self.world.gravity.y = 0;
M.Engine.update(self.engine, 122000 / 60);
}
}
当我尝试通过
self
、或 self.engine
、或 self.world
时,我只是让应用程序崩溃。它说超出了最大调用堆栈大小。
我需要从
self.engine
中提取哪些数据才能通过 WebSockets 整齐地发送给客户端?
我知道我需要尸体的位置数据。但即使我尝试
return self.engine.world.bodies;
然后它也会崩溃。
如何在不崩溃或超出堆栈大小的情况下将引擎/世界简单地提供给客户端?
如果服务器负责运行 Matter.js 引擎,则将 Matter.js 对象从服务器发送到客户端可能是不必要的,而且成本高昂。
一种可能的设计是服务器发出客户端在每次更新时渲染游戏状态所需的最少量的序列化信息。这是特定于应用程序的,但可能归结为从您的 mjs 身体中选取顶点并告知客户玩家位置、移动、分数等。
一旦客户端收到状态,他们就负责渲染它。这可以通过 Matter.js、canvas、p5.js、纯 HTML 或其他任何东西来完成。客户端还负责向服务器报告与游戏相关的鼠标和键盘操作,以供游戏引擎逻辑使用。
这是一个最小、完整的示例,说明其工作原理:
package.json
:{
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.19.2",
"matter-js": "^0.20.0",
"socket.io": "^4.6.1"
},
"engines": {
"node": "16.x"
}
}
server.js
:const express = require("express");
const Matter = require("matter-js");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server);
app.use(express.static("public"));
const frameRate = 1000 / 30;
const canvas = {width: 300, height: 200};
const boxes = 20;
const boxSize = 20;
const wallThickness = 20;
const entities = {
boxes: [...Array(boxes)].map(() =>
Matter.Bodies.rectangle(
Math.random() * canvas.width,
boxSize,
Math.random() * boxSize + boxSize,
Math.random() * boxSize + boxSize,
)
),
walls: [
Matter.Bodies.rectangle(
canvas.width / 2, 0,
canvas.width,
wallThickness,
{isStatic: true}
),
Matter.Bodies.rectangle(
0, canvas.height / 2,
wallThickness,
canvas.height,
{isStatic: true}
),
Matter.Bodies.rectangle(
canvas.width,
canvas.width / 2,
wallThickness,
canvas.width,
{isStatic: true}
),
Matter.Bodies.rectangle(
canvas.width / 2,
canvas.height,
canvas.width,
wallThickness,
{isStatic: true}
),
]
};
const engine = Matter.Engine.create();
Matter.Composite.add(engine.world, Object.values(entities).flat());
const toVertices = e => e.vertices.map(({x, y}) => ({x, y}));
setInterval(() => {
Matter.Engine.update(engine, frameRate);
io.emit("update state", {
boxes: entities.boxes.map(toVertices),
walls: entities.walls.map(toVertices),
});
}, frameRate);
io.on("connection", socket => {
socket.on("register", cb => cb({canvas}));
socket.on("player click", coordinates => {
entities.boxes.forEach(box => {
// servers://stackoverflow.com/a/50472656/6243352
const force = 0.01;
const deltaVector = Matter.Vector.sub(box.position, coordinates);
const normalizedDelta = Matter.Vector.normalise(deltaVector);
const forceVector = Matter.Vector.mult(normalizedDelta, force);
Matter.Body.applyForce(box, box.position, forceVector);
});
});
});
server.listen(process.env.PORT, () =>
console.log("server listening on " + process.env.PORT)
);
public/index.html
:<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.6.1/socket.io.js"></script>
<script>
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
const socket = io();
const draw = (body, ctx) => {
ctx.beginPath();
body.forEach(e => ctx.lineTo(e.x, e.y));
ctx.closePath();
ctx.fill();
ctx.stroke();
};
socket.once("connect", () => {
socket.emit("register", res => {
canvas.width = res.canvas.width;
canvas.height = res.canvas.height;
});
});
socket.on("update state", ({boxes, walls}) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#111";
ctx.strokeStyle = "#111";
walls.forEach(wall => draw(wall, ctx));
ctx.fillStyle = "#aaa";
boxes.forEach(box => draw(box, ctx));
});
document.addEventListener("mousedown", e => {
socket.emit("player click", {x: e.offsetX, y: e.offsetY});
});
</script>
</body>
还存在其他方法,并且将一些引擎逻辑转移给客户端可能会有优势,因此请将此视为概念验证。请参阅这篇文章了解更多设计建议。