欢迎光临散文网 会员登陆 & 注册

人人都是秋名山车神——Unity实现简化版卡丁车漂移

2020-02-08 23:09 作者:皮皮关做游戏  | 我要投稿

作者:Truly

大家好。

前一阵子,Unity出了个名为Karting Microgame的教程项目,里边有一个赛车小游戏示例场景,行驶、转弯、漂移等主要功能都几乎都是通过数学计算来完成的,同时还提供了场景资源供使用者自定义修改完善游戏。这让我想起在YouTube上mix and jam大神出过一个制作马里奥赛车漂移的视频,于是也想尝试自己捣鼓一个简化版的漂移(用刚体加力什么的),实现效果如下:

一、前期准备

1 导入资源(使用其他资源的可跳过)

在Asset Store里搜索并下载Karting Microgame > 导入,写文章时该资源暂时还不支持Unity 2019.x版本。

2 组合模型(使用其他资源的可跳过)

导入资源后新建场景,这次主要用到的是包里提供卡丁车以及赛道的模型,脚本另外重新写。

(1) 组合卡丁车

Hierarchy窗口中新建空的GameObject命名为KartPlayer

②打开文件夹 Assets > Karting >Models

③找到Kart并拖到KartPlayer下实例化。

④继续在Models文件夹中找到WartWheelsPlayer,并把他们拖到Kart GameObject下实例化,成为Kart子物体,层次结构如下:


(2)分配Animator Controller(播放Idle动画,可以不做)

①打开Assets > Karting > Animations > Controllers文件夹,里边的Animator Controller已经配好Idle动画。

②把KartControllrt分配给Kart GameObject的Animator组件(拖到Kart上)。

③把PlayerControllrt分配给Player GameObject的Animator组件(拖到Player 上)。


3 添加并且配置Collider与RigidBody

①添加Capsule Collider,设置参数(根据使用模型自行调节),使Capsule Collider大小与Kart相匹配,如下图:

  • Center:0.6

  • Height:2

  • Direction:Z-Axis

②添加Rigidbody,配置如下:

  • Drag:2,增加阻力,用于模拟摩擦力。

  • Freeze Rotation:勾选X轴和Z轴,防止加力的时候翻车。

现在Ctrl+P试执行,车和人物都在播放动画,Player可能需要微调一下位置,调整完成后效果如下:

终于把前置准备工作完成了,接下来开始整理一下实现思路。


二、需求分析

1 这次目标只实现最基本的几个功能:

(1)方向键控制移动。

(2)按住空格漂移。

(3)漂移结束时获得加速。

(4)漂移粒子特效随着漂移时间变色。


2 实现思路

(1)根据以上的功能,在Update()实现控制操作:

①首先获取竖直和水平方向的输入。

②当按住空格并且有水平输入时,在落地的瞬间 && 不在漂移状态 &&有一定速度时开始漂移

③松开空格键时,如果在漂移,则根据漂移等级给予加速,并且停止漂移。

代码如下:

void Update()

    {

        //输入相关

        v_Input = Input.GetAxisRaw("Vertical");     //竖直输入

        h_Input = Input.GetAxisRaw("Horizontal");   //水平输入

 

        //按下空格起跳

        if (Input.GetKeyDown(KeyCode.Space))

        {

            if (isGround)   //如果在地上

            {

                Jump();

            }

        }

 

        //按住空格,并且有水平输入:开始漂移

        if (Input.GetKey(KeyCode.Space) && h_Input != 0)

        {

            //落地瞬间、不在漂移并且速度大于一定值时开始漂移

            if (isGround && !isGroundLastFrame && !isDrifting && kartRigidbody.velocity.sqrMagnitude > 10)

            {

                StartDrift();   //开始漂移

            }

        }

 

        //放开空格:漂移结束

        if (Input.GetKeyUp(KeyCode.Space))

        {

            if (isDrifting)

            {

                Boost(boostForce);//加速

                StopDrift();//停止漂移

            }

        }

    }

 

(2)在FixedUpdate()中进行逻辑运算:

①控制车的转向。

  • 车要与地面大致平行。

  • 根据水平输入,计算车将会绕自己的Y轴如何旋转。

②计算添加到车上的力的大小以及方向。

③如果在漂移,计算漂移等级同时根据漂移等级改变粒子特效颜色。

④为了足够的简单粗暴,功能最终是通过旋转刚体以及给刚体加力来完成的。

代码如下:

private void FixedUpdate()

    {

        //车转向

        CheckGroundNormal();        //检测是否在地面上,并且使车与地面保持水平

        Turn();                     //输入控制左右转向

 

        //起步时 力递增

        IncreaseForce();

        //漂移加速后/松开加油键 力递减

        ReduceForce();

 

 

        //如果在漂移

        if (isDrifting)

        {

            CalculateDriftingLevel();   //计算漂移等级

            ChangeDriftColor();         //根据漂移等级改变颜色

        }

 

        //根据上述情况,进行最终的旋转和加力

        kartRigidbody.MoveRotation(rotationStream);

        //计算力的方向

        CalculateForceDir();

        //移动

        AddForceToMove();

    }

 

