# Seed Framework **Repository Path**: cuiz_playgame/seed-framework ## Basic Information - **Project Name**: Seed Framework - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2026-04-27 - **Last Updated**: 2026-05-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Seed Framework Seed Framework 是一个轻量级确定性联机框架,适合做小型联机游戏、玩法验证 Demo、回放验证和一致性校验。 它当前的定位很明确: - 服务器负责连接、匹配、固定帧广播、断线重连窗口。 - 客户端负责上传玩家输入、接收帧包、驱动本地确定性模拟。 - 游戏本身只需要处理“如何把输入转成命令”和“如何推进自己的 simulation”。 ## 目录结构 ```text proto/ 协议单一来源 client/SFClient/ 客户端源码包(含 Client/Abstractions、Client/Internal) server/SFServer/ 服务端源码包(含 Server/Abstractions、Server/Internal) samples/ 示例项目 docs/ 补充文档 ``` 重点说明: - `proto/seed.proto` 是唯一协议源文件。 - `client` 和 `server` 不再各自维护一份 proto。 - 修改协议后,统一运行 `proto/generate-csharp.bat` 生成 C# 文件到两边的 `Protocol/Generated`。 ## 联机层架构(面向接口) 联机逻辑按**小接口**拆分,由 `SfClientSession` / `SfRelayServer` 在构造函数中显式装配默认实现;对外保持稳定入口,对内可替换组件(例如自定义传输层)。不采用大而全的「万能 Net」单接口,避免职责再次堆叠。 **客户端**(命名空间多为 `SF.ClientRuntime`): | 位置 | 作用 | |------|------| | [SfClientSession.cs](client/SFClient/Client/SfClientSession.cs) | 门面:装配、编排收包、对外 API 与事件 | | `client/SFClient/Client/Abstractions/` | 契约:`IClientTransport`、`IClientProtocolChannel`、`IClientOutGameFlow`、`IClientFrameSync`、`IClientReconnect`,以及可选旁路 `IReplayRecorder`、`IClientHashChecker` | | `client/SFClient/Client/Internal/` | 默认实现:TCP、长度前缀 + Proto、局外/局内/重连 | **服务端**(命名空间多为 `SF.ServerRuntime`): | 位置 | 作用 | |------|------| | [SfRelayServer.cs](server/SFServer/Server/SfRelayServer.cs) | 门面:编排 `RunAsync` / `StopAsync` | | `server/SFServer/Server/Abstractions/` | 契约:`IServerTransport`、`IServerClientChannel`、`IServerOutGameFlow`、`IRoomManager`、`IServerFrameScheduler`、`IServerFrameSync`、`IReconnectStore` 等 | | `server/SFServer/Server/Internal/` | 默认实现:TCP 监听、每连接协议通道、房间与帧广播、重连窗口 | **替换传输**:默认 TCP;若换 KCP、WebSocket 等,实现 `IClientTransport` / `IServerTransport`,并在实现上保证与文档一致的**可靠、有序**字节流语义(局外与帧同步不感知 TCP/UDP 差异),再通过高级构造注入: ```csharp var client = new SfClientSession(options, new MyClientTransport()); var server = new SfRelayServer(options, new MyServerTransport()); ``` 日常接入仍优先使用 `new SfClientSession(options)` 与 `new SfRelayServer(options)`。 ## 协议结构 当前协议分成两大类消息: 1. 局外消息 连接、匹配、心跳、重连、通知等都走 `OutOfGameMessage` 2. 局内消息 游戏帧同步统一走 `InGameFramePackage` 顶层结构: ```proto message ClientPacket { oneof payload { ClientOutOfGameMessage out_of_game = 1; InGameFramePackage in_game = 2; } } message ServerPacket { oneof payload { ServerOutOfGameMessage out_of_game = 1; InGameFramePackage in_game = 2; } } ``` 局内帧结构: ```proto message InGameFramePackage { int32 frame_index = 1; repeated GameCommand commands = 2; } message GameCommand { int32 player_id = 1; int32 command_flag = 2; oneof command_data { EmptyCommandData empty = 3; RawPayloadCommandData raw_payload = 4; } } ``` 设计原则: - 顶层是否“局内/局外”由 `oneof` 决定,不额外加重复 flag。 - `OutOfGameMessage` 内部具体是哪种局外消息,也由 `oneof` 决定。 - 只有 `GameCommand` 保留 `command_flag`,用于区分移动、攻击、交互等游戏命令。 - `raw_payload` 留给具体游戏自己定义 proto 或自定义编解码逻辑。 ## 协议生成 修改 [proto/seed.proto](proto/seed.proto) 后,运行: ```bat proto\generate-csharp.bat ``` 脚本会把生成结果写到: ```text client/SFClient/Protocol/Generated/Seed.cs server/SFServer/Protocol/Generated/Seed.cs ``` 要求: - `protoc` 已加入环境变量 - 或者命令行中可以直接执行 `protoc` 这样做的目的是避免客户端和服务端协议各改各的,最终不一致。 ## 快速运行 先启动最小服务端示例: ```powershell dotnet run --project samples\ConnectionTest\Server\ConnectionTest.Server.csproj ``` 再启动两个客户端: ```powershell dotnet run --project samples\ConnectionTest\Client\ConnectionTest.Client.csproj ``` 运行后流程是: 1. 客户端连接服务器 2. 服务器分配 `ClientId` 3. 客户端发送 `Ready` 4. 服务端匹配到足够玩家后下发 `MatchReady` 5. 服务器开始按固定帧间隔广播 `InGameFramePackage` ## 客户端接入 把下面目录复制到你的客户端工程中: ```text client/SFClient ``` Unity 项目通常放到: ```text Assets/Scripts/SFClient ``` 客户端门面是 [SfClientSession.cs](client/SFClient/Client/SfClientSession.cs);领域契约见上文「联机层架构」中的 `Client/Abstractions`。 最小接入示例: ```csharp using SF.ClientRuntime; var client = new SfClientSession(new SfClientSessionOptions { IP = "127.0.0.1", Port = 5000, ClientVersion = "my-game", SubmitLeadFrames = 2, }); client.Joined += clientId => { Console.WriteLine($"client id: {clientId}"); }; client.Matched += match => { Console.WriteLine( $"matched, players={string.Join(",", match.PlayerIds)}, seed={match.RandomSeed}, interval={match.FrameIntervalMs}ms"); }; client.FrameReceived += (_, e) => { // 在这里把 e.Package 转成你的游戏输入,然后推进 simulation.Step(...) }; client.NoticeReceived += message => { Console.WriteLine(message); }; await client.ConnectAsync(); await client.ReadyAsync(); ``` 如果要指定地址连接: ```csharp await client.ConnectAsync("127.0.0.1", 5000); ``` ## 客户端常用流程 标准使用顺序: 1. 创建 `SfClientSession` 2. `ConnectAsync` 3. 等待 `Joined` 4. `ReadyAsync` 5. 等待 `Matched` 6. 开始定时上传玩家输入 7. 处理 `FrameReceived` 8. 驱动本地 simulation 上传输入: ```csharp byte[] payload = EncodeMyGameCommand(command); await client.SubmitLatestInputAsync(payload); ``` 如果你已经有明确的游戏命令编号,也可以直接传 `command_flag`: ```csharp await client.SubmitInputAsync(commandFlag, payload); ``` 说明: - `command_flag` 是给游戏命令做快速分类的。 - `payload` 是游戏自己的命令数据。 - 如果你的游戏已经有自己的 proto,可以把自己的命令 proto 序列化成 `payload`。 ## 游戏侧命令建议 推荐分层: ```text Seed Framework 外层协议 -> GameCommand(command_flag + raw_payload) 游戏自己的协议 -> MoveCommand / AttackCommand / UseItemCommand / ... ``` 也就是说: 1. 游戏先把自己的命令序列化成 `byte[]` 2. 放进 `GameCommand.raw_payload` 3. 通过帧包发送 4. 接收后再由游戏自己的 codec 或 proto 去反序列化 这样框架保持通用,游戏逻辑也不会和框架协议耦合死。 ## 服务端接入 把下面目录复制到你的服务端工程中: ```text server/SFServer ``` 服务端门面是 [SfRelayServer.cs](server/SFServer/Server/SfRelayServer.cs);领域契约见 `Server/Abstractions`。 最小服务端示例: ```csharp using System.Net; using SF.ServerRuntime; var server = new SfRelayServer(new SfRelayServerOptions { IP = IPAddress.Any, Port = 5000, MaxPlayersPerRoom = 2, FrameIntervalMs = 100, }); await server.RunAsync(); ``` 服务端当前负责: - 分配客户端 ID - 匹配 `Ready` 的客户端 - 为每局生成随机种子 - 按固定帧间隔广播 `InGameFramePackage` - 维护重连窗口和待补发帧 ## 服务器帧同步逻辑 当前实现是: 1. 客户端提交当前输入 2. 服务器把输入记入当前帧 3. 到帧时钟时,服务器发布下一帧 4. 没有输入的玩家会被填充空命令 5. 客户端收到帧后推进本地 simulation 这个模型适合: - 贪吃蛇 - 地牢合作 - 轻量动作 - 战棋 - 小型房间制玩法 它不是强竞技回滚框架,目标是轻量、稳定、易接入。 ## 重连 当前已经支持断线重连窗口: - 客户端断开后,服务器会在一定时间内保留该客户端的房间上下文 - 客户端重新连接后发送 `ReconnectRequest` - 服务端返回 `ReconnectAccepted` 和缺失的待补帧 - 客户端按待补帧恢复本地帧缓冲 客户端重连 API 在 [SfClientSession.cs](client/SFClient/Client/SfClientSession.cs)(内部由 `IClientReconnect` 等组件实现): ```csharp await client.ReconnectAsync(); ``` ## 回放 通用回放能力在: ```text client/SFClient/Replay ``` 主要类型: - `ReplayRecorder` - `ReplaySessionRecord` - `ReplayPlayer` - `JsonReplayStorage` 典型流程: 1. 游戏开始时记录初始快照 2. 每次收到帧包时录制 `FramePackage` 3. 对局结束后序列化保存 4. 回放时重新读出帧包,重新驱动 simulation ## 推荐接入方式 如果你准备做 Unity 地牢联机,推荐这样分: 1. `SFClient` 负责网络、匹配、帧包收发、重连、回放基础设施 2. `DungeonSimulation` 纯 C# 确定性逻辑,不依赖 Unity 物理 3. `DungeonCommand.proto` 地牢游戏自己的命令定义 4. `DungeonCommandCodec` 游戏命令和 `raw_payload` 的互转 这样后面换成别的游戏时,只需要替换游戏命令和 simulation,不需要推翻框架协议。 ## 示例项目 当前示例: - `samples/ConnectionTest/Server` - `samples/ConnectionTest/Client` - `samples/Snake/Server` - `samples/Snake/Client` 其中: - `ConnectionTest` 适合看最小联机链路 - `Snake` 适合看完整的帧同步、输入转命令、回放集成方式