Unity入门教程(下)

一、概要

在 Unity入门教程(上) 中我们创建了一个游戏项目,并且创建了玩家角色和小球这些游戏对象,还通过添加游戏脚本实现了小方块的弹跳。虽然功能比较简单,但是完整地表现了使用Unity开发游戏的大体流程。

为了让这个游戏变得更加有趣,下面我们要进一步完善玩家角色和小球的动作。

二、让小球飞起来(物理运动和速度)

目前小球是静止在空中的,下面我们来尝试让它朝玩家角色飞去。

为了令小球能够模拟物理运动,需要添加Rigidbody组件。同时还需要创建一个Ball的脚本。此操作在Unity入门教程(上)中的步骤十和步骤十一。

添加了Ball脚本以后,我们要对Start方法作如下修改

    void Start () {
        this.GetComponent<Rigidbody>().velocity = new Vector3(-8.0f, 8.0f, 0.0f); //设置向左上方的速度
    }

游戏开始后,小球将向画面左侧飞去

三、创建大量小球(预设游戏对象)

为了能够随时创建出小球对象,首先需要对小球对象进行预设。

1,请将层级视图中的Ball项文本拖拽到项目视图中

项目视图中将出现Ball项。同时,层级视图中的Ball项文本将会变为蓝色。

2,将项目视图中的Ball预设拖拽到场景视图中

可以看到场景中会多出一个小球对象。

预设了游戏对象后,我们就能够非常容易地创建出多个同样的物体。

3,将Player和Floor游戏对象也做成预设

四、整理项目视图

1,用文件夹将这些项目归类整理

在项目视图左上角的菜单中点击Create→Folder后,项目视图中将生成一个文件夹,将名字改为Prefabs。

2,将预设Ball Prefab拖拽到Prefabs文件夹下

点击Prefabs文件夹,可以看到刚才移动的Ball预设。接着把Player预设和Floor预设也移动到Prefabs文件夹下。

3,采用同样的方式创建Scenes、Scripts、Materials文件夹,并把各项目放到相应的文件夹下

注意在创建前务必先点击项目视图左侧的Assets图标以确保当前文件夹回到Assets。

五、发射小球(通过脚本创建游戏对象)

1,在窗口顶部菜单中依次点击GameObject→Create Empty

由于该游戏对象被用作发射台,因此命名为Launcher

2,对游戏对象Launcher进行预设

3,创建Launcher脚本

4,将Launcher脚本添加到Launcher预设中去(另外一种方法)

(1)在项目视图中切换到Prefabs文件夹,点击选中Launcher预设。此时检视面板上将显示Launcher的相关信息,然后点击最下方的Add Component按钮

 (2)在标题为Component的下拉菜单中点击最下方的Script项。点击后菜单将向左移动,显示出所有创建好的脚本。找到Launcher脚本并点击。

小结:现在我们已经知道在检视面板中也可以添加组件,除此之外,还可以使用窗口顶部菜单或者直接拖拽。

5,编辑Launcher脚本

除了Update方法有变动之外,还增加了ballPrefab变量。

Instantiate是通过预设生成游戏对象实例的方法。不过脚本中并没有对ballPrefab变量进行初始化的代码,所以在游戏运行前必须先在检视面板中对ballPrefab变量赋予预设对象值。

public class Launcher : MonoBehaviour {
    public GameObject ballPrefab;  //小球预设
    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        if (Input.GetMouseButtonDown(1))  //点击鼠标右键后触发
        {
            Instantiate(this.ballPrefab);  //创建ballPrefab的实例
        }
    }
}

从项目视图中选择Launcher预设。可以看到在检视面板中的Launcher(Script)标签下显示有Ball Prefab项。脚本代码中声明的所有public成员变量都将在这里列出。

往类中新添加的变量默认表示为None(GameObject),意味着该变量还未被赋值。请将项目视图中的Ball预设拖拽到这里(鼠标左键按着不要松手)。

6,运行游戏

每次单击鼠标右键时,都会射出一个小球。

这里,为了和预设对象分开,我们把脚本中通过Instantiate方法生成的游戏对象称为实例,把产生实例的过程称为实例化。

六、删除画面外的小球(通过脚本删除游戏对象)

我们的游戏现在出现了一个Bug:发射出去的小球永远不会消失。

游戏运行时由脚本动态生成的游戏对象也会被显示在层级地图中。每点击一次鼠标,层级视图中都会增加一个Ball(Clone)游戏对象。因此即使小球已经跑出游戏画面之外,这些游戏对象也并未消失。

跑出画面之外的小球不会再回到画面中,所以完全可以删除。

在脚本Ball.cs中添加OnBecameInvisible方法,该方法可以被添加到Ball类定义范围内的任意位置。

public class Ball : MonoBehaviour {
    //添加:游戏对象跑出画面外时被调用的方法
    void OnBecameInvisible()
    {
        Destroy(this.gameObject);  //删除游戏对象
    }

    // Use this for initialization
    void Start () {
        this.GetComponent<Rigidbody>().velocity = new Vector3(-8.0f, 8.0f, 0.0f); //设置向左上方的速度
    }
    
