【Unity3D开发小游戏】VVVVV游戏

博客分类: Unity3D-Game 阅读次数: comments

【Unity3D开发小游戏】VVVVV游戏

@[TOC]

一、前言

本教程将展示如何在特里·卡瓦纳赫的启发下,制作一款简单而令人上瘾的平台游戏-VVVVV游戏。 这个游戏规则很简单,用户必须通过水平行走或倒转重力来控制一个小个子通过某种奇怪的迷宫。

我们的游戏很容易做。它只需要一个水平图像,一个尖峰图像,一个检查点图像和一个Player图像。

像往常一样,一切都会尽可能简单地解释,这样每个人都能理解它。

效果图: 在这里插入图片描述

二、源码

UI资源和源代码请搜索QQ群:1040082875下载

三、正文

版本

==Unity5.0.0f4==

1.摄像机设置

我们选择主照相机,然后我们可以设置背景色黑色并调整大小如下图所示:

在这里插入图片描述

2.平面

绘制平面 我们将创建一个大的图像,添加一些背景模式,然后剪出一些区域供玩家行走: 在这里插入图片描述 注意:右击图像,选择另存为。保存到Assets/Sprites文件夹。

导入平面 导入到项目中: 在这里插入图片描述 导入设置: 在这里插入图片描述 注:Pixels Per Unit设成16这意味着16x16像素将适合在游戏世界的一个单位。我们将使用这个值作为我们所有的纹理,因为V尖峰将是16x16像素,这应该是一个单位的游戏世界。

之后,我们可以从项目区进入在Hierarchy为了把它添加到游戏世界中:

在这里插入图片描述

3.平面物理

目前,我们的形象实际上只是游戏世界中的一个形象。我们可以看到它,但它什么也做不了,玩家将无法在上面行走。为了让它成为物理世界的一部分,我们将添加一个碰撞器,这样玩家就可以在上面行走并与之碰撞。

添加碰撞器: 在这里插入图片描述 我们看场景就能看到结果了。Unity只是试图将多边形围绕在我们复杂的水平形状上: 在这里插入图片描述 问题是,多边形只是一个估计,它还不是我们想要的。现在,只有我们用红色突出显示的区域实际上是可行走的:

在这里插入图片描述 Unity允许我们通过单击以下按钮修改多边形碰撞器: 在这里插入图片描述 之后,我们可以修改场景就像这样: 在这里插入图片描述 让我们修改整个多边形,直到它完全符合我们的要求: 在这里插入图片描述 现在平台的碰撞器是非常好的。

4.V字尖峰

好吧,让我们增加我们的难度,增加红色V尖峰。我们将首先绘制一幅16x16px图像: 在这里插入图片描述 注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。

导入设置: 在这里插入图片描述 让我们把它拖入场景中,它放在(-2, -12, 0)位置 在这里插入图片描述 ==注意:如果我们把所有的V型尖峰定位在整数位置上是比较好的,比如(-2, -12, 0),而不是像(-2.03 -11.99, 0)==

让我们再一次加入碰撞器,使V字尖峰 成为物理世界的一部分。这一次我们不需要Polygon Colllider 2D,因为它的形状非常简单。

我们只需要添加一个Box Collider 2D碰撞器: 在这里插入图片描述 现在我们可以选择V尖峰并复制粘贴: 在这里插入图片描述 然后把它放在前一个旁边: 在这里插入图片描述 我们将继续复制和定位V尖峰,直到我们的水平区域被填满: 在这里插入图片描述 注意:我们可以旋转Z轴,来旋转V尖峰

为了保持整齐呃,我们再Hierarchy中,将它们拖到level_blue对象下面,成为其子对象: 在这里插入图片描述 注意:我们还把所有的V都重命名为“V”而不是“V1”, “V2”等。

5.玩家

