当前位置: 首页 > article >正文

Unity3D 完整直升机控制器(虚拟仿真级别)

采用了MVC框架,以四轴驱动的方式对直升机的启动、飞行做了仿真模拟,包括但不限于参数设置、启动发动机和旋翼、数据显示、HUD、UI、升降、水平移动、转弯等。

文末有完整的工程资源链接。

489cdfe32d1e44fe88a1ee8f56b2a526.png

954961c85a5c405c9429a5e3d8111f9b.png

b734a4b756ab4e28aba2007987a0c2fa.png

589c51d9333a4e25ac2cadfe5df50884.png

1.旋翼

直升机飞行过程中,有顶部的主旋翼和尾部的尾桨需要转动。

在旋翼加速状态时,悬疑的转速逐渐增加到最大。旋翼转速达到最大时,直升机才能起飞。起飞后,旋翼转速始终保持不变。

旋翼的脚本RotorRotation.cs拖拽到主旋翼和尾桨上。

c3570b6df45c44c8b799c1320a5b68a5.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 旋翼旋转
/// </summary>
public class RotorRotation : MonoBehaviour
{
    public enum EnumRotateAxis
    {
        X, Y, Z
    }
    public EnumRotateAxis RotateAxis; // 螺旋桨转动的轴
    public bool IsReverse; // 是否反方向旋转
    public float Speed; // 转速,度
    private Vector3 m_V3Euler;
    private float m_RotateDegree; // 度



    /// <summary>
    /// Start is called before the first frame update
    /// </summary>
    void Start()
    {
        m_V3Euler = transform.localEulerAngles;
    }


    /// <summary>
    /// Update is called once per frame
    /// </summary>
    void Update()
    {
        if (IsReverse)
        {
            m_RotateDegree -= Speed * Time.deltaTime;
        }
        else
        {
            m_RotateDegree += Speed * Time.deltaTime;
        }

        // 防止m_RotateDegree数值过大
        m_RotateDegree = m_RotateDegree % 360;

        switch (RotateAxis)
        {
            case EnumRotateAxis.X:
                transform.localRotation = Quaternion.Euler(m_RotateDegree, m_V3Euler.y, m_V3Euler.z);
                break;
            case EnumRotateAxis.Y:
                transform.localRotation = Quaternion.Euler(m_V3Euler.x, m_RotateDegree, m_V3Euler.z);
                break;
            default:
                transform.localRotation = Quaternion.Euler(m_V3Euler.x, m_V3Euler.y, m_RotateDegree);
                break;
        }
    }
}

代码中定义了一个枚举EnumRotateAxis,根据枚举可以确定不同螺旋桨的旋转方向。

在Update函数中使用switch语句根据旋转轴使用Quaternion.Euler方法旋转轴。

在Update函数中有这样的一条语句:rotateDegree = rotateDegree % 360; 该语句对使用转速算出来的度数对360取余,我个人理解的主要目的是为了防止转速较高旋转的过快。

旋转轴的选择和是否反转,取决于模型。我的工程里主旋翼选择Y轴、IsReverse=false,尾桨选择X轴,IsReverse=true。

2.输入管理器

直升机的操控较为复杂,所以我没有用Unity的InputManager,而是自己写了一个InputManager.cs。

转向踏板,控制转向。总距操纵杆,控制主旋翼的桨叶角,也就是控制升降。周期变距杆,控制水平方向的移动。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 输入管理器
/// </summary>
public class InputManager : MonoBehaviour
{
    public float Sensitivity = 3.0f; // 敏感度

    [Space(20)]
    [Header("转向踏板")]
    public KeyCode KeyTurnLeft = KeyCode.A;
    public KeyCode KeyTurnRight = KeyCode.D;
    [Header("总距操纵杆")]
    public KeyCode KeyMoveDown = KeyCode.S;
    public KeyCode KeyMoveUp = KeyCode.W;
    [Header("周期变距杆")]
    public KeyCode KeyMoveLeft = KeyCode.LeftArrow;
    public KeyCode KeyMoveRight = KeyCode.RightArrow;
    public KeyCode KeyMoveBack = KeyCode.DownArrow;
    public KeyCode KeyMoveForward = KeyCode.UpArrow;


