学习|Unity3D使用协程实现减速停车效果

学更好的别人,

做更好的自己。

——《微卡智享》

本文长度为4303,预计阅读11分钟

前言

上一篇《学习|Unity3d的导航实现循环线路移动》讲了一下定制循环行驶路线的方法,在视频中还有一个就是非会员的车辆需要人工收费,所以就要有一个减速停车等待的动画效果,本篇就来讲讲怎么用Unity3d的协程来实现的这一效果。

实现效果

上面的动图中我们可以看到无卡车辆在到达起杆前有一个慢慢减速后停止的效果(如果动图不明显可以看看历史文章里的这个视频),实现这个效果我用的是协程的方式,其实在FixUpdate的函数中进行处理应该效果会更好一些,但是也是为了掌握协程这个技巧,所以才用的协程的方式来实现的。

关于协程

微卡智享

协程本身有点像线程,但又不同于线程,协程本身还是在主程序中运行的,完全不用考虑使用线程时如线程锁或是线程同步的问题。

Update()函数中我们可以知道是每一帧都调用的,在每一帧处理时我们可能会有不少事件需要进行判断处理,如果需要判断的事件还需要有计时的处理时,在Update中看代码的可读性非常差,所有这里我们就可以用到协程了。

官方文档Monobehaviour的函数执行顺序图,就对协程再次执行的时机做了很好的描述:

相关函数

函数参数
开启协程StartCoroutine(string methodName)输入参数名
StartCoroutine(IEnumerator method),输入方法名,此方法可以有多个参数
终止协程
StopCoroutine(string methodName);//终止指定的协程
StopAllCoroutine();//终止所有协程
挂起协程
yield return 0;//程序在下一帧中从当前位置继续执行
yield return null;//程序在下一帧中从当前位置继续执行
yield return new WaitForSeconds(N);//程序等待N秒后从当前位置继续执行
yield new WaitForEndOfFrame();//在所有的渲染以及GUI程序执行完成后从当前位置继续执行
yield new WaitForFixedUpdate();//所有脚本中的FixedUpdate()函数都被执行后从当前位置继续执行
yield return WWW;//等待一个网络请求完成后从当前位置继续执行
yield return StartCoroutine(xxx);//等待一个xxx的协程执行完成后从当前位置继续执行
yield break;//如果使用yield break语句,将会导致协程的执行条件不被满足,不会从当前的位置继续执行程序,而是直接从当前位置跳出函数体,回到函数的根部

程序实现

微卡智享

01

碰撞器与钢体的设置

上图中红色框是我们设置的一个boxCollider(盒形碰撞器),而蓝框的车里面我们也加入了一个碰撞器,还有一个钢体。

在红框里的碰撞器我们把isTrigger打上勾,代表是触发器,这样两个物体碰撞是不会产生物理效果了,只会生成触发的事件。这样基本就设置好了,下面的就是我们在代码里实现了。

实现思路

1.  当两个物体碰撞触发事件后,判断是否是会员车辆,如果不是进入停车减速的协程,设置一个停车减速的时间为参数。

2.  根据输入的减速时间参数先计算出大约多少帧,然后用当前的车速除帧数得到每一帧应该减的速度为多少,设置循环,每一帧降低刚才计算要减的车速,直到停车

3.  设置一个停止时长

4. 再按刚才的帧数每帧再增加车速,直到恢复原来的速度

核心代码

    IEnumerator StopAndStartCar(float seconds)
    {
        Debug.Log("time:" + Time.deltaTime);
        //计算输入时长大约多少帧
        float fps = seconds / Time.deltaTime;
        //计算每帧要调整的车速
        float speed = oldspeed / fps;


        Debug.Log("fps:" + fps);
        Debug.Log("speed:" + speed);
        //减速停车
        for (int i=0; i < fps; ++i)
        {
            if (nav.speed > 0)
            {
                nav.speed -= speed;
                if (nav.speed < 0) nav.speed = 0;
            }
            yield return null;
        }
        if (nav.speed != 0) nav.speed = 0;
        //等待0.5秒
        yield return new WaitForSeconds(1f);
        //启动加速
        for(int i=0; i < fps; ++i)
        {
            nav.speed += speed;
            yield return null;
        }
        nav.speed = oldspeed;
    }

