示例教程 H5 Netcode
示例教程 H5 Netcode
本教程将展示如何使用 @u3d-uos/h5-netcode 网络同步框架开发 H5 实时多人游戏,并将服务端部署到 UOS Multiverse,让玩家直接通过浏览器联机。
该框架面向浏览器端实时多人游戏,采用客户端预测 + 服务端校正 + 快照插值架构,服务端为 Node.js 程序。结合 Codely AI Agent(需安装 UOS Skills),即可快速完成从游戏逻辑实现到 Multiverse 部署的全流程。
1. 启用 Multiverse
完成 创建 UOS APP 后,在项目的控制面板,点击 Multiverse「免费试用」,默认选择"按需模式",点击「立即开通」进入 Multiverse 项目设置页面。
2. 示例:一个最简多人游戏
以下是一个完整可运行的示例,4 个文件,玩家用 WASD 控制圆点在 Canvas 上移动,开多个窗口即可看到其他玩家。
共享游戏逻辑
// game.js — 客户端和服务端共用
export function simulate(state, input, dt) {
return {
x: state.x + input.dx * 200 * dt,
y: state.y + input.dy * 200 * dt,
};
}
export function reconcile(predicted, authoritative) {
const dx = predicted.x - authoritative.x;
const dy = predicted.y - authoritative.y;
return Math.sqrt(dx * dx + dy * dy);
}
export function interpolate(before, after, t) {
return {
x: before.x + (after.x - before.x) * t,
y: before.y + (after.y - before.y) * t,
};
}
export function createInitialState(_clientId) {
return { x: 400, y: 300 };
}服务端
// server.js
import { simulate, createInitialState } from './game.js';
import { ServerLoop } from '@u3d-uos/h5-netcode/server';
import { SocketIOServerTransport } from '@u3d-uos/h5-netcode/transport/socketio/server';
import { MultiverseSDK } from '@u3d-uos/multiverse-sdk';
const PORT = Number(process.env.PORT ?? 3000);
const transport = new SocketIOServerTransport();
const server = new ServerLoop(transport, {
tickRate: 30,
simulate,
createInitialState,
});
await transport.listen(PORT, '0.0.0.0'); // 必须监听 0.0.0.0
server.start();
console.log(`服务器已启动,端口 ${PORT}`);
// 通知 Multiverse 平台:服务已就绪,可以分配玩家
const sdk = await MultiverseSDK.getInstance();
await sdk.ready();
console.log('SDK ready');
async function shutdown() {
server.gracefullyShutdown();
await sdk.shutdown();
process.exit(0);
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);客户端
// client.js
import { ClientLoop } from '@u3d-uos/h5-netcode/client';
import { SocketIOTransport } from '@u3d-uos/h5-netcode/transport/socketio/client';
import { simulate, reconcile, interpolate, createInitialState } from './game.js';
const canvas = document.getElementById('arena');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('status');
// 从 Allocation 返回的 ip 和映射端口构造地址
const transport = new SocketIOTransport(`http://${serverIp}:${serverPort}`);
const client = new ClientLoop(transport, {
simulate,
createInitialState,
reconcileFn: reconcile,
interpolateFn: interpolate,
});
// 键盘输入
const keys = { up: false, down: false, left: false, right: false };
const keyMap = { ArrowUp: 'up', KeyW: 'up', ArrowDown: 'down', KeyS: 'down',
ArrowLeft: 'left', KeyA: 'left', ArrowRight: 'right', KeyD: 'right' };
window.addEventListener('keydown', e => { const k = keyMap[e.code]; if (k) { keys[k] = true; e.preventDefault(); } });
window.addEventListener('keyup', e => { const k = keyMap[e.code]; if (k) keys[k] = false; });
// 渲染循环
function render() {
client.setInput({
dx: (keys.right ? 1 : 0) - (keys.left ? 1 : 0),
dy: (keys.down ? 1 : 0) - (keys.up ? 1 : 0),
});
ctx.fillStyle = '#111318';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (const r of client.getRemoteEntities()) { // 其他玩家(紫色)
drawDot(r.data.x, r.data.y, '#a78bfa');
}
const auth = client.authoritative;
if (auth) drawDot(auth.data.x, auth.data.y, 'rgba(248,113,113,0.3)'); // 权威影子(红)
const pred = client.predicted;
if (pred) drawDot(pred.data.x, pred.data.y, '#4ade80'); // 本地预测(绿)
requestAnimationFrame(render);
}
function drawDot(x, y, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, 16, 0, Math.PI * 2);
ctx.fill();
}
async function connect() {
await transport.connect();
client.start();
statusEl.textContent = 'connected · ' + client.localClientId;
}
connect();
requestAnimationFrame(render);网页
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>h5-netcode demo</title>
<style>
body { margin: 0; background: #111318; color: #c8ccd0;
font-family: monospace; display: flex; flex-direction: column;
align-items: center; padding: 20px; }
canvas { border: 1px solid #232730; margin-top: 12px; }
</style>
</head>
<body>
<h3>WASD / 方向键移动 · 开多个窗口可看到远程玩家</h3>
<div id="status">connecting…</div>
<canvas id="arena" width="800" height="600"></canvas>
<script type="module" src="./client.js"></script>
</body>
</html>3. 部署到 Multiverse
服务端接入 Multiverse SDK 的关键改动只有两处(示例中已包含):
transport.listen(PORT, '0.0.0.0')— 监听地址必须是0.0.0.0,不能只绑127.0.0.1- 端口监听后调用
sdk.ready(),退出前调用sdk.shutdown()
将服务端代码打包为 zip 后,在 「Multiverse -> 启动配置」 页面创建配置、上传镜像、填写入口(node server.js)和端口,测试通过后发布,开通地域,分配实例即可。
分配成功后,在服务器详情页取得 IP 与映射端口,将客户端
SocketIOTransport的地址替换为http://${serverIp}:${serverPort}即可联机。注意要用映射端口,不是代码里的容器内部端口。
如果你使用 Codely,可以直接告诉 AI 助手「帮我把 h5-netcode 服务端部署到 Multiverse」,它会自动完成镜像上传、配置创建、实例分配的全流程。
4. 更多实践
更多接入细节请参考项目仓库中的 Tutorial,包含逐玩家模式、共享世界模式、WebRTC 传输、断线重连、遥测等完整教程。