    [Space(20)]
    [Header("发动机按钮")]
    public KeyCode keyMenu = KeyCode.Escape;
    [Header("发动机按钮")]
    public KeyCode KeyEngine = KeyCode.E;
    [Header("旋翼按钮")]
    public KeyCode KeyRotor = KeyCode.R;
    [Header("切换视角按钮")]
    public KeyCode KeyPerspective = KeyCode.V;
    [Header("地图按钮")]
    public KeyCode KeyMap = KeyCode.M;
    [Header("HUD按钮")]
    public KeyCode KeyHUD = KeyCode.H;


    [Space(20)]
    [Header("输入值")]
    [Range(-1, 1)] public float TurnLeftRight;
    [Range(-1, 1)] public float MoveDownUp;
    [Range(-1, 1)] public float MoveLeftRight;
    [Range(-1, 1)] public float MoveBackFoward;



    /// <summary>
    /// Update is called once per frame
    /// </summary>
    void Update()
    {
        // 闲置输入值的大小
        MoveBackFoward = Mathf.Clamp(MoveBackFoward, -1, 1);
        TurnLeftRight = Mathf.Clamp(TurnLeftRight, -1, 1);
        MoveLeftRight = Mathf.Clamp(MoveLeftRight, -1, 1);
        MoveDownUp = Mathf.Clamp(MoveDownUp, -1, 1);
    }


    /// <summary>
    /// 固定帧更新
    /// </summary>
    void FixedUpdate()
    {
        if (Input.GetKey(KeyTurnLeft))
        {
            TurnLeftRight = Mathf.Lerp(TurnLeftRight, -1, Time.deltaTime * Sensitivity);
        }
        else if (Input.GetKey(KeyTurnRight))
        {
            TurnLeftRight = Mathf.Lerp(TurnLeftRight, 1, Time.deltaTime * Sensitivity);
        }
        else if ((Input.GetKey(KeyTurnLeft) && Input.GetKey(KeyTurnRight)) || (!Input.GetKey(KeyTurnLeft) && !Input.GetKey(KeyTurnRight)))
        {
            TurnLeftRight = Mathf.Lerp(TurnLeftRight, 0, Time.deltaTime * Sensitivity);
        }
        if (TurnLeftRight < -0.99f)
        {
            TurnLeftRight = -1;
        }
        else if (TurnLeftRight > -0.01f && TurnLeftRight < 0.01f)
        {
            TurnLeftRight = 0;
        }
        else if (TurnLeftRight > 0.99f)
        {
            TurnLeftRight = 1;
        }

        if (Input.GetKey(KeyMoveBack))
        {
            MoveBackFoward = Mathf.Lerp(MoveBackFoward, -1, Time.deltaTime * Sensitivity);
        }
        else if (Input.GetKey(KeyMoveForward))
        {
            MoveBackFoward = Mathf.Lerp(MoveBackFoward, 1, Time.deltaTime * Sensitivity);
        }
        else if ((Input.GetKey(KeyMoveBack) && Input.GetKey(KeyMoveForward)) || (!Input.GetKey(KeyMoveBack) && !Input.GetKey(KeyMoveForward)))
        {
            MoveBackFoward = Mathf.Lerp(MoveBackFoward, 0, Time.deltaTime * Sensitivity);
        }
        if (MoveBackFoward < -0.99f)
        {
            MoveBackFoward = -1;
        }
        else if (MoveBackFoward > -0.01f && MoveBackFoward < 0.01f)
        {
            MoveBackFoward = 0;
        }
        else if (MoveBackFoward > 0.99f)
        {
            MoveBackFoward = 1;
        }

        if (Input.GetKey(KeyMoveLeft))
        {
            MoveLeftRight = Mathf.Lerp(MoveLeftRight, -1, Time.deltaTime * Sensitivity);
        }
        else if (Input.GetKey(KeyMoveRight))
        {
            MoveLeftRight = Mathf.Lerp(MoveLeftRight, 1, Time.deltaTime * Sensitivity);
        }
        else if ((Input.GetKey(KeyMoveLeft) && Input.GetKey(KeyMoveRight)) || (!Input.GetKey(KeyMoveLeft) && !Input.GetKey(KeyMoveRight)))
        {
            MoveLeftRight = Mathf.Lerp(MoveLeftRight, 0, Time.deltaTime * Sensitivity);
        }
        if (MoveLeftRight < -0.99f)
        {
            MoveLeftRight = -1;
        }
        else if (MoveLeftRight > -0.01f && MoveLeftRight < 0.01f)
        {
            MoveLeftRight = 0;
        }
        else if (MoveLeftRight > 0.99f)
        {
            MoveLeftRight = 1;
        }


        if (Input.GetKey(KeyMoveDown))
        {
            MoveDownUp = Mathf.Lerp(MoveDownUp, -1, Time.deltaTime * Sensitivity);
        }
        else if (Input.GetKey(KeyMoveUp))
        {
            MoveDownUp = Mathf.Lerp(MoveDownUp, 1, Time.deltaTime * Sensitivity);
        }
        else if ((Input.GetKey(KeyMoveDown) && Input.GetKey(KeyMoveUp)) || (!Input.GetKey(KeyMoveDown) && !Input.GetKey(KeyMoveUp)))
        {
            MoveDownUp = Mathf.Lerp(MoveDownUp, 0, Time.deltaTime * Sensitivity);
        }
        if (MoveDownUp < -0.99f)
        {
            MoveDownUp = -1;
        }
        else if (MoveDownUp > -0.01f && MoveDownUp < 0.01f)
        {
            MoveDownUp = 0;
        }
        else if (MoveDownUp > 0.99f)
        {
            MoveDownUp = 1;
        }
    }
}

