如果服务器负责运行 matter.js 引擎,则可能没有必要将 matter.js 对象从服务器发送到客户端。
一种可能的设计是让服务器发出客户端所需的最少数量的序列化信息,以在每个滴答时呈现游戏状态。这是特定于应用程序的,但可能归结为从您的 mjs 主体中选择顶点并通知客户端玩家位置、移动、得分等。
一旦客户端收到状态,他们就负责渲染它。这可以使用 matter.js、canvas、p5.js、纯 HTML 或其他任何东西来完成。客户端还负责向服务器报告游戏相关的鼠标和键盘操作,以供游戏引擎逻辑使用。
这里有一个最小的、完整的例子来说明它是如何工作的:
package.json:
{
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.17.1",
"matter-js": "^0.16.1",
"socketio": "^1.0.0"
},
"engines": {
"node": "12.x"
}
}
server.js:
const express = require("express");
const Matter = require("matter-js");
const app = express();
const http = require("http").createServer(app);
const io = require("socket.io").listen(http);
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.World.add(engine.world, [].concat(...Object.values(entities)));
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: canvas}));
socket.on("player click", coordinates => {
entities.boxes.forEach(box => {
// https://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);
});
});
});
app.use(express.static("public"));
app.get("/", (req, res) =>
res.sendFile(__dirname + "/views/index.html")
);
http.listen(process.env.PORT, () =>
console.log("http listening on " + process.env.PORT)
);
index.html:
<script src="https://cdn.socket.io/socket.io-1.2.0.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>
Live demo 和 project code 出现故障。
存在其他方法,将一些引擎逻辑转移到客户端可能会有优势,因此请将此视为概念验证。有关其他设计建议,请参阅 this post。