基于Unity的2D小游戏 SpeedDown 开发笔记


主要内容:在Sunnyland游戏的设计基础上,新增了物理组件Joint系列、DrawGizmos辅助设计工具、LineRenderer线渲染器组件的使用、随机生成prefabs、计时器等功能。


一、场景搭建

1、项目创建与素材导入

创建一个2 D项目,在Asset Store中搜索导入Pixel Adventure 1素材包(It’s Free)。

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_游戏开发

2、开发平台选择与屏幕分辨率调整

在File -> building setting 选择开发平台;

在Hierarchy窗口新建一个3 D项目->Quad,在Quad->Scale中更改x、y,更改背景的比例(参考值10:9),同时更改Main Camera的size(参考值为8),使得摄像机能够完整显示Quad,调整相机位置.z为负值,使得游戏画面能够正常显示

3、背景(QUAD)设置

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_ide_02

在Assets文件夹中Create材质Material,命名为BackGround,将BackGround的Shader更改为Unlit->Texture(即仅仅作为一个图像)

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_unity_03

更改素材BackGround文件图片中的Pixels Per Unit设置为素材本身的像素(此处为64),调整Wrap Mode为repeat,调整材质的Tiling,这样背景图片能够根据设置的铺设贴图行列数组成背景图,如图所示。通过改变offset(偏移)来实现视觉上的移动效果。

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_ide_04

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_Unity 限制屏幕的长宽比_05

创建脚本实现背景的移动动画效果:

获得Material材质球,注意Quad中没有Material,GetComponent通过获得Renderer.material 来得到对象。

设置一个speed,在update方法基于speed参数,改变材质的Offset,代码如下所示。

public class Animationbg : MonoBehaviour
{
    Material material;
    Vector2 movement;

    public Vector2 speed;


    void Start()
    {
        material = GetComponent<Renderer>().material; //注意不是GetComponent<Material>
    }

    // Update is called once per frame
    void Update()
    {
        movement += speed * Time.deltaTime;  //*deltaTime等价于*0.02,使得图片变化更顺畅
        material.mainTextureOffset = movement;
    }
}

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_Unity 限制屏幕的长宽比_06

通过在unity中设置speed值,调整背景图横向、纵向移动速度。

4、SPIKES(尖刺)布置

Spikes素材的整理:找到素材Traps->Spikes->Idle,InputSettings -> Pixel Per Unit更改为16,Fliter mode设置为Point(对于像素资源来说,Point可以让贴图显示得更清楚,具体解释见Unity 纹理资源简介(5.3.7))

Create Empty,命名为TopSpikes,将贴图拖入项目,调整尖刺的方向和大小,Ctrl+D创建多个,铺满背景图上端。(Tips:选中Spike,按住V键,即可自动选择边界点,自动贴合,避免手动调整产生误差)

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_游戏开发_07

添加collider(碰撞体),让玩家产生碰撞效果,起到一个trigger作用,角色触碰触发死亡:

为Spikes添加组件Polygon Collider 2D(多边形碰撞体),打开Used By Composite(作为整合体的一部分);

添加组件Composite Collider 2D(合成碰撞体),在自动添加的rigidbody中,设置BodyType为Static(静态),固定rigidbody,再将Collider位置调整到一个合适的位置。

二、Platforms制作

1、基础平台BASICPLANTFORM制作

素材位置:Traps->Falling Plantforms。

Pixels Per Unit = 16,直接拖入Hierarchy中,命名为BasicPlantform。

添加动画效果:(新建文件夹Assets->Animation->Plantforms->BasicPlantform,养成整理各类型文件的好习惯)选中gameobject,在Animation窗口创建动画,选中所有BasicPlantforms动画素材,拖入Animation窗口当中,调整采样频率Samples = 10(2019版在Animation窗口右侧点击齿轮图标,选择show sample rate)。

为了使palyer可以在平台上站立,为平台添加Box Collider 2D,Edit 其大小。

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_Time_08

2、旋转平台ROTATEPLANTFORM制作

素材位置:Traps->Plantforms。

Pixels Per Unit = 16,直接拖入Hierarchy中,命名为Rotate Plantform。

