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

devtools/2024/11/17 8:49:54/

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

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

489cdfe32d1e44fe88a1ee8f56b2a526.png

3d8111f9b.png" alt="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

3d9812bfe.png" alt="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.ppmy.cn/devtools/134666.html

相关文章

Windows 小记 5 -- 判断账户是否是管理员账户

判断账户是否位于管理员用户组的方法有&#xff1a; net localgroup Administrators "账户名" /add 如果返回“操作成功完成”&#xff0c;则表明之前不是管理员账户。那么再调用 /del 删除添加的管理员。 如果返回 “\n发生系统错误 1378。\n指定的帐户名已是此组…

【0x001C】HCI_Write_Page_Scan_Activity详解

目录 一、命令概述 二、命令格式和参数说明 2.1. HCI_Write_Page_Scan_Activity命令格式 2.2. Page_Scan_Interval 2.3. Page_Scan_Window 三、响应事件及参数说明 3.1. HCI_Command_Complete事件 3.2. Status 3.3. 示例 四、命令执行流程 4.1. 命令发起阶段(主机端…

基于BERT的命名体识别(NER)

基于BERT的命名实体识别&#xff08;NER&#xff09; 目录 项目背景项目结构环境准备数据准备代码实现 5.1 数据预处理 (src/preprocess.py)5.2 模型训练 (src/train.py)5.3 模型评估 (src/evaluate.py)5.4 模型推理 (src/inference.py) 项目运行 6.1 一键运行脚本 (run.sh)6…

AI写作(十)发展趋势与展望(10/10)

一、AI 写作的崛起之势 在当今科技飞速发展的时代&#xff0c;AI 写作如同一颗耀眼的新星&#xff0c;迅速崛起并在多个领域展现出强大的力量。 随着人工智能技术的不断进步&#xff0c;AI 写作在内容创作领域发挥着越来越重要的作用。据统计&#xff0c;目前已有众多企业开始…

async 和 await的使用

一、需求 点击按钮处理重复提交&#xff0c;想要通过disabled的方式实现。 但是点击按钮调用的方法里有ajax、跳转、弹窗等一系列逻辑操作&#xff0c;需要等方法里流程都走完&#xff0c;再把disabled设为false&#xff0c;这样下次点击按钮时就可以继续走方法里的ajax等操作…

Java读取WPS excel.xlsx嵌入图片

1. 背景&原因 经常有读取Excel文件的需求&#xff0c;开发者大多使用apache poi或者基于此的工具进行excel内容读取&#xff0c;前不久遇到了一个需求&#xff0c;读取每一行内容&#xff0c;但每一行都包含图片文件&#xff0c;发现无法通过已封装的工具读取excel的图片内…

无人机检测车辆——多目标检测

目录 YOLOv3&#xff08;You Only Look Once version 3&#xff09;简介 YOLOv3 的主要特点 YOLOv3 的结构 1. 特征提取网络&#xff08;Backbone&#xff09; 2. 检测头&#xff08;Head&#xff09; 3. 输出层 YOLOv3 损失函数 YOLOv3 的优势 YOLOv3 的应用 YOLOv3…

Scrapy并发请求深度解析:如何高效控制爬虫速度

标题&#xff1a;Scrapy并发请求深度解析&#xff1a;如何高效控制爬虫速度 引言 在Python的Scrapy框架中&#xff0c;合理设置并发请求数量是提高爬虫效率和遵守网站爬取规则的关键。本文将详细解释如何在Scrapy中设置并发请求的数量&#xff0c;并提供代码示例&#xff0c;…