mcpField
M
multiplayer-game

multiplayer-game

github.com/rivet-dev开发447.2k 次安装1 天前更新
代码仓库

关于 multiplayer-game

为使用 RivetKit 开发多人游戏的开发者提供实现指南,按游戏类型整理服务器架构模式。内容涉及模拟、同步、匹配、持久化与反作弊。

主要功能

  1. 01游戏循环与 tick 频率模式
  2. 02物理和空间索引指南
  3. 03状态同步与数据复制
  4. 04Actor 拓扑与匹配模式
  5. 05持久化与反作弊实践

适用场景

  1. 01设计游戏的匹配和生命周期流程
  2. 02选择服务器模拟及状态同步模式
  3. 03将大型世界划分为 chunk Actor
  4. 04校验客户端移动和游戏操作

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rivet-dev/skills --skill multiplayer-game

安装后在 Claude Code 中输入/multiplayer-game确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

BASE_SKILL.md
index.json
openapi.json
reference/actors/
access-control.md
actions.md
actor-runtime-socket.md
appearance.md
authentication.md
communicating-between-actors.md
connections.md
debugging.md
design-patterns.md
destroy.md
errors.md
events.md
fetch-and-websocket-handler.md
helper-types.md
http-api.md
input.md
inspector-tabs.md
keys.md
kv.md
lifecycle.md
limits.md
metadata.md
queues.md
reference/actors/quickstart/
backend.md
cloudflare.md
effect.md
next-js.md

详细介绍

多人游戏

重要:在进行任何操作之前,必须先阅读此技能目录中的 BASE_SKILL.md。其中包含有关调试、错误处理、状态管理、部署和项目设置的重要指导。这些规则和模式适用于所有 RivetKit 工作。以下所有内容均假定你已阅读并理解该文件。

可运行示例

如果需要参考实现,请阅读以下模板中的原始可运行示例代码:

使用 RivetKit 构建多人游戏的模式,可作为实用清单,按游戏类型进行调整。

入门代码

从 GitHub 上的一个可运行示例开始,并根据你的游戏进行调整。匹配和生命周期流程不要从头开始编写。

游戏分类入门代码常见示例
大逃杀GitHubFortnite、Apex Legends、PUBG、Warzone
竞技场GitHubCall of Duty TDM/FFA、Halo Slayer、Counter-Strike 休闲模式、VALORANT 非排位模式、Overwatch 快速比赛、Rocket League
IO 风格GitHubAgar.io、Slither.io、surviv.io
开放世界GitHubMinecraft 生存服务器、类似 Rust 的世界、MMO 区域/分块世界
派对GitHubFall Guys 私人大厅、自定义游戏房间、社交派对对局
2D 物理GitHub俯视视角物理格斗游戏、2D 竞技场游戏、平台格斗游戏
3D 物理GitHub物理沙盒对局、3D 竞技场游戏、移动玩法场景
排位GitHub国际象棋排位赛、竞技卡牌游戏、决斗竞技场排位队列
回合制GitHub国际象棋通信赛、Words With Friends、异步棋盘游戏
放置类GitHubCookie Clicker、Idle Miner Tycoon、Adventure Capitalist

服务端模拟

游戏循环与 Tick 频率
模式适用场景实现指南
固定实时循环大逃杀、竞技场、IO 风格、开放世界、排位在 run 中运行,并使用 sleep(tickMs);当 c.aborted 时退出。
由操作驱动的更新派对、回合制仅在操作/事件发生时修改状态并广播,而非按计划 Tick。
粗粒度离线推进任何包含放置进度的模式使用 c.schedule.after(...) 设置较长的时间窗口(例如 5 到 15 分钟),并根据经过的实际时间补算进度。
物理

从简单游戏的自定义运动学逻辑开始。当需要关节、堆叠刚体、高密度碰撞或复杂形状(旋转多边形、胶囊体、凸包、三角网格)时,再切换到完整的物理引擎。

每个模拟只选用一种引擎。不要在后端模拟路径中使用仅面向前端的库,并将服务器状态视为权威状态。

维度首选引擎备用引擎示例代码
2D@dimforge/rapier2dplanck-js、matter-jsGitHub
3D@dimforge/rapier3dcannon-es、ammo.jsGitHub
空间索引

