这是3D游戏编程课程的第一次作业,主要是为了我们熟悉OnGUI事件,并提升我们的API文档阅读能力。
作业展示视频链接:点我直达
因此,在开始正文之前,先贴一下官方API手册中的IMGUI部分的链接~点我直达
仔细阅读API文档之后,我可以了解到,Unity3D中的IMGUI的控制和传统的界面控制有所不同,我平时更加适应的是Listener模式,即对UI事件进行监听,监听到事件后进行操作,改变UI界面。但是Unity3D中,界面是不断刷新的,因此,想要改变界面,就应该在设计界面的时候,在相应的地方使用变量,实际操作时,改变变量的值即可改变界面的显示。
因此,我在项目中,设计了3个变量。
private int[] chessState; //存储棋盘上每个棋子位的状态,空为0,下了X为1,下了O为-1
private int onTurn; //存储接下来落子的顺序 首先是X下,为1,O为-1
private int info; //存储游戏状态信息,等待开始状态为0,对弈状态为1,有一方获胜为2,平局为3
并在Start()函数中进行初始化。
private void Start()
{
chessState = new int[9]; //对三个变量进行初始化
onTurn = 1;
info = 0;
}
接下来,要做的是设计棋盘,首先是选择布局方式,官方提供给我们的一共有两种布局方式,手动布局和自动布局,由于我不想输一堆乱七八糟的数字,所以我选择了自动布局的方式。自动布局一共分了五行三列。在OnGUI()函数中。代码如下:
private void OnGUI() //采用自动布局方式,分为3列五行,居中显示
{
GUILayout.BeginArea(new Rect(Screen.width/2-75, Screen.height/2-125, 150, 250));
GUILayout.BeginVertical();
GUILayout.BeginHorizontal();
InitChess(0);
InitChess(1);
InitChess(2);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
InitChess(3);
InitChess(4);
InitChess(5);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
InitChess(6);
InitChess(7);
InitChess(8);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label(InfoMap(info));
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("ReStart"))
{
ResetGame();
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.EndArea();
CheckWin();
}
OnGUI函数中,要做的主要就是以上几件事情。首先,利用GUILayout的BeginArea,BeginVertical,BeginHorizontal函数进行布局,其次,根据InitChess函数装载显示棋盘和更改棋盘,利用Lable标签进行游戏信息的显示,设置ReStart按钮帮助用户重新开始游戏。设置检查对局状态的函数CheckWin()。关于函数的命名,有一点挺有意思的,VS会提示我函数名的命名首字母要大写,这和别的语言不太一样。
接下来我一个一个介绍这些函数。首先是InitChess();
private void InitChess(int x) //用于生成棋盘与棋子的方法
{
if (GUILayout.Button(ChessMap(chessState[x])))
{
if (chessState[x] == 0 && info < 2)
{
chessState[x] = onTurn;
onTurn = -onTurn;
}
}
}
输入的参数是棋盘格子的序号,一共九个格子,从左往右从上到下依次编号,创建格子时,要对其状态进行判断,这个格子上是空的,是放了“X”棋子,还是放了“O”棋子,这些信息被存储在chessState这个变量中。当按钮被点击时,需要判断这个按钮上是不是已经有棋子了,还有现在是不是已经有人获胜了,如果是的话,就不需要操作,只有在该格子没有棋子且两人还在对弈状态时,才可以下子。下子后,要更改chessState变量中该格子的状态,并将onTurn中存储的信息改变。另外,为了方便显示棋子,我还写了一个工具函数,用于将棋子状态转换为具体显示的信息。该工具函数如下:
private string ChessMap(int x) //工具函数,将chessState内的值转化为显示的棋子
{
switch (x)
{
case 0: return "";
case 1: return "X";
case -1: return "O";
default: return "wrong";
}
}
然后是设置显示游戏信息的标签
GUILayout.Label(InfoMap(info));
同样,为了方便显示,我又写了一个工具函数,如下:
private string InfoMap(int x) //工具函数,将info的值转化为具体的游戏信息与提示
{
switch (x)
{
case 0: return "Prapre to start the game!";
case 1: return "It's " + ChessMap(onTurn) + "'s turn.";
case 2: return ChessMap(-onTurn) + " win!";
case 3: return "You are about the same!";
default: return "wrong!";
}
}
这里需要注意一点,就是自动布局和手动布局中空间的创建方式是不一样的,在手动布局中,是GUI.Lable,而在自动布局中,是GUILayout.Lable。
接下来是ReStart按钮,用于重置游戏,其实本质就是重置一开始设置的变量,并没有啥技术含量。
private void ResetGame() //初始化游戏
{
for (int i = 0; i < 9; i++)
{
chessState[i] = 0;
onTurn = 1;
info = 1;
}
}
最后是一个检查是否获胜或者平局的CheckWin函数。共有八种获胜的方式,这里采用穷举法,而对于平局的判断,则是判断在没有获胜的情况下,棋盘是否已经被下满。
private void CheckWin() //检查是否有一方获胜或者平局,通过穷举八种或者的情况判断获胜,在未获胜的情况下棋子下满即为平局。
{
if (Math.Abs(chessState[0]+chessState[1]+chessState[2])==3 ||
Math.Abs(chessState[3] + chessState[4] + chessState[5]) == 3 ||
Math.Abs(chessState[6] + chessState[7] + chessState[8]) == 3 ||
Math.Abs(chessState[0] + chessState[3] + chessState[6]) == 3 ||
Math.Abs(chessState[1] + chessState[4] + chessState[7]) == 3 ||
Math.Abs(chessState[2] + chessState[5] + chessState[8]) == 3 ||
Math.Abs(chessState[0] + chessState[4] + chessState[8]) == 3 ||
Math.Abs(chessState[2] + chessState[4] + chessState[6]) == 3 )
{
info = 2;
}
else
{
bool draw = true;
for (int i = 0; i < 9; i++)
{
if (chessState[i] == 0)
{
draw = false;
break;
}
}
if (draw) info = 3;
}
}