8. 暂停按钮,方块落下停止与游戏结束

专栏收录该内容

Hi I'm Shendi



暂停按钮

在游戏右上角制作暂停按钮,PageGame下的Canvas右键 - UI - 按钮,创建一个按钮

将按钮的宽高改为 60,按钮内文本内容为 | | ,将按钮的源图像改为圆圈,调整以下透明度,字体大小改为26

将其拖到右上角,Rect Transform同样到右上角,如下

暂停按钮


这样暂停按钮的样式就做好了

当按钮点击后,就暂停游戏,并弹出继续和返回的面板



暂停面板制作

暂停面板包含继续和返回按钮

在 Canvas 处右键 UI - 面板,创建面板,在面板下再创建一个面板,调整第二个面板大小和颜色

创建面板


在第二个面板下创建两个按钮,分别为继续和返回按钮,将按钮文字更改,文字大小设置为26,按钮宽高设置为150x150,按钮源图像改为 Input 那个,结构与效果如下

结构

效果


当点击暂停按钮时出现这个面板,所以默认取消活动状态,点击面板,取消右上角的勾选

UI方面懒得使用PS绘图了,这样也挺好,精简风...



暂停处理

当按下暂停按钮时游戏暂停,并且弹出暂停面板

让游戏暂停,可以使用 Time.timeScale = 0;

Time.timeScale 是Unity的一个全局时间缩放属性

它可以控制游戏世界里的游戏时间流逝的快慢度。它的范围一般在0.0-1.0之间,0.0表示时间完全停止,1.0表示正常时间流逝,大于1.0表示快进时间,小于1.0表示慢动作。

并且之前在 State 中的定义了一个变量作为判断是否为暂停状态

那么只需要在 Main 加入以下代码即可实现暂停游戏了

public GameObject panelPause;

/** 暂停游戏点击 */
public void PauseGame()
{
    State.isGamePause = true;
    Time.timeScale = 0;

    // 显示暂停面板
    panelPause.SetActive(true);
}

在暂停按钮点击事件处增加事件(之前做过的增加按钮点击事件操作),选择摄像机,右边调用的函数选择 PauseGame

点击层级面板的摄像头,在 Main 脚本处,将暂停面板拖入 panelPause

将 PageGame 取消活动状态(取消勾选),启动游戏看下效果

暂停效果


在跳跃到空中时,点击暂停按钮,可以看到角色停在了空中(暂停了)



继续按钮与返回按钮

当点击继续按钮时就将暂停所做的操作复原即可

/** 继续按钮点击 */
public void ContinueGame()
{
    State.isGamePause = false;
    Time.timeScale = 1;

    // 隐藏暂停面板
    panelPause.SetActive(false);
}

同样在继续按钮点击事件处增加事件,对象选择摄像机,选择 ContinueGame 函数

增加事件


这样继续按钮就完成了

而返回按钮,点击后首先需要将游戏结束,然后返回到主界面

可以编写一个 StopGame 函数用来专门处理游戏结束,包括游戏结束的结算等。

一般返回按钮是不结算直接返回到主页这样,但这里直接走正常游戏结束的流程了

至于游戏结束具体操作,等到后面再去做,现在这里定义一个壳子并绑定给返回按钮即可

/** 游戏结束 */
public void StopGame()
{

}

同上面那些按钮一样,给返回按钮增加点击事件...函数指定为 StopGame



方块的落下

根据最开始的策划,开始游戏后方块落下,并随着时间,落下的速度将越来越快

因为涉及到具体数值,这里不使用刚体来让方块落下,而是使用直接移动方块的方式

这里还需要策划一下方块的生成时间,方块每5秒生成一个,随着时间推移将生成的越来越快,10秒减少0.5秒,最快1秒生成一个

默认五秒,一秒是50次,也就是50*5次


落下速度初始每秒一个方块高度,方块高度为0.5,也就是每次下落 0.5/50,每十秒增加0.1,最大为4