3.直升机驾驶

创建脚本HelicopterDrive.cs,拖拽到Player上。

9f19d5865251427eb04452962cc14498.png

3.1.处理发动机和旋翼

3.1.1.直升机的状态

/// <summary>
/// 直升机状态
/// </summary>
public enum EnumHeliState
{
    Idle, // 闲置
    EngineOn, // 发动机打开
    RotorAC, // 旋翼加速
    RotorDC, // 旋翼减速
    RotorMax // 旋翼最大速(可以飞行)
}

3.1.2.处理发动机

按E键控制发动机的开关,让直升机在闲置状态和发动机开启状态中切换,并控制音频和旋翼的转速。

    /// <summary>
    /// 处理发动机
    /// </summary>
    void HandleEngine()
    {
        // 控制发动机
        if (Input.GetKeyUp(Controller.Inputs.KeyEngine))
        {
            // 当直升机处于闲置状态时,可开启发动机,进入发动机启动状态
            if (HeliState == EnumHeliState.Idle)
            {
                // 调整发动机转速
                DOTween.To(() => EngineSpeed, x => EngineSpeed = x, EngineSpeedMax, EngineTime);
                // 控制音频
                AsEngine.Play();

                StartCoroutine(DelayHandleEngine(EngineTime, true));
            }
            // 当直升机处于发动机启动状态时,可关闭发动机,进入闲置状态
            else if (HeliState == EnumHeliState.EngineOn)
            {
                // 调整发动机转速
                DOTween.To(() => EngineSpeed, x => EngineSpeed = x, 0, EngineTime);
                // 控制音频
                AsEngine.Stop();

                StartCoroutine(DelayHandleEngine(EngineTime, false));
            }
        }

        // 控制音高
        AsEngine.pitch = 3.0f * (EngineSpeed / EngineSpeedMax);
    }
    /// <summary>
    /// 延时处理发动机
    /// </summary>
    /// <param name="t"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    IEnumerator DelayHandleEngine(float t, bool b)
    {
        yield return new WaitForSeconds(t);

        // 切换直升机状态
        if (b)
        {
            ChangeHeliState(EnumHeliState.EngineOn);
        }
        else
        {
            ChangeHeliState(EnumHeliState.Idle);
        }
    }

其中用到了DOTween.To方法,可以让数值、向量等像DOTween动画一样变化。

  DOTween.To(()=>变量,x=> 变量=x , 变量目标值, 过渡时间);

3.1.3.处理旋翼

