Unity3d-简单井字棋
- 作业目的:熟悉IMGUI的使用,和基础的Unity3d操作
- 游戏玩法:选择两个模式,1.Player vs Player 2.Computer vs Player,当其中一种棋子连成三个则这个棋子的玩家获胜。
- 技术限制:仅允许使用IMGUI构成UI
游戏实现
首先搭建游戏菜单界面:
- 通过GUIStyle设置字体大小和颜色
使用GUI.Label创建文本,GUI.Label的第一个参数是Rect类型的位置,表示标签在屏幕上的矩形位置,Rect中的参数分别是:起点x坐标,起点y坐标,标签宽度,标签高度。第二个参数text类型是String,标签的内容,第三个参数style类型是GUIStyle,标签使用的样式。
使用GUI.Button来创建按钮,参数列表与GUI.Label类似,用if来判断Button是否被点击,若点击了则进入相应的游戏模式
GUIStyle fontStyle = new GUIStyle()
{
fontSize = 25
};
fontStyle.normal.textColor = new Color(255, 255, 255);
GUIStyle fontStyle1 = new GUIStyle()
{
fontSize = 30
};
fontStyle1.normal.textColor = new Color(255, 255, 255);
GUI.Label(new Rect(413, 50, 100, 50), "井字游戏", fontStyle1);
if(gamestate == GameState.end)
{
if (GUI.Button(new Rect(400, 200, 140, 50), "Player vs Player"))
{
gamestate = GameState.mode1;
isWin = false;
}
if (GUI.Button(new Rect(400, 280, 140, 50), "Player vs Computer"))
{
gamestate = GameState.mode2;
isWin = false;
}
}
- 不同游戏模式的游戏逻辑
- Player vs Player
- 使用循环建立3X3的棋盘,因为这段代码在OnGUI中,每一帧监控空白格子是否被按下,从而实现落子
- 陷阱:判断棋盘每一格的值从而建立Button的内容是X还是O,应该写在判断空白格子被点击前面,否则会造成看似已经落过子但是可以重新点击。(也可以在空白格子被点击的判断条件中加入判断棋盘board的值)
- Player vs Player
if(gamestate == GameState.mode1)
{
FixedUI(fontStyle);
bool full = true;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (board[i, j] == 1)
{
GUI.Button(new Rect(350 + i * 80, 100 + j * 80, 80, 80), "X");
}
else if(board[i, j] == 2)
{
GUI.Button(new Rect(350 + i * 80, 100 + j * 80, 80, 80), "O");
}
else
{
full = false;
}
//如果空白格子被点击
if (GUI.Button(new Rect(350 + i * 80, 100 + j * 80, 80, 80), ""))
{
if(!isWin)
{
if (click == 1) //X的回合
{
board[i, j] = 1; //棋盘下X
}
if (click == -1) //O的回合
{
board[i, j] = 2; //棋盘下O
}
click = -click;
}
}
}
}
- Compuert vs Player
- 检测AI是否有获胜的机会以及玩家是否有获胜的机会来判断落子,玩家回合逻辑与Player vs Player逻辑相同
- 我的AI逻辑是是否AI有两颗子连在一起,如果有则下一步下子让他们连成三个,若没有,则判断玩家是否有两颗子连在一起,如果有则下子堵住这两个,若没有,则随机落子。(因为前面两个逻辑是一样的,所以封装为同一函数,用mod来标记检测的是AI还是玩家)
- 检测AI是否有获胜的机会以及玩家是否有获胜的机会来判断落子,玩家回合逻辑与Player vs Player逻辑相同
bool CheckGo(int pos_x,int pos_y,int mod) //检查AI和玩家是否有获胜机会
{
int piecesNum = 0;
for (int i = 0; i < 3; i++)
{
piecesNum = 0;
bool empty = false;
for (int j = 0; j < 3; j++)
{
if (board[i, j] == mod)
{
piecesNum++;
}
else if (board[i, j] == 0)
{
pos_x = i;
pos_y = j;
empty = true;
}
}
if (piecesNum == 2 && empty && board[pos_x,pos_y] == 0)
{
board[pos_x, pos_y] = 1;
click = -click;
return true;
}
}
for (int i = 0; i < 3; i++)
{
piecesNum = 0;
bool empty = false;
for (int j = 0; j < 3; j++)
{
if (board[j, i] == mod)
{
piecesNum++;
}
else if (board[j, i] == 0)
{
pos_x = j;
pos_y = i;
empty = true;
}
}
if (piecesNum == 2 && empty && board[pos_x, pos_y] == 0)
{
board[pos_x, pos_y] = 1;
click = -click;
return true;
}
}
piecesNum = 0;
bool empty1 = false;
for (int i = 0; i < 3; i++)
{
if (board[i, i] == mod)
{
piecesNum++;
}
else if (board[i, i] == 0)
{
pos_x = i;
pos_y = i;
empty1 = true;
}
}
if (piecesNum == 2 &&empty1 && board[pos_x, pos_y] == 0)
{
board[pos_x, pos_y] = 1;
click = -click;
return true;
}
piecesNum = 0;
empty1 = false;
for (int i = 0; i < 3; i++)
{
int j = 2 - i;
if (board[i, j] == mod)
{
piecesNum++;
}
else if (board[i, j] == 0)
{
pos_x = i;
pos_y = j;
empty1 = true;
}
}
if (piecesNum == 2 && empty1 && board[pos_x, pos_y] == 0)
{
board[pos_x, pos_y] = 1;
click = -click;
return true;
}
return false;
}
- 在OnGUI中轮到AI的回合的代码
- 这里在判断是否AI和玩家取胜之后,如果都不满足则随机落子
- 陷阱:在这里遇到玩家落子的时候,Unity就卡住了,只能用任务管理器直接结束掉进程,而且不知道什么时候会出现这种情况,卡住后无法获得任何引起bug的信息。后来多次查找后发现是while循环的问题,因为随机查找空的格子,可能导致很长时间无法找到,所以增加了计数器,如果5次随机位置都是下过子的地方,则手动找到一个空的位置,让它落子。
if (click == 1 && !isWin) //AI的回合
{
int a = 1, b = 2, c = 0; //1代表检测X是否有取胜机会,2代表检测O是否有取胜机会
if (!CheckGo(c, c, a))
{
if (!CheckGo(c, c, b))
{
int pos_x, pos_y;
System.Random ran = new System.Random();
pos_x = ran.Next(0, 2);
pos_y = ran.Next(0, 2);
int count = 0;
while (board[pos_x, pos_y] != 0 && Check() == 0)
{
pos_x = ran.Next(0, 2);
pos_y = ran.Next(0, 2);
count++;
if(count == 5)
{
FindEmpty();
break;
}
}
if(count != 5)
{
board[pos_x, pos_y] = 1;
click = -click;
}
}
}
}
- 判断游戏结束和其他按键的搭建
void FixedUI(GUIStyle fontStyle) //固定不变的UI
{
int result = Check();
if (result == 1)
{
GUI.Label(new Rect(430, 350, 100, 50), "X wins!", fontStyle);
isWin = true;
}
else if (result == 2)
{
GUI.Label(new Rect(430, 350, 100, 50), "O wins!", fontStyle);
isWin = true;
}
if (GUI.Button(new Rect(420, 390, 100, 50), "Reset"))
{
Reset();
isWin = false;
}
if (GUI.Button(new Rect(420, 450, 100, 50), "Return"))
{
Reset();
gamestate = GameState.end;
isWin = false;
}
if (!isWin && click == 1)
{
GUI.Label(new Rect(430, 350, 100, 50), "X turn", fontStyle);
}
if (!isWin && click == -1)
{
GUI.Label(new Rect(430, 350, 100, 50), "O turn", fontStyle);
}
}
void Reset()
{
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
board[i, j] = 0; //每一个小格子都没有棋子
}
最后实现结果如图
- 背景由Plane组成,将相机调整到拍摄整个Plane的位置,将c#代码挂载到Plane上
完整代码见github地址:井字棋