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

此篇学习的目的是编写一个小游戏。在小游戏里,你需要不停的向朝你狂奔而来的动物扔出食物(类似于战机游戏)。此篇学习的材料请点击链接.

放置你的玩家

按照上一篇的惯例,Unity学习_JuniorProgrammer_1.1_玩家控制, 为新项目创建一个U3D项目。

添加玩家动物和食物

此步较为简单,遂略过。主要,在 Course Library>Materials 里可以看到不同的材质。将材质拖动到物体上即可更换物体的材质。记得将拖进来的Human重命名为 Player.

使玩家在限制范围内移动

使玩家可以水平移动 Player,并且要使 Player 在限制内移动而不是可以一直移动出边界。那么除了使用 Horizontal Input, 还要添加范围的判段语句。

为Player创建一个名为 PlayerController C# 脚本,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Update is called once per frame
public float xRange = 10.0f;
public float speed = 10.0f;
public float horizontalInput;
void Update()
{
horizontalInput = Input.GetAxis("Horizontal");
transform.Translate(Vector3.right * horizontalInput * Time.deltaTime * speed);
// Instead of restricting player in the bound, we make the player appear in the opposite side once it exceed the range
if (transform.position.x > xRange)
{
transform.position = new Vector3(-xRange, transform.position.y, transform.position.z);
}
if (transform.position.x < -xRange)
{
transform.position = new Vector3(xRange, transform.position.y, transform.position.z);
}
}

这段代码使玩家可以左右操控角色,并且当角色越界xRange时将其传送到另外一边界。

让食物飞

制作食物飞行的预制体

为食物添加一个向前移动的代码,使当游戏开始时候他会自动向前移动。

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

当我们对某一个物体完成编辑(添加脚本,更换材质等),并且以后想重复调用时,可以对其创建预制体(Prefabs)。在Asset文件夹下创建Prefabs
文件夹。将编辑好scale 的食物文件拖到Prefabs文件夹下,选择 Original Prefab. 这会为编辑好的文件创建一个预制体,并且存储在Prefabs 文件夹内。

预制体
存储好的预制体会在文件夹里显示为蓝色小方块。

为了让player可以在游戏里生成预制体,我们为Player Controller脚本再多加一个对于Unity GameObject的引用。

1
public GameObject projectfilePrefab;

然后,将预制体从文件夹拖动到player 的Inspector新生成的projectilePrefab框内。注意不要直接将Hierarchy里的食物文件拖到player上,否则以后在生成的时候会尝试生成场景里已经存在的该物体,而不是新的prefab。

添加预制体引用

添加好后,我们希望按下空格可以使游戏中的player向前发射一个食物。这里的发射我们使用Unity内的实例化方法Instantiate. Instantiate有好几个重载方法,这里我们使用下图的

Instantiate函数

PlayerControllerUpdate方法里添加如下代码

1
2
3
4
if (Input.GetKeyDown(KeyCode.Space))
{
Instantiate(projectfilePrefab, transform.position, projectfilePrefab.transform.rotation);
}

测试你的代码,现在按下空格可以生成向前飞行的食物了!(这里我们选的是披萨。)

按下空格后生成的预制体复制品

让动物也跑起来

接下来我们制作几个动物的预制体,步骤和制作飞行食物的预制体一样!只是对所有的动物,让他们的 Rotation 的 Y 值设为 180,让
他们朝向 Player. 并且别忘了将刚才的 MoveForward 脚本挂在他们身上,让他们也能向前跑。如果你喜欢,可以调整不同动物的速度。在 Hierarchy
里多选可以同时编辑多个物体的相同的值(如下图)!

同时编辑

随机生成动物

本节我们实现动物的随机生成,并让它们向我们奔来。

创建生成助理

Hierarchy内创建一个新的 Empty Object, 命名为 SpawnManager. 我们将要用它生成动物。在随机生成中,我们会调用到Unity 中自带的随机数函数Random.Range().该函数有两个重载:

1
2
3
4
// 等概率返回[a,b]中任意浮点数
Random.Range(float a, float b)
// 等概率返回[a,b)中任意整数
Random.Range(int a, int b)

