Unity跑酷游戏中的路点生成算法
最近做了一个小的跑酷游戏,今天就我前几天写的 游戏玩家跟随在跑道上的路点行走的简单逻辑进行一下梳理,希望大家和我自己都能够有一定的进步。
下面我先说一下该款游戏的一些有必要知道的前提。跑道是动态生成的,而路点又是作为跑道子对象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类对象来创建跑道的函数。