Unity3D 完整直升机控制器(虚拟仿真级别)
采用了MVC框架,以四轴驱动的方式对直升机的启动、飞行做了仿真模拟,包括但不限于参数设置、启动发动机和旋翼、数据显示、HUD、UI、升降、水平移动、转弯等。
文末有完整的工程资源链接。
1.旋翼
直升机飞行过程中,有顶部的主旋翼和尾部的尾桨需要转动。
在旋翼加速状态时,悬疑的转速逐渐增加到最大。旋翼转速达到最大时,直升机才能起飞。起飞后,旋翼转速始终保持不变。
旋翼的脚本RotorRotation.cs拖拽到主旋翼和尾桨上。
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上。
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
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 完全的直升机控制器插件(虚拟仿真级别)