Unity3D教程:跟随实现
发表于2018-04-07
跟随在游戏中也很常见,特别在一些RPG游戏里面,想实现起来也不太难,下面就用一个例子来说明跟随的实现方法。如下图所示,红球是我们的主角,而点击蓝球NPC1和绿球NPC2之后,蓝球和绿球就自动跟随红球了,玩家则演变成控制一个队列。然后在屏幕的左上角显示当前的队列,玩家点击相应的名字,则取消跟随了,这个球就恢复原来的状态了。
同时可以看到,我们的主角红球实现了,并且在带队的时候,始终朝向自动寻路的重点。
这里例子当然完全可以将简陋的Plane换成3D场景,球体换成相应的主角FBX模型,并且UI弄得更好。
一、场景布置
1、3D部分和《【Unity3D】刚体自动寻路的抖动问题和运动边界、空气墙的制作》(点击打开链接)基本一样,在一个Plane上放好3个球体Sphere,并且给这3个不同的球体上不同的纯色材质。同时在4周整4个空气墙,防止球的掉落。注意给球体都上刚体rigidbody属性,并且取消勾选Is Kinematic,而主控的红球勾选Is Kinematic。然后给红球上Nav Mesh Agent组件,完成自动寻路的烘培。
对于红球的速度和加速度分别设置为20和10,让自动寻路更加平稳。
2、而2D的UI部分,在一个Panel里面,分列3个宽和高几乎相同的按钮即可,当然你也可以根据你游戏里面,一个队列,最大允许NPC数量来设置按钮,比如这里一个队列最多就3个球,随意我就摆3个按钮。
二、脚本编写
在主角红球Hero上面摆定如下的Hero.cs:
using UnityEngine; using UnityEngine.UI;//用到了UI using System.Collections; using System.Collections.Generic;//用到了容器类List public class Hero : MonoBehaviour { public GameObject Plane;//地板 public GameObject NPC1;//蓝球 public GameObject NPC2;//绿球 public GameObject Panel;//左上角,显示当前队列里面有多少跟随着的面板 private NavMeshAgent navMeshAgent;//自动寻路的组件 private List<GameObject> FollowQueue;//用于存放跟随者的List private List<GameObject> FollowCanelButtonQueue;//存放左上角取消跟随按钮的List void Start() { navMeshAgent = gameObject.GetComponent<NavMeshAgent>();//初始化navMeshAgent FollowQueue = new List<GameObject>(); FollowCanelButtonQueue = new List<GameObject>(); FollowQueue.Add(gameObject);//队列里面必有可控主角红球 for (int i = 0; i < 3; i++) {//获取左上角取消跟随按钮,放进List GameObject button = GameObject.Find("Button" + i); FollowCanelButtonQueue.Add(button); } } void Update() { if (Input.GetMouseButtonDown(0))//鼠标左键点下 { Ray mRay = Camera.main.ScreenPointToRay(Input.mousePosition);//住摄像机向鼠标位置发射射线 RaycastHit mHit; if (Physics.Raycast(mRay, out mHit))//射线检验 { if (mHit.collider.gameObject == Plane && !navMeshAgent.hasPath)//如果当前的红球已经完成自动寻路停下来了,可以让其进行下一次寻路了。 { navMeshAgent.SetDestination(mHit.point);//mHit.point就是射线和plane的相交点,实为碰撞点 gameObject.transform.LookAt(navMeshAgent.destination);//让红球面向寻路的终点 /*控制 跟随队列FollowQueue中 球的移动*/ for (int i = 1; i < FollowQueue.Count; i++) { GameObject follower = FollowQueue[i]; GameObject follower_leader = FollowQueue[i - 1]; /*让追随者follower始终面向寻路的终点*/ follower.transform.position = follower_leader.transform.position - (navMeshAgent.destination - gameObject.transform.position).normalized; } } /*根据点击对象,开始相应的跟随处理*/ if (mHit.collider.gameObject == NPC1 && !FollowQueue.Contains(NPC1)) { start_follow(NPC1); } if (mHit.collider.gameObject == NPC2 && !FollowQueue.Contains(NPC2)) { start_follow(NPC2); } } } /*左上角的跟随队列信息UI*/ if (FollowQueue.Count > 1)//如果队列中的对象少于1就没必要显示了 { Panel.SetActive(true); for (int i = 0; i < 3; i++)//将队列中的按钮中的Text换成队列中对象的名字 { GameObject button = FollowCanelButtonQueue[i]; if (i < FollowQueue.Count) { button.SetActive(true); button.transform.Find("Text").GetComponent<Text>().text = FollowQueue[i].name; } else//之后的button就不要显示了 { button.SetActive(false); } } } else { Panel.SetActive(false); } } /*开始跟随的处理*/ private void start_follow(GameObject next_follower) { /*将跟随者压入跟随队列*/ FollowQueue.Add(next_follower); GameObject follower = FollowQueue[FollowQueue.Count - 1]; GameObject follower_leader = FollowQueue[FollowQueue.Count - 2]; follower.rigidbody.isKinematic = true;//跟随者开始不受物理引擎影响,这句主要是为了避免自动寻路的鬼畜问题 follower.transform.position = follower_leader.transform.position + follower.transform.position.normalized;//让追随者靠近被被追者 follower.transform.parent = follower_leader.transform;//让追随者follower和被追随者follower_leader一起位移 } /*取消追随的处理*/ public void canel_follow(int i) { FollowQueue[i].rigidbody.isKinematic = false;//跟随者重新受物理引擎影响 FollowQueue[i].transform.parent = null;//解除和被追随者follower_leader一起位移 FollowQueue.Remove(FollowQueue[i]);//在跟随队列中解除 } }
并且在除第0个button外,设置点击事件,事件都是Hero.cs中的public void canel_follow(int i),传入参数分别是1~n(这里是n是2),用以与跟随序列FollowQueue中的对象对应起来,则大功告成。
上述脚本中有几个难点的:
(1)首先是用户点击某一个球的时候,要拉近可控主角,应该说是此时追随序列中最后一个球和用户点击的球的距离。在private void start_follow(GameObject next_follower)中的follower.transform.position = follower_leader.transform.position + follower.transform.position.normalized;。
normalized就是矢量归一化,也就是将矢量各分量除以向量的模长,用于即按比例缩短到单位长度,保证方向不变。
也就是方向向量(1,1),因为模长是根号2,所以归一化之后为(根号2/2,根号2/2)
这句的意思是这样的,如下图所示,刚好用于拉近两个东西的距离。
(2)在移动过程中的让追随者follower始终面向寻路的终点Update()函数中的follower.transform.position = follower_leader.transform.position -(navMeshAgent.destination - gameObject.transform.position).normalized;。意思是这样的:
(3)至于取消队列的管理倒是比较简单了。
来自:https://blog.csdn.net/yongh701/article/details/77530690