按R键控制旋翼的开关,让直升机在发动机开启状态和发动机最大苏状态中切换,并控制音频和旋翼的转速。其中旋翼加速状态和旋翼减速状态是过渡状态。

    /// <summary>
    /// 处理旋翼
    /// </summary>
    void HandleRotor()
    {
        // 传递旋翼转速
        MainRotor.Speed = m_MainRotorSpeed;
        TailRotor.Speed = m_TailRotorSpeed;

        // 控制旋翼
        if (Input.GetKeyUp(Controller.Inputs.KeyRotor))
        {
            // 当直升机处于发动机启动状态时,可开启旋翼,进入旋翼加速状态,延时后为旋翼最大速状态
            if (HeliState == EnumHeliState.EngineOn)
            {
                // 切换直升机状态
                ChangeHeliState(EnumHeliState.RotorAC);
                // 调整旋翼转速
                DOTween.To(() => m_MainRotorSpeed, x => m_MainRotorSpeed = x, MainRotorSpeedMax, RotorTime);
                DOTween.To(() => m_TailRotorSpeed, x => m_TailRotorSpeed = x, TailRotorSpeedMax, RotorTime);
                // 控制音频
                AsMainRotor.Play();
                AsTailRotor.Play();

                StartCoroutine(DelayHandleRotor(RotorTime, true));
            }
            else if (HeliState == EnumHeliState.RotorMax)
            {
                if (IsLand)
                {
                    // 切换直升机状态
                    ChangeHeliState(EnumHeliState.RotorDC);
                    // 调整旋翼转速
                    DOTween.To(() => m_MainRotorSpeed, x => m_MainRotorSpeed = x, 0, RotorTime * 0.5f);
                    DOTween.To(() => m_TailRotorSpeed, x => m_TailRotorSpeed = x, 0, RotorTime * 0.5f);
                    // 控制音频
                    AsMainRotor.Stop();
                    AsTailRotor.Stop();

                    StartCoroutine(DelayHandleRotor(RotorTime * 0.5f, false));
                }
            }
        }

        // 控制音高
        AsMainRotor.pitch = m_MainRotorSpeed / MainRotorSpeedMax;
        AsTailRotor.pitch = 1.5f * (m_TailRotorSpeed / TailRotorSpeedMax);

        // 处理主旋翼周期变距倾转
        TfFeather.localEulerAngles = new Vector3(V2Cyclic.y, 0, -V2Cyclic.x) * 7.5f;
    }
    /// <summary>
    /// 延时处理旋翼
    /// </summary>
    /// <param name="t"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    IEnumerator DelayHandleRotor(float t, bool b)
    {
        yield return new WaitForSeconds(t);

        // 切换直升机状态
        if (b)
        {
            ChangeHeliState(EnumHeliState.RotorMax);
        }
        else
        {
            ChangeHeliState(EnumHeliState.EngineOn);
        }
    }

这里有周期变距(主旋翼的父物体)角度的调整。

这里说一下大多数直升机的飞行原理:

周期变距向前俯,向前飞。周期变距向后仰,向后飞。周期变距向左歪,向左飞。周期变距向右歪,向后飞。

主旋翼桨叶角的大小控制升力的大小。尾桨桨叶角的大小控制转向力的大小。

目录

1.旋翼

2.输入管理器

3.直升机驾驶

3.1.处理发动机和旋翼

3.1.1.直升机的状态

3.1.2.处理发动机

3.1.3.处理旋翼

3.2.输入值与受力分析

3.2.1.处理输入

3.2.2.处理受力与受力分析

3.2.3.处理数据

3.3.直升机的移动与旋转


 

3.2.输入值与受力分析

3.2.1.处理输入

    /// <summary>
    /// 处理输入
    /// </summary>
    void HandleInput()
    {
        // 处理输入值
        Pedals = Controller.Inputs.TurnLeftRight;
        Collective = Controller.Inputs.MoveDownUp;
        V2Cyclic = new Vector3(Controller.Inputs.MoveLeftRight, Controller.Inputs.MoveBackFoward);

        // 处理传动值
        TransmissionH1 = Mathf.Lerp(TransmissionH1, Pedals, Time.deltaTime);
        TransmissionV1 = Mathf.Lerp(TransmissionV1, Collective, Time.deltaTime * 0.2f);
        TransmissionH2 = Mathf.Lerp(TransmissionH2, V2Cyclic.x, Time.deltaTime * 0.2f);
        TransmissionV2 = Mathf.Lerp(TransmissionV2, V2Cyclic.y, Time.deltaTime * 0.05f);
    }

由于直升机的重量和阻力,所以接收输入之后还需要再次差值运输,达到平滑延迟的效果。