每个方块血量值默认为1,每十秒增加0.2(无上限)

在State中定义这些初始值

/** 当前方块生成的速度 */
public static float boxCurSpawnSpeed = boxSpawnSpeed;
/** 方块的生成速度默认值 */
public static float boxSpawnSpeed = 50 * 5;
/** 方块的生成速度最大值 */
public static float boxSpawnSpeedMax = 50;
/** 方块的生成速度调整间隔(50为1秒) */
public static float boxSpawnSpeedTime = 50*10;
/** 方块的生成速度每次调整的大小 */
public static float boxSpawnSpeedTimeNum = 25;

/** 当前方块落下速度 */
public static float boxCurSpeed = boxSpeed;
/** 方块落下初始速度 */
public static float boxSpeed = boxSize / 50;
/** 方块落下最大速度 */
public static float boxSpeedMax = 4 / 50;
/** 方块落下初始速度调整间隔 */
public static float boxSpeedTime = 50*10;
/** 方块落下初始速度每次调整的大小 */
public static float boxSpeedTimeNum = 0.1f / 50;

/** 当前方块血量值 */
public static float boxCurHealth = boxHealth;
/** 方块血量值 */
public static float boxHealth = 1;
/** 方块血量值调整的间隔 */
public static float boxHealthTime = 50*10;
/** 方块血量值每次调整的大小 */
public static float boxHealthTimeNum = 0.2f;

在Main的开始游戏部分将上面定义的三个当前值初始化

public void StartGame()
{
    // 初始化值
    State.boxCurSpawnSpeed = State.boxSpawnSpeed;
    State.boxCurSpeed = State.boxSpeed;
    State.boxCurHealth = State.boxHealth;
}

方块的生成

首先实现生成和大体部分,在 Main 中的 FixedUpdate 来实现这个功能

之前在State编写了生成方块的函数,这里直接使用即可生成方块了

/** 方块生成冷却的计时器,50一秒, */
private float boxSpawnTotol = 0;
/** 方块生成速度调整计时器,到达指定大小后调整生成速度 */
private float boxSpawnTimeTotol = 0;

/** 方块下落速度调整计时器,到达指定大小后调整下落速度 */
private float boxSpeedTotol = 0;

void FixedUpdate()
{
    if (State.isGameStart && !State.isGamePause)
    {
        boxSpawnTotol--;

        boxSpawnTimeTotol++;
        boxSpeedTotol++;

        // 方块生成
        if (boxSpawnTotol <= 0)
        {
            State.CreateRanBox();
            boxSpawnTotol = State.boxCurSpawnSpeed;
        }

        // 调整生成间隔
        if (boxSpawnTimeTotol >= State.boxSpawnSpeedTime)
        {
            if (State.boxCurSpawnSpeed > State.boxSpawnSpeedMax)
            {
                State.boxCurSpawnSpeed -= State.boxSpawnSpeedTimeNum;
            }
            boxSpawnTimeTotol = 0;
        }

        // 调整下落速度
        if (boxSpeedTotol >= State.boxSpawnSpeedTime)
        {
            if (State.boxCurSpeed > State.boxSpeedMax)
            {
                State.boxCurSpeed += State.boxSpeedTimeNum;
            }
            boxSpeedTotol = 0;
        }
    }
}

这样,运行游戏,就可以看到方块生成了,并随着时间增加,生成的越来越快

在开始游戏中,需要将上述定义的参数重新初始化

public void StartGame()
{
    boxSpawnTotol = 0;
    boxSpawnTimeTotol = 0;
    boxSpeedTotol = 0;
}

创建一个空对象作为生成方块的父对象,方便管理

在 PageGame 右键新建一个空对象,同背景一样,拖到最上方,在Main中定义如下

public GameObject boxs;

点击摄像机,将创建的对象拖到boxs处就可以了

