请选择 进入手机版 | 继续访问电脑版

查看: 43147|回复: 536

[原创] 使用Unity创建3D无尽跑酷游戏(上)

  [复制链接]

910

主题

1570

帖子

2万

贡献

管理员

Rank: 9Rank: 9Rank: 9

积分
20185
QQ
发表于 2016-10-25 12:05:17 | 显示全部楼层 |阅读模式
本教程将教大家如何使用Unity来制作一款3D跑酷游戏,由于篇幅较长,所以分为上下两部分,今天先为大家介绍上半部分。

下载示例工程:
游客,如果您要查看本帖隐藏内容请回复


相信大家至少都体验过一款跑酷游戏,一般3D跑酷游戏都有个第三视角的相机,跟随主角朝着某个特定的方向前进。游戏过程中,主角还要设法躲避周围出现的各种各样的危险物体,一旦撞上就会导致主角死亡。游戏关卡可以是变换火车轨道躲避障碍(如《地铁酷跑》),或在拐角处向左或向右拐弯,从而安全跟随设计的路经奔跑(如《神庙逃亡》)。

0.jpg
《地铁酷跑》游戏
1.jpg
《神庙逃亡》游戏


本教程共有两个关卡,包含所有的游戏机制。这些关卡有些共同点,但也有明显的区别。

准备资源

这是一个3D游戏,需要一些3D资源。如果无法自己制作,可以借助Asset Store资源商店,其中就有很多很棒的免费资源。本教程用到的资源包如下:


游戏概述

在游戏启动时,玩家能够看到屏幕上有两个按钮。可以选择游戏的两个关卡之一,一个是“旋转型道路”,另一个是“直线型道路”。

在“旋转型道路”关卡中,Max随着狭小的平台(“路径”)前进直到终点。在到达终点之前,游戏引擎会随机选取下个平台出现的位置,是在左边、右边还是正前方。下一段路经从当前路经的终点开始。当Max到达当前路经的终点时,玩家必须快速向左边或者右边滑动,或者继续前行。如果玩家没有及时操作,Max有可能撞墙并死亡(下图中红色的墙)。当在道路上奔跑时,Max能够捡起前方的糖果增加得分,还能(必须!)跃过一些障碍,当然,为了跟随新出现的路径,Max还可以左右滑动进行拐弯。
2.png
旋转道路关卡


注意:这里玩家输入说的是“滑动”。后面会介绍,这个游戏有两种输入方式(按方向键,或者在触摸屏上滑动)。因此,当提到“滑动”,就表明是在触摸屏上操作。

在“直线型道路”关卡中,Max随着一个宽阔的平台持续向前移动。玩家能够左右移动(像在普通的道路上),同时捡起糖果来得分。他也必须以切换轨道或是跳跃的方式,躲避随机出现的障碍物。如果Max撞到障碍物,他会死亡并且游戏结束。

3.png
直线型道路关卡


在两种关卡中,理论上游戏可以无限持续下去。当Max撞上红色的墙(旋转型关卡中)或撞到障碍物(两个关卡中)时游戏结束。游戏结束后玩家可以轻轻点击屏幕重新开始。最终,Max奔跑得越久,游戏得分越高。

深入探究

下面实现游戏代码并构建Unity场景。首先来介绍两个关卡都会用到的共同类,然后再分别详细解释各个关卡。接下来介绍这个游戏需要用到的几个类。

游戏输入

开发者能够轻松实现两种输入方式供玩家选择。能通过鼠标/键盘(左,右以及上方向键)或者触屏输入。两种输入方式都以类的方式创建,实现一个特殊接口以便游戏主线程检测输入结果,而不需要了解输入是如何实现的。

关卡介绍

Intro Level是玩家启动游戏后见到的第一个场景。

4.png
Intro level主屏幕