如果对不依赖物理系统来完成功能有兴趣的小伙伴可以参考Karting Microgame的教程项目。


三 功能实现

有了大概的思路之后,开始着手实现功能。

1 漂移

本次想实现的是类似与马里奥赛车那中非拟真的漂移,因此下边只为了简单实现表现效果而给刚体加的力,并非拟真的受力分析。

(1)根据触地时的水平输入,决定漂移时的车头朝向。

(2)由于漂移时合速度的方向与车头的朝向是不一致的,所以需要为最终的合力方向添加一个偏移量m_DriftOffset。以左漂移为例,合速度方向为车头朝向的右前方,如图所示(绿线为偏移后加力方向):

(3)开始漂移时播放粒子特效。

代码如下:

    //开始漂移并且决定漂移朝向

    public void StartDrift()

    {

        isDrifting = true;

       

        //根据水平输入决定漂移时车的朝向,因为合速度方向与车身方向不一致,所以为加力方向添加偏移

        if (h_Input < 0)

        {

            driftDirection = DriftDirection.Left;

            //左漂移时,合速度方向为车头朝向的右前方,偏移具体数值需结合实际自己调试

            m_DriftOffset = Quaternion.Euler(0f, 30, 0f);

        }

        else if (h_Input > 0)

        {

            driftDirection = DriftDirection.Right;

            m_DriftOffset = Quaternion.Euler(0f, -30, 0f);

        }

 

        //播放漂移粒子特效

        PlayDriftParticle();

    }

 

(3)停止漂移:把恢复漂移朝向,并且调整偏移。

  public void StopDrift()

    {

        isDrifting = false;

        driftDirection = DriftDirection.None;

        driftPower = 0;

        m_DriftOffset = Quaternion.identity;

        StopDriftParticle();

    }

 

2 转向

(1)通过射线检测地面法线,使车与地面大致水平

  • 在靠近车头中心和车尾中心两个位置分别添加一个空的GameObject,从这两个点往车底方向打射线,射线长度比发射点到车底的距离稍长。

  • 如果其中一条射线打中地面,则判断车在地面上,获取击中的地面的法线。

  • 车的Up方向为两地面的法线相加的方向(主要用在上下坡的时候)。


代码如下:

    public void CheckGroundNormal()

    {

        //从车头中心附近往下打射线,长度比发射点到车底的距离长一点

        RaycastHit frontHit;

        bool hasFrontHit = Physics.Raycast(frontHitTrans.position, -transform.up, out frontHit, groundDistance, LayerMask.GetMask("Ground"));

 

        //从车尾中心附近往下打射线

        RaycastHit rearHit;

        bool hasRearHit = Physics.Raycast(rearHitTrans.position, -transform.up, out rearHit, groundDistance, LayerMask.GetMask("Ground"));

 

        isGroundLastFrame = isGround; //储存上一帧是否在地面

        if (hasFrontHit || hasRearHit)//更新判断现在是否在地面

        {

            isGround = true;

        }

        else

        {

            isGround = false;

        }

 

        //使车与地面水平

        Vector3 tempNormal = (frontHit.normal + rearHit.normal).normalized;

        Quaternion quaternion = Quaternion.FromToRotation(transform.up, tempNormal);

        rotationStream = quaternion * rotationStream;

    }

 

(2)通过水平输入控制转向

马里奥赛车在漂移的时候只需按着漂移键和加油键就能按照原先朝向一直绕着某个点作圆周的运动,并且可以通过左右方向键进行微调。因此推断如下:

①漂移时需要自带一个方向的旋转。

②此时通过输入可控的旋转角速度要小于漂移自带旋转的角速度,否则会漂移到一定程度回反向飘。

③当车后退时,旋转方向与前进时相反

大概路线如图(以左漂移为例):

根据流程对RotationStream变量进行处理,最终用kartRigidbody.MoveRotation(rotationStream)使车转向。

RotationStream处理流程:

代码如下(由于时间关系,角度直接写死了,有兴趣的同学可以自己细调):

public void Turn()

    {

        //只能在移动时转弯

        if (kartRigidbody.velocity.sqrMagnitude <= 0.1)

        {

            return;

        }

 

        //漂移时自带转向

        if (driftDirection == DriftDirection.Left)

        {

            rotationStream = rotationStream * Quaternion.Euler(0, -40 * Time.fixedDeltaTime, 0);

        }

        else if (driftDirection == DriftDirection.Right)

        {

            rotationStream = rotationStream * Quaternion.Euler(0, 40 * Time.fixedDeltaTime, 0);

        }

 

        //后退时左右颠倒

        float modifiedSteering = Vector3.Dot(kartRigidbody.velocity, transform.forward) >= 0 ? h_Input : -h_Input;

 

        //输入可控转向:如果在漂移,可控角速度为30,否则平常状态为60.

        turnSpeed = driftDirection != DriftDirection.None ? 30 : 60;

        float turnAngle = modifiedSteering * turnSpeed * Time.fixedDeltaTime;

        Quaternion deltaRotation = Quaternion.Euler(0, turnAngle, 0);

 

        rotationStream = rotationStream * deltaRotation;//局部坐标下旋转,这里有空换一个简单的写法

    }

 