Main脚本指定的对象


后续方块可以被一个个消灭,以及方块的下落和停止,所以需要新建一个脚本,绑定在生成的小方块上

新建一个脚本,命名为Box

在Main中给小正方形加Box组件,以及碰撞体组件,代码如下

// 方块生成
if (boxSpawnTotol <= 0)
{
    var obj = State.CreateRanBox();
    obj.transform.parent = boxs.transform;
    obj.transform.localPosition = Vector2.zero;

    // 遍历所有子物体给其加上Box脚本组件
    for (var i = 0; i < obj.transform.childCount; i++)
    {
        var tmp = obj.transform.GetChild(i);
        tmp.AddComponent<Box>();
        tmp.AddComponent<BoxCollider2D>();
    }

    boxSpawnTotol = State.boxCurSpawnSpeed;
}

会发现有一个问题,就是方块生成了但是看不见,原因是 Z 轴太靠近了,调整一下PageGame的Z轴为0即可

运行后查看小方块,发现绑定上了Box和碰撞体组件了

组件的绑定效果



下落

速度什么的都在 Main 中处理了,下落在 Box 中处理,直接设置即可

代码如下

void FixedUpdate()
{
    if (State.isGameStart && !State.isGamePause)
    {
        transform.position -= new Vector3(0, State.boxCurSpeed);
    }
}

运行后可以发现源源不断的方块往下落了,并且角色可以踩在方块上

方块下落



停留在地面

当方块到达地面时将停下来,并作为地面的一份子

可以通过碰撞检测来实现

  • void OnCollisionEnter(Collision collision) {} 当物体发生碰撞时调用
  • void OnCollisionExit(Collision collision) {} 当物体碰撞后离开碰撞体时调用
  • void OnCollisionStay(Collision collision) {} 当物体粘在另一个物体上时每帧调用

我们使用的是 2D,所以需要在函数和参数后加上 2D

例如void OnCollisionEnter2D(Collision2D collision)


Unity中的碰撞/触发需要两个对象都有碰撞器(Collider),以及一方拥有刚体(Rigidbody)

而给正方形加上刚体则会一直往下落,我们是使用代码来控制方块下落,所以需要给刚体的重力设置为0

在创建正方形的地方(Main)给正方形增加刚体组件

// 遍历所有子物体给其加上Box脚本组件
for (var i = 0; i < obj.transform.childCount; i++)
{
    var tmp = obj.transform.GetChild(i);
    tmp.AddComponent<Box>();
	tmp.AddComponent<BoxCollider2D>();
    var rigidbody = tmp.AddComponent<Rigidbody2D>();
    rigidbody.gravityScale = 0;
}

运行后会发现四分五裂的方块

四分五裂的方块

这是因为小方块之间互相碰撞了,如何解决这个问题?那就将碰撞体大小改小一点就好了,使其相互之间碰撞距离够不着

改小0.05即可

var boxColidder = tmp.AddComponent<BoxCollider2D>();
boxColidder.size -= new Vector2(0.05f, 0.05f);

将最上面一排的尖刺的Collider改为触发器,否则有可能方块卡在最上方

改为触发器


现在就只用处理碰撞操作了,在 Box 脚本里

在此之前,我们需要先给物体设置标签,以区分碰撞的是什么物体

给角色预制体设置标签 Player

标签


方块预制体添加标签,Box,并选择

游戏内限制范围的三个墙设置为Wall标签,没有就添加

墙


我们还需要新建一个标签叫做BoxWall,当方块到达地面时就改为这个标签



标签处理完后处理碰撞检测,当碰撞的是Wall或者BoxWall时,将方块停止移动并标签改为BoxWall

当碰撞到的是Player时,游戏结束

需要让方块停止先要增加一个标志位,Box 更改如下

// 方块是否执行完成
public bool isOk = false;