SpawnManager添加如下脚本:

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 SpawnManager : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
public GameObject[] animalPerfabs;
private float spawnRangeX = 20.0f;
private float spawnPosZ = 20.0f;
void Update()
{
if (Input.GetKeyDown(KeyCode.S))
{
// we need to randomly generate animal index and spawn position
Vector3 spawnPos = new Vector3(Random.Range(-spawnRangeX, spawnRangeX), 0, spawnPosZ);
int animalIndex = Random.Range(0, animalPerfabs.Length);
Instantiate(animalPerfabs[animalIndex], spawnPos, animalPerfabs[animalIndex].transform.rotation);
}
}
}

保存后,我们可以在spawnManager的 Inspector 处看到一个空的 animalPrefabs 列表。我们手动地从 Project 的 Animals 文件夹下拖动一些动物预制体添加进去。

添加动物预制体

接下来每按一次S键,便会随机生成一只动物。其中动物的位置由spawnPos随机sample出来,具体生成哪只动物由animalIndex随机生成出来。

更改摄像机透视选项

因为此游戏模拟的是顶视的类2D效果,所以可以在 main camera处将透视设置改为正交投影 (Orthographic)。

更改透视设置

给它一张嘴

这一张我们让动物动起来,并且使得动物能够吃掉我们扔出去的食物。

以时间为参考量生成动物

在玩游戏的时候,我们希望时不时地从屏幕边缘生成与一些动物向我们跑来,而不必每次都要按 S 键。这时我们需要用到 InvokeRepeating函数,文档如下图所示。该函数会在time秒调用一次 methodName 方法,并且在之后的每 RepeatRate再调用一次。

InvokeRepeating

所以我们把随机生成写成一个函数,并在 Start 里调用该方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Start()
{
InvokeRepeating("SpawnRandomAnimal", 2, 1.5f);
}

// Update is called once per frame
public GameObject[] animalPerfabs;
private float spawnRangeX = 20.0f;
private float spawnPosZ = 20.0f;
void Update()
{

}

void SpawnRandomAnimal()
{
Vector3 spawnPos = new Vector3(Random.Range(-spawnRangeX, spawnRangeX), 0, spawnPosZ);
int animalIndex = Random.Range(0, animalPerfabs.Length);
Instantiate(animalPerfabs[animalIndex], spawnPos, animalPerfabs[animalIndex].transform.rotation);
}

添加碰撞检测

我们想让食物和动物碰撞的时候,两者消失,意味着食物被动物吃掉了。
随便选择刚才的一个动物,进入预制体编辑模式。在预制体编辑模式下编辑并保存的操作会自动作用在所有的被实例化的预制体上。
为刚才选中的动物添加 Box Collider, 添加方形碰撞体积,并且编辑到合适大小。记得将 is trigger 打上勾,这样当有其他物体与
此动物相撞的时候,会返回一个trigger变量便于我们在代码里操作。

在预制体模式下编辑狐狸的碰撞盒

为所有在生成列表里的动物和食物都添加碰撞盒。

最后,为食物添加一个 RigidBody,在有碰撞体积的时候,至少要有一者有 RigidBody 属性才能生效,这里注意勾掉重力,这里我们是不需要重力的。

碰撞时让两者消失

创建一个DetectCollisions脚本,并添加到每一个预制体上。

Unity有自带的碰撞检测发生函数 OnTriggerEnter。我们将重写这个函数为

OnTriggerEnter

1
2
3
4
5
void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
Destroy(other.gameObject);
}

当触发器被触发时,会返回碰撞体other。这里的other其实是飞过来的食物,这里将两者直接 Destroy.

让越界的物体消失

最后,我们不想让游戏中存在过多的无限飞行的披萨。简单的越界检测,仿照之前的检测角色越界。创建DestroyOutOfBound脚本,在Start后加上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Update is called once per frame
private float topBound = 30.0f;
private float lowerBound = -10.0f;
void Update()
{
if (transform.position.z > topBound)
{
Destroy(gameObject);
}
else if (transform.position.z < lowerBound)
{
Debug.Log("Game Over!");
Destroy(gameObject);
}
}

然后添加到每个动物和食物的预制体上。这里的 Debug.Log()是提示当有动物没被接住的时候游戏结束。Debug.Log
是 Unity 用来调试代码的时候的常用手段。

最后清理在 Hierarchy 中的预制体,就可以尝试开始游戏啦!