3.2.2.处理受力与受力分析

    /// <summary>
    /// 处理受力
    /// </summary>
    void HandleForce()
    {
        if (HeliState == EnumHeliState.RotorMax)
        {
            // 处理垂直方向的力
            if (TransmissionV1 > 0.1f)
            {
                m_UpForce = Mathf.Lerp(m_UpForce, m_UpForceStasis + ((UpForceMax - m_UpForceStasis) * TransmissionV1), Time.deltaTime);
            }
            else if (TransmissionV1 >= -0.1f && TransmissionV1 <= 0.1f)
            {
                m_UpForce = Mathf.Lerp(m_UpForce, m_UpForceStasis, Time.deltaTime);
            }
            else if (TransmissionV1 < -0.1f)
            {
                if (m_UpForceStasis == 0)
                {
                    m_UpForce = Mathf.Lerp(m_UpForce, 0, Time.deltaTime);
                }
                else
                {
                    m_UpForce = Mathf.Lerp(m_UpForce, m_UpForceStasis + (m_UpForceStasis * TransmissionV1), Time.deltaTime);
                }
            }


            if (!IsLand)
            {
                // 处理水平左右方向的力
                if (TransmissionH2 > 0.1f)
                {
                    m_HorizontalForce = Mathf.Lerp(m_HorizontalForce, ForwardForceMax * TransmissionH2 * 0.25f, Time.deltaTime);
                }
                else if (TransmissionH2 >= -0.1f && TransmissionH2 <= 0.1f)
                {
                    m_HorizontalForce = Mathf.Lerp(m_HorizontalForce, 0, Time.deltaTime);
                }
                else if (TransmissionH2 < 0.1f)
                {
                    m_HorizontalForce = Mathf.Lerp(m_HorizontalForce, ForwardForceMax * TransmissionH2 * 0.25f, Time.deltaTime);
                }

                // 处理水平前后方向的力
                if (TransmissionV2 > 0.1f)
                {
                    m_ForwardForce = Mathf.Lerp(m_ForwardForce, ForwardForceMax * TransmissionV2, Time.deltaTime);
                }
                else if (TransmissionV2 >= -0.1f && TransmissionV2 <= 0.1f)
                {
                    m_ForwardForce = Mathf.Lerp(m_ForwardForce, 0, Time.deltaTime);
                }
                else if (TransmissionV2 < 0.1f)
                {
                    m_ForwardForce = Mathf.Lerp(m_ForwardForce, ForwardForceMax * TransmissionV2 * 0.25f, Time.deltaTime);
                }

                // 处理转向力
                if (TransmissionH1 > 0.1f)
                {
                    m_TurnForce = Mathf.Lerp(m_TurnForce, TurnForceMax * TransmissionH1, Time.deltaTime);
                }
                else if (TransmissionH1 >= -0.1f && TransmissionH1 <= 0.1f)
                {
                    m_TurnForce = Mathf.Lerp(m_TurnForce, 0, Time.deltaTime);
                }
                else if (TransmissionH1 < 0.1f)
                {
                    m_TurnForce = Mathf.Lerp(m_TurnForce, TurnForceMax * TransmissionH1, Time.deltaTime);
                }
            }
        }
    }

