@colyseus/ 反应
反应钩 科利赛 多人应用.

安装
npm install @colyseus/react
同伴依赖性 : @colyseus/sdk, (中文(简体) ), @colyseus/schema,以及 react (单位:千美元).
服务器侧渲染
每个钩子在服务器上都是安全的 一个房间是现场客户端
连接并且从未被串行用于水合, 所以在服务器上的钩子
仿佛无所事事 useRoomState() 返回时 undefined, (中文(简体) ),
useRoom() 报告 isConnecting: true,预测钩子返回
undefined连接在某种效果中发生,所以客厅到达
水分补充后立即恢复.
创建连接状态而不是假设状态:
const players = useRoomState((state) => state.players);
if (!players) return <Spinner />; // server render + first client paint
货包船a "use client" 指令,这样您就可以从一个
Next.js App 路由器服务器组件没有构建失败 。 钩子
就像任何反应钩一样.
钩子
useRoom(callback, deps?)
管理Colyseus房间连接的生命周期。 处理连接,断开挂载, 当依赖关系改变时重新连接 。 正确使用 React PrintMode .
import { Client } from "@colyseus/sdk";
import { useRoom } from "@colyseus/react";
const client = new Client("ws://localhost:2567");
function Game() {
const { room, error, isConnecting } = useRoom(
() => client.joinOrCreate("game_room"),
);
if (isConnecting) return <p>Connecting...</p>;
if (error) return <p>Error: {error.message}</p>;
return <GameView room={room} />;
}
第一个参数是返回 a 的回调 Promise<Room> - 任何Colyseus配对方法都有效(joinOrCreate, (中文(简体) ), join, (中文(简体) ), create, (中文(简体) ), joinById, (中文(简体) ), consumeSeatReservation) (中文(简体) ).
根据依赖性变化重新连接 :
const { room } = useRoom(
() => client.joinOrCreate("game_room", { level }),
[level],
);
何时 level 更改前一个房间左边,并建立新的连接.
有条件连接 :
传递 falsy 值以跳过连接直到满足条件 :
const { room } = useRoom(
isReady ? () => client.joinOrCreate("game_room") : null,
[isReady],
);
useRoomState(room, selector?)
订阅 Colyseus 房间状态变化并返回不可改变的平面对象快照 。 状态树的未更改部分保留了commerds之间的偏好等同,所以当它们实际使用的数据被改变时,只重新激活组件.
import { useRoom, useRoomState } from "@colyseus/react";
function Game() {
const { room } = useRoom(() => client.joinOrCreate("game_room"));
const state = useRoomState(room);
if (!state) return <p>Waiting for state...</p>;
return <p>Players: {state.players.size}</p>;
}
使用选择器 以订阅状态的子集 :
const players = useRoomState(room, (state) => state.players);
仅读取组件 players 当玩家地图改变时, 将会重新回复.
useRoomMessage(room, type, callback)
订阅 Colyseus 房间信息。 召回器保存在回放器中,所以它总是更新而无需重新订阅. 当房间改变或组件卸载时自动取消订阅.
import { useRoom, useRoomMessage } from "@colyseus/react";
function Chat() {
const { room } = useRoom(() => client.joinOrCreate("game_room"));
const [messages, setMessages] = useState<string[]>([]);
useRoomMessage(room, "chat", (message) => {
setMessages((prev) => [...prev, message]);
});
return (
<ul>
{messages.map((msg, i) => <li key={i}>{msg}</li>)}
</ul>
);
}
过 "*" 作为所有消息类型的监听类型.
useLobbyRoom(callback, deps?)
连接到 Colyseus 大厅 并提供了可用房间的现场更新清单。 随着房间的创建、更新和去除,清单将自动保持.
import { Client } from "@colyseus/sdk";
import { useLobbyRoom } from "@colyseus/react";
const client = new Client("ws://localhost:2567");
function Lobby() {
const { rooms, error, isConnecting } = useLobbyRoom(
() => client.joinOrCreate("lobby"),
);
if (isConnecting) return <p>Connecting...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{rooms.map((room) => (
<li key={room.roomId}>
{room.name} — {room.clients}/{room.maxClients} players
</li>
))}
</ul>
);
}
返回值:
| 外地 | 类型 | 说明 |
|---|---|---|
rooms | RoomAvailable<Metadata>[] | 可用房间实况列表 |
room | Room | undefined | 地下大厅连接 |
error | Error | undefined | 连接出错( 如果有) |
isConnecting | boolean | true 在连接大厅时 |
useQueueRoom(connect, consume, deps?)
管理一个 Colyseus 匹配队列的整个生命周期:连接到排队室,跟踪组大小,接收座位预定,确认,并消耗座位加入匹配室. 把两个房间都清理干净.
import { Client } from "@colyseus/sdk";
import { useQueueRoom } from "@colyseus/react";
const client = new Client("ws://localhost:2567");
function Matchmaking() {
const { room, clients, isWaiting, error } = useQueueRoom(
() => client.joinOrCreate("queue", { rank: 1200 }),
(reservation) => client.consumeSeatReservation(reservation),
);
if (error) return <p>Error: {error.message}</p>;
if (room) return <GameScreen room={room} />;
if (isWaiting) return <p>Waiting for match... {clients} players in group</p>;
return <p>Connecting...</p>;
}
第一个参数连接到队列室. 第二个论点是: SeatReservation 一旦找到匹配点——使用 client.consumeSeatReservation() 加入比赛室.
返回值:
| 外地 | 类型 | 说明 |
|---|---|---|
room | Room | undefined | 火柴房,一旦座椅被消耗 |
queue | Room | undefined | 等待时的队列室( 匹配后未定义) |
clients | number | 当前配对组中的客户数 |
seat | SeatReservation | undefined | 座位预定,一旦收到 |
error | Error | undefined | 连接或匹配出错 |
isWaiting | boolean | true 连接到队列并等待匹配时 |
网码钩 (Colyseus 0.18+)
反应约束 @colyseus/sdk 预测工具——客户预测,调节和远程平滑. 见 网码文件 对于基本的概念.
一条规则是: 结构通过快照实现,运动通过预测读取实现. useRoomState 以补丁速率重交 原始同步值 - 从它做位置 让你焦躁的补丁率运动。 预测值/模拟值改变每个框架,必须远离反应渲染周期:用 predict.value(instance, field) 在您的帧环( R3F) useFrame,或 usePredictLoop 并写入参考文献.
下面所有钩子都以两种形式存在: 独立(useX(room, ...))和被捆绑(由 createRoomContext(options),没有房间争论). 将预测选项通过一次 createRoomContext({ predict: { mode: "lerp", delay: 100 } })。 。 。 .
usePredict(room, options?)
返回共享的房间 Predict 举个例子. Predict.get() 每个呼叫构造一个新实例,所以钩子会记住每个房间一个,参考点数消费者,当最后的消费者卸载时处理.
useInput(room, options?)
回到房间 InputHandle (单位:千美元)room.input() ——一能,每室稳定. 阶段字段开始 input.data,则 input.send() 每一固定步骤 usePredictLoop 调用.
usePredictLoop(room, onSteps, options?)
拥有每帧驱动程序: 呼叫 predict.tick(now) 每帧一次,然后将到期的固定步骤数交给您——相位+准确发送回调内的许多输入("读取前发送"). 默认它会自己运行 requestAnimationFrame; 与 { external: true } 它返回一个 drive(now?) 函数从您已有的框架循环调用 :
// React Three Fiber — drive at an early priority so ticks precede reads:
const drive = usePredictLoop(onSteps, { external: true });
useFrame(() => drive(), -1);
useReconciler(room, select, options)
积极预测你控制的实体—— predict.reconciler() 处理的生命周期:等待实体产出(select: (state, room) => instance),如果服务器替换实例,则重新创建控制器,并在卸载上处理. options.input 默认为房间的把手.
const me = useReconciler(
(state, room) => state.players.get(room.sessionId),
{ step: (ctx, p, cmd) => applyInput(p, cmd, ctx.dt), smoothing: 15, snap: 5 },
);
useAttachAll(room, key, config, deps?)
被动平滑为根态收藏—— predict.attachAll() 作为一种效果,脱去在卸载上。 在单独的调用中附加角字段 :
useAttachAll("players", { mode: "lerp", fields: ["x", "y", "z"] });
useAttachAll("players", { mode: "lerp", fields: ["heading"], angle: true });
useEventChannel(room, options)
乐观事件频道—— predict.defineEvent() 以倾覆而未登起者发誓, 加反应:调用组件在预测/确认/弹出/未预测上重新调用,所以旗形的引出工作以平面渲染码:
const pickups = useEventChannel({
confirmOn: { collection: "items", field: "alive", equals: false },
});
const hidden = !item.alive || pickups?.has(id);
useEntityInstance(room, select) 编号 : useSessionEntity(room, collectionKey)
选择一个 已解码的计划实例 (Prectt APIs 键关闭),只有当其身份变化时才会重新发回——从不在字段更新上发回. useSessionEntity 常见情况: 由 sessionId。 。 。 .
getSchemaInstance(snapshot)
桥梁 useRoomState 将快照折回解码实例中 predict.value() 阅读您从快照列表中获取的实体:
const source = getSchemaInstance(playerSnapshot);
useFrame(() => { ref.current.position.x = predict.value(source, "x"); });
useInputBuffer()
这个 "缓冲,然后消耗" 作为钩子的输入食谱 : press() 在事件处理器中, consume() 在发送回路中,一个水龙头在精确的固定步骤上降落——在0步框架上从未丢失,在多步框架上从未加倍.
完整的实例(R3F + 预测 + 远程麻黄),见 r3f-lobby-car-原型。 。 。 .
背景情况
createRoomContext()
创建一组钩子和一个 RoomProvider 组件,共享一个跨React调和器边界的单室连接(如DOM + React Three Fiber). 房间被储存在一个封闭的外置商店,而不是反应上下文,因此钩子在任何导入它们的调和树上工作.
import { Client } from "@colyseus/sdk";
import { createRoomContext } from "@colyseus/react";
const client = new Client("ws://localhost:2567");
const { RoomProvider, useRoom, useRoomState } = createRoomContext();
包装您的应用程序 RoomProvider数字 :
function App() {
return (
<RoomProvider connect={() => client.joinOrCreate("game_room")}>
<UI />
<Canvas>
<GameScene />
</Canvas>
</RoomProvider>
);
}
RoomProvider 接受一个 connect 调用( 与独立 useRoom 钩子)和可选 deps 阵列。 传递一个 falsy 值到 connect 推迟连接.
使用任意组件中的钩子——DOM或R3F:
function UI() {
const { room, error, isConnecting } = useRoom();
const players = useRoomState((state) => state.players);
if (isConnecting) return <p>Connecting...</p>;
if (error) return <p>Error: {error.message}</p>;
return <p>Players: {players?.size}</p>;
}
回来的 useRoom() 财务报告和审定财务报表 useRoomState(selector?) 工作与独立的钩子相同,但不要要求你通过房间作为辩论.
createLobbyContext()
创建一个 LobbyProvider 财务报告和审定财务报表 useLobby 在您的应用软件上分享大厅室数据的钩子——当您需要房间元数据时, 不仅在大厅屏幕上, 喜欢 createRoomContext,它使用一个闭合镜外商店,所以钩子可以跨越调和器边界工作.
import { Client } from "@colyseus/sdk";
import { createLobbyContext, createRoomContext } from "@colyseus/react";
const client = new Client("ws://localhost:2567");
const { LobbyProvider, useLobby } = createLobbyContext<MyMetadata>();
const { RoomProvider, useRoom, useRoomState } = createRoomContext();
包装您的应用程序 LobbyProvider (可与 RoomProvider:
function App() {
return (
<LobbyProvider connect={() => client.joinOrCreate("lobby")}>
<RoomProvider connect={() => client.joinOrCreate("game_room")}>
<UI />
<Canvas>
<GameScene />
</Canvas>
</RoomProvider>
</LobbyProvider>
);
}
LobbyProvider 接受一个 connect 回调( 和 useLobbyRoom)和可选 deps 阵列。 大厅连接独立于游戏室.
从任何组件访问大厅数据——甚至游戏深处:
function RoomBrowser() {
const { rooms, error, isConnecting } = useLobby();
if (isConnecting) return <p>Loading rooms...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{rooms.map((room) => (
<li key={room.roomId}>
{room.metadata.displayName} — {room.clients}/{room.maxClients}
</li>
))}
</ul>
);
}
回来的 useLobby() 钩子提供与 useLobbyRoom (单位:千美元)rooms, (中文(简体) ), room, (中文(简体) ), error, (中文(简体) ), isConnecting) (中文(简体) ).
贷项
灵感和以前的工作 @ pedr0fontoura (英语) —— 说 使用密码。 。 。 .
重写和新建 useRoomState() (单位:千美元) @FTWinston (英语)。 。 。 .