Untiy3D学习——第一个3D小游戏项目1

前言

Unity 3D的学习,相比系统的按部就班的学习,我更希望直接通过做一些小的项目来实现。这样效率更高,因为毕竟自己的空闲时间不多。所以这几天在网上看了一些其他人用Unity制作游戏流程的演示视屏,同时也为拟定了一个小游戏作为自己的第一个Unity项目。

第一个3D游戏的目标与计划

以下为我拟定的游戏目标:

  • 游戏类型: 3D赛车竞技游戏

  • 游戏评判标准: 完成一次比赛的最终得分

  • 记分规则:

    • 比赛用时: 时间越短,分数越高
    • 奖励: 赛道上的奖励物品,得到的越多分数越高
    • 陷阱与障碍: 赛道上的陷阱和障碍需要避开,触碰到的越多,分数越少

作为Unity初学者我的第一个游戏,很显然是无法指望一下就做得很完善好玩的。所以其他的一些细节我都先不考虑,做出一个最简单最基本都游戏原型来再说,然后再进一步完善方案。

考虑游戏需要的一些基本元素,用C#编写脚本,我将制作方案简化如下:

  1. 场景——马路

    整个场景的主体就是马路,且不考虑其他复杂类型,直接做成一条水平平行且笔直的马路。

  2. Player——小球

    赛车游戏的player主题本来是赛车的,但是其建模和动力系统的控制都太复杂,所以这里直接用简单的球体来代替。

  3. 键盘输入来控制运动

    将小球加入物体系统,外物受力为重力和摩擦力。小球的运动直接简化为前后左右四个方向的受力,通过键盘来控制。

  4. 计时器

    赛车类的游戏最重要的就是比赛的用时,所以需要计时器来记录

  5. 起点和重点的感应

    为了精确计时,应当设置起点和终点的感应,来配合定时器一起计时

  6. 菜单

    一个完整的游戏的必备,负责设置游戏的一些参数,在这里就可以简化到只有游戏的暂定和继续以及游戏的退出。

创建场景和Player

用Unity内置的3D对象Plane或者Quad来做马路,用球体模型Sphere来充当Player,在透视视图中将两个调整到合适的位置。如下图为我摆放的马路、小球和主摄像机的位置:

场景中各元素的位置

直接将贴图推到马路上,Unity会自动创建其材质,就如上图所示。

而对于小球,为了达到方案中受力的作用,我们需要将其加入物理系统。添加小球的
Rigbody(刚体)组件:Add Component –> Physics –> Rigidbody ,在其中勾选“Use Gravity”,使其受重力的影响。

实际上在Rigbody中还可以设置如下选项(选择常用的说明):

  • Mass : 物体的质量
  • Drag: 空气阻力
  • Angular Drag: 转动空气阻力
  • Is Kinematic: 选中则物体不受物体系统的控制

脚本的运行框架

在Unity中一般会在物体对象上创建Script组件来控制对象的行为和游戏的运行流程,其中每个脚本都有共同的内置方法,其中一些重要的方法的执行顺序为:

Awake() --> Start() --> FixedUpdate() --> Update() --> LateUpdate() --> OnGUI() --> Destroy()


  1. Awake

    Awake方法是在脚本对象建立起来之后最先调用

  2. Start

    Start方法是在第一帧Updata前调用

  3. FixedUpdate、Update、LateUpdate

    这三个方法是在当MonoBehaviour启用时不断被调用的,update跟当前平台的帧数有关,而FixedUpdate是真实时间,所以处理物理逻辑的时候要把代码放在FixedUpdate而不是Update,LateUpdate是在所有Update函数调用后被调用。

    Update是在每次渲染新的一帧的时候才会调用,也就是说,这个函数的更新频率和设备的性能有关以及被渲染的物体(可以认为是三角形的数量)。在性能好的机器上可能fps 30,差的可能小些。这会导致同一个游戏在不同的机器上效果不一致,有的快有的慢。因为Update的执行间隔不一样了。

    而FixedUpdate,是在固定的时间间隔执行,不受游戏帧率的影响。所以处理Rigidbody的时候最好用FixedUpdate。FixedUpdate的时间间隔可以在项目设置中更改,Edit->ProjectSetting->time 找到Fixedtimestep,就可以修改了。

  4. OnGUI

    渲染和处理GUI事件时调用,UI相关的操作需要在这里面实现。

  5. Destroy

    Destroy方法在对象销毁时调用。

输入系统和运动控制

如方案所叙,小球的动力来源为,在水平面上前后左右四个方向上的外力,四个方向上的外力的有无和作用时间由键盘来控制。