3.2.3.处理数据

    /// <summary>
    /// 处理数据
    /// </summary>
    void HandleData()
    {
        // 处理高度和判断是否着陆
        RaycastHit hit;
        Vector3 direction = transform.TransformDirection(Vector3.down);
        Ray ray = new Ray(transform.position, direction);
        if (Physics.Raycast(ray, out hit, 3000, LayerGround))
        {
            Height = hit.distance;
            if (Height > HeightMin * 0.01f)
            {
                IsLand = false;
            }
            else
            {
                IsLand = true;
            }
        }

        // 处理海拔
        Altitude = Controller.TfHeliTransform.position.y;

        // 处理角度
        if (Controller.TfHeliTransform.eulerAngles.x <= -180)
        {
            PitchAngle = Controller.TfHeliTransform.eulerAngles.x + 360;
        }
        else if (Controller.TfHeliTransform.eulerAngles.x > -180 && Controller.TfHeliTransform.eulerAngles.x <= 180)
        {
            PitchAngle = Controller.TfHeliTransform.eulerAngles.x;
        }
        else if (Controller.TfHeliTransform.eulerAngles.x > 180)
        {
            PitchAngle = Controller.TfHeliTransform.eulerAngles.x - 360;
        }

        if (Controller.TfHeliTransform.eulerAngles.y <= -180)
        {
            YawAngle = Controller.TfHeliTransform.eulerAngles.y + 360;
        }
        else if (Controller.TfHeliTransform.eulerAngles.y > -180 && Controller.TfHeliTransform.eulerAngles.y <= 180)
        {
            YawAngle = Controller.TfHeliTransform.eulerAngles.y;
        }
        else if (Controller.TfHeliTransform.eulerAngles.y > 180)
        {
            YawAngle = Controller.TfHeliTransform.eulerAngles.y - 360;
        }

        if (Controller.TfHeliTransform.eulerAngles.z <= -180)
        {
            RollAngle = Controller.TfHeliTransform.eulerAngles.z + 360;
        }
        else if (Controller.TfHeliTransform.eulerAngles.z > -180 && Controller.TfHeliTransform.eulerAngles.z <= 180)
        {
            RollAngle = Controller.TfHeliTransform.eulerAngles.z;
        }
        else if (Controller.TfHeliTransform.eulerAngles.z > 180)
        {
            RollAngle = Controller.TfHeliTransform.eulerAngles.z - 360;
        }

        // 处理均衡升力
        if (IsLand)
        {
            m_UpForceStasis = 0;
        }
        else
        {
            m_UpForceStasis = m_Rigidbody.mass * -Physics.clothGravity.y /
                Mathf.Cos((Mathf.Abs(PitchAngle) + Mathf.Abs(RollAngle)) * Mathf.Deg2Rad);
        }

        // 处理速度
        HorizontalVelocity = Vector3.Distance(new Vector3(m_Rigidbody.velocity.x, 0, m_Rigidbody.velocity.z), Vector3.zero);
        VerticalVelocity = m_Rigidbody.velocity.y;
        ForwardVelocity = m_Rigidbody.velocity.z;
        RightVelocity = m_Rigidbody.velocity.z;
        YawVelocity = m_Rigidbody.angularVelocity.y * Mathf.Rad2Deg;
    }

键盘的操作不如操纵杆,为了便于操作,定义了一个升力的均衡值m_UpForceStasis。

当直升机着陆时,m_UpForceStasis为0,即在不控制直升机升降时,升力会自动恢复为0。

当直升机离地时,m_UpForceStasis为直升机收到的重力,直升机会悬停。

3.3.直升机的移动与旋转

这些都是在FixedUpdate()里的。

    /// <summary>
    /// 升降
    /// </summary>
    void FixedLift()
    {
        m_Rigidbody.AddRelativeForce(Vector3.up * m_UpForce);
    }

    /// <summary>
    /// 移动
    /// </summary>
    void FixedMove()
    {
        m_Rigidbody.AddRelativeForce(Vector3.right * m_HorizontalForce);
        m_Rigidbody.AddRelativeForce(Vector3.forward * m_ForwardForce);
    }

    /// <summary>
    /// 转向
    /// </summary>
    void FixedTurn()
    {
        m_Rigidbody.AddRelativeTorque(0, m_TurnForce, 0);
    }

    float tx = 0, ty = 0;
    /// <summary>
    /// 倾斜和左右移动
    /// </summary>
    void FixedTilt()
    {
        tx = Mathf.Lerp(tx, V2Cyclic.y * 7.5f, Time.deltaTime * 0.5f) / (m_Rigidbody.mass / 9000);
        ty = Mathf.Lerp(ty, -V2Cyclic.x * 25.0f, Time.deltaTime * 0.5f) / (m_Rigidbody.mass / 9000);
        //m_V3BaseTilying = new Vector3(m_ForwardForce / m_Rigidbody.mass / 94, transform.localEulerAngles.y, -m_HorizontalForce / m_Rigidbody.mass / 4);
        m_V3BaseTilying = new Vector3(tx, transform.localEulerAngles.y, ty);
        transform.rotation = Quaternion.Euler(m_V3BaseTilying);
    }

直升机的受力已经写好了,所以这里让受力与传动值或者输入值绑定就可以了。

3.4.布朗运动

为了让直升机飞行的姿态更加自然,我们让直升机在飞行时进行布朗运动。布朗运动的振幅与直升机的高度相关。

