github傳送門:https://github.com/dongzizhu/unity3DLearning/tree/master/hw6/Patrol
視頻傳送門:https://space.bilibili.com/472759319
要求
- 遊戲設計要求:
- 創建一個地圖和若干巡邏兵(使用動畫);
- 每個巡邏兵走一個3~5個邊的凸多邊型,位置數據是相對地址。即每次確定下一個目標位置,用自己當前位置爲原點計算;
- 巡邏兵碰撞到障礙物,則會自動選下一個點爲目標;
- 巡邏兵在設定範圍內感知到玩家,會自動追擊玩家;
- 失去玩家目標後,繼續巡邏;
- 計分:玩家每次甩掉一個巡邏兵計一分,與巡邏兵碰撞遊戲結束;
- 程序設計要求:
- 必須使用訂閱與發佈模式傳消息
- subject:OnLostGoal
- Publisher: ?
- Subscriber: ?
- 工廠模式生產巡邏兵
訂閱與發佈模式
爲什麼需要訂閱與發佈模式
就像我們訂閱報刊,有什麼時事新聞出現,報社(發佈者)可以第一時間印刷數百份乃至數千份報紙(傳播媒介),發到各個訂閱了該報紙的人(訂閱者)手上,這樣就完成了信息傳播。
與觀察者模式的區別
在觀察者模式中,觀察者需要直接訂閱目標事件;在目標發出內容改變的事件後,直接接收事件並作出響應,往往發佈者需要對所有的觀察者維持一個列表,從而在每次發生變化時通知所有觀察者。在發佈訂閱模式中,發佈者和訂閱者之間多了一個發佈通道;一方面從發佈者接收事件,另一方面向訂閱者發佈事件;訂閱者需要從事件通道訂閱事件,以此避免發佈者和訂閱者之間產生依賴關係,從而降低代碼的耦合性。
在本次遊戲中的應用
我們通過訂閱與發佈模式,讓巡邏兵將自己的狀態傳遞展示出來,然後由EventManager觀察並做出反應。
代碼
其他的模式如工廠模式產生士兵,以及MVC架構這裏就不再贅述了,感興趣的讀者可以看之前的博客。
這裏主要講一下訂閱與發佈的模式以及巡邏兵的行動邏輯。
void Update () {
//Debug.Log("here1");
if (Vector3.Distance(gameStatusOp.getHeroPosition().position, gameObject.transform.position) <= 10f)
{
if (!isCatching)
{
isCatching = true;
}
addAction.addDirectMovement(this.gameObject);
}
else
{
if (isCatching)
{
//stop catching
gameStatusOp.heroEscapeAndScore();
isCatching = false;
addAction.addRandomMovement(this.gameObject, false);
}
else
addAction.addRandomMovement(this.gameObject, true);
}
}
在PatrolControl中,如果hero出現在當前patrol的附近,那麼開始追逐;如果hero離開了,那麼停止追逐,同時將hero逃跑的信息通過firstController也就是之類的gameStatusOp發佈。然後在決定當前狀態後,和之前一樣(MVC模式),調用actionManager來控制移動。
void OnCollisionStay(Collision e)
{
if (e.gameObject.name.Contains("Patrol"))
{
isCatching = false;
addAction.addRandomMovement(this.gameObject, false);
gameStatusOp.heroEscapeAndScore();
}
if (e.gameObject.name.Contains("hero"))
{
gameStatusOp.patrolHitHeroAndGameover();
Debug.Log("Game Over!");
}
}
當與物體相撞時,如果是和其他巡邏兵撞在一起,那麼同樣停止追逐,同時發佈逃跑狀態;如果撞到了hero,則發佈遊戲結束的狀態。
public class GameEventManager : MonoBehaviour {
public delegate void GameScoreAction();
public static event GameScoreAction myGameScoreAction;
public delegate void GameOverAction();
public static event GameOverAction myGameOverAction;
private FirstControl scene;
void Start () {
scene = (FirstControl)Director.getInstance().sceneCtrl;
scene.gameEventManager = this;
}
void Update () {
}
//hero escape
public void heroEscapeAndScore() {
if (myGameScoreAction != null)
myGameScoreAction();
}
//hero gets caught
public void patrolHitHeroAndGameover() {
if (myGameOverAction != null)
myGameOverAction();
}
}
而每次發佈的狀態都有eventManager訂閱,在被調用相應的函數後實現對遊戲狀態和分數的調整。
至於巡邏兵的巡邏軌跡則主要由下一個方向決定。我將每個巡邏兵上一次行動的方向保存在PatrolLastDir中,將行動的距離保存在PatrolMoveLength中,然後在非追逐狀態下,巡邏兵會走一個一定距離的矩形。
int getRandomDirection(int index, bool isActive) {
int randomDir;
if (!isActive) {
if(PatrolLastDir[index] < 2)
randomDir = PatrolLastDir[index] + 1;
else
randomDir = -1;
PatrolLastDir[index] = randomDir;
PatrolMoveLength[index] = 0;
}
else {
PatrolMoveLength[index]++;
Debug.Log(PatrolMoveLength[index]);
if(PatrolMoveLength[index] > 1000f){
if(PatrolLastDir[index] < 2)
randomDir = PatrolLastDir[index] + 1;
else
randomDir = -1;
PatrolLastDir[index] = randomDir;
PatrolMoveLength[index] = 0;
}
else{
randomDir = PatrolLastDir[index];
}
}
return randomDir;
}
其他具體的代碼就不貼上來了,感興趣的請移步github。