在小球上添加脚本组件,通过代码检测键盘输入,从而控制外力的作用。Unity中由Input类来操作外部的输入,用w、s、a、d四个键分别控制四个方向,检测C#代码如下:

1
2
3
4
bool hasUpKey = Input.GetKey(KeyCode.W);
bool hasDownKey = Input.GetKey (KeyCode.S);
bool hasLeftKey = Input.GetKey (KeyCode.A);
bool hasRightKey = Input.GetKey (KeyCode.D);

要产生力的作用,需要用刚体组件中的AddForce方法,获取组件的方法需要用到ameObject.GetComponent方法,如在x轴正方向上添加一个值为2的力的代码如下:

1
gameObject.GetComponent<Rigidbody> ().AddForce (2, 0, 0);

以上这些逻辑代码在FixedUpdate方法中实现。

计时器和触发器

Unity中提供Time类实现时间的操作,为了实现计时器的功能,只需要计算起始时间和当前时间的时间差即可,同样在FixedUpdate函数里面实现:

1
spendTime = Time.time - startTime;

起点和终点的触发器,可以用碰撞触发来实现。在马路的起点和终点处分别放置碰撞物体,并在其在Box Collider组件中勾选Is Tigger,下图所示的为起点触发设置:

起点触发

在小球的脚本中添加OnTriggerEnter函数,当小球碰撞到了起点线就会触发这个函数,并通过参数来区分碰撞的物体,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
void OnTriggerEnter(Collider other)
{
if (other.name == "startLine")
{
raceStart = true;
startTime = Time.time;
}
else if (other.name == "endLine")
{
raceFinish = true;
}
}

定时时间显示可以在OnGUI方法中通过GUI.Label函数来实现,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
GUI.color = new Color(0xff, 0xff, 0);
GUI.skin.label.fontSize = 15;

if (raceStart)
{
timeStr = "Timer: " + spendTime.ToString ("f2") + "s";
}
else
{
timeStr = "Timer: 0s";
}

GUI.Label(new Rect(Screen.width-150, 20, Screen.width, Screen.height), timeStr);

菜单创建

制作菜单有如下步骤:

  1. 创建文字UI

    通过创建UI –> Text来实现,在Text组件中设置文字的各种属性:如显示文字、字体、大小、颜色等等文字格式,另外还可以添加阴影等文字特效组件,如下图所示:

    UI Text的组件设置

    在这个本次制作中我制作了两个菜单项:一个为主菜单,一个为子菜单。主菜单一直三个功能:游戏的暂停、继续和推出,子菜单为推出确认菜单。将菜单拖放到合适的位置,如下图所示:

    Unity菜单制作

  2. 添加Button组件

    在菜单中有些是提示的文字,有些是提供交互的按钮选项。对于那些需要有按钮功能的文字,要在其各自对象下添加Button组件:Add Commponent –> UI –> Button,如下图所示:

    Unity菜单文字添加按钮功能

    在Button组件中设置各种配置如颜色,当然最重要的是添加其点击处理函数。

  3. 添加按钮点击消息处理函数以及功能的实现

    在脚本中编写各个按钮点击处理函数,并在各自的按钮组件中正确的指定。在相关按钮处理函数中实现各自的功能:

    1. 暂停/继续

      同通过Time.timeScale可以控制游戏的运行快慢,将Time.timeScale 设置为0表示暂停播放,Time.timeScale设置为1为正常运行。

      同时需要在逻辑计算的代码部分,加入暂停的判断。因为Time.timeScale只能显示的画面停下来,而输入、计算等仍需要手动的控制停止。

    2. 隐藏子菜单

      UI Text需要用Canves对象来承载,所以Canves对象是UI Text对象的父级对象,将主菜单和子菜单的分别放在不同的Canves对象中,通过控制子菜单的Canves对象来设置隐藏,代码如下:

      1
      2
      quitMenu = quitMenu.GetComponent<Canvas> ();
      quitMenu.enabled = false;

摄像机的跟随

在游戏运行时,游戏的画面都是从摄像机的拍到的,所以为保证在小球,游戏画面还能够看到比较清晰的看到小球视角,就需要将摄像机跟这小球一起移动。这里包括两方面的内容:一是位置的跟随,一是视角的跟随。

  1. 位置的跟随

    位置的跟随可以先获取小球的位置,然后在设置摄像机的位置,在摄像机脚本的Update方法中,在这里我实现的是摄像机的水平高度不变,其他两个轴的位置与小球保持固定,如下代码:

    1
    transform.position = new Vector3(playerObj.transform.position.x + 4, transform.position.y, playerObj.transform.position.z);
  2. 视角跟随

    视角跟随有非常简单的方法,可以将摄像机的视角中心对准小球:

    1
    transform.LookAt (playerObj.transform);
Compartir