东方project是一个典型的2d射击游戏(STG),这里我要实现的是一个简单的双人联机版 东方project 游戏,内容涵盖客户端的开发和服务端的开发,主要目的是实践网络游戏的同步。git
源代码仓库托管于giteeweb
贴图资源是来自网上下载的《东方地灵殿》图集,而后本身用PS切了切,这里给张地灵殿的游戏截图。typescript
不须要严格意义上的帐户系统,因此这方面的通讯操做仅仅是客户端发出申请,服务器提供数据而已。服务器
服务器操做 | 客户端操做 |
---|---|
登记客户端链接,返回玩家id | 链接服务器,申请玩家id |
检查房间状态,返回成功与否 | 退出/进入/建立/随机 房间 |
是否全部玩家都已经申请开始游戏,若是是则开始游戏 | 申请开始游戏 |
采用帧锁定同步机制,初步决定是使用严格帧锁定,这里是严格帧锁定相关的参考资料websocket
对于这个流程,在开始实现服务端的时候会再作分析网络
基本思路是这样的,玩家操做先发送给服务器,等到收到服务器返回的关键帧后,再执行操做。架构
关键在于,若是不收到服务器提供的关键帧,则游戏要暂停,等待服务器关键帧抵达后才继续进行。socket
序号 | 服务器操做 | 客户端操做 |
---|---|---|
1 | 关键帧计时器开始执行 | 控制帧计时器开始执行 |
2 | 进入关键帧若是已经收到全部客户端的控制帧,则继续下一步,不然回到上一步 | - |
3 | 整合控制帧,向全部客户端广播关键帧,重启关键帧计时 | 收到关键帧 |
4 | - | 播放游戏,使用关键帧提供的控制帧做为玩家输入 |
5 | - | 进入客户端控制帧 |
6 | - | 整合控制帧间的玩家输入,键盘状态,做为控制帧发送给服务器 |
7 | 收到控制帧 | 若是没有收到关键帧,发送完控制帧后继续等待 |
游戏的操做很简单,这里能够给出全部的操做类型ui
操做 | 键位定义 |
---|---|
上下左右八方向移动 | 方向键 |
射击键 | z |
减速 | x |
符卡 | c |
客户端须要传递的主要就是键盘的状态,按下仍是松开,这样子。code
有了流程,那么能够定义出服务器的接口了
接口 | 描述 | 参数 | 结果 |
---|---|---|---|
login | 申请玩家id,也能够视做登陆 | 无 | {token: string} |
curr-room | 当前所处的房间 | 无 | {room_id: number} |
join-room | 加入房间,须要提供要加入的房间id | room_id | {result: boolean} |
new-room | 建立房间 | 无 | {room_id: number} |
rand-room | 随机加入房间,若是没有房间,则返回空 | 无 | {room_id: number or null} |
quit-room | 退出房间,理论上应该不会出现错误 | 无 | {result:boolean} |
start | 申请开始游戏,全部人都发出申请开始游戏后,则游戏开始 | 无 | {result: true} |
cancel-start | 取消申请开始游戏 | 无 | {result: true} |
// typescript 编写的示例 // 也能够用其余方式编写,好比 protobuf 啊 // 而后拿 C++ 或者 rust 写服务端也没问题 // 不过为了开发速度,因此先拿 typescript 和 websocket 写个原型 interface ICtrlFrame { // 这是个 tuple // 第一个number表示在x轴上的移动,负数表示向左,正数表示向右 // 第二个表示在y轴上的移动,负数表示向下,正数表示向上 motion:[number,number]; // 射击键的按压状态 fire: boolean; // 低速键的按压状态 slow: boolean; // 符卡键的按压状态 spell: boolean; }
interface IKeyFrame { // 关键帧的序号 frameIndex: number; // 控制帧 // {[index:number]: ICtrlFrame} 这个写法是 ts 特有的 // 用起来至关于其余语言的 HashTable 之类的,举个例子来讲 // 差很少像是 C++ 的 std::map<int, ICtrlFrame> // Python 的 dict 这样 // 顺便一提 Python3 的 type annotation 能够指定 Dict[int,ICtrlFrame] 这样的类型 // import typing // ctrl: Dict[int,ICtrlFrame] = {} # 像这样 // 可是没有强类型检查 ctrl: {[index:number]: ICtrlFrame}; }