前排叠甲:不要用IMGUI(即时模式GUI)制造游戏!!!(此文纯属个人瞎揣摩+奥秘力气作用)

Unity官方文档如是说:

The IMGUI system is not generally intended to be used for normal in-game user interfaces that players might use and interact with.

IMGUI is a code-driven GUI system, and is mainly intended as a tool for programmers.

IMGUI是代码驱动的主要面向程序员的工具,一般不用于正常游戏内的玩家交互界面。与之相对应的,Unity有一套基于游戏对象的UI逻辑,此处就不赘述了。

叠甲完毕,正文开始。

IMGUI基础

要运用IMGUI,需求在C#脚本中承继MonoBehaviour类,并且编写OnGUI办法,就像这样:

public class Example : MonoBehaviour {
    void OnGUI() {
        if (GUILayout.Button("Press Me"))
            Debug.Log("Hello!");
    }
}

现在来学习一下:OnGUI办法会在游戏循环的烘托过程中,场景烘托之后被调用,它的作用是在正常的画面之上掩盖一层GUI,因而它是显现在画面最上层的,不必考虑被其他元素掩盖的问题。一起注意到OnGUI办法是在游戏循环中与烘托被一同调用的,因而每帧画面OnGUI都会被调用一次(这和Update办法是一样的)。

看到if语句里的GUILayout.Button了吗?它的作用是在屏幕上制作一个按钮,在IMGUI中的一切组件都是通过这样的方式进行制作的。括号内的字符串则是咱们要在按钮上显现的文字信息,而GUILayout.Button办法将会回来一个布尔值,它代表了组件的某种状态,比如当咱们按下鼠标时,Button办法将会回来true,从而进入if办法的内部逻辑。当然,除了Button还有其他的GUI组件任君挑选,这儿也不展开了,详见☛Unity官方文档(GUI)☚。

值得一提的是,由于每个循环OnGUI办法都会被调用一次,这意味着即使这个GUI组件只是一个局部变量,在它完结它的烘托使命,脱离OnGUI办法后就会被销毁并消失,但下一次调用OnGUI办法时又会在同样的位置创建出一个新的组件,在运用者的眼里,它仿佛就一直待在那里,正因如此,咱们不需求在类中显式地界说GUI组件成员

拓展:这儿运用的是GUILayout的Button办法,GUILayout的组件一种主动布局组件,它不需求咱们声明组件的制作位置,这关于需求快速简略的游戏内Debug界面的程序员来说是一个不错的解决方案,详见☛Unity官方文档(GUILayout)☚。

在写好C#脚本后。还需求将脚本挂载到某个场景物体上,让游戏能够调用它,这个物体能够随意挑选,一般挑选一个便利定位的物体会比较好。

好了,想必有经验的程序员到这儿已经明白怎样写出一款贪吃蛇游戏了吧~

游戏逻辑

状态图:

stateDiagram-v2
[*] --> Start
Start --> Init
Init --> GameLoop
GameLoop --> GameLoop
GameLoop --> Over
Paused --> GameLoop
GameLoop --> Paused
Over --> Init
Over --> [*]
  • Start状态需求完结如背景图片、蛇的图片等美术资源的加载
  • Init状态需求完结全局变量等游戏逻辑所依赖的变量的初始化
  • GameLoop状态则是游戏进行中
  • Paused状态下游戏暂停
  • Over状态游戏完毕

代码完成

代码完成这一部分属所以懂的不屑于看,看的都不懂(开玩笑的),这儿只提一下两个要害算法:

  • 蛇的移动
  • 果子生成

蛇的移动实际上指身体各个部位怎样移动,从物理的角度看,身体各个部分的移动方向只受其相邻“前面”的身体部位的移动方向的影响,详细而言有以下递推式(drct[i]表明第i截身体的移动方向):

drct[0] = 键盘输入方向

drct[i] = drct[i-1]

不需求多复杂的办法,直接模拟即可,需求运用结构体一起记载身体各部分的坐标以及方向:

struct body {
    int x, y, direction;
};

并且将一切的身体依照自始至终的顺序放在数组中,在每次移动时都自始至终遍历此数组,每次先对当前的一小截身体进行移动,然后将移动的方向向数组尾部传导:

void NextMove(body[] bodies, int bodyLength) {
    Move(bodies[0]);    //先移动头
    for(int i=1; i<bodyLength; i++) {
        Move(bodies[i]);
        bodies[i].direction = bodies[i-1].direction;
    }
}

果子生成实际上是要找到任意一个这样的位置:它上面没有任何的阻挡,且在咱们限制的场所范围内。这实际上十分容易完成,只需求在 [ 1, 场所行数场所列数 – 被占用的格子数 ] 范围内生成一个随机数R,然后依照某种顺序遍历整个场所,在统计到第R个空的格子时即可放置果子:

void GenerateFruit(int[,] gridState, int rows, int columns, ref int occupied) {
    int R = Random.Range(1, rows*columns - occupied);
    int i, j, count = 0;
    for(i=0; i<rows; i++) {
        for(j=0; j<columns; j++) {
            if(gridState[i][j] == 0)
                count++;
            if(count == R) {
                gridState[i, j] = 1;
                occupied++;
                break;
            }
        }
    }
}

最终提一嘴怎么完成每秒移动一次:咱们需求用到Unity提供的Time.deltaTime,它的值是上一帧到这一帧之间通过的时刻,咱们只需求一个静态成员累计通过的时刻,在累计时刻超越1s后将其减去1s并执行移动函数即可:

public class Example : MonoBehaviour {
    private static float totalTime = 0.0f;
    private static float waitTime = 1.0f;
    //...
    void OnGUI() {
        totalTime += Time.deltaTime;
        if (totalTime >= waitTime) {
            totalTime -= waitTime;
            //TODO
        }
        //...
    }
    //...
}

除了以上提到的这些,还能够加入积分等来丰富游戏内容。

作用预览

最终的作用(背景图运用Stable Diffusion生成):

在Unity上用IMGUI写个贪吃蛇

– fin –