上面这个就是实现减速后停止0.5秒,然后再加速的协程方法。

当我们进入触发函数时判断不是会员车辆加入了一个启动协程的方法,参数输入的是3f(即3秒)。这样我们的停车减速的效果就实现了。

完整代码

using System.Collections;
using System.Collections.Generic;
using System.Data;
using UnityEngine;
using UnityEngine.AI;


public class NavCar : MonoBehaviour
{
    //定义接收导航网络组件
    private NavMeshAgent nav;
    //坐标点列表
    private List<Vector3> destpoints;
    //导航下一个坐标点
    private int nextindex;
    //离导航坐标点的距离 
    private float calcdist = 5f;
    private float dist = 0f;


    private TextMesh textMesh;
    private float oldspeed;


    // Start is called before the first frame update
    void Start()
    {
        //将定义的路线加入到List列表中
        destpoints = new List<Vector3>();
        destpoints.Add(GameObject.Find("RoadPoint0").transform.position);
        destpoints.Add(GameObject.Find("RoadPoint1").transform.position);
        destpoints.Add(GameObject.Find("RoadPoint2").transform.position);
        destpoints.Add(GameObject.Find("RoadPoint3").transform.position);


        textMesh = this.transform.GetComponentInChildren<TextMesh>();


        //获取当前车辆的NavMeshAgent
        nav = this.transform.GetComponent<NavMeshAgent>();
        oldspeed = nav.speed;


        //计算最近的点,获取下一点的序号
        Vector3 navpoint = this.transform.position;
        Debug.Log("now:" + navpoint);
        for (int i = 0; i < destpoints.Count; ++i)
        {
            //首先判断点在当前位置的前方还是后方,如果是后方不做计算
            Vector3 dir = destpoints[i] - navpoint;
            float dot = Vector3.Dot(transform.forward, dir);
            Debug.Log("dot:" + dot);


            //判断点在前方时才计算最近的点的距离
            if (dot > 0)
            {
                float tmpdist = Vector3.Distance(destpoints[i], navpoint);
                if (dist == 0)
                {
                    dist = tmpdist;
                    nextindex = i;
                }
                else if (dist > tmpdist)
                {
                    dist = tmpdist;
                    nextindex = i;
                }
                Debug.Log("position:" + destpoints[i] + "  dist:" + tmpdist);
            }


        }


        Debug.Log("final:" + nextindex + " dist:" + dist);
    }


    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.name == "GateDown")
        {
            Debug.Log(other.gameObject.name);
            if (textMesh.text != "会员车辆")
            {
                StartCoroutine(StopAndStartCar(3f));
            }
        }
    }


    IEnumerator StopAndStartCar(float seconds)
    {
        Debug.Log("time:" + Time.deltaTime);
        //计算输入时长大约多少帧
        float fps = seconds / Time.deltaTime;
        //计算每帧要调整的车速
        float speed = oldspeed / fps;


        Debug.Log("fps:" + fps);
        Debug.Log("speed:" + speed);
        //减速停车
        for (int i=0; i < fps; ++i)
        {
            if (nav.speed > 0)
            {
                nav.speed -= speed;
                if (nav.speed < 0) nav.speed = 0;
            }
            yield return null;
        }
        if (nav.speed != 0) nav.speed = 0;
        //等待0.5秒
        yield return new WaitForSeconds(1f);
        //启动加速
        for(int i=0; i < fps; ++i)
        {
            nav.speed += speed;
            yield return null;
        }
        nav.speed = oldspeed;
    }


    // Update is called once per frame
    void Update()
    {
        //判断距离是否在到达范围内,如果在走到一下个点
        if (Vector3.Distance(this.transform.position, destpoints[nextindex])< calcdist)
        {
            if (nextindex == destpoints.Count - 1)
            {
                nextindex = 0;
            }
            else
            {
                nextindex++;
            }
        }


        nav.SetDestination(destpoints[nextindex]);


    }
}


在动画中的起杆的动画也是按这个方法实现的,这里就不再进行描述了。

扫描二维码

获取更多精彩

微卡智享

「 往期文章 」

学习|Unity3d的导航实现循环线路移动

学习|C#线程中AutoResetEvent的使用

学习|C#的EventHandler的委托使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值