    // Update is called once per frame
    void Update () {
        
    }
}

OnBecameInvisible方法是在游戏对象移动到画面之外不再被绘制时被调用的方法。

Destroy(this.gameObject)则是删除游戏对象的方法。

注意:如果把参数设置成this的话,删除的就不是游戏对象,而是Ball脚本组件。

七、防止玩家角色在空中起跳(发生碰撞时的处理)

为了防止玩家角色在空中再次起跳,我们来添加下列处理

  • 添加着陆标记
  • 着陆标记值为false时不允许起跳
  • 将起跳瞬间的着陆标记设为false
  • 将着陆瞬间的着陆标记设为true

修改Player脚本,代码如下:

public class Player : MonoBehaviour {

    protected float jump_speed = 8.0f;  //设置起跳时的速度
    public bool is_landing = false;  //着陆标记

    // Use this for initialization
    void Start () {
        this.is_landing = false;
    }
    
    // Update is called once per frame
    void Update () {
        if(this.is_landing){  //着陆后触发
            if(Input.GetMouseButtonDown(0)){
                this.is_landing = false;  //将着陆标记设置为false(未着陆 = 在空中)
                this.GetComponent<Rigidbody>().velocity = Vector3.up * this.jump_speed;
            }
        }
    }

    //添加:和其他游戏对象发生碰撞时调用的方法
    void OnCollisionEnter(Collision collision)
    {
        this.is_landing = true;  //将着陆标记设置为true(着陆 = 在地面上)
    }
}

当一个游戏对象同其他对象发生碰撞时,OnCollisionEnter方法将被调用。

这是为了检查玩家角色是否着陆而添加的。在该方法中把着陆标记的值设为true。这样玩家角色就不能在空中再次起跳了。

八、禁止玩家角色旋转(抑制旋转)

在某种程度上完成了玩家角色和小球的脚本编程后,让我们来调整各相关参数,以使角色在起跳后能和小球发生碰撞。

这里我们采用下列值:

  • 玩家角色的位置:(-2.0,1.0,0.0)
  • 玩家角色的起跳速度(Player.cs脚本中jump_speed的值):8.0
  • 小球的位置:(5.0,2.0,0.0)
  • 小球的初始速度(Ball.cs脚本中使用Start方法设定的值):(-7.0,6.0,0.0)

1,选择项目视图中的Player并打开检视面板中的Rigidbody标签下的Constraints项

2,点击左边的三角形图标,下面会进一步显示Freeze Position和Freeze Rotation

其中Freeze Position对于将游戏对象的位置坐标固定在某些方向上,Freeze Rotation则用于固定其角度。

由于我们希望玩家角色只上下跳跃而不做左右和前后的移动,因此:

3,把Freeze Position的“X”“Z”前面的复选框选中。Freeze Rotation方面则把“X”“Y”“Z”全部选中

九、让玩家角色不被弹开(设置重量)

选择项目视图中的Ball预设,打开Rigidbody标签,将Mass项的值由1改为0.01。

Mass项用于设定游戏对象的重量。两个游戏对象发生碰撞时,Mass值较大的物体将保持原速度继续运动,相反Mass值较小的物体则容易因受到冲击而改变移动的方向。

十、让小球强烈反弹(设置物理材质)

1,创建物理材质

从项目视图的Create菜单中选择Physic Material,创建一个新材质并将其名称改为Ball Physic Material

相对于用来指定颜色等可以看见的属性材质,物理材质则是用于设定弹性系数和摩擦系数等与物理运动相关的属性。

2,修改属性值

在项目视图中选择Ball Physic Material后,在检视面板中选择Bounciness,将其值由0改为1。这个值越大,游戏对象越容易被“弹开”。

3,将新创建的材质拖拽到Ball预设下的Material

从项目视图中选择Ball预设,接着把Ball Physic Material拖拽到检视面板中Sphere Collider标签下的Material

或者可以点击Ball Physic Material右侧的圆形图标。这时Select Physic Material窗口将被打开,在这个“物理材质选择窗口”中也可以进行选择设定

十一、消除“漂浮感”(调整重力大小)

1,在窗口顶部菜单中依次点击Edit→Project Settings→Physics

检视面板中将切换显示PhysicsManager

2,将Gravity项的“Y”值稍微提高一些,在此设为-20

3,调整参数

通过增强重力可以减弱物体在运动时的“漂浮感”,不过跳跃的高度和小球的轨道也显得比原来低了。这种情况下,我们可以考虑调整为下列数值:

  • 玩家角色的起跳速度(Player.cs脚本中的jump_speed的值):12.0
  • 小球的初始速度(Ball.cs脚本中使用Start方法设定的值):(-10.0,9.0,0.0)

十二、调整摄像机的位置

1,选择摄像机后,场景视图右下角将出现一个小窗口。这是从摄像机看到的画面。如果无法看到这个窗口,请在检视面板中展开Camera标签

2,为了能够俯视地面,需要使摄像机在往上偏移的同时绕X轴旋转

调整角度时需把移动工具切换为旋转工具。

 用移动工具调整摄像机的位置

  用旋转工具调整摄像机的角度

3,在检视面板中输入数值(可根据自己喜好进行设置)

4,对比效果

调整摄像机前:

调整摄像机后:

十三、修复空中起跳的bug(区分碰撞对象)

1,bug的发现

 试玩游戏后,我们注意到玩家角色和小球碰撞后还可以再次起跳。这可能是因为防止空中跳跃的代码存在bug。

2,bug的证明

(1)游戏启动后,在层级视图中选择Player。可以在检视面板中的Player(Script)标签下看到Is_landing项。这就是在Player脚本中定义过的is_landing变量

(2)游戏刚开始时画面上还没有小球。随着玩家角色起跳,可以看到Is_landing复选框由取消变为了选中状态

跳跃过程中Is_landing为取消状态(值为false)

着陆后Is_landing为选中状态(值为true)

(3)修改Player.Update方法

void Update () {
        if(this.is_landing){  //着陆后触发
            if(Input.GetMouseButtonDown(0)){
                this.is_landing = false;  //将着陆标记设置为false(未着陆 = 在空中)
                this.GetComponent<Rigidbody>().velocity = Vector3.up * this.jump_speed;
                Debug.Break();
            }
        }
    }

修改后仅添加了Debug.Break方法的调用。在玩家角色起跳时的瞬间暂停游戏的运行。

按下播放控制工具条最右边的按钮

,在逐帧模式下可以看到玩家角色在一直上升。在玩家角色和小球碰撞的瞬间,Is_landing的值变成了true。(此处无法截图,见谅)

搞清楚了bug的原因,接下来就考虑解决bug的对策。

3,bug的解决

(1)首先需要区分开碰撞对象是地面还是小球

此处我们可以利用标签。需要对游戏对象的种类进行大致区分时,可以使用标签来分组。

添加标签到项目中,在项目视图中选择Floor预设→点击Untagged→点击Add Tag→点击Tags左侧的三角形→点击“+”→输入Floor→再次在项目视图中选择Floor预设→点击Untagged→点击Floor

(2),修改脚本

修改Player.OnCollisionEnter方法。在这里提醒下:记得删除了之前在Player.Update方法中添加的Debug.Break()。

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Floor")
        {
            this.is_landing = true;  //将着陆标记设置为true(着陆 = 在地面上)
        }
    }

使用了标签后就可以区分碰撞对象了。这样一来就只有在和地面碰撞时,也就是着陆时Is_landing的值才会变为true。

十四、小结

本次有关Unity入门的学习就暂时先告一段落。通过做一个小游戏项目的流程,让我切身体会到使用Unity开发游戏的大致流程,还有遇到Bug时的分析思路。

当然如果想通过一个小游戏的制作就学会Unity的全部技能是不可能的,后期在游戏开发的过程中,遇到了问题再去查找相应的答案,见招拆招,才是最有效的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏javascript趣味编程

9.1 汽包锅炉水位自动控制

1,水池Pool,底面积为1m2,初始液位为1m,水的初始容积为1m3,目标水位(targetLevel)控制在1.2m,实际液位(actualLevel)受入...

15300
来自专栏LinXunFeng的专栏

iOS - SceneKit显示与交互3D建模(一)

25240
来自专栏進无尽的文章

地图| 高德地图源码级使用大全

高德地图提供包括:web前端、Android、iOS、服务器、小程序等平台的地图服务, 地图功能众多,本文记载的只是自己遇到的一些问题,绝大部分功能只要参照官...

64020
来自专栏施炯的IoT开发专栏

PhoneFinder--寻找丢失的手机

    手机丢了怎么办?那就打电话给手机,如果运气好的话,捡到的好心人能够把手机还给你。如果手机是被偷的,那就没有办法了,即使手机开着,估计小偷也不会接电话。当...

29140
来自专栏NetCore

微信快速开发框架(五)-- 利用快速开发框架,快速搭建微信浏览博客园首页文章

这几天接连发布了《快速开发微信公众平台框架---简介》和《体验微信公众平台快速开发框架》几篇关于微信平台的文章,不过反响一般,可能需求不是很多吧。闲来无事,还是...

24490
来自专栏星流全栈

Github开源免费编程书籍

2.3K40
来自专栏逍遥剑客的游戏开发

Nebula3的场景管理

15160
来自专栏FreeBuf

我是怎么打开车库门的:ASKOOK手动解码及重放

本文以打开无线控制的电动车库卷帘门为目标,深入研究了ASK/OOK的编/解码,并用树莓派+五元钱的五元钱的发射模块实现了打开车库门的各种姿势。本文适用于主流31...

347100
来自专栏北京马哥教育

余生只够写50行代码,这么写绝对赚翻了

学Python最简单的方法是什么?推荐阅读:Python开发工程师成长魔法 假如有一天死神来找你,警告你最多只能再写50行代码,然后就得随他而去,应该写点什么...

33180
来自专栏王金龙的专栏

编程语言中那些有趣的命名

      学习NodeJS的时候,一定会用到其包管理器npm。npm的字面意思是node package manager,实际的含义也是这样,但是npm真正的...

15220

扫码关注云+社区

领取腾讯云代金券