一種手遊中實時戰鬥系統的設計思路

  • 引言前端

如今的手遊玩法愈來愈複雜,特別是戰鬥系統,不再是之前那種簡單的回合制模式。愈來愈多的手遊採用了實時戰鬥的模式(如刀塔傳奇),玩法有點相似於之前的即時戰略遊戲,這對於程序設計提出了更高的要求。本文提出了一種手遊中實時戰鬥系統可行的設計思路。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();

}
相關文章
相關標籤/搜索