void FixedUpdate()
{
    if (State.isGameStart && !State.isGamePause && !isOk)
    {
        transform.position -= new Vector3(0, State.boxCurSpeed);
    }
}

游戏结束的处理在 Main 内,将 Main 改为单例模式,bing改为单例模式,并将唯一对象提供出来,Main增加如下代码

/** 单例 */
public static Main _instance;
private void Awake()
{
    _instance = this;
}


Box 中碰撞体处理代码如下

void OnCollisionEnter2D(Collision2D collision)
{
    switch (collision.gameObject.tag)
    {
        case "Wall":
        case "BoxWall":
            // 将父对象的所有子对象都执行完成
            for (var i = 0; i < transform.parent.childCount; i++)
            {
                var obj = transform.parent.GetChild(i);
                obj.tag = "BoxWall";
                obj.GetComponent<Box>().isOk = true;
            }
            break;
        case "Player":
            Main._instance.StopGame();
            break;
    }
}

运行游戏,发现方块落在地面后就停留了,并且可以堆叠(前提是角色不去干扰,撞方块,因为游戏结束部分还没做)


效果图



游戏结束处理

之前说过游戏结束的条件

  • 角色碰到落下的方块(标签为Box)
  • 角色碰到顶部尖刺
  • 暂停面板的返回

游戏结束自然就需要涉及到结算部分,于是在 State 中定义结算部分

/** 结算 */
public static void settle() {}

至于结算部分,后面再去做

当游戏结束,弹出结算面板,其中有一个确定按钮,点击就返回主页

于是在 Main 的 StopGame 中进行以下操作

  • 显示结算面板
  • 调用结算函数
  • 暂停游戏
  • 隐藏暂停面板
  • ...

先把结算面板做出来,与暂停面板一样,直接复制暂停面板,调整一下大小,位置,按钮文字,效果如下即可

结算面板


在 Main 中,增加一个按钮点击的函数,游戏结束代码与结算点击代码如下

结算点击将一切状态恢复到最开始的时候

public GameObject panelSettle;

/** 游戏结束 */
public void StopGame()
{
    State.isGameStart = false;
    State.isGamePause = false;

    // 显示结算面板,隐藏暂停面板
    panelSettle.SetActive(true);
    panelPause.SetActive(false);

    Time.timeScale = 0;
}

/** 结算完成按钮点击 */
public void SettleOkClick()
{
    // 隐藏game显示main
    pageGame.SetActive(false);
    pageMain.SetActive(true);

    // 隐藏结算面板
    panelSettle.SetActive(false);

    // 销毁角色
    State.PlayerDestory();
    // 销毁游戏内生成的方块
    foreach (Transform child in boxs.transform)
    {
        GameObject.Destroy(child.gameObject);
    }

    //  显示背景
    State.isBackgroundPlay = true;
    Background._instance.StartCoroutine(Background._instance.CreateRanBox());

    Time.timeScale = 1;
}

其中启动背景的 CreateRanBox 函数是没有加 public 的,需要加上,在Background中

public IEnumerator CreateRanBox()

绑定 SettleOkClick 到确认按钮的点击事件, 并把结算面板拖到摄像机的Main脚本中

这样,游戏就可以正常结束并重新开始游戏了



尖刺处理

碰到尖刺也会让游戏结束,之前将 Spikes 的碰撞体的是触发器勾选了,于是编写一个脚本放到Spikes上,脚本就命名为Spikes吧

同方块一样,不过这里是触发检测

使用 OnTriggerEnter2D,代码如下

void OnTriggerEnter2D(Collider2D collision)
{
    // 是角色直接游戏结束
    if (collision.gameObject.tag == "Player")
    {
        Main._instance.StopGame();
    }
}

效果如下

碰到尖刺游戏结束



本文链接:https://sdpro.top/blog/html/article/1068.html

♥ 赞助 ♥

尽管去做,或许最终的结果不尽人意,但你不付出,他不付出,那怎会进步呢?