3. 首页的设计与制作

专栏收录该内容

Hi I'm Shendi


开始首页的制作


首页设计

首页,即用户打开后见到的第一个页面。

设计是比较头疼的,根据上节的策划,主页里有大概以下按钮

  • 开始
  • 商店
  • 历史分数

游戏名称也是要有的,一般都放在最开头

背景的话,就用不断下坠的方块好了

再加一个关于的按钮用于写一些格外信息


有了想法,就开始制作了



首页制作开始

打开项目,当前摄像机是横屏的,调整成竖屏

点击 Game,选择分辨率,如果没有竖屏的分辨率那就手动加一个,例如 720*1280

调整竖屏


将当前项目平台切换为WebGL,按下Ctrl+Shift+B,或者点击文件 - 生成设置,找到WebGL,右边有unity图标代表已经是使用了,如果没有就点击右下角的切换平台

切换平台



制作背景

首先把背景制作出来,背景嘛,就从上往下掉落方块,生成速度和x位置随机

在层级界面右键,创建空对象,命名为Background

Background


将这个空对象作为背景,拉到摄像头最上方


新建一个C#脚本,命名为Background,然后将脚本拖到层级面板的Background

双击脚本打开,如果没有VisualStudio则先下载安装,里面选择C#和Unity

有的话,则点击编辑 - 首选项,外部工具,编辑器选择Visual Studio

首选项


新建完后代码是如下的

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

public class Background : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {

    }
}

其中Start是脚本第一次加载时执行,且只执行一次,Update是每一帧都执行



制作小方块(预制体)

在层级面板右键,2D对象,

创建小方块


创建后将方块缩放调整到合适的大小,例如0.5x0.5

方块大小


在层级面板将物体重命名,新建 Resources 文件夹,在文件夹内新建Prefabs文件夹,在层级面板将物体拖到Prefabs文件夹,就成了预制体了



创建状态类(工具类)

游戏必然会有很多的状态,例如现在制作首页,背景是一直生成方块往下移动,当打开商店或者开始游戏时,背景这个操作就需要暂停

新建一个脚本文件,作为全局属性类,例如State类

代码如下

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

/**
 * 包含全局属性/状态
 */
public class State
{
    /** 背景是否播放 */
    public static bool isBackgroundPlay = true;

    #region 预制体部分
    /** 小方块 */
    public static GameObject box = Resources.Load<GameObject>("Prefabs/方块");

    /** 获取随机的小方块,通过box物体 */
    public static GameObject createRanBox()
    {
        GameObject obj = GameObject.Instantiate(box);

        return obj;
    }

    #endregion

}

最上面一行的 isBackgroundPlay 代表控制背景是否继续生成小方块的变量,true继续,false停止

上面的 GameObject 类代表游戏对象,Resources.Load 是加载 Resources 文件夹内的文件,我们的预制体在 Prefabs 文件夹下,名称为方块,于是这样加载预制体


createRanBox 是随机生成方块,其中的 GameObject.Instantiate 是将预制体实例化(放到游戏场景中)

目前这个函数仅仅是创建了预制体,并返回预制体实例,先看下效果,后面会增加方块数量以及颜色



生成一个小方块


将Background类的start调用createRanBox函数,运行看下效果

void Start()
{
	State.createRanBox();
}

运行前

运行前效果


运行后,可以看到多出了一个方块了

多出的方块


但是这个物体不是Background对象子对象,于是将 Background 的 start 改成以下代码

void Start()
{
	State.createRanBox().transform.parent = transform;
}

transform是获取到物体的 Transform 组件,parent 就是父对象,当前Background脚本绑定在了Background对象上,于是直接使用 transform 代表 Background 的 Transform,也就是新生成的小方块的父物体是Background

运行,可以看到方块变成了Background的子物体了


物体



背景蓝色有点违和,点击 Main Camera,调整一下颜色

摄像机背景



方块整体的生成

一个方块整体由多个小方块组成,方块颜色和小方块数量随机,这里试了下,横排最多10个方块,竖着不超过3方块,于是限制生成的方块的小方块数量范围为 1-15

首先更改以下 State 内的 createRanBox 函数,首字母大写(C#的规范)

将简单的随机生成颜色,数量制作出来

简单介绍一下随机数的使用 Random.Range(0, 2);,代表随机0和1,小数的话就0(包括)到1(不包括)之间的任何小数

代码如下

 public static GameObject CreateRanBox()
 {
     // 随机小方块数量,随机颜色
     var ranNum = Random.Range(1, 16);
     var ranColor = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f));

     GameObject parentObj = new GameObject("方块集");
     for (int i = 0; i < ranNum; i++)
     {
         GameObject obj = GameObject.Instantiate(box);

         var render = obj.GetComponent<Renderer>();
         render.material.color = ranColor;

         // 随机位置

         obj.transform.parent = parentObj.transform;
     }

     return parentObj;
 }