随着难度的提高,现在是时候把玩家添加到我们的游戏中了。 我们需要两个闲置的动画(面向左和右) 还有两部动画(面向左和右) 如下图所示: 在这里插入图片描述 注意:右键另存为,保存到项目中Assets/Sprites文件夹

导入设置 在这里插入图片描述 分割图片 我们的玩家图像实际上是由几个小片组成的动画。我们在上面的图像设置Sprite Mode:Multiple 这告诉Unity,我们的图像有多个切片。让我们点击Sprite Editor按钮,以便我们可以编辑切片: 在这里插入图片描述 之后我们会切片具有下列设置的图像: 在这里插入图片描述 Unity自动检测图像中的切片。我们现在要做的就是选择Apply并关闭Sprite Editor

然后让我们看看在项目区,所有的片段都是玩家的子部分: 在这里插入图片描述

6.玩家动画

好的,现在我们有了动画切片,我们可以用它创建我们的4幅动画。作为提醒,下面是我们需要的动画:

让我们创建闲置右动画。我们将首先选择项目区的切片0和1: 在这里插入图片描述 然后将它们拖到场景: 在这里插入图片描述 我们将它保存到PlayerAnimation文件夹中,命名为idle_right.anim

然后我们可以在项目区看到: 在这里插入图片描述 第一个文件是动画状态机,它指定动画速度和动画状态。第二个是动画本身。

我们将在其余的动画中重复这个过程。

这是我们在Hierarchy看上去的样子: 在这里插入图片描述 然后删除player_2、player_4、player_6 在这里插入图片描述 然后在项目区删除player_2、player_4、player_6的动画状态机: 在这里插入图片描述

7.玩家动画状态机

现在我们有四个动画文件,但还不知道什么时候播放哪个动画。 所以我们需要Unity的动画系统,做一个动画状态机,它有4个状态:

好的,让我们在项目区双击player_0状态机: 在这里插入图片描述 现在我们可以在Animator看到状态机: 在这里插入图片描述 创建其他状态 这个idele_right状态已经在动画器中,所以让我们添加idele_left状态,只需从文件夹中拖动idele_left动画文件到Animator: 在这里插入图片描述 对于其余两种状态,可以重复这一过程: 在这里插入图片描述 创建参数 因为玩家只有水平移动方向。 那么参数为0代表只是站在那里,大于0说明往左,小于0说明往右

让我们点击参数选项卡的左边,点击+号,添加一个INT类型的参数DirX: 在这里插入图片描述 稍后,我们可以通过编写以下内容从脚本中设置参数:

GetComponent<Animator>().SetInteger("DirX", 0);

创建Transitions 我们希望Unity能够根据这个参数自动切换到不同的动画状态。让我们选择idel_right状态,右键单击它,选择过渡并将白色箭头拖到向右拖到walk_right: 在这里插入图片描述 然后我们点击白色箭头,设置参数大于0的时候是向左移动(小于0是向右移动): 在这里插入图片描述 注意:我们还禁用了Has Exit Time因为我们不需要一个基于时间的过渡。

现在我们可以创造另一个Transition从walk_right回到idle_right。这一次,我们将使用条件那x等于0: 在这里插入图片描述 为了更多的转换,我们将重复这个工作流:

以下是动画状态机与所有这些转换的外观: 在这里插入图片描述 注:橙色状态是默认的,或者换句话说:游戏开始后,我们处于橙色状态。

动画速度 这里还有最后一次调整。让我们选择walk_left和walk_right然后把他们的速度到0.7这样动画就不会出现得太快: 在这里插入图片描述 我们刚刚创建了动画状态机。因此,Unity将自动在动画之间切换,并根据当前状态一遍又一遍地播放它们。剩下我们要做的就是在我们的脚本中设置动画之间参数。 那些曾经尝试用脚本手动实现所有这些状态的人会意识到动画状态机会有多方便。

8.玩家物理