对于非物理空间查询,请使用专用索引,而不是朴素的 O(n^2) 检查:

索引类型建议
AABB 索引对于 AOI、可见性和非碰撞体实体,动态集合使用 rbush,静态集合使用 flatbush。
点索引对于最近邻或半径范围内的查询,请使用 d3-quadtree。

网络与状态同步

网络代码
模式适用场景实现方式
混合模式(客户端移动,服务器战斗)射击游戏、动作体育游戏、排位决斗客户端负责移动,并以受限频率发送位置更新。服务器进行验证以防作弊。战斗(投射物、命中、伤害)完全由服务器权威控制。
服务器权威并进行插值IO 风格游戏、持久化世界客户端发送输入命令。服务器按固定刻模拟并发布权威状态快照。客户端在快照之间进行插值。
服务器权威(基础逻辑)回合制、事件驱动服务器验证并应用离散操作(回合、阶段转换、投票)。客户端显示已确认的状态。
实时数据模型
  • 快照与差异:将状态作为事件发布。加入或重新同步时发送完整快照,之后每刻发送差异以进行常规更新。
  • 按刻批处理:保持事件精简且类型明确。每刻批量处理高频更新。
  • 避免使用 UI 框架状态处理游戏更新:模拟应使用 requestAnimationFrame 或 Canvas/Three.js 循环,而不是 React 状态。UI 框架状态仅用于菜单、HUD 和表单。
  • 广播与按连接发送:共享更新使用 c.broadcast(...),私有或面向单个玩家的数据使用 conn.send(...)。
共享模拟逻辑

