Homework07
專案地址
使用說明:創建一個空GameObject將GameModel.cs掛載到新建游戲物件即可
結果演示

智能巡邏兵
1. 游戲設計要求
- 創建一個地圖和若干巡邏兵(使用影片);
- 每個巡邏兵走一個3~5個邊的凸多邊型,位置資料是相對地址,即每次確定下一個目標位置,用自己當前位置為原點計算;
- 巡邏兵碰撞到障礙物,則會自動選下一個點為目標;
- 巡邏兵在設定范圍內感知到玩家,會自動追擊玩家;
- 失去玩家目標后,繼續巡邏;
- 計分:玩家每次甩掉一個巡邏兵計一分,與巡邏兵碰撞游戲結束;
2. 程式設計要求
- 必須使用訂閱與發布模式傳訊息
- subject:OnLostGoal
- Publisher: ?
- Subscriber: ?
- 工廠模式生產巡邏兵
實作程序
制作預制
制作主角Hero、巡邏兵Patrol、地圖SceneMode的預制如下圖
主要代碼實作如下
-
GameModel.cs
游戲邏輯主體,實作包括生產英雄、巡邏兵、移動、追捕等,在師兄博客的基礎上做了改進,實作了hero碰撞柵欄和邊界的檢測,只能從柵欄之間的空隙穿過(原實作hero可任意穿過柵欄并在走出邊界后會掉落)
using System.Collections; using System.Collections.Generic; using UnityEngine; using Com.Patrols; public class GameModel : SSActionManager, ISSActionCallback { public GameObject PatrolItem, HeroItem, sceneModelItem, canvasItem; private SceneController scene; private GameObject myHero, sceneModel, canvasAndText; private List<GameObject> PatrolSet; private List<int> PatrolLastDir; private Holes myHoles; // 巡邏兵正常狀態的速度 private const float PERSON_SPEED_NORMAL = 0.01f; // 巡邏兵追捕狀態的速度 private const float PERSON_SPEED_CATCHING = 0.01f; void Awake() { PatrolFactory.getInstance().initItem(PatrolItem); myHoles = new Holes(); myHoles.leftUpHole = new Hole(-4.0f, -1.0f, 16.0f, 17.0f); myHoles.leftMidHole = new Hole(-6.0f, -5.0f, 10.0f, 14.0f); myHoles.leftDownHole = new Hole(-4.0f, -1.0f, 6.5f, 7.5f); myHoles.MidHole = new Hole(0.0f, 1.5f, 10.0f, 14.0f); myHoles.rightUpHole = new Hole(2.0f, 4.0f, 17.0f, 18.0f); myHoles.rightMidHole = new Hole(4.5f, 5.5f, 10.0f, 14.0f); myHoles.rightDownHole = new Hole(2.0f, 4.0f, 8.0f, 9.0f); } protected new void Start () { scene = SceneController.getInstance(); scene.setGameModel(this); // 生產英雄+巡邏兵 genHero(); genPatrols(); sceneModel = Instantiate(sceneModelItem); canvasAndText = Instantiate(canvasItem); } protected new void Update() { base.Update(); } void genHero() { myHero = Instantiate(HeroItem); } void genPatrols() { PatrolSet = new List<GameObject>(6); PatrolLastDir = new List<int>(6); Vector3[] posSet = PatrolFactory.getInstance().getPosSet(); for (int i = 0; i < 6; i++) { GameObject newPatrol = PatrolFactory.getInstance().getPatrol(); newPatrol.transform.position = posSet[i]; newPatrol.name = "Patrol" + i; PatrolLastDir.Add(-2); PatrolSet.Add(newPatrol); addRandomMovement(newPatrol, true); } } // hero移動 public void heroMove(int dir) { myHero.transform.rotation = Quaternion.Euler(new Vector3(0, dir * 90, 0)); int index = getHeroStandOnArea(); Vector3 herolPos = myHero.transform.position; float posX = herolPos.x; float posZ = herolPos.z; switch (dir) { case Diretion.UP: heroMoveUp(index, posX, posZ); // myHero.transform.position += new Vector3(0, 0, 0.1f); break; case Diretion.DOWN: heroMoveDown(index, posX, posZ); // myHero.transform.position += new Vector3(0, 0, -0.1f); break; case Diretion.LEFT: heroMoveLeft(index, posX, posZ); // myHero.transform.position += new Vector3(-0.1f, 0, 0); break; case Diretion.RIGHT: heroMoveRight(index, posX, posZ); // myHero.transform.position += new Vector3(0.1f, 0, 0); break; } } private void heroMoveUp(int index, float posX, float posZ) { switch (index) { case 0: case 1: case 2: if (posZ + 1 < FenchLocation.FenchTop) myHero.transform.position += new Vector3(0, 0, 0.1f); break; case 3: if (posZ + 1 < FenchLocation.FenchHori) { myHero.transform.position += new Vector3(0, 0, 0.1f); } else if(myHoles.leftMidHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(0, 0, 1.0f); } break; case 4: if (posZ + 1 < FenchLocation.FenchHori) { myHero.transform.position += new Vector3(0, 0, 0.1f); } else if(myHoles.MidHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(0, 0, 1.0f); } break; case 5: if (posZ + 1 < FenchLocation.FenchHori) { myHero.transform.position += new Vector3(0, 0, 0.1f); } else if(myHoles.rightMidHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(0, 0, 1.0f); } break; } } private void heroMoveDown(int index, float posX, float posZ) { switch (index) { case 0: if (posZ - 1 > FenchLocation.FenchHori) { myHero.transform.position += new Vector3(0, 0, -0.1f); } else if(myHoles.leftMidHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(0, 0, -1.0f); } break; case 1: if (posZ - 1 > FenchLocation.FenchHori) { myHero.transform.position += new Vector3(0, 0, -0.1f); } else if(myHoles.MidHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(0, 0, -1.0f); } break; case 2: if (posZ - 1 > FenchLocation.FenchHori) { myHero.transform.position += new Vector3(0, 0, -0.1f); } else if(myHoles.rightMidHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(0, 0, -1.0f); } break; case 3: case 4: case 5: if (posZ - 1 > FenchLocation.FenchBottom) myHero.transform.position += new Vector3(0, 0, -0.1f); break; } } private void heroMoveLeft(int index, float posX, float posZ) { switch (index) { case 0: case 3: if (posX - 1 > FenchLocation.FenchLeft) myHero.transform.position += new Vector3(-0.1f, 0, 0); break; case 1: if (posX - 1 > FenchLocation.FenchVertLeft) { myHero.transform.position += new Vector3(-0.1f, 0, 0); } else if(myHoles.leftUpHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(-1.0f, 0, 0); } break; case 4: if (posX - 1 > FenchLocation.FenchVertLeft) { myHero.transform.position += new Vector3(-0.1f, 0, 0); } else if(myHoles.leftDownHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(-1.0f, 0, 0); } break; case 2: if (posX - 1 > FenchLocation.FenchVertRight) { myHero.transform.position += new Vector3(-0.1f, 0, 0); } else if(myHoles.rightUpHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(-1.0f, 0, 0); } break; case 5: if (posX - 1 > FenchLocation.FenchVertRight) { myHero.transform.position += new Vector3(-0.1f, 0, 0); } else if(myHoles.rightDownHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(-1.0f, 0, 0); } break; } } private void heroMoveRight(int index, float posX, float posZ) { switch (index) { case 0: if (posX + 1 < FenchLocation.FenchVertLeft) { myHero.transform.position += new Vector3(0.1f, 0, 0); } else if(myHoles.leftUpHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(1.0f, 0, 0); } break; case 3: if (posX + 1 < FenchLocation.FenchVertLeft) { myHero.transform.position += new Vector3(0.1f, 0, 0); } else if(myHoles.leftDownHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(1.0f, 0, 0); } break; case 1: if (posX + 1 < FenchLocation.FenchVertRight) { myHero.transform.position += new Vector3(0.1f, 0, 0); } else if(myHoles.rightUpHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(1.0f, 0, 0); } break; case 4: if (posX + 1 < FenchLocation.FenchVertRight) { myHero.transform.position += new Vector3(0.1f, 0, 0); } else if(myHoles.rightDownHole.isInHole(posX, posZ)) { myHero.transform.position += new Vector3(1.0f, 0, 0); } break; case 2: case 5: if (posX + 1 < FenchLocation.FenchRight) myHero.transform.position += new Vector3(0.1f, 0, 0); break; } } // 動作結束后 public void SSActionEvent(SSAction source, SSActionEventType eventType = SSActionEventType.Completed, SSActionTargetType intParam = SSActionTargetType.Normal, string strParam = null, object objParam = null) { if (intParam == SSActionTargetType.Normal) { addRandomMovement(source.gameObject, true); } else { addDirectMovement(source.gameObject); } } // isActive說明是否主動變向(動作結束) public void addRandomMovement(GameObject sourceObj, bool isActive) { int index = getIndexOfObj(sourceObj); int randomDir = getRandomDirection(index, isActive); PatrolLastDir[index] = randomDir; sourceObj.transform.rotation = Quaternion.Euler(new Vector3(0, randomDir * 90, 0)); Vector3 target = sourceObj.transform.position; switch (randomDir) { case Diretion.UP: target += new Vector3(0, 0, 1); break; case Diretion.DOWN: target += new Vector3(0, 0, -1); break; case Diretion.LEFT: target += new Vector3(-1, 0, 0); break; case Diretion.RIGHT: target += new Vector3(1, 0, 0); break; } addSingleMoving(sourceObj, target, PERSON_SPEED_NORMAL, false); } int getIndexOfObj(GameObject sourceObj) { string name = sourceObj.name; char cindex = name[name.Length - 1]; int result = cindex - '0'; return result; } int getRandomDirection(int index, bool isActive) { // -1左 0上 1右 2下 int randomDir = Random.Range(-1, 3); // 當碰撞時,不走同方向 if (!isActive) { while (PatrolLastDir[index] == randomDir || PatrolOutOfArea(index, randomDir)) { randomDir = Random.Range(-1, 3); } } else { while (PatrolLastDir[index] == 0 && randomDir == 2 || PatrolLastDir[index] == 2 && randomDir == 0 || PatrolLastDir[index] == 1 && randomDir == -1 || PatrolLastDir[index] == -1 && randomDir == 1 || PatrolOutOfArea(index, randomDir)) { randomDir = Random.Range(-1, 3); } } return randomDir; } // 判斷巡邏兵是否越界 bool PatrolOutOfArea(int index, int randomDir) { Vector3 patrolPos = PatrolSet[index].transform.position; float posX = patrolPos.x; float posZ = patrolPos.z; switch (index) { case 0: if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertLeft || randomDir == 2 && posZ - 1 < FenchLocation.FenchHori) return true; break; case 1: if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertRight || randomDir == -1 && posX - 1 < FenchLocation.FenchVertLeft || randomDir == 2 && posZ - 1 < FenchLocation.FenchHori) return true; break; case 2: if (randomDir == -1 && posX - 1 < FenchLocation.FenchVertRight || randomDir == 2 && posZ - 1 < FenchLocation.FenchHori) return true; break; case 3: if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertLeft || randomDir == 0 && posZ + 1 > FenchLocation.FenchHori) return true; break; case 4: if (randomDir == 1 && posX + 1 > FenchLocation.FenchVertRight || randomDir == -1 && posX - 1 < FenchLocation.FenchVertLeft || randomDir == 0 && posZ + 1 > FenchLocation.FenchHori) return true; break; case 5: if (randomDir == -1 && posX - 1 < FenchLocation.FenchVertRight || randomDir == 0 && posZ + 1 > FenchLocation.FenchHori) return true; break; } return false; } // 巡邏兵追捕hero public void addDirectMovement(GameObject sourceObj) { int index = getIndexOfObj(sourceObj); PatrolLastDir[index] = -2; sourceObj.transform.LookAt(sourceObj.transform); Vector3 oriTarget = myHero.transform.position - sourceObj.transform.position; Vector3 target = new Vector3(oriTarget.x / 4.0f, 0, oriTarget.z / 4.0f); target += sourceObj.transform.position; addSingleMoving(sourceObj, target, PERSON_SPEED_CATCHING, true); } void addSingleMoving(GameObject sourceObj, Vector3 target, float speed, bool isCatching) { this.runAction(sourceObj, CCMoveToAction.CreateSSAction(target, speed, isCatching), this); } void addCombinedMoving(GameObject sourceObj, Vector3[] target, float[] speed, bool isCatching) { List<SSAction> acList = new List<SSAction>(); for (int i = 0; i < target.Length; i++) { acList.Add(CCMoveToAction.CreateSSAction(target[i], speed[i], isCatching)); } CCSequeneActions MoveSeq = CCSequeneActions.CreateSSAction(acList); this.runAction(sourceObj, MoveSeq, this); } // 獲取hero所在區域編號 public int getHeroStandOnArea() { return myHero.GetComponent<HeroStatus>().standOnArea; } } -
SSActionManager.cs
用動作佇列實作巡邏兵正常和追捕這兩個動作的流暢切換和管理,包括碰撞的檢測,正常巡邏時的轉向等
using UnityEngine; using System.Collections; using System.Collections.Generic; //并發順序 public class SSActionManager : MonoBehaviour { private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); private List<SSAction> waitingAdd = new List<SSAction>(); private List<int> waitingDelete = new List<int>(); protected void Start () { } protected void Update() { foreach (SSAction ac in waitingAdd) { actions[ac.GetInstanceID()] = ac; } waitingAdd.Clear(); foreach (KeyValuePair<int, SSAction> kv in actions) { SSAction ac = kv.Value; if (ac.destroy) { waitingDelete.Add(kv.Key); } else if (ac.enable) { ac.Update(); } } foreach (int key in waitingDelete) { SSAction ac = actions[key]; actions.Remove(key); Object.Destroy(ac); } waitingDelete.Clear(); } public void runAction(GameObject gameObj, SSAction action, ISSActionCallback manager) { // 先把該物件現有的動作銷毀(與原來不同的動作型別) for (int i = 0; i < waitingAdd.Count; i++) { if (waitingAdd[i].gameObject.Equals(gameObj)) { SSAction ac = waitingAdd[i]; waitingAdd.RemoveAt(i); i--; Object.Destroy(ac); } } foreach (KeyValuePair<int, SSAction> kv in actions) { SSAction ac = kv.Value; if (ac.gameObject.Equals(gameObj)) { ac.destroy = true; } } action.gameObject = gameObj; action.transform = gameObj.transform; action.callBack = manager; waitingAdd.Add(action); action.Start(); } } -
PatrolBehaviour.cs
掛載到每個巡邏兵,實作巡邏兵對hero的感知和追蹤,以及正常和追捕狀態的轉換,若處于追捕狀態,而發現玩家已不在自己的區域,說明在剛一瞬間,玩家逃離了自己區域,此時會添加隨機動作,即繼續巡邏,
using System.Collections; using System.Collections.Generic; using UnityEngine; using Com.Patrols; public class PatrolBehaviour : MonoBehaviour { private IAddAction addAction; private IGameStatusOp gameStatusOp; public int ownIndex; // 判斷是否在追捕狀態 public bool isCatching; void Start () { addAction = SceneController.getInstance() as IAddAction; gameStatusOp = SceneController.getInstance() as IGameStatusOp; ownIndex = getOwnIndex(); isCatching = false; } void Update () { checkNearByHero(); } int getOwnIndex() { string name = this.gameObject.name; char cindex = name[name.Length - 1]; int result = cindex - '0'; return result; } // 檢測所在區域有無hero void checkNearByHero () { // hero在自己區域 if (gameStatusOp.getHeroStandOnArea() == ownIndex) { if (!isCatching) { isCatching = true; addAction.addDirectMovement(this.gameObject); } } else { if (isCatching) { gameStatusOp.heroEscapeAndScore(); isCatching = false; addAction.addRandomMovement(this.gameObject, false); } } } void OnCollisionStay(Collision e) { // 碰到圍欄,選擇下一個點移動 if (e.gameObject.name.Contains("Patrol") || e.gameObject.name.Contains("fence") || e.gameObject.tag.Contains("FenceAround")) { isCatching = false; addAction.addRandomMovement(this.gameObject, false); } // 碰到hero,游戲結束 if (e.gameObject.name.Contains("Hero")) { gameStatusOp.patrolHitHeroAndGameover(); Debug.Log("Game Over!"); } } } -
GameEventManager.cs
采用訂閱和發布模式,傳遞得分、游戲結束等訊息,當玩家得分或游戲結束時,都會GameEventManager的得分/游戲結束方法
using System.Collections; using System.Collections.Generic; using UnityEngine; using Com.Patrols; public class GameEventManager : MonoBehaviour { public delegate void GameScoreAction(); public static event GameScoreAction myGameScoreAction; public delegate void GameOverAction(); public static event GameOverAction myGameOverAction; private SceneController scene; void Start () { scene = SceneController.getInstance(); scene.setGameEventManager(this); } void Update () { } // hero逃離巡邏兵追捕,得分+1 public void heroEscapeAndScore() { if (myGameScoreAction != null) { myGameScoreAction(); } } // hero被巡邏兵捕獲,游戲結束 public void patrolHitHeroAndGameover() { if (myGameOverAction != null) { myGameOverAction(); } } } -
GameStatusText.cs
傳遞訊息的輔助函式
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class GameStatusText : MonoBehaviour { private int score = 0; // 0為score,1為gameover private int textType; void Start () { distinguishText(); } void Update () { } void distinguishText() { if (gameObject.name.Contains("Score")) textType = 0; else textType = 1; } void OnEnable() { GameEventManager.myGameScoreAction += gameScore; GameEventManager.myGameOverAction += gameOver; } void OnDisable() { GameEventManager.myGameScoreAction -= gameScore; GameEventManager.myGameOverAction -= gameOver; } void gameScore() { if (textType == 0) { score++; this.gameObject.GetComponent<Text>().text = "Score: " + score; } } void gameOver() { if (textType == 1) this.gameObject.GetComponent<Text>().text = "Game Over!"; } }
參考文章
往屆師兄博客
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/223705.html
標籤:其他
上一篇:cocos之游戲手柄控制實體

