本文记录Unity Learn, Create with code 1, Unit 1内容

启动U3D,创建一个3D场景

创建一个新3D项目

打开unity hub, 选择3D core, 自行选择命名你的新project. 然后需要我们导入官网提供的资源包.

在Unity中,选择 Assets > Import Package > Custom Package 找到 Prototype 1 Starter Files.
导入包

导入后,在下方的导航栏里,双击打开Assets > Scenes > Prototype 1.
Prototype 1

之后我们便可以看到一个公路的场景。公路场景

Unity 基础操作

在scene中,按住右键可以调整摄像机角度。按住右键时,可以用wasd前后左右移动摄像机,qe上下移动摄像机。移动鼠标滚轮可以放大缩小画面,按住鼠标中键可以拖动画面(平行移动摄像机)。当你飞太远找不到你想要的物体时,选中物体按F即可使摄像机聚焦该物体。按住alt拖动左键可以使摄像机绕焦点旋转,拖动右键可使画面放大或者缩小。

为场景添加一个小车

在Assets > Course Library > Vehicles 里,将任意小车拖拽到Hierarchy窗口。选中刚才的小车按F,摄像机会聚焦到小车。整个的unity界面看起来应该如下图所示。

unity界面

左边Hierarchy的蓝色方框是我们刚才添加的小车。小车在unity里被识别为一个object,其object的属性可以在右侧的Inspector里查看以及编辑。红色方框是一些常见的transformation工具,可以允许我们调整物体的大小,位置,角度。同样的,我们也可以在右侧的Inspector,transform属性里手动调整。

为场景添加一个障碍物

在Course Library > Obstacles为场景添加一个障碍物,并且在右侧的Inspector>transform模块内重置它的位置。Reset按钮可以在transform的小窗口右上角的三个小点…的下拉菜单里找到。然后再Inspector里,将障碍物的z坐标设置为25。最后将添加的小车和障碍物重新命名为Vehicle和obstacle。

调整游戏的摄像头

注意,在没有开始游戏之前,我们观测场景的摄像头并不是游戏开始后的摄像头。游戏开始后的摄像头是Hierarchy里的main camera。为了保证游戏开始后我们可以驾驶这辆车,我们需要将摄像头移动到车后方。

在选中main camera后,我们可以在画面右下角看到他的preview,如下图。
主摄像机预览

配合旋转和移动工具,将摄像头调整到想要的位置,在移动中按住ctrl可以移动整数个单位,直到进入游戏后效果大致如下图。
调整主摄像机位置

让小车动起来

创建代码Scripts文件夹

在Project窗口,Assets文件夹下, 创建名问Scripts的文件夹。这个文件夹会放置以后我们所写的所有 C# 脚本文件。在刚才创建好Scripts文件夹下,创建名为 PlayerControllerC# 脚本。并且将创建好的脚本拖到Vehicle的Inspector里。同样也可以直接在其Inspector里的 Add Component 直接创建。

为小车添加脚本

使小车匀速前进的代码

双击打开刚才创建的PlayerController脚本中,我们会看到如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{

}
}

除了使用的库,一个继承了MonoBehaviour的公有类PalyerController, 还有两个方法StartUpdate, 并且贴心地写好了注释. MonoBehaviour类提供了一个框架,允许你在编辑器中将脚本附加到Unity的GameObject上,同时也提供了一些有用事件的hook,如StartUpdate。更详细的Monobehaviour的介绍可以参考Unity的官方文档.这里的Start方法会在游戏开始的第一帧调用,一般用来初始化一些属性。Update方法会在每一帧刷新的时候调用。

在脚本里可以调用和操作GameObject的各类属性,这里为了让小车移动,我们需要让小车每一帧都向前挪动一点位置。GameObject的位置由其transform属性的position决定,所以我们需要对这一变量在每一帧进行修改。注意到,Unity引擎采用的是左手系,并且这里的前方是z轴。

