引言前端
如今的手遊玩法愈來愈複雜,特別是戰鬥系統,不再是之前那種簡單的回合制模式。愈來愈多的手遊採用了實時戰鬥的模式(如刀塔傳奇),玩法有點相似於之前的即時戰略遊戲,這對於程序設計提出了更高的要求。本文提出了一種手遊中實時戰鬥系統可行的設計思路。java
設計需求mysql
實時戰鬥,不一樣於早期頁遊和手遊單純的看戰報或回合制模式,整個戰鬥過程是流暢和連貫的,人物的移動、攻擊、技能釋放都不會讓玩家感受到停滯,總體感受相似於傳統的即時戰略遊戲(魔獸、星際等),玩家在遊戲中的指令(如釋放技能)能夠實時獲得執行。程序員
這裏帶來的問題是,如何設計一個穩定且高效的戰鬥系統,來知足多人戰鬥時可能的高併發;不會由於高併發對服務器形成太重的負擔,不會對玩家帶來糟糕的延時體驗;同時數目繁多的兵種和技能要可以穩定有序地工做在這個系統中,不會讓程序員疲於應付而無所適從。sql
下面針對這些需求提出了一種設計思路。數據庫
設計要點:服務器
內存化併發
當玩家在線人數不少時,若是仍是將每次數據修改入庫,勢必會帶來很大的cpu開銷。筆者曾經參與一個項目,當同時在線人數達到500時,服務器用於mysql的cpu佔用率飆到了800%。後來通過分析,有很大一部分數據不必實時入庫,例如戰場上的NPC數據,相對不敏感,即便服務器重啓也無所謂,這部分數據能夠全走內存;另有一部分玩家相關數據能夠採用異步存儲的方式,戰鬥線程直接操做內存,另有一監控線程視狀況每隔一段時間將內存數據刷入數據庫。異步
單線程ide
也許你會說,如今的多核服務器爲何還要用單線程?這是由於單線程有它的好處,一是不用費心費力去解決死鎖等併發問題,一般一個前後關係形成的死鎖問題會佔用程序員大量的解決時間;二是有了前面的內存化,戰鬥線程再也不會由於數據庫讀寫等耗時操做而卡幀,因此咱們徹底能夠用這樣一個模型來解決問題:只有一個後臺線程在逐幀循環,每幀的戰鬥數據推送前端;玩家的操做(如釋放技能)不直接執行,而是交由後臺線程排隊後逐個執行。執行的時刻多是當前幀,或者推遲到下一幀,總之由後臺線程統籌規劃。這樣就避免了由於併發帶來的一些未知問題。
分解
咱們來比較一下兩種程序設計思路:一是把全部的戰鬥邏輯都寫在後臺線程裏,一大堆if-else和for循環耦合在一塊兒;二是將複雜問題分解到不少類中,每一個類只負責處理它應該處理的事情,後臺線程作的事情只是按必定的順序把這些類組織起來。能夠明顯看到第二種方法更加清爽,代碼可維護性更好,程序員也更喜歡。事實上,不少戰鬥系統都一樣能夠分解爲battleUnit, state, skill, buff等基本的單元。下面會專門舉例說明。
舉例:
下面這個例子假定戰鬥發生在一個戰場(FightScene)中,戰場中有許多戰鬥單位(FightUnit),有一個戰鬥引擎(FightEngine)負責開啓後臺線程,每幀遍歷一次戰場中的各個戰鬥單位,進行相應的動做。玩家釋放技能的操做,由事件(Event)的方式通知對應的戰鬥單位,更改它的狀態機使之進入技能狀態(SkillState)並執行釋放技能和添加buff(Buff)的操做。整個系統只有一個線程,戰鬥過程模塊化,結構清晰,易於擴展。
// buff public interface Buff { // 進入時調用 public void enter(); // 退出時調用 public void exit(); // 每幀執行 public void tick(long interval); }
// 事件 public interface Event { }
import java.util.List; // 戰場 public class FightScene { // 攻方列表 private List<FightUnit> attList; // 守方列表 private List<FightUnit> defList; // 後臺線程每隔一幀執行一次 public void tick(long interval) { for (FightUnit unit : attList) unit.tick(interval); for (FightUnit unit : defList) unit.tick(interval); } // 尋找指定戰鬥單位 public FightUnit findUnit(int side, int index) { if (side == 1) { return attList.get(index); } else { return defList.get(index); } } }
import java.util.List; // 戰鬥單位(玩家或者npc) public class FightUnit { // 事件列表 private List<Event> eventList; // buff列表 private List<Buff> buffList; // 當前狀態(狀態機) private State state; // 添加事件 public void addEvent(Event event) { eventList.add(event); } // 添加buff public void addBuff(Buff buff) { buffList.add(buff); } // 每幀執行 public void tick(long interval) { for (Event event : eventList) { if (event instanceof SkillEvent){ int skillId = ((SkillEvent)event).getSkillId(); // 退出舊的狀態,進入新的狀態 state.exit(); state = new SkillState(this, skillId); state.enter(); } } for (Buff buff : buffList) { buff.tick(interval); } state.tick(interval); } }
// 戰鬥引擎 public class FightEngine { private FightScene fightScene; // 幀間隔 public static final long TICK_INTERVAL = 50; // 啓動戰鬥線程 public void startFightThread() { new Thread(){ public void run() { while (true) { try { long startTime = System.currentTimeMillis(); fightScene.tick(TICK_INTERVAL); long endTime = System.currentTimeMillis(); // 補足一幀剩餘時間 Thread.sleep(TICK_INTERVAL - (endTime - startTime)); } catch (Exception e) { // TODO } } } }.start(); } // 釋放技能(這裏是玩家操做) public void releaseSkill(int skillId, int side, int index) { /* * 斷定條件(能量不足、戰鬥已結束等) * TODO * ... * * */ // 添加事件到戰鬥單元 FightUnit unit = fightScene.findUnit(side, index); unit.addEvent(new SkillEvent(skillId)); } }
// 技能事件(一種事件的類型) public class SkillEvent implements Event{ // 技能id private int skillId; public SkillEvent(int skillId) { this.skillId = skillId; } public int getSkillId(){ return skillId; } }
// 技能狀態(一種狀態類型) public class SkillState implements State{ // 技能id private int skillId; // 戰鬥單位 private FightUnit unit; public SkillState(FightUnit unit, int skillId) { this.unit = unit; this.skillId = skillId; } // 進入時調用 public void enter() { } // 離開時調用 public void exit() { } // 每幀執行 public void tick(long interval) { /* * 釋放技能的邏輯(一連串使人眼花繚亂的效果...) * TODO * ... * * */ // 添加buff(假定這個技能會給本身加buff) Buff buff = /*...*/ unit.addBuff(buff); buff.enter(); } }
// 戰鬥單位的狀態 public interface State { // 每幀執行 public void tick(long interval); // 進入時調用 public void enter(); // 離開時調用 public void exit(); }