项目地址:https://github.com/beijian128/framesync
在线体验:点击此处传送- 帧同步联机Demo

服务端核心代码
// FrameSyncInstance 帧同步系统的全局实例
var FrameSyncInstance = NewFrameSync()
// FrameSync 管理多人游戏的帧同步逻辑
type FrameSync struct {
// curFrame 存储当前帧所有玩家的操作指令
// key: 玩家连接会话, value: 包含该玩家所有操作指令的结构体
// 注意:暂不考虑历史帧存储(不支持断线重连和回放功能)
curFrame map[appframe.GateSession]*cmsg.PlayerInput
// ticker 控制帧同步的定时器
ticker *time.Ticker
// userCnt 玩家ID计数器,用于分配唯一ID
userCnt uint64
}
// NewFrameSync 创建并初始化帧同步实例
func NewFrameSync() *FrameSync {
return &FrameSync{
curFrame: make(map[appframe.GateSession]*cmsg.PlayerInput),
}
}
// OnInputCommand 处理玩家输入指令
// session: 发送指令的玩家会话
// msg: 包含移动方向的输入指令消息
func (fs *FrameSync) OnInputCommand(session appframe.GateSession, msg *cmsg.InputCommand) {
// 从会话管理器获取完整的会话对象
s, _ := SessionMgrInstance.getSession(session.ID())
// 如果该玩家在当前帧还没有输入记录,则初始化
if fs.curFrame[session] == nil {
fs.curFrame[session] = &cmsg.PlayerInput{
PlayerId: s.userid, // 玩家ID
Commands: []*cmsg.InputCommand{}, // 指令列表
}
}
// 将新指令添加到该玩家的当前帧输入中
fs.curFrame[session].Commands = append(fs.curFrame[session].Commands, msg)
// 移动速度常量
speed := int32(3)
// 根据输入指令更新玩家位置
if msg.Down {
s.y += speed // 向下移动
}
if msg.Up {
s.y -= speed // 向上移动
}
if msg.Left {
s.x -= speed // 向左移动
}
if msg.Right {
s.x += speed // 向右移动
}
}
// OnAddSession 处理新玩家连接
// ss: 新加入的玩家会话
func (fs *FrameSync) OnAddSession(ss *session) {
// 分配唯一玩家ID
fs.userCnt++
ss.userid = fs.userCnt
// 设置随机初始位置(1-100范围内)
ss.y = rand.Int31n(100) + 1
ss.x = rand.Int31n(100) + 1
// 为玩家分配随机颜色
ss.color = generateRandomColor()
// 通知所有现有玩家有新玩家加入
SessionMgrInstance.execByEverySession(func(s *session) {
// 向每个现有会话发送所有玩家信息(包括新玩家)
SessionMgrInstance.execByEverySession(func(s2 *session) {
s.SendMsg(&cmsg.PlayerEnter{
PlayerId: s2.userid, // 玩家ID
X: s2.x, // X坐标
Y: s2.y, // Y坐标
Color: s2.color, // 玩家颜色
})
})
})
}
// StartSync 启动帧同步循环
// ctx: 上下文对象,用于控制协程退出
func (fs *FrameSync) StartSync(ctx context.Context) {
// 创建16毫秒间隔的定时器(约60FPS)
fs.ticker = time.NewTicker(16 * time.Millisecond)
go func() {
for {
select {
case <-ctx.Done(): // 收到退出信号
fs.ticker.Stop() // 停止定时器
logrus.Debug("FrameSync exit") // 记录调试日志
return // 退出协程
case <-fs.ticker.C: // 定时器触发
AppInstance.Post(func() {
// 向所有会话广播当前帧的所有玩家输入
SessionMgrInstance.execByEverySession(func(s *session) {
for _, input := range fs.curFrame {
s.SendMsg(input) // 发送玩家输入数据
}
})
clear(fs.curFrame) // 清空当前帧数据
})
}
}
}()
}
// RemoveSession 处理玩家退出
// sess: 要移除的玩家会话
func (fs *FrameSync) RemoveSession(sess *session) {
delete(fs.curFrame, sess.GateSession) // 从当前帧移除该玩家
// 通知其他玩家该玩家已退出
SessionMgrInstance.execByEverySession(func(s *session) {
if s.ID() == sess.ID() { // 不通知自己
return
}
s.SendMsg(&cmsg.PlayerLeave{PlayerId: sess.userid}) // 发送玩家离开消息
})
}
C/S 交互 消息定义:
// ================== 输入指令 ==================
message InputCommand {
bool up = 1; // W键
bool left = 2; // A键
bool down = 3; // S键
bool right = 4; // D键
}
// ================== 玩家指令集 ==================
message PlayerInput {
uint64 playerId = 1;
repeated InputCommand commands = 2; // 该玩家的所有指令
}
// 新玩家刚进入,同步一次状态
message PlayerEnter{
uint64 playerId = 1;
int32 x = 2;
int32 y = 3;
string color = 4;
}
// 某个玩家离开
message PlayerLeave{
uint64 playerId = 1;
}
客户端代码和更多完整代码详见 https://github.com/beijian128/framesync