/// <summary>
/// 布朗运动
/// </summary>
void FixedBrownian()
{
    if (Height <= HeightMin * 0.1f)
    {
        Brownian.V3PositionScale = Vector3.zero;
        Brownian.V3RotationScale = Vector3.zero;
    }
    else if (Height > HeightMin * 0.1f && Height < HeightMin * 1.1f)
    {
        Brownian.V3PositionScale = new Vector3(0.25f, 1f, 0.25f) * (Height - HeightMin * 0.1f) / HeightMin;
        Brownian.V3RotationScale = new Vector3(1f, 0.5f, 0.25f) * (Height - HeightMin * 0.1f) / HeightMin;
    }
    else if (Height > HeightMin * 1.1f)
    {
        Brownian.V3PositionScale = new Vector3(0.25f, 1f, 0.25f);
        Brownian.V3RotationScale = new Vector3(1f, 0.5f, 0.25f);
    }
}

布朗运动的插件我会免费放在这里,BrownianMotion.cs的脚本拖在这里。

Unity3D 布朗运动算法插件 Brownian Motion

a3fece2608f4463eb6ef5fa3d9812bfe.png

4.用户操作界面

4.1.视图层和仪表盘显示器界面的定义

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 视图层
/// </summary>
public class ViewLayer : MonoBehaviour
{
    public ControllerLayer Controller; // 控制层
    public MonitorUI Monitor; // 仪表盘显示器UI


    [Space(50)]
    [Header("…………UI…………")]
    [Header("大地图")]
    public Image PageMap; // 大地图页面


    [Space(20)]
    [Header("HUD")]
    public Image PageHUD; // HUD页面





    [Space(50)]
    [Header("…………其它…………")]
    public Transform TfFollow; // 跟随锚点
    public Camera CamMain; // 主摄像机
    public Transform TfTarget; // 主摄像机目标
    public Camera CamFPS; // 舱内视角摄像机
    public Camera CamMap; // 大地图摄像机
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 仪表盘显示器UI
/// </summary>
public class MonitorUI : MonoBehaviour
{
    [Header("HUD")]
    public Image ImgBG; // 背景图
    public Image ImgPitch; // 俯仰角
    public Text TextRollAngle; // 横滚角

    [Space(10)]
    [Header("速度")]
    public Image ImgHorizontalVelocity; // 水平速度
    public Text TextHorizontalSpeed;
    public Slider SldVerticalVelocity; // 垂直速度
    public Text TextVerticalVelocity;
    public Slider SldForwardVelocity; // 前进速度
    public Text TextForwardVelocity;
    public Slider SldRightVelocity; // 横移速度
    public Text TextRightVelocity;
    public Slider SldYawVelocity; // 转向速度
    public Text TextYawVelocity;

    [Space(10)]
    [Header("小地图")]
    public Image ImgHSI; // 罗盘
}

4.2.处理输入

依然是通过我自己写的InputManager来控制鼠标键盘的输入。

    /// <summary>
    /// 处理输入
    /// </summary>
    void HandleInput()
    {
        if (Input.GetKeyUp(Inputs.KeyPerspective))
        {
            SwitchPerspective();
        }
        if (Input.GetKeyUp(Inputs.KeyMap))
        {
            SwitchMapPage();
        }
        if (Input.GetKeyUp(Inputs.KeyHUD))
        {
            SwitchHUDPage();
        }
    }

4.3.主视角、大地图视角跟随直升机

    /// <summary>
    /// 跟随直升机
    /// </summary>
    void FollowHelicopter()
    {
        // Follow同步
        View.TfFollow.position = HeliDrive.transform.position;
        View.TfFollow.eulerAngles = new Vector3(0, HeliDrive.transform.eulerAngles.y, 0);

        // 大地图摄像机同步位置
        View.CamMap.transform.position = new Vector3(HeliDrive.transform.position.x, 9000, HeliDrive.transform.position.z);
    }

4.4.显示数据

在仪表盘显示器的画布中显示数据。反正这些都不需要操作,所有之后再通过图层打在UI画布上。

