最近做了一個小的跑酷游戲,今天就我前幾天寫的 游戲玩家跟隨在跑道上的路點行走的簡單邏輯進行一下梳理,希望大家和我自己都能夠有一定的進步。
下面我先說一下該款游戲的一些有必要知道的前提。跑道是動態生成的,而路點又是作為跑道子對象waypoints的子對象(簡單而言,就是孫子對象)存在。所以,路點也就是動態創建,動態銷毀了。我的思路是在游戲剛剛加載時,由RacetrackGenerator腳本類在初始化時先動態生成兩個跑道,而每個跑道都有一個自己的WaypointsManager(路點管理器腳本),用來管理自身的所有路點。我們生成跑道的邏輯是這樣的:當游戲角色跑到第一個跑道的最后一個路點的時候,在第二段跑道后面動態生成第三段跑道,接在第二段跑道上面,過幾秒鍾銷毀第一段跑道,此時游戲角色在第二段跑道上已經是恣意馳騁了。等到游戲角色跑到第二段跑道最后一個路點時,與前面相同的邏輯,在第三段跑道的末尾接上動態生成的第四段跑道,幾秒鍾之后,游戲角色已經跑完的第二段跑道就會銷毀。此時,跑道上只剩下了第三段和第四段兩段跑道。之后,就是重復這樣的邏輯,知道玩家跑到想歇一歇喝杯茶的程度。
說到這里,可能大家都感覺邏輯異常簡單,但是卻不知道如何下手。問題主要是:我們如何能夠判斷玩家跑到當前跑道的最后面,也就是最后一個路點???
故名思議,我們判斷玩家跑道第幾個路點,當然不是負責動態生成跑道的腳本負責嘍,根據面向對象的單一職責原則,每一個類只要負責好自己的那點兒事就好了,何必替別人操心呢。所以,我自己設計了一個路點管理器類用來管理跑道上的路點waypoint。具體職責是:當游戲角色接近一個路點時,將目標路點設置為當前接近游戲角色路點的下一個路點。負責此功能的函數簽名是:public Vector3 GetTargetWayPoint(Transform player)。具體代碼我會在后面貼出,大家可以先理順一下思路。我經常看別人的博客,不怎么看具體思路,直接看代碼,然后就雲里霧里了,大家可以看懂思路,然后自己寫屬於自己的代碼,我的代碼就權當拋磚引玉了。回到那個函數上來,見名知意,這個函數就是用來獲取目標路點的,何為目標路點呢,就是游戲角色所要到達的目標點,當然了,當游戲角色接近該目標點,該目標點又會變成了下一個路點。
我的代碼有一個局限性,就是路點在跑道上的設置是有講究的,換句話說,路點必須在跑道上這樣設置:就是第一個動態生成的跑道上的waypoint0要設置在游戲角色的后面,此時,游戲角色離waypoint1要遠一些作為當前游戲角色的目標路點。
當游戲角色跑到當前跑道最后一個路點時,即目標路點為最后一個路點,在此條件下,當游戲角色距離該目標路點很近時,目標路點變為下一段跑道上的第0個路點(程序員通常都是從0開始計數的 >_<)。
但是問題又來了,我們的路點管理器只能夠管理當前跑道的路點,怎么能夠管別人的家務事(不能夠管理下一個跑道上的路點)。所以,從當前跑道 到 下一段跑道的變換就需要另一個必要的腳本來控制了,負責此功能的腳本類就是RaceTrackGenerator了。我在該類中引用了兩個對象: WaypointsManager currentWaypointsManager 和 WaypointsManager nextWaypointsManager。這兩個對象從字面上很容易理解,就是游戲角色所在跑道上的路點管理器和下一段跑道上的路點管理器。最關鍵的是:我們如何正確的取得這兩個路點管理器。我們前面說過,跑道生成器腳本類負責在初始化時動態加載兩個跑道,那么一開始,currentWaypointsManager 和nextWaypointsManager也就有着落了。就是獲取這兩個動態生成的跑道游戲對象的路點管理器腳本類組件,哈哈,是不是有點繞,看兩遍就理順了。
跑道生成器腳本類中始終存在着兩個游戲對象的引用:
GameObject currentRaceTrack;
GameObject nextRaceTrack;
這樣環環相扣的邏輯就是用 簡單的 “只需要兩個”的邏輯就可以完美的解決。其實,我發現,很多算法都可以只是用 “只需要兩個”的思路解決。以后的博客中,我好好整理一下這種解決問題的思路。
路點管理器中引用了一個WaypointsManager腳本類的 nextWaypointsManager對象,並且存在一個set方法,用來在RaceTrackGenerator腳本類中設置路點管理器腳本類對象的下一個路點管理器對象。那我們就看一下代碼吧,因為網速原因,早上寫得一些文字都沒保存上,所以肯定會有銜接不上的地方,等我腦子清醒點兒的時候再改一下,謝謝大家。

