本文档提供所有 MiaoNet 数据包类型的完整参考。
graph TB
PKT[数据包] --> CONN[连接相关]
PKT --> PLAYER[玩家相关]
PKT --> CHAT[聊天相关]
PKT --> SYS[系统相关]
CONN --> CONN1[PacketClientInitial]
CONN --> CONN2[PacketGotKicked]
PLAYER --> PLAYER1[玩家加入/离开]
PLAYER --> PLAYER2[状态同步]
PLAYER --> PLAYER3[位置同步]
PLAYER --> PLAYER4[表情系统]
PLAYER1 --> P1[PacketPlayerJoined]
PLAYER1 --> P2[PacketPlayerLeft]
PLAYER2 --> P3[PacketPlayerFrame]
PLAYER2 --> P4[PacketPlayerStateFlags]
PLAYER2 --> P5[PacketPlayerGraphicsUpdate]
PLAYER3 --> P6[PacketPlayerMapChanged]
PLAYER3 --> P7[PacketPlayerMapChangedResponse]
PLAYER3 --> P8[PacketPlayerMapRoomChanged]
PLAYER3 --> P9[PacketPlayerChannelMove]
PLAYER4 --> P10[PacketEmote]
PLAYER4 --> P11[PacketEmoteText]
PLAYER4 --> P12[PacketSendEmote]
PLAYER4 --> P13[PacketSendEmoteText]
CHAT --> C1[PacketChatMessage]
CHAT --> C2[PacketSendChatMessage]
CHAT --> C3[PacketChatCommand]
SYS --> S1[PacketRequest]
SYS --> S2[PacketResponse]
SYS --> S3[PacketPlayerNotification]
- PacketClientInitial - 客户端初始化数据
- PacketGotKicked - 被踢出通知
- PacketPlayerJoined - 玩家加入通知
- PacketPlayerLeft - 玩家离开通知
- PacketPlayerFrame - 玩家帧数据
- PacketPlayerStateFlags - 玩家状态标志
- PacketPlayerGraphicsUpdate - 玩家外观更新
- PacketPlayerMapChanged - 地图变更通知
- PacketPlayerMapChangedResponse - 地图变更响应
- PacketPlayerMapRoomChanged - 房间变更通知
- PacketPlayerChannelMove - 频道切换
- PacketEmote - 表情通知
- PacketEmoteText - 文字表情通知
- PacketSendEmote - 发送表情
- PacketSendEmoteText - 发送文字表情
- PacketChatMessage - 聊天消息
- PacketSendChatMessage - 发送聊天消息
- PacketChatCommand - 聊天命令
- PacketRequest - 请求基类
- PacketResponse - 响应基类
- PacketPlayerNotification - 玩家通知基类
方向: 服务器 → 客户端
时机: 握手成功后立即发送
用途: 向客户端提供初始化所需的所有数据
结构:
public sealed class PacketClientInitial : IPacket<PacketClientInitial>
{
public PlayerInfo SelfPlayerInfo { get; } // 自己的玩家信息
public IReadOnlyCollection<ChannelInfo> Channels { get; } // 频道列表
public IReadOnlyCollection<Player> Players { get; } // 当前在线玩家列表
public struct Player
{
public int ChannelID { get; } // 所在频道
public PlayerInfo PlayerInfo { get; } // 玩家基本信息
public PlayerLocation Location { get; } // 位置信息
public PlayerGraphicsInfo? GraphicsInfo { get; } // 图形信息(可选)
public PlayerState? State { get; } // 状态信息(可选)
}
}序列化格式:
+----------------+------------------+------------------+
| SelfPlayerInfo | Channels (Array) | Players (Array) |
+----------------+------------------+------------------+
Player 序列化:
+--------+-------------+------------+----------+--------------------+-------+
| Flags | GraphicsInfo| State |ChannelID | PlayerInfo |Location|
| 1 byte | (条件) | (条件) | 4 bytes | (PlayerInfo 格式) |(Loc格式)|
+--------+-------------+------------+----------+--------------------+-------+
Flags:
Bit 0: HasGraphicsInfo
Bit 1: HasState
使用场景:
- 客户端连接成功后
- 初始化玩家列表
- 初始化频道列表
- 缓存已知玩家的图形信息
流程:
sequenceDiagram
Client->>Server: HandshakeData
Server->>Server: 验证并创建玩家
Server->>Client: PacketClientInitial
Client->>Client: 初始化 MiaoNetContext
Client->>Client: 填充玩家列表
Client->>Client: 设置当前频道
方向: 服务器 → 客户端
时机: 玩家被踢出时
用途: 通知客户端被踢出的原因
结构:
public sealed class PacketGotKicked : IPacket<PacketGotKicked>
{
// 当前未完全实现
}
public enum KickedReason : byte
{
Manually, // 手动踢出
InvalidPacket, // 无效的数据包
InvalidPacketWithState // 带状态的无效数据包
}状态: 🚧 部分实现
方向: 服务器 → 客户端
时机: 新玩家加入服务器时
用途: 通知所有客户端有新玩家加入
结构:
public sealed class PacketPlayerJoined : IPacket<PacketPlayerJoined>
{
public int ChannelID { get; } // 玩家所在频道
public PlayerInfo PlayerInfo { get; } // 玩家信息
}序列化格式:
+-----------+-------------+
| ChannelID | PlayerInfo |
| 4 bytes | (Info格式) |
+-----------+-------------+
接收处理:
- 将玩家添加到在线玩家列表
- 如果在同频道,更新玩家列表 UI
- 可选:显示 "XXX 加入了游戏" 消息
方向: 服务器 → 客户端
时机: 玩家离开服务器时
用途: 通知所有客户端有玩家离开
结构:
public sealed class PacketPlayerLeft : PacketPlayerNotification, IPacket<PacketPlayerLeft>
{
public int PlayerID { get; } // 离开的玩家 ID
public enum LeftReason
{
Manually, // 主动断开
Inactive, // 超时无响应
Interrupted // 连接中断
}
public LeftReason Reason { get; set; } // 离开原因
}序列化格式:
+----------+
| PlayerID |
| 4 bytes |
+----------+
注: Reason 当前未序列化
接收处理:
- 从在线玩家列表移除玩家
- 销毁对应的 Ghost 实体(如果存在)
- 更新玩家列表 UI
- 可选:显示 "XXX 离开了游戏" 消息
方向: 双向(主要是客户端 → 服务器 → 其他客户端)
频率: ~60 次/秒
用途: 实时同步玩家的位置、动画和状态
结构:
public sealed class PacketPlayerFrame : IPacket<PacketPlayerFrame>
{
public Vector2 Position { get; } // 玩家位置 (8 bytes)
public ushort AnimationID { get; } // 动画 ID (2 bytes)
public ushort AnimationFrame { get; } // 动画帧 (2 bytes)
public Vector2 Scale { get; } // 缩放 (8 bytes)
public FrameFlags Flags { get; } // 标志位 (2 bytes)
public byte Dashes { get; set; } // 冲刺数 (1 byte, 条件)
[Flags]
public enum FrameFlags : ushort
{
FacingLeft = 1 << 0, // 朝向左
StartDash = 1 << 1, // 开始冲刺
EndDash = 1 << 2, // 结束冲刺
DashesChange = 1 << 3 // 冲刺数改变
}
}序列化格式:
+----------+-------+----------+-------+-------+--------+
| Position | AniID | AniFrame | Scale | Flags | Dashes |
| 8 bytes | 2 B | 2 B | 8 B | 2 B | 1 B |
+----------+-------+----------+-------+-------+--------+
总大小: 22-23 字节 (Dashes 条件包含)
优化点:
- 只在
DashesChange标志为 true 时才包含Dashes字段 - 使用
FrameFlags打包多个布尔值 - 总大小约 26 字节,适合高频传输
发送时机 (客户端):
- 每帧更新(如果状态有显著变化)
- 可以跳帧以节省带宽
接收处理 (客户端):
- 查找对应的
MiaoNetGhost实体 - 更新实体位置和动画
- 处理冲刺状态变化
- 插值以平滑显示
方向: 客户端 → 服务器 → 其他客户端
频率: 按需
用途: 同步不那么时间敏感的状态标志
结构:
public sealed class PacketPlayerStateFlags : IPacket<PacketPlayerStateFlags>
{
[Flags]
public enum StateFlags : ushort
{
PlayerDied = 1 << 0, // 玩家死亡
PlayerRespawning = 1 << 1 // 玩家重生中
// 可扩展更多标志
}
public StateFlags Flags { get; }
}序列化格式:
+-------+
| Flags |
| 2 B |
+-------+
使用场景:
- 玩家死亡
- 玩家重生
- 未来可能:头发被吹动、精灵模式变化等
方向: 客户端 → 服务器 → 其他客户端
频率: 按需(玩家更改外观时)
用途: 更新玩家的外观信息
结构:
public sealed class PacketPlayerGraphicsUpdate : IPacket<PacketPlayerGraphicsUpdate>
{
public enum UpdateFlags : byte
{
HairDash0, // 更新 0 冲刺头发
HairDash1, // 更新 1 冲刺头发
HairDash2, // 更新 2 冲刺头发
HairFeather, // 更新飞行头发
SpriteMode // 更新精灵模式
}
// 实现细节待定
}状态: 🚧 未完全实现
方向: 客户端 → 服务器
时机: 玩家切换地图或进入/离开调试地图时
用途: 通知服务器玩家的位置变化
结构:
public sealed class PacketPlayerMapChanged : IPacket<PacketPlayerMapChanged>
{
public PlayerLocation Location { get; set; } // 新位置
public PlayerState? InitialState { get; } // 初始状态(可选)
}序列化格式:
+----------+----------+---------------+
| Location | HasState | InitialState |
| (Loc格式)| 1 byte | (State格式) |
+----------+----------+---------------+
位置类型:
InMap: MapSid ≠ "", MapRoom ≠ "" - 在普通地图的房间中InDebugMap: MapSid ≠ "", MapRoom = "" - 在调试地图None: MapSid = "", MapRoom = "" - 不在地图(主菜单等)
发送时机:
- 进入新地图
- 离开地图
- 进入/离开调试地图
方向: 服务器 → 客户端
时机: 服务器处理 PacketPlayerMapChanged 后
用途: 向客户端发送同房间的其他玩家信息
结构:
public sealed class PacketPlayerMapChangedResponse : IPacket<PacketPlayerMapChangedResponse>
{
public struct Player : IRefBinarySerializable<Player>
{
public int PlayerID { get; } // 玩家 ID
public PlayerState State { get; } // 玩家状态
public PlayerGraphicsInfo? GraphicsInfo { get; } // 图形信息(可选)
}
public IReadOnlyCollection<Player> Players { get; }
}序列化格式:
+------------------+
| Players (Array) |
+------------------+
每个 Player:
+----------+-------+----------------+
| PlayerID | State | GraphicsInfo |
| 4 bytes |(State)| (条件包含) |
+----------+-------+----------------+
服务器逻辑:
- 确定同房间的其他玩家
- 对于每个玩家:
- 必定包含
PlayerState - 如果客户端未缓存,包含
PlayerGraphicsInfo
- 必定包含
- 发送响应
客户端处理:
- 接收玩家列表
- 为每个玩家创建
MiaoNetGhost实体 - 缓存新的
PlayerGraphicsInfo
方向: 客户端 → 服务器 → 其他客户端
时机: 玩家在同一地图内切换房间时
用途: 快速通知房间变化,无需完整的地图变更流程
结构:
public sealed class PacketPlayerMapRoomChanged : IPacket<PacketPlayerMapRoomChanged>
{
public string MapRoom { get; } // 新房间名称
}序列化格式:
+----------+
| MapRoom |
| (string) |
+----------+
注意:
- 仅用于房间变化(MapRoom 变化)
- 不用于进入/离开调试地图(应使用
PacketPlayerMapChanged) - 地图切换也使用
PacketPlayerMapChanged
方向: 双向
时机: 玩家切换频道时
用途: 请求/通知频道切换
结构:
public sealed class PacketPlayerChannelMove : IPacket<PacketPlayerChannelMove>
{
public int ChannelID { get; } // 目标频道 ID
}序列化格式:
+-----------+
| ChannelID |
| 4 bytes |
+-----------+
流程:
sequenceDiagram
Client->>Server: PacketPlayerChannelMove (目标频道)
Server->>Server: 验证频道
alt 成功
Server->>原频道玩家: PacketPlayerLeft
Server->>Client: PacketPlayerChannelMove (确认)
Server->>新频道玩家: PacketPlayerJoined
else 失败
Server->>Client: PacketResponse (错误)
end
方向: 服务器 → 客户端 (广播)
时机: 玩家发送表情后
用途: 显示玩家的表情气泡
结构:
public sealed class PacketEmote : PacketPlayerNotification, IPacket<PacketEmote>
{
public int PlayerID { get; } // 发送者
public EmoteData Emote { get; } // 表情数据
}序列化格式:
+----------+-------+
| PlayerID | Emote |
| 4 bytes |(Emote)|
+----------+-------+
接收处理:
- 查找玩家的 Ghost 实体
- 在玩家上方创建表情气泡
- 播放表情动画
方向: 客户端 → 服务器
时机: 玩家发送表情时
用途: 请求发送表情
结构:
public sealed class PacketSendEmote : IPacket<PacketSendEmote>
{
public EmoteData Emote { get; } // 要发送的表情
}服务器处理:
- 验证表情 ID 是否有效
- 广播
PacketEmote给同房间玩家
方向: 服务器 → 客户端 (广播)
时机: 玩家发送文字表情后
用途: 显示玩家的文字气泡
结构:
public sealed class PacketEmoteText : PacketPlayerNotification, IPacket<PacketEmoteText>
{
public int PlayerID { get; } // 发送者
public string Text { get; } // 文字内容
}限制:
- 文字长度限制(具体值待定)
- 可能的内容过滤
方向: 客户端 → 服务器
时机: 玩家发送文字表情时
用途: 请求发送文字表情
结构:
public sealed class PacketSendEmoteText : IPacket<PacketSendEmoteText>
{
public string Text { get; } // 要发送的文字
}方向: 服务器 → 客户端
时机: 有聊天消息时
用途: 传递聊天消息
结构:
public sealed class PacketChatMessage : IPacket<PacketChatMessage>
{
public ChatMessageType Type { get; set; } // 消息类型
public int? SourcePlayer { get; set; } // 发送者 ID(可选)
public string Content { get; set; } // 消息内容
}
public enum ChatMessageType : byte
{
Chat, // 普通聊天
PrivateMessage, // 私聊
Server, // 服务器消息
ServerChat // 服务器聊天
}序列化格式:
+------+-----------+--------------+---------+
| Type | HasPlayer | SourcePlayer | Content |
| 1 B | 1 B | 4 B (条件) | string |
+------+-----------+--------------+---------+
消息类型说明:
| 类型 | SourcePlayer | 颜色 | 用途 |
|---|---|---|---|
| Chat | 必需 | 默认 | 频道聊天 |
| PrivateMessage | 必需 | 特殊色 | 私聊 |
| Server | null | 系统色 | 系统消息 |
| ServerChat | 可选 | 默认 | 服务器代发 |
客户端处理:
- 根据类型选择显示样式
- 查找发送者名称(如果有)
- 添加到聊天历史
- 显示在聊天框
方向: 客户端 → 服务器
时机: 玩家发送聊天消息时
用途: 发送聊天消息
结构:
public sealed class PacketSendChatMessage : IPacket<PacketSendChatMessage>
{
public string Content { get; } // 消息内容
}服务器处理:
- 检查内容是否为命令(以
/开头) - 如果是命令,解析并执行
- 如果是聊天,广播给同频道玩家
- 可能的过滤和验证
方向: 客户端 → 服务器
时机: 玩家执行命令时
用途: 执行游戏内命令
结构:
public sealed class PacketChatCommand : IPacket<PacketChatCommand>
{
public string Command { get; } // 命令名
public IReadOnlyList<CommandSegment> Args { get; } // 参数列表
}状态: 🚧 部分实现
用途: 请求-响应模式的基类
结构:
public abstract class PacketRequest<TResponse> : IPacket
where TResponse : PacketResponse
{
public int RequestID { get; set; } // 请求 ID,用于匹配响应
}使用:
- 子类继承此类
- 发送请求时分配唯一的 RequestID
- 等待对应 RequestID 的响应
用途: 响应的基类
结构:
public abstract class PacketResponse : IPacket
{
public int RequestID { get; set; } // 对应的请求 ID
public PacketResponse(int requestID)
{
RequestID = requestID;
}
}使用:
- 子类继承此类
- ResponseID 匹配原始请求的 RequestID
- 客户端根据 RequestID 路由到正确的回调
用途: 带玩家 ID 的通知基类
结构:
public abstract class PacketPlayerNotification
{
public int PlayerID { get; } // 相关的玩家 ID
public virtual void Serialize(ref RefBinaryWriter writer)
=> writer.Write(PlayerID);
}继承者:
PacketPlayerLeftPacketEmotePacketEmoteText
泛型版本:
public sealed class PacketPlayerNotification<TPacket> : IPacket<PacketPlayerNotification<TPacket>>
where TPacket : IPacket<TPacket>
{
public int PlayerID { get; }
public TPacket Packet { get; }
}用于包装任意数据包并附加玩家 ID。
| 数据包 | 最小大小 | 最大大小 | 平均大小 |
|---|---|---|---|
| PacketPlayerFrame | 22 B | 23 B | ~22 B |
| PacketChatMessage | ~10 B | ~500 B | ~50 B |
| PacketPlayerJoined | ~15 B | ~50 B | ~30 B |
| PacketPlayerLeft | 4 B | 4 B | 4 B |
| PacketEmote | ~20 B | ~50 B | ~30 B |
| PacketPlayerMapChanged | ~20 B | ~100 B | ~40 B |
| PacketClientInitial | 100 B | 10 KB+ | ~2 KB |
graph LR
C[客户端] -->|PacketSendChatMessage| S[服务器]
C -->|PacketPlayerFrame| S
C -->|PacketPlayerMapChanged| S
C -->|PacketSendEmote| S
C -->|PacketPlayerChannelMove| S
S -->|PacketClientInitial| C
S -->|PacketChatMessage| C
S -->|PacketPlayerJoined| C
S -->|PacketPlayerLeft| C
S -->|PacketEmote| C
S -->|PacketPlayerFrame| C
S -->|PacketPlayerMapChangedResponse| C
S -->|PacketGotKicked| C
| 状态 | 说明 |
|---|---|
| ✅ | 完全实现并在使用 |
| 🚧 | 部分实现或待完善 |
| ❌ | 未实现 |
| 数据包 | 状态 |
|---|---|
| PacketClientInitial | ✅ |
| PacketPlayerJoined | ✅ |
| PacketPlayerLeft | ✅ |
| PacketPlayerFrame | ✅ |
| PacketChatMessage | ✅ |
| PacketSendChatMessage | ✅ |
| PacketEmote | ✅ |
| PacketSendEmote | ✅ |
| PacketEmoteText | ✅ |
| PacketSendEmoteText | ✅ |
| PacketPlayerMapChanged | ✅ |
| PacketPlayerMapChangedResponse | ✅ |
| PacketPlayerMapRoomChanged | ✅ |
| PacketPlayerChannelMove | ✅ |
| PacketPlayerStateFlags | ✅ |
| PacketPlayerGraphicsUpdate | 🚧 |
| PacketGotKicked | 🚧 |
| PacketChatCommand | 🚧 |
| PacketRequest/Response | 🚧 |
- 架构设计 (Architecture.md) - 系统架构
- 通信协议 (Protocol.md) - 协议详解
- 数据结构 (DataStructures.md) - 数据结构参考