让我们在场景中选择玩家,将玩家的位置设置在(10, -10, 0): 在这里插入图片描述 添加碰撞器: 在这里插入图片描述 下面是场景中的场景: 在这里插入图片描述 添加刚体: 在这里插入图片描述 注意:我们用了一个有点大的Gravity Scale使玩家快速下降的值。我们还开启了Fixed Angle属性以确保玩家在与某物发生碰撞后不会开始旋转。

9.玩家移动

添加脚本Player,双击打开脚本,删除Start函数,因为我们不需要它:

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {
    
    // Update is called once per frame
    void Update () {
    
    }
}

为了实现水平移动,我们需要运动速度变量,让我们设置一个speed变量:

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {
    // Movement Speed
    public float speed = 30;
    
    // Update is called once per frame
    void Update () {
    
    }
}

现在我们可以使用Update函数使玩家移动。 我们希望当玩家按右或左箭头键时被检测到,我们可以使用GetAxisRaw函数:

// Update is called once per frame
void Update () {    
    // Horizontal Movement
    float h = Input.GetAxisRaw("Horizontal");
}

注意:GetAxisRaw函数返回0如果不按方向,1如果用户按下指示正确的方向和-1如果他按下指示左边方向。

现在我们可以用我们的h值设置刚体的速度属性,这就是使玩家移动的原因。 这个velocity总是运动方向乘以速度。 在我们的例子中,移动方向是Vector2.right,我们可以把它看作水平方向。 速度将是我们的速度变量乘以h变量使其变为正或负,这取决于我们需要的方向:

// Update is called once per frame
void Update () {    
    // Horizontal Movement
    float h = Input.GetAxisRaw("Horizontal");
    GetComponent<Rigidbody2D>().velocity = Vector2.right * h * speed;
}

我们别忘了DirX这里的动画参数也是:

// Update is called once per frame
void Update () {    
    // Horizontal Movement
    float h = Input.GetAxisRaw("Horizontal");
    GetComponent<Rigidbody2D>().velocity = Vector2.right * h * speed;
    
    // Animation
    GetComponent<Animator>().SetInteger("DirX", (int)h);
}

如果我们保存脚本并按下Play然后我们现在可以移动玩家: 在这里插入图片描述 让我们来看看最有趣的机制,重力逆转。听起来有些复杂的东西,实际上在Unity中是很简单的。首先,我们要检查是否按下了空格键。 然后我们把gravityScale的值设为-1,这就逆转了它。我们还旋转玩家,使其看起来也相反:

// Update is called once per frame
void Update () {    
    // Horizontal Movement
    float h = Input.GetAxisRaw("Horizontal");
    GetComponent<Rigidbody2D>().velocity = Vector2.right * h * speed;
    
    // Gravity Changes
    if (Input.GetKeyDown(KeyCode.Space)) {
        GetComponent<Rigidbody2D>().gravityScale *= -1;
        transform.Rotate(0, 180, 180);
    }
    
    // Animation
    GetComponent<Animator>().SetInteger("DirX", (int)h);
}

注意:我们在Z轴,使其看起来倒立,180°Y轴可以水平地镜像它。这样,我们的左右动画看起来仍然是正确的与反向重力。

我们按下Play然后我们可以通过按空格键来重力扭转: 在这里插入图片描述 这里只有一点小问题。现在我们可以随时扭转地心引力,即使是在坠落的时候: 在这里插入图片描述 要防止这种情况发生,必须确保重力只能在地面上倒转。出于某种原因(这有点复杂),Unity没有isGrounded函数,我们可以自己写一个IsGrounded函数:

public bool isGrounded() {
    // Get Bounds and Cast Range (extents.y+20%)
    Bounds bounds = GetComponent<Collider2D>().bounds;
    float range = bounds.extents.y * 1.2f;

    // Calculate a position slightly 'below' the collider
    // (via transform.up so it's gravity tolerant)
    Vector2 v = bounds.center - transform.up * range;
    
    // Linecast upwards
    RaycastHit2D hit = Physics2D.Linecast(v, bounds.center);

    // Was there something in-between, or did we hit ourself?
    return (hit.collider.gameObject != gameObject);
}

