【Unity3D】跟随

跟随在游戏中也很常见,特别在一些RPG游戏里面,他实现起来也不太难,下面就用一个例子来说明这个问题。如下图所示,红球是我们的主角,而点击蓝球NPC1和绿球NPC2之后,蓝球和绿球就自动跟随红球了,玩家则演变成控制一个队列。然后在屏幕的左上角显示当前的队列,玩家点击相应的名字,则取消跟随了,这个球就恢复原来的状态了。

同时可以看到,我们的主角红球实现了《【Unity3D】自动寻路》(点击打开链接),并且在带队的时候,始终朝向自动寻路的重点。

这里例子当然完全可以将简陋的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)至于取消队列的管理倒是比较简单了。


  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值