剩下的就是随机位置,不重合,并且方块有相邻方块即可

我的思路是,先创建第一个小方块,记录这个方块的位置,其余方块都根据这个方块的位置来随机上下左右生成

限制一下,第一个方块在最顶部,因为是Background的子物体,那么就是y为0,高度不超过三方块,于是y只能为0,1,-1,x的话,经尝试,x为-2.55和2.55正好到达屏幕两端,小方块大小是0.5


于是制作一个二维数组来记录位置

第二维代表y,大小为3(0=-0.5,1=0,2=0.5)

第一维代表x,大小为11,其中5代表中心为0(0=-2.5,1=-2,2=-1.5...5=0,6=0.5,7=1...10=2.5)


值为位置信息 Vector2,空代表这个位置没有方块。

这里需要注意下,Vector2默认值为 Vector2.zero


这样思路就清晰许多了:

从中心方块出发,随机在中心方块上下左右生成方块,如果有则换方向,没有则记录生成的位置,下次使用这个位置继续随机上下左右生成,如果上下左右都没有可生成的地方了,就又从中心方块出发随机..并且需要判断当前y位置的x方块是否大于一半(也就是6个)

开始编写代码,定义中心位置

/** 小方块数组中心位置 */
private const int xCenter = 5, yCenter = 1;

CreateRanBox 函数代码如下

public static GameObject CreateRanBox()
{
    // 随机小方块数量,随机颜色
    var ranNum = Random.Range(1, 16);
    var ranColor = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f));

    GameObject parentObj = new GameObject("方块集");

    // 记录已经使用过的位置
    Vector2[,] posMap = new Vector2[3,11];
    Vector2 centerPos = Vector2.zero;
    // 上一次位置
    int upX = -1, upY = -1;

    for (int i = 0; i < ranNum; i++)
    {
        GameObject obj = GameObject.Instantiate(box);

        var render = obj.GetComponent<Renderer>();
        render.material.color = ranColor;

        obj.transform.parent = parentObj.transform;

        // 记录第一个位置
        if (i == 0) {
            centerPos = new Vector2(Random.Range(-2.55f, 2.56f), 0);
            posMap[yCenter, xCenter] = centerPos;
            obj.transform.localPosition = centerPos;
        } else {
            if (upX == -1 || upY == -1)
            {
                upX = xCenter;
                upY = yCenter;
            }

            // 随机上下左右,0上,1下,2左,3右
            List<int> dirs = new List<int>();
            dirs.Add(0); dirs.Add(1); dirs.Add(2); dirs.Add(3);
            for (int j = 0; j < 4; j++)
            {
                int direction = dirs[Random.Range(0, dirs.Count)];
                // x减往左,加往右,y减往下,加往上
                switch (direction)
                {
                    case 0: {
                        int y = upY + 1;
                        if (y >= 3 || posMap[y, upX] != Vector2.zero)
                        {
                            dirs.Remove(direction);
                        }
                        else
                        {
                            // 1代表0.5大小,以数组中间点为界x=5,y=1,得出差距,然后*0.5得到实际位置
                            float realX = (upX - 5) * 0.5f, realY = (y - 1) * 0.5f;

                            posMap[y, upX] = new Vector2(centerPos.x + realX, centerPos.y + realY);
                            obj.transform.localPosition = posMap[y, upX];

                            upY = y;
                            goto posforEnd;
                        }
                        break;
                    }
                    case 1: {
                        int y = upY - 1;
                        if (y < 0 || posMap[y, upX] != Vector2.zero)
                        {
                            dirs.Remove(direction);
                        }
                        else
                        {
                            float realX = (upX - 5) * 0.5f, realY = (y - 1) * 0.5f;

                            posMap[y, upX] = new Vector2(centerPos.x + realX, centerPos.y + realY);
                            obj.transform.localPosition = posMap[y, upX];

                            upY = y;
                            goto posforEnd;
                        }
                        break;
                    }
                    case 2: {
                        int x = upX - 1;
                        if (x < 0 || posMap[upY, x] != Vector2.zero)
                        {
                            dirs.Remove(direction);
                        }
                        else
                        {
                            float realX = (x - 5) * 0.5f, realY = (upY - 1) * 0.5f;

                            // x位置是否超出宽度 -2.55和2.55
                            float resultX = centerPos.x + realX;
                            if (resultX < -2.55 || resultX > 2.55)
                            {
                                dirs.Remove(direction);
                                break;
                            }

                            posMap[upY, x] = new Vector2(resultX, centerPos.y + realY);
                            obj.transform.localPosition = posMap[upY, x];

                            upX = x;
                            goto posforEnd;
                        }
                        break;
                    }
                    case 3: {
                        int x = upX + 1;
                        if (x >= 11 || posMap[upY, x] != Vector2.zero)
                        {
                            dirs.Remove(direction);
                        }
                        else
                        {
                            float realX = (x - 5) * 0.5f, realY = (upY - 1) * 0.5f;

                            // x位置是否超出宽度 -2.55和2.55
                            float resultX = centerPos.x + realX;
                            if (resultX < -2.55 || resultX > 2.55)
                            {
                                dirs.Remove(direction);
                                break;
                            }

                            posMap[upY, x] = new Vector2(resultX, centerPos.y + realY);
                            obj.transform.localPosition = posMap[upY, x];

                            upX = x;
                            goto posforEnd;
                        }
                        break;
                    }
                }
            }

            // 如果没有合适方向的话从中心方向开始
            if (dirs.Count == 0)
            {
                // 直接销毁当前方块,初始化值,然后重新开始本次循环即可
                GameObject.Destroy(obj);
                // 避免死循环,直接少生成一个方块了
                // i--;
                upX = -1;
                upY = -1;
            }

            posforEnd:;
        }
    }

    return parentObj;
}

运行后效果如下

效果


虽然效果并不是很好,但是至少达到了

在调试的时候需要注意死循环等问题,开启vs的附加到Unity功能,死循环就停止,可以避免unity卡死导致只能任务管理器关闭重新打开的问题



让方块往下落

方块已经创建好了,接着让其往下落就可以了

运行后发现方块不是在最顶端,于是首先给拿到的物体设置一下位置


Background

var obj = State.CreateRanBox();
obj.transform.parent = transform;
// 有父对象则使用localPosition
obj.transform.localPosition = Vector2.zero;

因为是用作背景,往下落的话使用 Rigidbody 组件就可以了

Rigidbody,刚体组件,可以模拟物体受重力的影响


用代码给方块增加刚体组件,给速度设一个随机值

Rigidbody rigidbody = obj.AddComponent<Rigidbody>();
rigidbody.velocity = Vector3.down * Random.Range(0.1f,2f);


运行后可以看到方块往下掉落了

方块掉落



定时创建方块

接下来只需要定时创建方块然后让其往下落就实现背景了

直接放在 Update 函数是不可取的,一秒执行60次太多了,不需要那么多方块


使用 InvokeRepeating 函数可以在给定的时间间隔内重复调用该函数或代码

0-1.8秒之间随机生成一个方块好了,2秒后销毁,代码如下 (Background中)

void Start()
{
    InvokeRepeating("CreateRanBox", 0, Random.Range(0, 1.8f));
}

/** 创建方块,三秒后销毁 */
void CreateRanBox()
{
    if (State.isBackgroundPlay) {
        var obj = State.CreateRanBox();
        obj.transform.parent = transform;
        obj.transform.localPosition = Vector2.zero;

        Rigidbody rigidbody = obj.AddComponent<Rigidbody>();
        rigidbody.velocity = Vector3.down * Random.Range(0.1f, 2f);

        Destroy(obj, 2);
    }
}

运行后即可看到源源不断下落的方块了



制作界面

制作完背景后,就开始制作主页具体按钮了

但背景有点显眼,可以做一个全屏半透明遮罩


半透明遮罩

在层级面板右键 - UI - 图像,创建UI界面

UI界面


创建后会发现有一个很大很大的框框,缩放,看到一个小正方形,和一个风车一样的东西

ui

将小正方形拉满整个方框,然后把中间那个风车一样的东西(四个三角形)拉到四个角,这样就可以自适应了

在点击正方形,在右边找到Image属性,点击颜色,拖拽其中A部分(透明的),调整为想要的透明,遮罩就做好了

遮罩

可以给这个图片命个名称...




名称与按钮制作

游戏名称为:跳跳快乐方块

竖屏,于是直接将名称放到最上方就可以了

UI都放到Canvas里面,于是右键Canvas - UI - 文本

因为我是新版,选择的是 UI - 旧版 - 文本

调整一下位置,设置一下字体大小,样式,对齐,颜色,然后把那风车一样的四个三角形分期文本四个角

标题制作


这样,标题就制作完成了


开始制作按钮,在最开始设计的是有四个按钮

  • 开始
  • 商店
  • 历史分数
  • 关于游戏

同样的,层级面板 Canvas 右键 - UI - 按钮,就创建了一个按钮

同样,我使用的也是旧版的按钮

创建按钮会自动创建一个文本,在按钮的里面

按钮的文本


更改这个文本即可更改按钮的文本内容,更改为开始游戏,调整一下字体大小与按钮大小,调整一下位置,然后将四个三角形对准按钮的四个角就可以了

然后创建剩下的三个按钮

运行效果如下

首页效果


至此,首页制作完成



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

♥ 赞助 ♥

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