Update里添加:

1
transform.Translate(Vector3.forward * Time.deltaTime * 20);

这里 transform.Translate()Game Object做平移的方法,传入参数为一个Vector3Vector3.forward是值为Vector3(0,0,1)向量的简写。Time.deltaTime 指从上一帧到当前帧的秒间隔(只读)。由于速度不随帧率变化,所以我们每帧调用的时候都要保证使用Time.deltaTime保证我们的移动是匀速的。

为小车和障碍物添加物理

选择我们的Vehicle和Obstacle, 在Add Component处选择添加Rigidbody给小车和障碍物以刚体属性。同时,小车和障碍物已经有了Mesh Collider这个属性。MeshCollider是unity判断物体间碰撞的碰撞体积,不一定与原物体的网格是相同的。注意当只有碰撞体积但是没有刚体属性的时候,物体之间是不会发生碰撞的。在添加Rigidbody之后,给小车更高的质量,使其更接近真实(Rigidbody里的Mass属性)。

更多的障碍物!

现在让我们为小车添加几个更多的障碍物,右键选中Hierarchy里的Obstacle点击duplicate, 或者直接Ctrl+D,调整你想要的障碍物的位置。

然后点击game play按钮,观察小车撞飞障碍物吧!

芜湖起飞

操控小车

为小车设置速度-Scripts里的public变量

在脚本里对Update方法做如下修改:

1
2
3
4
5
6
// Update is called once per frame
public float speed = 20;
void Update()
{
transform.Translate(Vector3.forward * Time.deltaTime * speed);
}

可以看到我们为小车设置了一个public浮点数变量叫做speed,然后再Inspector里面,我们可以看到在PlayerController区域多出来了一个speed变量。这样的变量允许我们在开发时实时调整其数值,方便观察最终效果。

可手动调整的public变量

让摄像机跟随小车移动

为了让摄像机跟随小车移动,我们需要实时获取小车的位置,并且将摄像机的位置与小车的位置绑定。为摄像机main camera创建脚本,名为FollowPlayer. 修改MonoBehaviour 内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FollowPlayer : MonoBehaviour
{
// Start is called before the first frame update
public GameObject player;
private Vector3 offset = Vector3.zero;
void Start()
{
// 获取初始小车和相机的相对位置之差作为offset
offset = player.transform.position - transform.position;
}

// Update is called once per frame
void LateUpdate()
{
transform.position = player.transform.position - offset;
}
}

然后将Vehicle拖到摄像机属于player的公有变量内。试运行便可以得到跟随小车移动的摄像机。注意到我们这里使用了LateUpdate而非普通的Update这是因为如果小车的位置更新与摄像机的位置更新都发生在Update方法内,则两者顺序的时候不一定是固定的,这时会观察到画面会有抖动等不稳定现象。而LateUpdate方法在所有Update调用完之后才调用,可以避免此类问题。

用键盘控制小车移动

Unity提供了Input Manager来作为用户与游戏交互的操作接口。在 Edit > Project Settings可以找到.
Input Manager

这里我们会用到默认的 Horizontal 和 Vertical 两个按钮。同时,可以自定义按钮,以及修改size调整 Input Manager 的按钮个数。在PlayerController脚本内,将代码修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PlayerController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
public float speed = 20.0f;
public float turn_speed = 45.0f;
private float horizontal_input;
private float forward_input;
void Update()
{
horizontal_input = Input.GetAxis("Horizontal");
forward_input = Input.GetAxis("Vertical");
// move the car based on the vertical input
transform.Translate(Vector3.forward * Time.deltaTime * speed * forward_input);
// rotate the car based on the horizonrtal input
transform.Rotate(Vector3.up * Time.deltaTime * turn_speed * horizontal_input);
}
}

这里我们定义前进速度speed和旋转速度turn_speed,并且由上下左右键或者wasd键控制。