目录前言
本篇的代码是基于Unity3D 系列课程 “Create with Code” 第一章 “Player Control” 改进而来
- 背景
- 设计
- 代码实现
- 效果展示
- 参考文献
背景
“Create with Code” 课后出了一道题,要求是做一款类似于 “Flappy Bird” 的3D小游戏。玩家仅通过键盘上下键或W/S键控制小鸟上下飞行来避开障碍物。
出题人提供了很多精美的素材,但只是用来做这么个玩意有点大材小用了,于是一个大胆的想法浮现在笔者脑海中。
一想到飞行模拟,笔者第一反应就是《战地风云》系列游戏里的战斗机驾驶。在游戏中,飞机的运动满足以下几点:
- 飞机遵循牛顿力学定律框架,在游戏中主要受发动机推力,风阻力,翼面升力和自身重力的影响。
- 载具可在玩家鼠标和键盘的共同操作下,实现现实中飞机的绝大部分机动动作,如空翻,桶滚等。其中键盘控制飞机围绕 y y y 轴的旋转,鼠标控制飞机围绕 x x x 和 z z z 轴的旋转。
- 摄像机 (camera) 以第三人称视角始终位于飞机后上方,并以 “看向(Look At)” 的方式朝向飞机。
基于上述三点,笔者做出了如下设计:
- 由于Unity中默认刚体(Rigit Body)遵循牛顿三大定律,因此我不需要做出额外的更改。
- 升力是飞机克服重力实现飞行的必要条件,当升力大于重力时,飞机就能实现飞行。在现实中,升力的计算涉及到空气的流体力学。为了简化模型,游戏飞机的升力大小仅取决于飞机的速度。升力与速度之间为线性关系,即:
f o r c e = k ∗ s p e e d + b force=k*speed+b force=k∗speed+b
- 在现实中,飞机的速度与加速度取决于发动机的推力和发动机功率,同时速度还会收到风阻力的影响。要逼真的模拟这么多因素一方面会消耗算力,另一方面写多错多容易引入问题。为此游戏中简化了速度模型,使得速度仅与推力呈线性关系,推力则由玩家的W/S键盘输入控制。同时速度在每时每刻都会以固定的频率减小,当玩家持续施加推力时,飞机增加的速度可以抵消见效的速度;而当玩家停止控制,飞机则会缓慢减速,借此以模拟风阻力的减速作用。需要注意的是,飞机的速度不可能无限增大,也不可能为负值。
s
p
e
e
d
=
{
s
p
e
e
d
+
k
e
y
b
o
a
r
d
i
n
p
u
t
if
s
p
e
e
d
<
m
a
x
s
p
e
e
d
s
p
e
e
d
−
f
r
i
c
t
i
o
n
if
s
p
e
e
d
>
0
0
if
s
p
e
e
d
<
0
speed = begin{cases} speed + keyboard input &text{if } speed < max speed \ speed - friction &text{if } speed > 0\ 0 &text{if } speed < 0 end{cases}
speed=⎩⎪⎨⎪⎧speed+keyboard inputspeed−friction0if speed
- 飞机可以分别绕 x y z x y z x y z 轴旋转以实现自由飞行。其中,绕 y y y 轴通过键盘A/D输入控制,绕 z z z 轴旋转通过鼠标水平输入控制,绕 x x x 轴旋转通过鼠标垂直输入控制。此外,为了模拟现实中升力和速度对飞机操纵的影响,游戏中为飞行控制添加了阻尼机制,当飞机速度越低时,每单位输入能造成的旋转角度越小,用以对应现实中飞机的“失速”情况。
- 为了实现摄像机能始终跟随在飞机背后且时刻看向飞机,首先可以想象一下,摄像机和飞机都处于同一个平面,当飞机旋转一定角度时,视角也在相同的方向上旋转相同的角度,但摄像机需要旋转更长的弧距,才能保证摄像机,飞机,旋转轴心三点在同一条直线上。把这个机制应用在三维球体中,则无论飞机在哪个方向旋转,旋转角度多少,视角都会在飞机后方。以此为基础,通过调用Unity的 LookAt() 接口,就可以使摄像机朝向飞机
- 飞机控制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerControllerX : MonoBehaviour
{
//public GameObject mainCamera;
private float speed = 0.0f;
private float rotationSpeed = 30.0f;
private float damping = 0.0f;
private float keyboardXInput;
private float keyboardYInput;
private float mouseYInput;
private float mouseXInput;
private float zoomScale;
private float gravity = 9.81f;
private float lift = 0.0f;
private Rigidbody rb;
// Start is called before the first frame update
void Start() {
rb = GetComponent();
}
// Update is called once per frame
void Update() {
keyboardYInput = Input.GetAxis("Vertical");
keyboardXInput = Input.GetAxis("Horizontal");
mouseYInput = Input.GetAxis("Mouse Y");
mouseXInput = Input.GetAxis("Mouse X");
if (speed <= 20.0f)
speed = speed + keyboardYInput*0.1f;
if (speed <= 0.0f)
speed = 0.0f;
else
speed = speed - 0.05f;
lift = 0.9f * speed;
if (lift > 9.81f)
lift = 9.81f;
rb.AddForce(Vector3.up*lift - Vector3.up*gravity);
damping = 0.1f * speed;
if (damping > 1.0f)
damping = 1.0f;
transform.Translate(Vector3.forward * speed * Time.deltaTime);
transform.Rotate(Vector3.up * rotationSpeed * keyboardXInput * damping * Time.deltaTime);
transform.Rotate(-1 * Vector3.right * rotationSpeed * mouseYInput * damping * Time.deltaTime);
transform.Rotate(-1 * Vector3.forward * rotationSpeed * mouseXInput * damping * Time.deltaTime);
}
}
- 摄像机控制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FollowPlayerX : MonoBehaviour
{
public GameObject player;
private Vector3 offset;
private Vector3 zoom;
public float zoomScale = 2.0f;
private float playerRotateY;
private float playerRotateX;
private float scrollWheelInput;
void Start() {}
void LateUpdate() {
scrollWheelInput = Input.GetAxis("Mouse ScrollWheel");
zoom = new Vector3(0, -1*zoomScale*scrollWheelInput, zoomScale*scrollWheelInput);
offset = offset - zoom;
transform.rotation = player.transform.rotation;
playerRotateY = player.transform.eulerAngles.y;
playerRotateX = player.transform.eulerAngles.x;
Quaternion rotatiomXY = Quaternion.Euler(playerRotateX, playerRotateY, 0);
//transform.position = Vector3.Lerp(transform.position, player.transform.position + (rotatiomXY*offset), Time.deltaTime);
transform.position = player.transform.position + (rotatiomXY*offset);
}
}
Unity3D-飞行模拟
Unity Technologies. 2020. Create with Code, Unit 1: Player Control. Retrieved from: https://learn.unity.com/course/create-with-code?uv=2020.3