1 /* 2 * 負責生成跑道 3 * 具體功能: 1> 游戲開始時初始化創建兩段跑道 4 * 2> 創建跑道,並且改變當前跑道和下一段跑道 【耦合了創建障礙物和道具的代碼,這是設計不合理的地方】 5 * 3> 獲取當前跑道和下一段跑道的引用 6 * 7 */ 8 9 using UnityEngine; 10 using System.Collections; 11 using System.Collections.Generic; 12 13 public class RacetrackGenerator : MonoBehaviour { 14 public GameObject[] raceTrackPrefabs; 15 public ElementsGenerator elementsGenerator; //負責在跑道上動態生成障礙物、鑽石、道具、僵屍等等游戲元素 16 17 int maxRaceTrackTypeIndex; 18 19 GameObject currentRaceTrack; 20 GameObject nextRaceTrack; 21 WaypointsManager currentWaypointsManager; 22 WaypointsManager nextWaypointsManager; 23 // Use this for initialization 24 void Start () { 25 maxRaceTrackTypeIndex = raceTrackPrefabs.Length; 26 } 27 28 // Update is called once per frame 29 void Update () { 30 31 } 32 33 public WaypointsManager GetCurrentWaypointsManager() 34 { 35 return currentRaceTrack.GetComponent<WaypointsManager> (); 36 } 37 /// <summary> 38 /// 在下一條跑道的終點創建新的跑道,並銷毀當前跑道。 39 /// 缺點:該函數中還實現了其他功能 40 /// 1> 因為該函數中有設置動態生成的跑道中的路點管理器腳本,這些是動態生成跑道,設置當前跑道必須的,所以 41 /// 創建陷阱、道具等等都不得不加到這個函數中 42 /// </summary> 43 /// <param name="racePos">Race position.</param> 44 void CreateRacetrack(Vector3 racePos) 45 { 46 int i = Random.Range (0, maxRaceTrackTypeIndex); 47 GameObject tmpRaceTrack = Instantiate (raceTrackPrefabs[i], 48 racePos, Quaternion.identity) as GameObject; 49 50 nextWaypointsManager.SetNextWaypointsManager (tmpRaceTrack.GetComponent<WaypointsManager> ()); 51 //掃尾 destroy掉raceTrack,並且將raceTrack = raceTrackNext, raceTrackNext = tmpRaceTrack; 52 Destroy (currentRaceTrack,2f); 53 54 currentRaceTrack = nextRaceTrack; 55 currentWaypointsManager = currentRaceTrack.GetComponent<WaypointsManager> (); 56 /**********************************************************************************/ 57 //每次當前路點管理器變化時,將該函數綁定到當前路點管理器的事件中 58 currentWaypointsManager.GenerateRacetrackEvent += CreateRacetrack; 59 /**********************************************************************************/ 60 nextRaceTrack = tmpRaceTrack; 61 nextWaypointsManager = nextRaceTrack.GetComponent<WaypointsManager> (); 62 63 #region 每當 當前跑道變化時,在變化后的當前跑道上動態創建陷阱、道具、鑽石和敵人 64 65 //在每個路點處生成障礙物 66 List<Transform> waypointList = elementsGenerator.GetWaypointList(currentWaypointsManager); 67 //調用ElementsGenerator的具體的生成障礙物的共有方法,生成的障礙物作為當前跑道的子物體存在,我們就不用 68 //考慮什么時候銷毀它了,因為當大橋銷毀時,道具也跟着銷毀了 69 elementsGenerator.CreateTraps(waypointList,currentRaceTrack.transform); 70 71 #endregion 72 } 73 /// <summary> 74 /// 創建兩個相互連接的跑道,設置好他們相連的位置,以及第一個路點管理器的下一個路點管理器,方便創建時使用下一個路點管理器來 75 /// 在下一段跑道的末端動態生成跑道 76 /// </summary> 77 public void Init() 78 { 79 int i = Random.Range (0, maxRaceTrackTypeIndex); 80 int j = Random.Range (0, maxRaceTrackTypeIndex); 81 //將當前的跑道在原點創建出來 82 currentRaceTrack = Instantiate (raceTrackPrefabs[i], 83 Vector3.zero, Quaternion.identity) as GameObject; 84 //raceTrackNext復制的位置是在raceTrack的子對象end_position出創建的 85 Transform createTrans = currentRaceTrack.transform.Find ("end_position"); 86 nextRaceTrack = Instantiate (raceTrackPrefabs[j], 87 createTrans.position, Quaternion.identity) as GameObject; 88 89 currentWaypointsManager = currentRaceTrack.GetComponent<WaypointsManager> (); 90 //將當前路點管理器的CreateRaceTrack函數 綁定到 生成跑道事件上 91 currentWaypointsManager.GenerateRacetrackEvent += CreateRacetrack; 92 93 nextWaypointsManager = nextRaceTrack.GetComponent<WaypointsManager> (); 94 currentWaypointsManager.SetNextWaypointsManager (nextWaypointsManager); 95 96 #region 當前跑道開始創建時,在當前跑道上動態創建陷阱、道具、鑽石和敵人 97 //在每個路點處生成障礙物 98 //Debug.Log (currentWaypointsManager); 99 List<Transform> waypointList = elementsGenerator.GetWaypointList(currentWaypointsManager); 100 //調用ElementsGenerator的具體的生成障礙物的共有方法,生成的障礙物作為當前跑道的子物體存在,我們就不用 101 //考慮什么時候銷毀它了,因為當大橋銷毀時,道具也跟着銷毀了 102 elementsGenerator.CreateTraps(waypointList,currentRaceTrack.transform); 103 #endregion 104 } 105 }

1 /* 2 * 對地形中的路點進行管理 3 * 確定當前路點下標,以及當前路點下一個路點的位置 4 * 確定下一個路點管理器,進行前后兩個路點管理器管理的路點的銜接 5 * 【當前路點管理器的最后一個路點 和 下一個路點管理器的第0個路點】 6 * 7 */ 8 using UnityEngine; 9 using System.Collections; 10 11 public delegate void notifyGenerateRacetrack(Vector3 generateTrans); 12 13 public class WaypointsManager : MonoBehaviour { 14 15 public event notifyGenerateRacetrack GenerateRacetrackEvent; 16 17 WaypointsManager nextWaypointsManager; //下一個賽道的路點管理器 18 Transform[] wayPoints; 19 int currentIndex; 20 int nextIndex; 21 // Use this for initialization 22 void Start () { 23 currentIndex = 1; 24 nextIndex = 2; 25 Transform wayPoints2 = transform.Find ("waypoints"); 26 wayPoints = new Transform[wayPoints2.childCount]; 27 for (int i=0; i<wayPoints2.childCount; i++) { 28 if(i<=9) 29 { 30 string childname = "waypoint"+i; 31 wayPoints[i] = wayPoints2.Find(childname); 32 } 33 else{ 34 string childname = "waypoint"+i; 35 wayPoints[i] = wayPoints2.Find(childname); 36 } 37 } 38 } 39 40 // Update is called once per frame 41 void Update () { 42 43 } 44 45 public void SetNextWaypointsManager(WaypointsManager next) 46 { 47 nextWaypointsManager = next; 48 } 49 50 public Transform GetFirstWaypointTransform() 51 { 52 return wayPoints [0]; 53 } 54 55 /// <summary> 56 /// 第一個路點一定要放在玩家前面 57 /// </summary> 58 /// <returns>The target way point.</returns> 59 /// <param name="player">Player.</param> 60 public Vector3 GetTargetWayPoint(Transform player) 61 { 62 //當目標點為最后一個路點時,動態生成新的跑道,返回新生成跑道的第一個路點,並經過一段時間銷毀該對象 63 if (currentIndex == wayPoints.Length - 1) { 64 //當前目標路點下標指向最后一個路點時,下一個目標路點下標變為0 65 nextIndex = 0; 66 //當最后一個路點距離玩家很近時,前一個路點變為最后一個路點。 67 //此時可以通知道路生成器動態生成第三段跑道 68 if ((wayPoints [currentIndex].position - player.position).sqrMagnitude < 2f) { 69 //nextIndex = 0 ; 70 //確定跑道生成位置 71 Transform nextRacetrackTrans = nextWaypointsManager.GetComponent<Transform> (); 72 Transform createTrans = nextRacetrackTrans.Find ("end_position"); 73 GenerateRaceTrackEventHandler (createTrans.position); 74 } 75 76 77 return wayPoints [currentIndex].position; 78 } 79 //當目標路點不是最后一個路點時 80 else { 81 if((wayPoints[currentIndex].position -player.position).sqrMagnitude <2f) 82 { 83 currentIndex = nextIndex; 84 nextIndex++; 85 } 86 87 return wayPoints[currentIndex].position; 88 } 89 } 90 91 public Vector3 GetNextTargetWaypoint(Transform player) 92 { 93 if (currentIndex < wayPoints.Length - 1) { 94 return wayPoints [nextIndex].position; 95 } else { 96 // if(nextWaypointsManager != null) 97 // { 98 // return nextWaypointsManager.GetFirstWaypoint().position; 99 // } 100 // else{ 101 // throw UnassignedReferenceException; 102 // } 103 return nextWaypointsManager.GetFirstWaypointTransform().position; 104 } 105 } 106 107 void GenerateRaceTrackEventHandler(Vector3 generatePos) 108 { 109 GenerateRacetrackEvent (generatePos); 110 } 111 }
WaypointsManager中那個事件的使用完全沒有必要,大家可以直接換成引用RacetrackGenerator類對象來創建跑道的函數。