1. 程式人生 > >Unity3d學習之路-簡單井字棋

Unity3d學習之路-簡單井字棋

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,標籤使用的樣式。
      picture

    • 使用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的值)
   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還是玩家)
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地址:井字棋