模型與影片
這是
3D游戲編程的第七次作業
文章目錄
- 模型與影片
- 說明檔案
- 作業內容
- 智能巡邏兵
- 效果展示
- 設計與實作
- 狀態機制作
- 預制制作
- 關鍵細節
- 代碼結構
- 傳送門
說明檔案
本次實驗完成了所有基本要求,盡量將步驟展示出,
閃光點:
影片狀態機制作細節、預制制作細節
詳細類圖以及代碼注釋
作業內容
智能巡邏兵
- 游戲設計要求:
- 創建一個地圖和若干巡邏兵(使用影片);
- 每個巡邏兵走一個3~5個邊的凸多邊型,位置資料是相對地址,即每次確定下一個目標位置,用自己當前位置為原點計算;
- 巡邏兵碰撞到障礙物,則會自動選下一個點為目標;
- 巡邏兵在設定范圍內感知到玩家,會自動追擊玩家;
- 失去玩家目標后,繼續巡邏;
- 計分:玩家每次甩掉一個巡邏兵計一分,與巡邏兵碰撞游戲結束;
- 程式設計要求:
- 必須使用訂閱與發布模式傳訊息
subject:OnLostGoal
Publisher: ?
Subscriber: ? - 工廠模式生產巡邏兵
- 必須使用訂閱與發布模式傳訊息
效果展示
宣告:本次實驗使用了優秀博客中的預制,偷了個懶,但是游戲的狀態機影片及碰撞體設定等都由自己完成,并基于優秀博客給出了我認為比較合適的設計(原優秀博客的代碼設計是比較亂的)
- 巡邏兵只在自己的區域內巡邏,不會跨區域追蹤
- 脫離追蹤時能夠獲得一分(動圖中在最后脫離第一個巡邏兵后分數從3變成4)
- 碰到巡邏兵后游戲結束,其他巡邏兵停止影片
設計與實作
狀態機制作
-
玩家狀態機
注意,在玩家影片狀態機中,有必要將Idle到Run變化的轉變中取消勾選has exit time,否則將會出現如果持續按前進,影片會一直停在Idle,這是因為:
hasExitTime:是否有退出時間,簡單理解:開啟表示等待當前影片進行完才可進行下一個影片;關閉表示可以立即打斷當前影片并播放下一個影片
所以只有取消勾選才能在持續按前進的時候馬上切換到Run影片
-
巡邏兵狀態機
與玩家狀態機類似,但是巡邏兵沒有死亡影片,只有在于玩家碰撞時的射擊影片,
預制制作
預制的制作有兩個重點,一個是巡邏兵的碰撞檢測,一個是地圖的區域檢測,
-
巡邏兵碰撞檢測:
巡邏兵的碰撞檢測分為兩部分:- 巡邏區域檢測:
加入一個盒式碰撞體,并為其載入碰撞邏輯:
巡邏區域對應是比較大的,所以如圖所示設定的差不多即可,相關的碰撞邏輯為:
public class PatrolZoneCollider : MonoBehaviour { void OnTriggerEnter(Collider collider) { if (collider.gameObject.tag == "Player") { //玩家進入巡邏兵的巡邏范圍 this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = true; this.gameObject.transform.parent.GetComponent<PatrolData>().player = collider.gameObject; } } void OnTriggerExit(Collider collider) { if (collider.gameObject.tag == "Player") { //玩家離開巡邏兵的巡邏范圍 this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = false; this.gameObject.transform.parent.GetComponent<PatrolData>().player = null; } } }也就是當玩家進入巡邏區域時,會馬上去追蹤玩家,
- 身體碰撞檢測:
加入一個膠囊碰撞體,并為其載入碰撞邏輯:
碰撞邏輯為:
public class PatrolCollider : MonoBehaviour { void OnCollisionEnter(Collision other) { //玩家與巡邏兵碰撞 if (other.gameObject.tag == "Player") { other.gameObject.GetComponent<Animator>().SetTrigger("death"); this.GetComponent<Animator>().SetTrigger("shoot"); Singleton<GameEventManager>.Instance.PlayerGameover(); } } }在玩家與巡邏兵直接碰撞后,玩家播放死亡影片、巡邏兵播放射擊影片,并且游戲結束,
- 巡邏區域檢測:
-
地圖內區域檢測:
這個檢測是為了實作最開始動圖的效果,防止巡邏兵跨區域追蹤,所以需要場景控制器維護一個關于玩家處于哪個區域的標價變數,并在玩家進入各個區域的碰撞體時觸發區域標記更新, 相關的檢測邏輯很簡單,只要修改玩家的所在區域標記即可:
public class AreaCollider : MonoBehaviour { public int sign = 0; void OnTriggerEnter(Collider collider) { //標記玩家進入自己的區域 if (collider.gameObject.tag == "Player") { FirstController firstController = SSDirector.GetInstance().CurrentScenceController as FirstController; firstController.wall_sign = sign; } } }
關鍵細節
-
動作管理:
游戲要求我們巡邏兵會在玩家進入其巡邏區域時追蹤,離開區域時繼續其普通巡邏動作,那么根據之前動作管理者的職責,巡邏、追蹤這兩個小動作,在完成時應該通知動作管理者,于是,在通知的時候我們就可以來判斷此時玩家的狀態,從而使得決定下一個動作應該是追蹤還是巡邏,
于是動作管理者SSActionManager將要判斷事件型別:public void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null) { if (intParam == 0) { //偵查兵跟隨玩家 PatrolFollowAction follow = PatrolFollowAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().player); this.RunAction(objectParam, follow, this); } else { //偵察兵按照初始位置開始繼續巡邏 GoPatrolAction move = GoPatrolAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().start_position); this.RunAction(objectParam, move, this); //玩家逃脫 Singleton<GameEventManager>.Instance.PlayerEscape(); } }而在兩個動作簡單類中,可以根據當前動作進行的狀態判斷未來動作應該是什么,舉個例子就是說,如果當前巡邏兵正在追蹤同一個區域內的玩家,如果被玩家逃離了,那么它應該通知動作管理者下一個動作是巡邏,于是向動作管理者發送的intParam引數為1.
相對應的代碼為:// PatrolFollowAction public override void Update() { if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0) { transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0); } if (transform.position.y != 0) { transform.position = new Vector3(transform.position.x, 0, transform.position.z); } transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime); this.transform.LookAt(player.transform.position); //如果偵察兵沒有跟隨物件,或者需要跟隨的玩家不在偵查兵的區域內 if (!data.follow_player || data.wall_sign != data.sign) { this.destroy = true; this.callback.SSActionEvent(this, 1, this.gameobject); } } -
訂閱者模式:
-
事件發布者:專門發布事件的類,訂閱者可以訂閱該類的事件,通知者可以用GameEventManager的方法發布訊息,觸發相應事件,也就通知到了訂閱者,
public class GameEventManager : MonoBehaviour { //分數變化 public delegate void ScoreEvent(); public static event ScoreEvent ScoreChange; //游戲結束變化 public delegate void GameoverEvent(); public static event GameoverEvent GameoverChange; //水晶數量變化 public delegate void CrystalEvent(); public static event CrystalEvent CrystalChange; // ... } -
訂閱者:
訂閱者就是主場景控制器,訂閱的方法如下,只要事件發布者檢測到有人通知,就會呼叫這里注冊的方法,void OnEnable() { GameEventManager.ScoreChange += AddScore; GameEventManager.GameoverChange += Gameover; GameEventManager.CrystalChange += ReduceCrystalNumber; } void OnDisable() { GameEventManager.ScoreChange -= AddScore; GameEventManager.GameoverChange -= Gameover; GameEventManager.CrystalChange -= ReduceCrystalNumber; } -
通知者:
前面其實已經提到過,通知者就是每個碰撞檢測代碼,比如上面提到的巡邏兵身體碰撞檢測:public class PatrolCollider : MonoBehaviour { void OnCollisionEnter(Collision other) { //玩家與巡邏兵碰撞 if (other.gameObject.tag == "Player") { other.gameObject.GetComponent<Animator>().SetTrigger("death"); this.GetComponent<Animator>().SetTrigger("shoot"); // 通過事件發布類來發布訊息 Singleton<GameEventManager>.Instance.PlayerGameover(); } } } -
相機跟隨:
為了視野更加廣,需要將相機跟隨人物,這樣就不必顯示和當前玩家位置太遠的區域,public class CameraTrans : MonoBehaviour { public GameObject follow; //跟隨的物體 public float smothing = 5f; //相機跟隨的速度 public bool getInit = false; //表明是否正確初始化了跟隨物體 Vector3 offset; //相機與物體相對偏移位置 void FixedUpdate() { // 等待正確獲得跟隨物體 if (!getInit) { if(follow == null) { return; } offset = transform.position - follow.transform.position; getInit = true; } //Vector3(0, 9, 0);主要是用于調整攝像機的高度 Vector3 target = follow.transform.position + offset + new Vector3(0, 9, 0); //攝像機自身位置到目標位置平滑過渡 transform.position = Vector3.Lerp(transform.position, target, smothing * Time.deltaTime); } }
代碼結構
- 檔案結構:
Model Controller View Action
- 類圖:

傳送門
本次作業也是進行的很坎坷,主要是對于影片狀態機的不熟悉造成的,另外感謝前文提到的優秀博客,通過模仿學習到了很多,并且也改進了一些代碼,如果想要知道代碼細節請前往gitee倉庫,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/223703.html
標籤:其他
下一篇:cocos之游戲手柄控制實體