共享模拟逻辑同时在客户端和服务器上运行。例如,用于整合速度并将位置限制在世界边界内的 applyInput(state, input, dt) 函数,可以在客户端用于预测,在服务器上用于验证。

  • 混合模式:客户端将共享移动逻辑作为主要权威来源运行,服务器运行该逻辑以进行防作弊验证。
  • 服务器权威模式:客户端仅将共享逻辑用于插值和预测。
  • 保持纯函数:仅包含移动整合、输入转换、碰撞辅助函数和常量。
  • 将共享代码放在 src/shared/ 中:将确定性辅助函数放在 src/shared/sim/* 中,且不产生副作用。
兴趣管理

控制每个客户端接收的内容,以减少带宽占用并防止信息泄露。

按玩家设置复制过滤器
  • 按相关性过滤:仅向每个客户端发送与该玩家相关的状态(邻近程度、视线、队伍或游戏阶段)。
  • 射击游戏和动作游戏:按邻近程度限制复制,并可选进行视野检查。
  • 仅在服务器端处理:客户端绝不能收到不应看到的数据。
分片世界
  • 划分大型世界:使用以 worldId:chunkX:chunkY 为键的区块 Actor。
  • 订阅附近区块:客户端只连接附近的分区(例如 3x3 区块窗口)。
  • 谨慎使用:仅在世界规模大且状态繁重时使用(沙盒建造游戏、大型多人在线游戏),不要将其作为小型对局的默认方案。

后端基础设施

持久化
  • 内存状态:最适合每个 tick 都会变化的实时游戏状态(玩家位置、输入、对局阶段、分数)。
  • SQLite(rivetkit/db):更适合需要查询、索引或长期持久化的大型或表格化状态(地图块、物品栏、匹配池)。由于多个操作可能并发访问同一 Actor,请通过队列串行处理数据库操作。
匹配模式

以下是各种架构模式中常用的构建块。

Actor 拓扑结构
基元适用场景典型所有权
matchmaker["main"] + match[matchId]基于会话的多人游戏(大逃杀、竞技场、排位赛、组队、回合制)匹配器负责发现和分配。对局负责管理生命周期和游戏状态。
chunk[worldId,chunkX,chunkY]需要分片的大型连续世界每个地图块负责本地玩家、地图块状态和本地模拟。
world[playerId]每位玩家独立的成长循环(放置类/单人世界状态)每位玩家的资源、建筑、计时器和成长进度。
player[username]在多场对局中复用的规范玩家资料/评分持久化的玩家统计数据(例如评分和胜/负场次)。
leaderboard["main"]多场对局/多名玩家共用的排名全局有序的分数条目和排行榜前列。
排队策略
  • 多名玩家可能同时访问匹配器,因此查找/创建、排队/取消排队和关闭等操作需要通过 Actor 队列串行处理,以避免竞争条件。
  • 对局内的操作(游戏玩法、计分)无需排队,除非它们会回写匹配器。

安全与反作弊

先采用以下基线措施,再根据竞技或高风险环境进一步加强防护。

基线检查清单
  • 身份:使用 c.conn.id 作为权威传输身份。将参数中的 playerId/username 视为不可信输入,并通过服务器签发的分配/加入票据进行绑定。
  • 授权:验证调用者是否有权修改目标实体(房间成员身份、回合所有权、仅限主机的操作)。
  • 输入验证:限制大小/长度,验证枚举值,并验证用户名(长度、允许的字符,避免无界 Unicode)。
  • 速率限制:对易被滥用的操作(聊天、加入/离开、开火、移动更新)实施逐连接速率限制。
  • 状态完整性:由服务器重新计算派生状态(分数、胜利条件、排名)。绝不允许由客户端权威地更改物品栏/货币/排行榜总分。
移动验证

对于采用客户端权威移动的任何模式(混合流程),客户端可以发送位置/旋转更新以实现平滑移动,但服务器必须:

  • 根据经过的时间限制每次更新的最大位移(速度上限)。
  • 拒绝或限制瞬移。
  • 限制世界边界(并在适用时进行基本碰撞检测)。
  • 限制更新频率(例如,最高 20Hz)。

架构模式

下面的每种游戏类型都会先提供一张概要表,然后详细介绍 Actor 和生命周期。

大逃杀
主题摘要
匹配立即分配到人数最多且尚未开始的大厅(人数相同时优先选择最早创建的大厅);玩家在大厅中等待,直到达到容量上限,然后比赛开始。
网络代码混合模式。客户端负责移动、摄像机和本地预测。服务端负责区域状态、弹丸、命中判定、淘汰、战利品和最终排名。
Tick 速率每秒 10 个 tick(100ms),使用固定循环推进区域并检查生命周期。
物理客户端负责移动,服务端进行反作弊验证;弹丸、命中和伤害由服务端权威处理。3D 使用 @dimforge/rapier3d,俯视 2D 使用 @dimforge/rapier2d。

Actor

  • 键:matchmaker["main"]

  • 职责:查找或创建大厅、跟踪待处理预留,并维护占用情况。

  • 操作

    • findMatch
    • pendingPlayerConnected
    • updateMatch
    • closeMatch
  • 队列

    • findMatch
    • pendingPlayerConnected
    • updateMatch
    • closeMatch
  • 状态

    • SQLite
    • matches
    • pending_players
    • player_count 包含已连接和待处理的玩家
  • 键:match[matchId]

  • 职责:运行大厅、进行中和已结束阶段,管理玩家状态、区域推进和淘汰。

  • 操作

    • connect
    • 移动和战斗操作
  • 队列

    • 无
  • 状态

    • JSON
    • phase
    • players
    • zone
    • eliminations
    • snapshot data

生命周期

sequenceDiagram
	participant C as Client
	participant MM as matchmaker
	participant M as match

	C->>MM: findMatch()
	alt no open lobby
		MM->>M: create(matchId)
	end
	MM-->>C: {matchId, playerId}
	C->>M: connect(playerId)
	M->>MM: pendingPlayerConnected(matchId, playerId)
	MM-->>M: accepted
	Note over M: lobby countdown -> live
	M-->>C: snapshot + shoot events
	M->>MM: closeMatch(matchId)
竞技场
主题摘要
匹配基于模式的固定容量队列(duo、squad、ffa),仅组建满员比赛,并预先分配队伍(FFA 除外)。
网络代码混合模式。客户端负责移动、预测和平滑处理。服务端负责队伍或 FFA 分配、弹丸、命中判定、阶段转换和计分。
Tick 速率每秒 20 个 tick(50ms),为进行中的队伍赛和 FFA 快照使用更紧凑的循环。
物理中到高强度;客户端负责移动,服务端进行验证,并权威处理战斗和实体。

Actor

  • 键:matchmaker["main"]

  • 职责:运行模式队列、组建满员比赛、分配队伍并发布分配结果。

  • 操作

    • queueForMatch
    • unqueueForMatch
    • matchCompleted
  • 队列

    • queueForMatch
    • unqueueForMatch
    • matchCompleted
  • 状态

    • SQLite
    • player_pool
    • matches
    • 以连接和玩家为键的 assignments
  • 键:match[matchId]

  • 职责:运行比赛阶段,并管理比赛中的玩家和队伍状态,以用于计分和胜利条件判定。

  • 操作

    • connect
    • 游戏操作
  • 队列

    • 无
  • 状态

    • JSON
    • phase
    • players
    • team assignments
    • score and win state

生命周期

sequenceDiagram
	participant C as Client
	participant MM as matchmaker
	participant M as match

	C->>MM: queueForMatch(mode)
	Note over MM: enqueue in player_pool
	Note over MM: fill when capacity reached
	MM->>M: create(matchId, assignments)
	Note over MM: persist assignments
	MM-->>C: assignmentReady
	C->>M: connect(playerId)
	Note over M: waiting -> live when all players connect
	M->>MM: matchCompleted(matchId)
IO 风格
主题摘要
匹配将玩家路由到未满员房间中人数最多的开放大厅;房间人数通过心跳更新,并在需要时自动创建新大厅。
网络代码采用服务器权威模式并进行插值。客户端发送输入意图并进行插值。服务器负责移动、边界、房间成员关系和规范快照。
Tick 速率每秒 10 个 tick(100ms),并定期发送轻量级房间快照。
物理强度低到中等;由服务器权威控制运动学移动,仅在碰撞变复杂时才升级为物理引擎。

Actor

  • 键:matchmaker["main"]

  • 职责:将玩家路由到人数最多的开放大厅,并跟踪预留名额和占用情况。

  • 操作

    • findLobby
    • pendingPlayerConnected
    • updateMatch
    • closeMatch
  • 队列

    • findLobby
    • pendingPlayerConnected
    • updateMatch
    • closeMatch
  • 状态

    • SQLite
    • matches
    • pending_players
    • 占用人数包括待处理的预留名额
  • 键:match[matchId]

  • 职责:运行每场比赛的移动模拟并广播快照。

  • 操作

    • connect
    • setInput
  • 队列

    • 无
  • 状态

    • JSON
    • players
    • inputs
    • movement state
    • snapshot cache

生命周期

sequenceDiagram
	participant C as Client
	participant MM as matchmaker
	participant M as match

	C->>MM: findLobby()
	alt no open lobby
		MM->>M: create(matchId)
	end
	MM-->>C: {matchId, playerId}
	C->>M: connect(playerId)
	M->>MM: pendingPlayerConnected(matchId, playerId)
	MM-->>M: accepted
	Note over M: fixed tick simulation
	M-->>C: snapshot events
	M->>MM: closeMatch(matchId)
开放世界
主题摘要
匹配客户端根据世界坐标进行分块路由,并通过相邻分块连接预加载附近的分块窗口。
网络代码沙盒模式采用混合方案(客户端移动并进行验证),或类 MMO 流程采用服务器权威模式。服务器负责分块路由、持久化和规范世界状态。
Tick 速率每个分块 Actor 每秒 10 个 tick(100ms),因此负载会随活跃分块数量增加。
物理大规模场景下为中到高强度;分块本地模拟可采用服务器权威模式(类 MMO),也可采用客户端移动并由服务器验证(类沙盒)。

Actor

  • 键:chunk[worldId,chunkX,chunkY]
  • 职责:负责分块本地的玩家、方块、移动 tick 和分块成员关系。
  • 操作
    • connect
    • enterChunk
    • addPlayer
    • setInput
    • leaveChunk
    • removePlayer
  • 队列
    • 无
  • 状态
    • JSON
    • connections
    • players
    • blocks,范围限定为一个分块键

生命周期

sequenceDiagram
	participant C as Client
	participant CH as chunk

	Note over C: resolve chunk keys from world position
	loop each visible chunk
		C->>CH: connect(worldId, chunkX, chunkY, playerId)
		Note over CH: store connection metadata
	end
	C->>CH: enterChunk/addPlayer
	loop movement updates
		C->>CH: setInput(...)
		CH-->>C: snapshot
	end
	C->>CH: leaveChunk/removePlayer or disconnect
	Note over CH: remove membership and metadata
派对
主题摘要
匹配由房主创建的私人派对流程,使用派对代码并通过明确加入的方式进入。
网络代码采用服务器权威模式(基础逻辑)。服务器负责成员关系、房主权限和阶段转换。
Tick 速率无持续 tick;更新由事件驱动(join、start、finish)。
物理以大厅为先的流程中强度较低;通常无需专用物理或索引,除非你添加实时迷你游戏。

Actor

  • 键:matchmaker["main"]

  • 职责:处理派对创建与加入流程、验证加入票据并跟踪派对人数。

  • 操作

    • createParty
    • joinParty
    • verifyJoin
    • updatePartySize
    • closeParty
  • 队列

    • createParty
    • joinParty
    • verifyJoin
    • updatePartySize
    • closeParty
  • 状态

    • SQLite
    • parties
    • 用于查找派对和验证加入的 join_tickets
  • 键:match[matchId]

  • 职责:负责派对成员、房主角色、准备标记和阶段转换。

  • 操作

    • connect
    • startGame
    • finishGame
  • 队列

    • 无
  • 状态

    • JSON
    • members
    • host
    • ready state
    • phase
    • party events

生命周期

房主流程
sequenceDiagram
	participant H as Host Client
	participant MM as matchmaker
	participant M as match

	H->>MM: createParty()
	MM-->>H: {matchId, partyCode, playerId, joinToken}
	H->>M: connect(playerId, joinToken)
	M->>MM: verifyJoin(...)
	MM-->>M: allowed
	M->>MM: updatePartySize(playerCount)
	H->>M: startGame() / finishGame()
	M->>MM: closeParty(matchId)
加入者流程
sequenceDiagram
	participant J as Joiner Client
	participant MM as matchmaker
	participant M as match

	J->>MM: joinParty(partyCode)
	MM-->>J: {matchId, playerId, joinToken}
	J->>M: connect(playerId, joinToken)
	M->>MM: verifyJoin(...)
	MM-->>M: allowed / denied
	M->>MM: updatePartySize(playerCount)
排位赛
主题摘要
匹配基于 ELO 的队列配对,等待时间越长,搜索范围越大。
网络代码混合模式。客户端负责移动,并进行本地预测和插值。服务器负责投射物、命中判定、比赛结果和评分更新。
Tick 频率每秒 20 个 tick(50ms),采用固定的实时 tick,以确保节奏确定和广播频率稳定。
物理强度中等到较高;客户端负责移动,服务器负责验证,并以服务器权威方式处理战斗和命中判定。

Actor

  • 键:matchmaker["main"]

  • 职责:运行基于评分的排队、配对、分配持久化和完成事件分发。

  • 操作

    • queueForMatch
    • unqueueForMatch
    • matchCompleted
  • 队列

    • queueForMatch
    • unqueueForMatch
    • matchCompleted
  • 状态

    • SQLite
    • player_pool
    • matches
    • 包含评分窗口和连接范围的 assignments
  • 键:match[matchId]

  • 职责:运行排位赛阶段、记录比分并报告胜者。

  • 操作

    • connect
    • 游戏操作
  • 队列

    • 无
  • 状态

    • JSON
    • phase
    • players
    • score
    • winner
    • completion payload
  • 键:player[username]

  • 职责:存储玩家的规范 MMR 以及胜负档案。

  • 操作

    • initialize
    • getRating
    • applyMatchResult
  • 队列

    • 无
  • 状态

    • JSON
    • rating
    • wins
    • losses
    • match counters
  • 键:leaderboard["main"]

  • 职责:存储并提供排名靠前的玩家。

  • 操作

    • updatePlayer
  • 队列

    • 无
  • 状态

    • SQLite
    • 排行榜分数记录
    • 排名列表顺序

生命周期

sequenceDiagram
	participant C as Client
	participant MM as matchmaker
	participant P as player
	participant M as match
	participant LB as leaderboard

	C->>MM: queueForMatch(username)
	MM->>P: initialize/getRating
	P-->>MM: rating
	Note over MM: store queue row + retry pairing
	MM->>M: create(matchId, assigned players)
	MM-->>C: assignmentReady
	C->>M: connect(username)
	M->>MM: matchCompleted(...)
	MM->>P: applyMatchResult(...)
	MM->>LB: updatePlayer(...)
	Note over MM: remove matches + assignments rows
回合制
主题摘要
匹配采用相同模式进行异步私人邀请和公共队列配对。
网络代码服务器权威(基本逻辑)。客户端可以先拟定移动,再提交。服务器负责回合归属、已提交的移动日志、回合顺序和完成状态。
Tick 频率无连续 tick;移动提交和回合转换会触发更新。
物理强度很低;没有实时物理循环,仅执行离散规则验证。索引是可选的,主要用于提升棋盘或查询的便利性,以适应大规模场景。

Actor

  • 键:matchmaker["main"]

  • 职责:处理异步比赛的私人邀请和公共队列配对。

  • 操作

    • createGame
    • joinByCode
    • queueForMatch
    • unqueueForMatch
    • closeMatch
  • 队列

    • createGame
    • joinByCode
    • queueForMatch
    • unqueueForMatch
    • closeMatch
  • 状态

    • SQLite
    • matches
    • player_pool
    • 用于邀请和队列映射的 assignments
  • 键:match[matchId]

  • 职责:负责棋盘状态、回合顺序、移动验证和最终结果。

  • 操作

    • connect
    • makeMove
  • 队列

    • 无
  • 状态

    • JSON
    • board
    • turns
    • players
    • connection presence
    • result

生命周期

公共队列
sequenceDiagram
	participant A as Client A
	participant B as Client B
	participant MM as matchmaker
	participant M as match

	A->>MM: queueForMatch()
	B->>MM: queueForMatch()
	Note over MM: pair first two queued players
	MM->>M: create(matchId) + seed X/O players
	MM-->>A: assignment/match info
	MM-->>B: assignment/match info
	A->>M: connect(playerId)
	B->>M: connect(playerId)
	A->>M: makeMove()
	B->>M: makeMove()
	opt all players disconnected for timeout
		Note over M: destroy after idle timeout
	end
	M->>MM: closeMatch(matchId)
私人邀请
sequenceDiagram
	participant A as Client A
	participant B as Client B
	participant MM as matchmaker
	participant M as match

	A->>MM: createGame()
	MM-->>A: {matchId, playerId, inviteCode}
	B->>MM: joinByCode(inviteCode)
	MM->>M: create(matchId) + seed X/O players
	MM-->>A: assignment/match info
	MM-->>B: assignment/match info
	A->>M: connect(playerId)
	B->>M: connect(playerId)
	A->>M: makeMove()
	B->>M: makeMove()
	M->>MM: closeMatch(matchId)
放置类游戏
主题概要
匹配没有匹配器;每位玩家使用一个独立的玩家 Actor 和一个共享的排行榜 Actor。
网络代码由服务器权威控制(基础逻辑)。客户端负责 UI 和建造意图。服务器负责资源、生产速率、建筑验证和排行榜总分。
计时频率不使用连续计时;使用 c.schedule.after(...) 设置较粗的时间间隔,并根据经过的实际时间计算离线追赶。
物理标准放置类游戏循环无需物理;状态转换是离散的(build、collect、upgrade),不需要空间索引。

Actors

  • 键:world[playerId]

  • 职责:管理一位玩家的进度、建筑、生产调度和状态更新。

  • 操作

    • initialize
    • build
    • collectProduction
  • 队列

    • 无
  • 状态

    • JSON
    • 每位玩家的建筑
    • resources
    • timers
    • progression state
  • 键:leaderboard["main"]

  • 职责:存储全局分数并提供排行榜更新。

  • 操作

    • updateScore
  • 队列

    • updateScore
  • 状态

    • SQLite
    • 以玩家为键的 scores 表
    • 当前排行榜总分

生命周期

sequenceDiagram
	participant C as Client
	participant W as world
	participant LB as leaderboard

	C->>W: getOrCreate(playerId) + initialize()
	Note over W: seed state + schedule collection
	W-->>C: stateUpdate
	loop gameplay loop
		C->>W: build() / collectProduction()
		W->>LB: updateScore(...)
	Note over LB: upsert scores
	LB-->>C: leaderboardUpdate
	W-->>C: stateUpdate
	end

参考索引

Actors
CLI
客户端
指南
部署
常规
自托管

评论

支持 Markdown