Unity3D教程:跟随实现

发表于2018-04-07
评论0 3.2k浏览
跟随在游戏中也很常见,特别在一些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

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引