3 移动

处理完转向问题,剩下只需要计算力的方向并且加力,车就能动起来了。

(1)在水平方向(车的XZ平面)上,从车的前方进行一个漂移造成的偏移m_DriftOffset(在第一步漂移时已经计算好)。

(2)当竖直输入 < 0时,往反方向加力。

//计算加力方向

    public void CalculateForceDir()

    {

        //往前加力

        if (v_Input > 0)

        {

            verticalModified = 1;

        }

        else if (v_Input < 0)//往后加力

        {

            verticalModified = -1;

        }

 

        forceDir_Horizontal = m_DriftOffset * transform.forward;       

    }

  

    //加力移动

    public void AddForceToMove()

    {

        //计算合力

        Vector3 tempForce = verticalModified * currentForce * forceDir_Horizontal;

 

        if (!isGround)  //如不在地上,则加重力

        {

            tempForce = tempForce + gravity * Vector3.down;

        }

 

        kartRigidbody.AddForce(tempForce, ForceMode.Force);

    } 

 

4 加速

放开空格键时,如果是漂移状态,则改变当前的力,并且激活拖尾,然后力再慢慢递减。

//加速

public void Boost(float boostForce)

    {

        //按照漂移等级加速:1 / 1.1 / 1.2

        currentForce = (1 + (int)driftLevel / 10) * boostForce;

        EnableTrail();

    }

 

//力递减

    public void ReduceForce()

    {

        float targetForce = currentForce;

        if (isGround && v_Input == 0)

        {

            targetForce = 0;

        }

        else if (currentForce > normalForce)    //用于加速后回到普通状态

        {

            targetForce = normalForce;

        }

 

        if (currentForce <= normalForce)

        {

            DisableTrail();

        }

       

        //每秒60递减,可调

        currentForce = Mathf.MoveTowards(currentForce, targetForce, 60 * Time.fixedDeltaTime);

    }

 

5 漂移特效变色

马里奥赛车中,漂移火花的颜色会随着时间或者搓方向键改变,这次只实现随着时间变化。

(1)脚本声明Color[]数组变量driftColors,并在Inspector面板中指定颜色。


(2)声明一个枚举变量作为漂移等级,当漂移时,在FixedUpdate中计算漂移等级。

    public void CalculateDriftingLevel()

    {

        driftPower += Time.fixedDeltaTime;

        //0.7秒提升一个漂移等级

        if (driftPower < 0.7)  

        {

            driftLevel = DriftLevel.One;

        }

        else if (driftPower < 1.4)

        {

            driftLevel = DriftLevel.Two;

        }

        else

        {

            driftLevel = DriftLevel.Three;

        }

    }

 

(3)在场景中把粒子拖为KartPlayer的子对象,并移到车轮底下,在Start()里获取所有特效。

    void Start()

    {

        //...

        //漂移时车轮下粒子特效

        wheelsParticeles = wheelsParticeleTrans.GetComponentsInChildren<ParticleSystem>();

    }

 

(4)根据漂移等级(作为颜色数组下标),改变粒子特效的颜色,这次用到的粒子特效是由多层叠加而成,所以需要遍历赋值。

    public void ChangeDriftColor()

    {

        foreach (var tempParticle in wheelsParticeles)

        {

            var t = tempParticle.main;

            t.startColor = driftColors[(int)driftLevel];

        }

    }

 

此外,如果需要比较好的摄像机跟随与特效效果可以使用Cinemachine进行调节,有兴趣的小伙伴可以参考工程里的设置。


结语:本篇中,通过旋转刚体并给刚体加力的方式,实现了卡丁车的漂移、转向、移动以及加速等功能,影响手感的参数强烈建议自己再调整,希望大家玩得开心,漂移时粒子特效的制作以后有机会再介绍~

参考资料:

Karting Microgame:Asset Store里可以下载,对不依赖物理系统来实现漂移感兴趣的小伙伴推荐参考这个工程里的脚本~

mix and jam:https://www.youtube.com/watch?v=Ki-tWT50cEQ

链接:https://pan.baidu.com/share/init?surl=3RIAzicgicL3xlnz2PZfEg

提取码:cghc




咱们的游戏开发交流群也欢迎强势插入:869551769

希望参与线下游戏开发学习的,欢~~~~~~迎访问:http://www.levelpp.com/

人人都是秋名山车神——Unity实现简化版卡丁车漂移的评论 (共 条)

分享到微博请遵守国家法律