新组件:Joint系列组件

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_Time_09

添加Hinge Joint 2D 组件(类似机械原理中的铰链),会自动添加rigid body 2D,修改Motor -> Maximum Motor Force(最大转矩) = 100(可根据需要调整)。

3、弹跳平台FANPLANTFORM制作

素材位置:Traps->Fan

Pixels Per Unit = 16,直接拖入Hierarchy中,命名为Fan Plantform。

动画逻辑:player踩在平台上,平台开始旋转,并将玩家弹起一定高度。这就需要有两个动画,一个idle状态(只有素材的第一帧),一个run状态(将素材全部拖进Animation)。

脚本实现:首先GetComponent< Animator >组件。利用OnCollisionEnter2D判断碰撞,需要判断碰撞的collision的tag == “Player”(需要在unity中添加Player的tag),直接Play动画名。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FanPlantform : MonoBehaviour
{

    Animator animator;
    // Start is called before the first frame update
    void Start()
    {
        animator = GetComponent<Animator>();
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Player"))
        {
            animator.Play("Fan_run");
        }
    }
}

4、摆动链球SPIKEDBALL制作

素材位置:Traps->Spiked Ball

Pixels Per Unit = 16,新建SpikedBall空项目,将Chain 和 Spike Ball拖入项目。

为实现球绕链顶点旋转,可以采用多种方法,比如复制多个Chain,利用Hinge joint组件创建铰链来实现;或者利用Distance Joint 2D组件来实现:

选择SpikedBall,添加Distance Joint 2D组件,为Chain添加Rigidbody,设置Body Type为Kinematic(或者为static),即将Chain固定在设定的位置上。选择SpikedBall,将Chain拖到Connected Rigidbody上。

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_Unity 限制屏幕的长宽比_10

为SpikedBall添加一个Circle Collider 2D ,调整圆的大小和位置,勾选isTrigger,作为一个碰撞体。

Line Renderer组件(线渲染器),绘制Ball和Chain之间的连线。修改材质为Default-Line,编写脚本获取Chain和Ball的两个点位置作为线的起点和终点。

为Spiked Ball添加脚本,通过SetPoint(int index,Vector3 position)方法,注意要一个index一个地添加。

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_Unity 限制屏幕的长宽比_11

设计结果如图所示:

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_ide_12

5、平台的运动

Create new C#Scripts,named Plantform.

由于平台的Position都是3维的,所以Vector3 movement,写一个MovePlatform()方法,transform.position += movement * Time.deltaTime,在Start方法中movement.y = speed(先全局public speed,方便在Unity中调试);

平台销毁:需要创建一个EmptyObject,命名为TopLine,设置在想要销毁平台的位置,在脚本中调用它的TopLine.transform.position.y,当平台运动到平台.position.y > TopLine.transform.position.y时,Destry(平台)。

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_游戏开发_13

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Plantform : MonoBehaviour
{
    Vector3 movement;
    public float speed;
    GameObject topLine;
    void Start()
    {
        movement.y = speed;
        topLine = GameObject.Find("TopLine");
    }

    // Update is called once per frame
    void Update()
    {
        MovePlantform();
    }

    void MovePlantform()
    {
        transform.position += movement * Time.deltaTime;

        if(transform.position.y >= topLine.transform.position.y)
        {
            Destroy(gameObject);
        }
    }

}

6、设置好平台之后,将其保存为Prefab,便于后面平台生成时调用与调试

三、Player制作

1、动画制作与切换

素材位置:Main Character -> Virtual Guy

Pixels Per Unit = 32,拖拽Idle第一帧动画到Hierarchy中,命名为Player

添加动画idle、run、fall、hit(dead),推荐采样频率为20-50。利用float speed,bool isOnGround,trigger hit来实现动画的切换。

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_unity_14

添加Rigidbody,Constraints -> Freeze Rotation z,避免player绕z轴翻转

添加 Box Collider 2D,Edit box的大小和位置

添加脚本PlayerController,其中角色的移动、动画切换等与SunnyLand教程 中的操作基本一致。需要注意的是Fanplatform的设定,让角色触碰到fanplatform时弹起,通过对FanPlatform添加tag,利用**collision.gameObject.CompareTag()**方法来实现。

