Unity3d學習之路-簡單井字棋
阿新 • • 發佈:2018-11-15
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地址:井字棋