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.rhkb.cn/news/473489.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

yum工具的学习

Linux下安装软件的方法 1.源代码安装 2.rmp包安装 3.包管理器进行安装 --- yum/apt Linux下载软件的过程 操作系统的好坏评估 -- 生态问题 yum具体操作 Linux软件安装所有人都能用&#xff0c;是以other的身份去执行可执行程序 文件拷贝&#xff08;sudo&#xff09;-- &g…

Linux:进程的优先级 进程切换

文章目录 前言一、进程优先级1.1 基本概念1.2 查看系统进程1.3 PRI和NI1.4 调整优先级1.4.1 top命令1.4.2 nice命令1.4.3 renice命令 二、进程切换2.1 补充概念2.2 进程的运行和切换步骤&#xff08;重要&#xff09; 二、Linux2.6内核进程O(1)调度队列&#xff08;重要&#x…

MongoDB在现代Web开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 引言 MongoDB 概述 定义与原理 发展…

爬取链家二手房房价数据存入mongodb并进行分析

感谢您的关注&#xff01;需要完整源码评论区获取~ 【实验目的】 1. 使用 python 将爬虫数据存入 mongodb&#xff1b; 2. 使用 python 读取 mongodb 数据并进行可视化分析。 【实验原理】 MongoDB 是文档数据库&#xff0c;采用 BSON 的结构来存储数据。在文档中可嵌套其…

Solana 区块链的技术解析及未来展望 #dapp开发#公链搭建

随着区块链技术的不断发展和应用场景的扩展&#xff0c;性能和可拓展性成为各大公链竞争的关键因素。Solana&#xff08;SOL&#xff09;因其高吞吐量、低延迟和低成本的技术特性&#xff0c;在众多区块链项目中脱颖而出&#xff0c;被誉为“以太坊杀手”之一。本文将从技术层面…

Vue通过file控件上传文件到Node服务器

功能&#xff1a; 多文件同步上传、拖动上传、实时上传进度条、上传前的删除文件、原生file控件的美化 搁置的功能: 取消上传(上传过程中取消,即取消网络请求abort)、上传文件夹、大文件切片、以及很多限制条件未处理(重复上传、文件格式。。。) bug: 文件总大小(。。。竟然从d…

Element-ui Select选择器自定义搜索方法

效果图 具体实现 <template><div class"home"><el-selectref"currencySelect"v-model"currency"filterable:spellcheck"false"placeholder"请选择":filter-method"handleCurrencyFilter"change&q…

JS的学习与使用

JS的学习与使用 一 什么是Javascript&#xff1f; Javascript是一门跨平台&#xff0c;面向对象的脚本语言&#xff0c;是用来控制网页行为的&#xff0c;它能使网页可以交互 java与Javascript是完全不同的语言&#xff0c;不论是概念还是设计&#xff0c;但是基础语法类似 E…

Docker:查看镜像里的文件

目录 背景步骤1、下载所需要的docker镜像2、创建并运行临时容器3、停止并删除临时容器 背景 在开发过程中&#xff0c;为了更好的理解和开发程序&#xff0c;有时需要确认镜像里的文件是否符合预期&#xff0c;这时就需要查看镜像内容 步骤 1、下载所需要的docker镜像 可以使…

【网络安全 | 漏洞挖掘】通过密码重置污染实现账户接管

未经许可,不得转载。 文章目录 密码重置污染攻击漏洞挖掘的过程目标选择与初步测试绕过 Cloudflare 的尝试发现两个域名利用 Origin 头部污染实现账户接管攻击流程总结在今天的文章中,我们将深入探讨一种 账户接管 漏洞,并详细分析如何绕过 Cloudflare 的保护机制,利用密码…

Redis 5 种基本数据类型详解

Redis 共有 5 种基本数据类型&#xff1a;String&#xff08;字符串&#xff09;、List&#xff08;列表&#xff09;、Set&#xff08;集合&#xff09;、Hash&#xff08;散列&#xff09;、Zset&#xff08;有序集合&#xff09;。 这 5 种数据类型是直接提供给用户使用的&…

AI 提示词(Prompt)入门 十:最佳实践|详细询问,提供细节!

1、原则解释 当与 ChatGPT 交流时&#xff0c;提供具体和详细的信息非常重要。 这样做可以帮助 ChatGPT 更准确地理解你的需求和上下文&#xff0c;从而生成更相关和有用的回答 明确的信息可以包括具体的问题背景、相关领域的说明、你所期望的答案类型等。 2、如何实践 明…

数据库的隔离机制---对MySQL 默认隔离级别的理解

参考&#xff1a; 脏读、幻读和不可重复读_脏读 ​​​​​​ 全网最详细MVCC讲解&#xff0c;一篇看懂 - 知乎全网最详细MVCC讲解&#xff0c;一篇看懂 - 知乎 面试官&#xff1a;MySQL 的默认隔离级别是什么?可以解决幻读问题吗&#xff1f; 目录 一、脏读、幻读、不可…

UNI-APP小程序答题功能开发(左右滑动,判断,填空,问答,答题卡,纠错,做题倒计时等)

原博&#xff1a;uni-app小程序答题功能开发(左右滑动,判断,填空,问答,答题卡,纠错,做题倒计时等)_uniapp答题模板-CSDN博客 标签&#xff1a; 小程序 uni-app 模板链接:答题模板 html部分 这里没啥好说的,就是根据不同的状态显示不同的内容 <template><view>…

SpringBoot多环境配置的实现

前言 开发过程中必然使用到的多环境案例&#xff0c;通过简单的案例分析多环境配置的实现过程。 一、案例 1.1主配置文件 spring:profiles:active: prod server:port: 80801.2多环境配置文件 开发环境 blog:domain: http://localhost:8080测试环境 blog:domain: https:/…

另外一种缓冲式图片组件的用法

文章目录 1. 概念介绍2. 使用方法2.1 基本用法2.2 缓冲原理3. 示例代码4. 内容总结我们在上一章回中介绍了"FadeInImage组件"相关的内容,本章回中将介绍CachedNetworkImage组件.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的CachedNetwo…

Linux下多线程

在Linux下的底层里并没有多线程这个概念&#xff0c;取而代之的是轻量级进程的概念。应为在Llinu下内核下并没有TCB,而只有PCB。 线程是什么 在⼀个程序⾥的⼀个执⾏路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“⼀个进程内部 的控制序…

Win10/11 安装使用 Neo4j Community Edition

如果你下载的是 Neo4j Community Edition 的压缩包&#xff0c;意味着你需要手动解压并配置 Neo4j。以下是详细的使用步骤&#xff1a; 0. 下载压缩包 访问Neo4j官网&#xff0c;找到 Community Edition 版本并选择 4.x 或者 5.x 下载&#xff1a;https://neo4j.com/deployme…

PCB+SMT线上报价系统+PCB生产ERP系统自动化拼板模块升级

PCB生产ERP系统的智能拼版技术&#xff0c;是基于PCB前端报价系统获取到的用户或市场人员已录入系统的板子尺寸及set参数等&#xff0c;按照最优原则或利用率最大化原则自动进行计算并输出拼版样式图和板材利用率&#xff0c;提高工程人员效率&#xff0c;减少板材的浪费。覆铜…

Excel根据条件动态索引单元格范围

假如我是一个老板&#xff0c;下面有数不胜数的员工&#xff0c;我要检查他们每周的工作产出&#xff0c;列一个排行榜&#xff0c;提高员工积极性&#xff0c;毕竟多劳多得嘛。 每天去手动统计&#xff0c;未免显得不太聪明&#xff0c;我们可以利用公式来解决这个问题。 我们…