为所有平台添加Layer:Plantform

Tips:利用Gizmos(线框),让范围检测可视化,便于调试

Gizmos.DrawWireSphere(groundCheck.transform.position,checkRadius);

注意下图中的蓝色circle即为绘制的检测范围

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_游戏开发_15

2、角色死亡(或者HIT)-> GAME OVER

死亡判断:Player碰撞到tag为Spike的物体。

实现:在hit(dead)动画最后一帧添加Envent Dead()。

3、PLAYER设置完成后保存为Prefab

四、随机生成平台

在屏幕下方某处生成平台,同时作为角色死亡的判定位置。 创建Empty,reset并调整位置,添加BoxCollider2D,勾选isTrigger,调整box形状,将tag设置为Spikes,如图所示

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_Unity 限制屏幕的长宽比_16

创建随机生成平台脚本:用一个List来保存所有要生成的平台类型,设置生成平台的时间间隔spawnTime,以及生成平台的左右位置(左右各设置一个生成极值)。

生成平台的种类可以利用C#自带随机函数生成,即生成int index = Random.Range(0,List.Count)

玩家游戏性调整:调整Platform List中各种平台占比,控制游戏难度,可以以此制作难度与时间相关的游戏

不连续生成两个SpikedBall方法:设置全局变量spikeNum,如果生成了一个SpikedBall,spikeNum++,生成第二个时,spikeNum = 0,同时将index重置为**[0,3)之间的整数,注意,需要设置SpikedBall不在List**中的前三个位置。

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_unity_17

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spawner : MonoBehaviour
{

    public List<GameObject> plantforms = new List<GameObject>();

    public float spawnTime;
    private float countTime;
    private Vector3 spawnPosition;
    int spikeNum = 0;


    // Update is called once per frame
    void Update()
    {
        SpawnPlantform();
    }

    public void SpawnPlantform()
    {
        countTime += Time.deltaTime;
        spawnPosition = transform.position;
        spawnPosition.x = Random.Range(-3.5f, 3.5f);

        if(countTime >= spawnTime)
        {
            CreatPlantform();
            countTime = 0;        }
    }

    public void CreatPlantform()
    {
        int index = Random.Range(0, plantforms.Count);
        //Instantiate(plantforms[index], spawnPosition, Quaternion.identity);
        if (index == 4)
        {
            spikeNum++;
        }
        if(spikeNum > 1)
         {
             spikeNum = 0;
            //countTime = spawnTime;
            index = Random.Range(0, 3);
            //return;
         }
        GameObject newPlantform = Instantiate(plantforms[index], spawnPosition, Quaternion.identity);
        newPlantform.transform.SetParent(this.gameObject.transform);
    }
}

五、游戏管理 & UI

添加UI->Text,调整位置、字体等,用来计时(利用Time.timeSinceLevelLoad)。

添加游戏结束的Panel,包含Text(GameOver)和两个Button(PlayAgain,Quit)

创建脚本GameManage,挂到Empty GaemManage上。

PlayAgain方法利用SceneManger.LoadScene(SceneManager.GetActiveScene().name);实现。

Quit利用**Application.Quit()**方法。

角色死亡时暂停游戏(Time.timeScale = 0),显示panel。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class GameManage : MonoBehaviour
{
    static GameManage instance;

    public Text timeScore;
    public GameObject GameOverUI;

    private void Awake()
    {
        if(instance != null)
        {
            Destroy(gameObject);
        }
        instance = this;
    }

    // Update is called once per frame
    void Update()
    {
        timeScore.text = Time.timeSinceLevelLoad.ToString("00.0");
    }

    public void RestartGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        Time.timeScale = 1f;
    }

    public void Quit()
    {
        Application.Quit();
    }

    public static void GameOver(bool dead)
    {
        if (dead)
        {
            instance.GameOverUI.SetActive(true);
            Time.timeScale = 0f;//Game pause;
        }
    }
}

Unity 限制屏幕的长宽比 unity设置游戏画面的大小_Unity 限制屏幕的长宽比_18

附:游戏Demo:spikedball

spikedball