注意:这实际上只是一些矢量数学和一些物理的东西。如果你不明白的话,不要太担心它。这个功能会运行得很好,很可能会被Unity自己的功能所取代。

让我们在Update函数利用我们的isGrounded函数,在按下的空格键的时候检查:

// Gravity Changes
if (Input.GetKeyDown(KeyCode.Space) && isGrounded()) {
    GetComponent<Rigidbody2D>().gravityScale *= -1;
    transform.Rotate(0, 180, 180);
}

如果我们保存脚本并按下Play现在我们只能站在地面上扭转重力。

10.检查点

创建检查点 让我们增加一些检查点,让游戏变得简单一些。我们首先画一个: 在这里插入图片描述 注意:右键另存为,保存到Assets/Sprites文件夹中

导入设置 在这里插入图片描述 设置坐标(10, -10, 0): 在这里插入图片描述 我们希望玩家可以显示在检查点前面,让我们选择player_0对象,然后更改Sprite Renderer的Order in Layer为1: 在这里插入图片描述 注意:数字越大越后被渲染,也就是说会显示在其他图像的前面

在这里插入图片描述

接着让我们给检查点添加碰撞器: 在这里插入图片描述 带着IsTrigger启用后,我们很快就可以使用OnTriggerEnter2D函数来确定玩家是否通过了触发器。

让我们复制几个检查点,并将其移到我们场景的不同位置: 在这里插入图片描述 注意:我们还将所有检查点重命名为“checkpoint”

穿过检查点 让我们打开Player脚本,现在,每当他走过一个检查站,我们想要保存检查站的位置,以便我们可以去那里,以防死亡。让我们添加另一个变量:

public class Player : MonoBehaviour {
    // Movement Speed
    public float speed = 30;
    
    // Last Checkpoint
    Transform check;
    
    ...
}

然后,我们可以在OnTriggerEnter2D收到以下信息:如果我们走过一个检查点,check的坐标就被设置为穿过的检查点的坐标:

void OnTriggerEnter2D(Collider2D co) {
    // Checkpoint?
    if (co.name == "checkpoint")
        check = co.transform;
}

就这么简单。

11.穿过V字尖峰

如果玩家不能死,我们的检查点是无用的,所以让我们再次修改我们的脚本。这一次,我们希望检测到与V字尖峰。 V字尖峰没有Is Trigger选项,这意味着这次我们需要OnCollisionEnter2D函数:

void OnCollisionEnter2D(Collision2D co) {
    // Collided with a V?
    if (co.collider.name == "V") {
        // Reset Rotation, Gravity, Velocity and go to last Checkpoint
        transform.rotation = Quaternion.identity;
        GetComponent<Rigidbody2D>().gravityScale = Mathf.Abs(GetComponent<Rigidbody2D>().gravityScale);
        GetComponent<Rigidbody2D>().velocity = Vector2.zero;
        transform.position = check.position;
    }
}

注意:开始时,我们会通过检查名字是否为V,来检测是否碰撞到V字尖峰。然后,我们将旋转重置为默认旋转(四元数。恒等式),使重力为正(向下),并重置刚体的速度。之后,我们只需将当前位置设置为最后一个检查点的位置。

如果我们保存脚本并按下Play之后,玩家将被重置为死后的最后一个检查点: 在这里插入图片描述 就这样,现在我们可以尝试通过第一层: 在这里插入图片描述

12.摘要

我们刚刚在Unity制作了一个有趣的小平台游戏。像往常一样,Unity处理所有复杂的事情(isGrounded 函数除外)。现在该由读者来让游戏变得有趣了。还有各种各样的东西仍然可以实现,例如: