前言
本文主要介绍了用Unity的即时设计GUI(IMGUI)来制作了一个仿《绝区零》推箱子的游戏的一个创作过程,制作这次的小游戏会用到C#,没学过编程的同学可能就不会太懂,但我希望能够尽可能减少代码部分的讲解,主要讲解设计过程中的思路和想法,代码我也会分享给大家。写这篇专栏,同样是基于分享目的,技术层面上我还有许多需要学习的地方。如果有需要改进的地方,也欢迎通过评论区或者私信反馈给我。
首先,先放游戏的展示视频:
游戏资源分享
GitHub:https://github.com/longmao20031028/GameDesign/tree/main/lab3
一、灵感
首先,我这边是有一个井字棋小游戏的参考代码的,我的目标是模仿(并超越)这个代码制作出一款小游戏或者应用。对于这个目标以及课程的提示下,我马上想到的就是制作一个计算器或者在此井字棋基础上做一个五子棋之类的,但这样就可能不太符合我个人的作风,于是我开始从另一个角度思考,为什么马上想到的就是五子棋和计算器?两者同样都布局简单且不需要太大的视图空间去实现。所以我就设定了一个5*5大小左右的范围,可能是和最近《绝区零》的游戏活动有关,我很快就想到了做走格子的游戏,又立马就想到了推箱子的玩法,更重要的是,容易找到现有的美术和音乐资源,也能使得简单的游戏显得更精致。经过我脑海中可行性的分析后,就开始制作这次的游戏。
二、游戏规则
玩家需要控制一个角色在一个二维的迷宫中将箱子推到指定的位置。以下是推箱子的基本游戏规则:
角色移动:玩家控制一个角色,通常可以向上、下、左、右四个方向移动。角色可以自由在空地上行走,但不能穿越墙壁或箱子。
推箱子:当角色站在箱子旁边时,角色可以向箱子所在的方向推动箱子。每次推只能推一个箱子,不能同时推多个箱子。如果箱子不能推动,那么玩家会和箱子交换位置。
目标点:关卡有两个目标点,玩家需要将箱子推到所有目标点上。
胜利条件:当所有箱子都被推到目标点时,玩家即通关成功。
三、游戏场景绘制
先新建一个3D模板项目
然后在界面左边右键新建一个3D对象Cube,然后把视角移到和摄像机以及Cube对齐。
改变Cube的大小,拉成一块背景板,在Assets窗口新建一个材质,在右边属性栏更改材质的颜色作为背景板的颜色,之后直接把这个材质拖到Cube上。这么做的原因是因为我觉得这样比较好看
最后我们新建一个文件夹,命名为Resources,用于存放我们需要会用到的素材(图片、音频等)。
四、MVC模式
本游戏的脚本代码会用到Model-View-Controller(模型-视图-控制器)模式进行编写。
简单来说,模型就是负责数据和业务逻辑的;视图主要负责数据的用户界面,不包含业务逻辑;控制器则负责接受用户的输入,通过调用模型和视图的方式去完成用户的请求。
游戏代码主要按照该模式分为三部分进行编写,但由于小游戏本身内容也不算很多,我就都写到同一个cs文件里面了。
在 GameModel 类中,处理了游戏的核心数据和逻辑,例如棋盘的状态、移动规则、检查游戏是否结束等。这部分不涉及视图或用户交互,仅仅管理和更新游戏的状态。
GameView 类负责显示游戏中的内容,包括绘制角色、箱子、墙壁和目标点。视图不包含任何游戏逻辑,只处理如何显示模型的状态,并在必要时播放音效。
剩下的Start()和OnGUI()函数则组成了控制器,处理用户输入并将操作应用于模型。它通过按钮响应玩家的移动操作,并调用模型的移动方法来更新游戏状态。如果模型发生了变化,则视图通过 Render 方法重新绘制新的状态。
具体的话可以参考这篇文章:
https://www.runoob.com/design-pattern/mvc-pattern.html
不理解也没关系,接下来我不会按照这个模型来讲解,专栏内容主要偏重于讲解制作过程中的思路。
五、游戏界面绘制
绘制游戏界面方面,主要用到了以下几行代码:
矩形:GUI.Box(new Rect(x, y, 长, 高), “显示的文本");
按钮:GUI.Button(new Rect(x, y, 长, 高), "显示的文本");
图片:GUI.DrawTexture(new Rect(x, y, 长, 高), Texture2D类型, 填充方式);
图片资源需要先通过
Texture2D
加载玩家、箱子、墙、目标点等图像资源
从演示视频看出,我把这个游戏界面主要分为:游戏窗口、棋盘、操作区。
其中游戏窗口比较凭感觉画,本质上也就一个矩形,不多赘述。
棋盘:
我们需要一个数组来存放棋盘中的内容,推箱子中的内容主要包括玩家、箱子、目标、障碍,我们约定,这些内容分别对应1、2、3、4存在于数组中,而0就代表空格子。然后再把数组中对应的内容通过绘制矩形或者图片的代码打印出来即可。不过,目标这个格子比较特殊,直接这么做的话可能在后面游戏的过程中出现一些问题,例如玩家经过这个格子会把该目标的数据覆盖掉,所以,我们还需要额外定义一些变量去存放目标的位置信息。
操作区:
操作区主要由几个按钮组成。通过 GUI.Button()
生成方向键按钮(上、下、左、右)以及提供一个"Restart"按钮,重置游戏状态。
当我们点击按钮后,程序将通过 Move()
方法执行移动逻辑:
如果下一个位置为空,玩家移动到该位置,原来的位置则变为空。
如果下一个位置有箱子,检查箱子前方的一个位置是否为空,如果为空则推动箱子,原来角色的位置变为空;如果是墙、箱子或障碍物,则与箱子交换位置。
如果前方是墙或障碍物,则不能移动。
六、获胜条件及获胜界面
作为一款推箱子的游戏,获胜的条件就是当所有目标点上都有箱子的时候,就判定玩家取得胜利。前文提到说目标点的位置信息是由额外定义的变量存储的,我们就对比下棋盘数组中的目标点上是不是箱子即可判定游戏是否达到获胜的条件。
当我们把所有的箱子推到目标点后,需要打印获胜界面,偷懒的话直接打印一行win就可以了。这里的话就模仿《绝区零》画了一个S,这里稍微偷懒了一下,用了绘制矩形再填色的方式,直接用图片的方式效果也许会更好。
主要的思路和之前画棋盘的方法差不多,只不过是用另外的数组来存放了S这个图案所对应的格子。在游戏获胜时绘制这个数组,否则就绘制另一个数组即可。
七、背景音乐以及音效
当我们做完这一切后,个人觉得有点太安静了,于是就加上了背景音乐和玩家移动时的音效。
背景音乐
我们回到unity界面,选中主摄像机,新建一个Audio Source 的组件,在AudioClip一栏选中自己想要添加的背景音乐即可。这里用的是游戏活动沙罗黄金周的背景音乐。
音效
我们可以在代码文件中添加一个 AudioSource和AudioClip字段,然后通过 AudioSource.PlayOneShot() 来播放音效。
八、运行流程
游戏启动时,Start() 方法初始化游戏模型和视图,并加载所有资源。
游戏渲染和用户交互逻辑在 OnGUI() 中调用执行:
首先,绘制游戏界面框架和按钮。
检查游戏是否结束,如果未结束则根据用户输入(按钮点击)移动玩家,并调用Render()更新视图。
如果玩家移动成功,播放移动音效。
当游戏胜利时,调用 WinRender() 显示胜利界面。
九、小结
总的来说,这个推箱子游戏大概就是这么实现的了,整体来说没有什么复杂的地方,只要懂得Unity的一些入门知识,再加上一点C系列的代码基础,就可以试着去制作一款小游戏了。可能是之前也写过像五子棋之类的游戏的原因吧,难度除了多花了一点时间之外,我觉得可能还没有安装Unity难度高...最后的话,如果文章出现了什么错误,或者不懂的地方,欢迎通过评论区或者私信反馈给我。