Intro level使用Unity UI控件,包含让玩家选择关卡的两个按钮。
[C#] 纯文本查看 复制代码
public class IntroLevel : MonoBehaviour {
 
    public void StraightLevelClick()
    {
        SceneManager.LoadScene("straightPathsLevel");
    }
 
    public void RotatedLevelClick()
    {
        SceneManager.LoadScene("rotatedPathsLevel");
    }
}


这个场景仅有一个脚本,包含两个方法。每个方法对应一个按钮的点击响应,利用Unity的SceneManager API让玩家进入下一关。

Constants

Constants类通常包含静态变量(整个项目中都可访问的变量),此外,这个类还能帮助避免对整数及(尤其是)字符串进行硬编码。
[C#] 纯文本查看 复制代码
public static class Constants
{
    public static readonly string PlayerTag = "Player";
    public static readonly string AnimationStarted = "started";
    public static readonly string AnimationJump = "jump";
    public static readonly string WidePathBorderTag = "WidePathBorder";
 
    public static readonly string StatusTapToStart = "Tap to start";
    public static readonly string StatusDeadTapToStart = "Dead. Tap to start";
}


这个类包含了一些对游戏有用的静态变量。

GameState枚举

用一个简单的枚举GameState来表示游戏的几种状态:
[C#] 纯文本查看 复制代码
public enum GameState
{
    Start,
    Playing,
    Dead
}


GameState枚举很简单,包含游戏的三个状态。即游戏未开始、游戏正在运行以及Max死亡。

TimeDestroyer

为了减轻RAM以及CPU的负担,一些游戏物体在一段时间后会被销毁。例如,Max经过后的道路就不必保留,同时也不必显示在屏幕中。简单的解决方法是一段时间后销毁这个物体,由TimeDestroyer类来实现。

[C#] 纯文本查看 复制代码
public class TimeDestroyer : MonoBehaviour
{
 
    void Start()
    {
        Invoke("DestroyObject", LifeTime);
    }
 
    void DestroyObject()
    {
        if (GameManager.Instance.GameState != GameState.Dead)
            Destroy(gameObject);
    }
 
    public float LifeTime = 10f;
}


游戏中的多种预制件都带有TimeDestroyer脚本,尤其是糖果、障碍物以及道路。它将在Max没有死亡的前提下,一段时间后让游戏对象消失。如果玩家在Max死亡后看到游戏对象消失就会比较尴尬。最后,公有字段LifeTime决定了游戏对象存在的时长。

障碍物

为了让游戏可玩性更强,需要在道路上放置一些障碍物。Max在宝贵的时间里,从旁边绕过(两个关卡)障碍物,或者从障碍物上方跳过(旋转型道路关卡)以避免碰撞。如果Max碰撞到障碍,则游戏结束。下图中可以看到用作障碍物的两个模型及组件。

5.png
2个障碍物模型预制件
6.png
Barrel model 组件


[C#] 纯文本查看 复制代码
public class Obstacle : MonoBehaviour {
 
    void OnTriggerEnter(Collider col)
    {
        //if the player hits one obstacle, it's game over
        if(col.gameObject.tag == Constants.PlayerTag)
        {
            GameManager.Instance.Die();
        }
    }
}



如上图所示,每个障碍物都是一个触发器类型的刚体。代码非常简单,就一个方法,当Max撞到障碍物时触发。发生碰撞,则Max死亡且游戏结束。

RedBorder

RedBorder用在“旋转型道路”关卡。用红色是因为它比较显眼,如果Max撞上它,它将杀死Max。Max必须躲避它们,同时跟随正确的路径到达下一段道路(可能在左侧、右侧或正前方)。

7.png
RedBorder预制件
8.png
RedBorder组件


RedBorder脚本关联RedBorder游戏对象。当Max碰到RedBorder就会死亡同时游戏结束。

Game Manager

Game Manager脚本用来保存一些基本属性,比如游戏状态以及玩家的“Die”方法。脚本代码如下:

[C#] 纯文本查看 复制代码
public class GameManager : MonoBehaviour
{
    void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        else
        {
            DestroyImmediate(this);
        }
    }
 
    private static GameManager instance;
    public static GameManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new GameManager();
            }
            return instance;
        }
    }


Game Manager脚本是一个单例,它在整个游戏过程中只有一个实例。这个单一实例可以通过Instance静态属性来获取。想了解更多关于单例模式的信息,请查看Unity Wiki。

[C#] 纯文本查看 复制代码
protected GameManager()
    {
        GameState = GameState.Start;
        CanSwipe = false;
    }
 
    public GameState GameState { get; set; }
 
    public bool CanSwipe { get; set; }
 
    public void Die()
    {
            UIManager.Instance.SetStatus(Constants.StatusDeadTapToStart);
            this.GameState = GameState.Dead;
    }
 
}


构造函数被声明为protected,这样外部类就无法初始化一个新的GameManager类(这对实现单例来说非常必要)。GameManager包含一个GameState枚举,一个bool值Can Swipe,用于控制是否接受玩家输入(仅“旋转型道路”关卡),以及一个公有方法Die,在Max撞到障碍物并死亡后调用。它会改变游戏状态,并在UI上显示Max死亡的相关信息。

Random Material

Random Material用来为地板上的方块随机选取一些颜色。

9.png
旋转型道路关卡,可以看到道路上的随机颜色块
10.png
6个材质用于为道路着色


这些材质位于Resources文件夹下,使用下面的代码加载:
[C#] 纯文本查看 复制代码
public class RandomMaterial : MonoBehaviour {
 
    // Use this for initialization
    void Awake () {
        GetComponent().material = GetRandomMaterial();
    }
 
    public Material GetRandomMaterial()
    {
        int x = Random.Range(0, 5);
        if (x == 0)
            return Resources.Load("Materials/redMaterial") as Material;
        else if (x == 1)
            return Resources.Load("Materials/greenMaterial") as Material;
        else if (x == 2)
            return Resources.Load("Materials/blueMaterial") as Material;
        else if (x == 3)
            return Resources.Load("Materials/yellowMaterial") as Material;
        else if (x == 4)
            return Resources.Load("Materials/purpleMaterial") as Material;
        else
            return Resources.Load("Materials/redMaterial") as Material;
    }
 
}


Awake期间,它会分配一个随机材质到游戏对象上进行随机着色。

Candy

绝大多数游戏为了让玩家开心,都有增加玩家得分的方式。例如,允许玩家相互竞争来增强可玩性等。本游戏选择一些美妙的3D糖果帮助玩家得分,当Max撞到糖果时增加得分。

下面列出了糖果模型/预制件以及candy_01的组件(其它三个类似)。

11.png
四个糖果预制件
12.png
Candy组件

[C#] 纯文本查看 复制代码
public class Candy : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        transform.Rotate(Vector3.up, Time.deltaTime * rotateSpeed);
    }
 
    void OnTriggerEnter(Collider col)
    {
        UIManager.Instance.IncreaseScore(ScorePoints);
        Destroy(this.gameObject);
    }
 
    public int ScorePoints = 100;
    public float rotateSpeed = 50f;
}


Candy脚本持续沿着Y轴旋转糖果,以便让玩家更容易发现。公有变量ScorePoints保存玩家得分,同时Candy也是一个触发刚体。Max撞击Candy后,Candy对象被摧毁,同时玩家得分。

UIManager

几乎所有游戏都有一个HUD(头显),例如一些2D文本或者图片,用来告知玩家游戏的相关信息。利用Unity的UI系统实现两个很简单的文本:
13.png
两个UI文本对象显示游戏状态以及当前得分


UIManager脚本代码如下:
[C#] 纯文本查看 复制代码
public class UIManager : MonoBehaviour
{
    void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        else
        {
            DestroyImmediate(this);
        }
    }
 
    //singleton implementation
    private static UIManager instance;
    public static UIManager Instance
    {
        get
        {
            if (instance == null)
                instance = new UIManager();
 
            return instance;
        }
    }
 
    protected UIManager()
    {
    }
 
    private float score = 0;
 
    public void ResetScore()
    {
        score = 0;
        UpdateScoreText();
    }
 
    public void SetScore(float value)
    {
        score = value;
        UpdateScoreText();
    }
 
    public void IncreaseScore(float value)
    {
        score += value;
        UpdateScoreText();
    }
 
    private void UpdateScoreText()
    {
        ScoreText.text = score.ToString();
    }
 
    public void SetStatus(string text)
    {
        StatusText.text = text;
    }
 
    public Text ScoreText, StatusText;
 
}


UIManager脚本拥有两个UI文本对象。第一个文本对象显示得分,第二个显示游戏状态。UIManager类本身是一个单例,同时包含一些公有方法,用于设置得分和状态的文本对象。还有保存玩家得分的私有整型变量,通过相应的公有方法来修改。两个游戏关卡都用到了该脚本。

Max动画

将Max模型导入Unity后可以看到Max内嵌的一些动画。本游戏会用到空闲、奔跑以及跳跃动画。

14.png
Max 3D模型的动画


使用Unity的Mecanim动画系统来驱动Max。可以在Mecanim里面创建一个状态机,来实现:
  • Max模型所有必要的状态
  • 每种状态对应一个动画
  • 状态之间的转换以及发生转换的条件


在此游戏中,使用两个boolean变量进行动画状态切换。实际上非常简单:游戏开始时,Max处于空闲状态。当游戏开始时,Max开始奔跑,因此切换到奔跑状态。当玩家向上滑屏(或者按向上方向键)时Max跳跃,因此切换到跳跃状态。在跳跃之后Max落下接触到地面后继续奔跑(回到奔跑状态)。下面可以看到Unity编辑器中的状态转换:

15.png
Max的动画状态机。可以看到有两个变量(jump及started)会触发状态改变
16.png
空闲状态下,started变量为真后切换到跑步状态/动画
17.png
当Max正在跑动时,如果jump变量为真,切换至跳跃状态(及动画)
18.png
将run动画指定给run状态


目前已经完善了动画状态,但如何修改这两个变量呢?这需要参考Max的动画控制器对象,这将在下篇介绍移动脚本时解释。到此教程的上半部分就结束了,下篇将继续为大家讲解剩下的内容。

相关阅读:

原文连接:Creating an infinite 3D runner game in Unity (like Temple Run, Subway Surfers), part 1
原文作者:Dimitris
感谢Unity官方翻译组成员“LemonC”对本文翻译所做的贡献。
转载请注明来源:Unity官方中文社区(forum.china.unity3d.com)。请勿私自更改任何版权说明信息。

0

主题

7

帖子

120

贡献

初级UU族—2级

Rank: 2

积分
120
发表于 2016-10-25 12:34:01 | 显示全部楼层
学习一下

0

主题

14

帖子

205

贡献

初级UU族—3级

Rank: 3Rank: 3

积分
205
发表于 2016-10-25 13:10:09 | 显示全部楼层
zzzzzzzzzzzzzzzzzzzzzz

0

主题

3

帖子

30

贡献

初级UU族—1级

Rank: 1

积分
30
发表于 2016-10-25 14:33:33 | 显示全部楼层
楼主好人,,好好学习

4

主题

23

帖子

835

贡献

中级UU族—1级

Rank: 4

积分
835
发表于 2016-10-25 15:15:23 来自手机 | 显示全部楼层
bbc不错,很详细

0

主题

2

帖子

30

贡献

初级UU族—1级

Rank: 1

积分
30
发表于 2016-10-25 15:34:05 | 显示全部楼层
楼主好人,,好好学习

0

主题

3

帖子

165

贡献

初级UU族—2级

Rank: 2

积分
160
发表于 2016-10-25 15:46:13 | 显示全部楼层
感谢分享

0

主题

6

帖子

55

贡献

初级UU族—2级

Rank: 2

积分
55
发表于 2016-10-25 16:25:15 来自手机 | 显示全部楼层
想要看看,学习学习

0

主题

6

帖子

165

贡献

初级UU族—2级

Rank: 2

积分
165
发表于 2016-10-26 00:49:16 | 显示全部楼层
非常感谢 学习一下 还是文字教程爽些

0

主题

2

帖子

45

贡献

初级UU族—1级

Rank: 1

积分
45
发表于 2016-10-26 01:41:26 | 显示全部楼层
baiduzhidao

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表