東方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}; }