    /// <summary>
    /// 显示数据
    /// </summary>
    public void FixedUpdateMonitor()
    {
        View.Monitor.ImgBG.transform.localPosition = new Vector3(0, HeliDrive.PitchAngle * 10, 0);
        View.Monitor.ImgPitch.transform.localPosition = new Vector3(0, HeliDrive.PitchAngle * 10, 0);
        View.Monitor.ImgBG.transform.localEulerAngles = new Vector3(0, 0, -HeliDrive.RollAngle);
        View.Monitor.TextRollAngle.text = HeliDrive.RollAngle.ToString("f1");

        View.Monitor.ImgHorizontalVelocity.transform.localEulerAngles = new Vector3(0, 0, -HeliDrive.HorizontalVelocity * 3.6f);
        View.Monitor.TextHorizontalSpeed.text = (HeliDrive.HorizontalVelocity * 3.6f).ToString("f0") + "km/h";
        View.Monitor.SldVerticalVelocity.value = HeliDrive.VerticalVelocity;
        View.Monitor.TextVerticalVelocity.text = HeliDrive.VerticalVelocity.ToString("f1") + "m/s";
        View.Monitor.SldForwardVelocity.value = HeliDrive.ForwardVelocity;
        View.Monitor.TextForwardVelocity.text = HeliDrive.ForwardVelocity.ToString("f1") + "m/s";
        View.Monitor.SldRightVelocity.value = HeliDrive.RightVelocity;
        View.Monitor.TextRightVelocity.text = HeliDrive.RightVelocity.ToString("f1") + "m/s";
        View.Monitor.SldYawVelocity.value = HeliDrive.YawVelocity;
        View.Monitor.TextYawVelocity.text = HeliDrive.YawVelocity.ToString("f2") + "°/s";

        View.Monitor.ImgHSI.transform.localEulerAngles = new Vector3(0, 0, HeliDrive.YawAngle);
    }

4.5.切换视角

    public EnumPerspectiveType PerspectiveType; // 视角
    public enum EnumPerspectiveType
    {
        TPS, // 与距离舱外视角
        FPS // 驾驶员仓内视角
    }
    /// <summary>
    /// 切换视角
    /// </summary>
    public void SwitchPerspective()
    {
        if (PerspectiveType == EnumPerspectiveType.FPS)
        {
            OnTPS();
        }
        else if (PerspectiveType == EnumPerspectiveType.TPS)
        {
            OnFPS();
        }
    }
    public void OnFPS()
    {
        PerspectiveType = EnumPerspectiveType.FPS;
        View.CamMain.gameObject.SetActive(false);
        View.CamFPS.gameObject.SetActive(true);
    }
    public void OnTPS()
    {
        PerspectiveType = EnumPerspectiveType.TPS;
        View.CamMain.gameObject.SetActive(true);
        View.CamFPS.gameObject.SetActive(false);
    }

4.6.控制页面

    public void SwitchMapPage()
    {
        if (View.PageMap.gameObject.active)
        {
            View.PageMap.gameObject.SetActive(false);
        }
        else
        {
            View.PageMap.gameObject.SetActive(true);
            View.PageHUD.gameObject.SetActive(false);
        }
    }

    /// <summary>
    /// 开关HUD页面
    /// </summary>
    public void SwitchHUDPage()
    {
        if (View.PageHUD.gameObject.active)
        {
            View.PageHUD.gameObject.SetActive(false);
        }
        else
        {
            View.PageMap.gameObject.SetActive(false);
            View.PageHUD.gameObject.SetActive(true);
        }
    }

5.资源链接

Unity3D 完全的直升机控制器插件(虚拟仿真级别)

 


http://www.kler.cn/a/399155.html

相关文章:

  • Nginx server_name配置错误导致路由upstream超时问题
  • 十:详解HTTP的请求行
  • JS的学习与使用
  • 微服务即时通讯系统的实现(客户端)----(3)
  • Visual Studio 2017 快捷键设置-批量注释和批量取消注释
  • java小练习
  • 日常ctf
  • Python自动检测requests所获得html文档的编码
  • 代码学习——进制转换
  • Vue中template模板报错
  • 51单片机应用开发---LCD1602显示应用
  • Qt对话框与界面设计——常见的对话框
  • 设计模式的基本概述
  • 04 - Clickhouse-21.7.3.14-2单机版安装
  • zabbix监控端界面时间与服务器时间不对应
  • redis集群:redis集群中的某个节点怎么单独重启(非docker安装)
  • C语言导航 4.1语法基础
  • LeetCode --- 143周赛
  • STM32 HAL 矩阵按键(轮询方式)
  • Android 项目依赖库无法找到的解决方案
  • 活着就好20241118
  • 海康IPC接入TRTC时,从海康中获取的数据显示时色差不正确
  • 使用 PyTorch 实现 AlexNet 进行 MNIST 图像分类
  • 从零开始学习 sg200x 多核开发之 milkv-duo256 编译运行 sophpi
  • Visual Studio 2022 安装
  • RabbitMQ 在 Java 和 Spring Boot 中的应用详解