用Unity3D实现简单的井字棋小游戏

用Unity3D实现简单的井字棋小游戏

项目地址

井字棋小游戏

完成效果图

请添加图片描述

实现思路

首先定义游戏的数据部分:

/* 井字棋中每一个棋格中的逻辑控制常量,代表这个棋格的状态 */
private const int NOPLAYER = 0;  // 0代表这个棋格没有玩家
private const int PLAYER1 = 1;  // 1代表玩家1占据这个棋格
private const int PLAYER2 = 2;  // 2代表玩家2占据这个棋格

/* 整个游戏需要用到的逻辑控制变量 */
private int gameTurn = PLAYER1;  // 游戏回合,PLAYER1代表这个游戏回合是玩家1的,PLAYER2代表是玩家2的回合
private int totalMoves = 0;  // 两个玩家总共进行的回合数
private int totalPlayer = 0;  // 游戏的玩家数,0代表还未选择游戏模式,1代表与电脑进行对决,2代表双人游戏
private int [,] chessBoard = new int [3, 3];  // 井字棋盘

/* 井字棋棋格的布局设置 */
private static int buttonWidth = 80;  // 每个棋格的宽度
private static int buttonHeight = 80;  // 每个棋格的高度
// 井字棋中最左上角第一个棋格的横坐标位置 
private static int firstButtonX = (Screen.width - 3 * TicTacToe.buttonWidth) / 2;
// 井字棋中最左上角第一个棋格的纵坐标位置 
private static int firstButtonY = Screen.height - 3 * TicTacToe.buttonHeight - 10;

/* 游戏界面设计用到的变量 */
public Texture2D backgroundImage;  // 游戏背景图片
public GUISkin gameSkin;  // 游戏控件的皮肤风格

接着确定游戏界面组成部分:

在这里插入图片描述

可以看到,游戏界面由四个部分组成:背景、标题、提示框、按钮,其中按钮又分为游戏模式选择按钮、棋格按钮、重置按钮三个部分,所以在最终显示场景的OnGUI()方法中,只有一个PlayGameSystem()方法,在PlayGameSystem()方法中,只有AddBackground()AddTitle()AddTip()AddButton()四个方法,分别添加背景、标题、提示框、按钮这四个部分。

AddBackground()AddTitle()这两个方法中,没有使用到什么复杂的逻辑,直接创建背景和标题即可:

/* 添加游戏背景 */
void AddBackground() {
    GUIStyle backgroundStyle = new GUIStyle();
    // 设置游戏背景图片
    backgroundStyle.normal.background = backgroundImage;
    GUI.Label(new Rect(0, 0, 710, 388), "", backgroundStyle);
}

/* 添加游戏标题 */
void AddTitle() {
    // 设置标题样式
    GUIStyle titleStyle = new GUIStyle();
    titleStyle.fontSize = 40;
    titleStyle.fontStyle = FontStyle.Bold;
    titleStyle.normal.textColor = Color.black;
    // 标题显示内容为TicTacToe Game
    GUI.Label(new Rect(Screen.width / 2 - 150, 20, 300, 50), "TicTacToe Game", titleStyle);
}

AddTip()这个方法中,除了设置皮肤风格外,还有一个GetTipText()方法,根据游戏的不同状态和是否有赢家,提示框的显示内容都在其中进行处理,方法代码如下:

/* 添加提示框 */
void AddTip() {
    // 选择Label的皮肤风格
    GUI.skin = gameSkin;
    // 根据是否有赢家获得提示框中的内容
    string text = GetTipText();
    // 绘制出提示框
    GUI.Label(new Rect(Screen.width / 2 - 320, 70, 650, 50), text);
}

/* 根据是否有赢家获得提示框中的内容 */
string GetTipText() {
    // 检查是否有赢家
    int winner = CheckWinner();
    switch (winner) {
        // 如果没有赢家
        case NOPLAYER:
            // 如果总玩家数为0,即还未选择游戏模式,那么提示选择游戏模式
            if (totalPlayer == 0) {
                return "Please choose a game mode.";
            } else if (totalMoves == 0) {  // 如果总回合数为0,说明还未开始游戏,提示点击棋格并进行游戏
                return "Click and play the game.";
            } else if (totalMoves == 9) {  // 如果总回合数为9,说明游戏结束且无玩家胜出,提示没有赢家
                return "No Winner!";
            } else {
                // 如果是单人模式且游戏正在进行,提醒正在进行单人游戏模式
                if (totalPlayer == 1) {
                    return "1 Player Mode Playing...";
                } else if (totalPlayer == 2) {  // 如果是双人模式且游戏正在进行,提醒正在进行双人游戏模式
                    return "2 Players Mode Playing...";
                }
                return "";
            }
        // 如果玩家1胜出
        case PLAYER1:
            // 提示框显示玩家1胜出
            return "Player1(O) Wins!";
        // 如果玩家2胜出
        case PLAYER2:
            // 提示框显示玩家2胜出
            return "Player2(X) Wins!";
        default:
            return "";
    }
}

检查是否有赢家,需要用到表示棋盘状态的一个二维数组chessBoard,声明如下:

private int [,] chessBoard = new int [3, 3];  // 井字棋盘

在这个用二维整型数组表示的棋盘中,0代表棋格没有玩家,1代表玩家1占据这个棋格,2代表玩家2占据这个棋格,用CheckWinner()方法对这个棋盘进行检查,可以知道是否存在赢家,代码如下(NOPLAYER代表0,PLAYER1代表1,PLAYER2代表2):

/* 检查是否有赢家 */
int CheckWinner() {
    // 一共有8种赢的情况,首先检查3行3列的6种赢的情况
    for (int i = 0; i < 3; ++i) {
        if (chessBoard[i, 0] != NOPLAYER && 
            chessBoard[i, 0] == chessBoard[i, 1] && 
            chessBoard[i, 1] == chessBoard[i, 2]) {
            // 有玩家胜出,那么游戏回合置为NOPLAYER,返回这个玩家对应的值,1代表玩家1,2代表玩家2
            gameTurn = NOPLAYER;
            return chessBoard[i, 0];
        }
        if (chessBoard[0, i] != NOPLAYER && 
            chessBoard[0, i] == chessBoard[1, i] && 
            chessBoard[1, i] == chessBoard[2, i]) {
            gameTurn = NOPLAYER;
            return chessBoard[0, i];
        }
    }
    // 检查对角线的2种赢的情况
    if (chessBoard[1, 1] != NOPLAYER) {
        if ((chessBoard[0, 0] == chessBoard[1, 1] && 
            chessBoard[1, 1] == chessBoard[2, 2]) || 
            (chessBoard[0, 2] == chessBoard[1, 1] && 
            chessBoard[1, 1] == chessBoard[2, 0])) {
            gameTurn = NOPLAYER;
            return chessBoard[1, 1];
        }
    }
    // 没人胜出,那么返回NOPLAYER,代表没有赢家
    return NOPLAYER;
}

根据游戏状态和赢家判断,就会在提示框显示相应的内容。

最后是AddButton()方法,里面处理设置按钮风格以外,由三个方法AddGameButton()AddResetButton()AddChooseGameModeButton()组成,分别是添加棋盘、添加重置按钮、添加游戏模式选择按钮:

/* 添加按钮实现的井字棋格和功能按钮 */
void AddButton() {
    // 选择按钮的皮肤风格
    GUI.skin = gameSkin;
    // 添加井字棋格
    AddGameButton();
    // 添加重置按钮
    AddResetButton();
    // 添加游戏模式选择按钮
    AddChooseGameModeButton();
}

AddResetButton()AddChooseGameModeButton()方法中,没有用到过于复杂的逻辑,只有用到一个InitGameWithTotalPlayer(int playersNum)的初始化游戏方法,代码如下:

/* 添加重置按钮 */
void AddResetButton() {
    GUIStyle resetStyle = new GUIStyle("button");
    resetStyle.fontSize = 20;
    // 按下重置按钮,游戏被初始化
    if (GUI.Button(new Rect(firstButtonX + 3 * buttonWidth + 50, Screen.height - 70, 80, 50), "Reset", resetStyle)) {
        InitGameWithTotalPlayer(totalPlayer);
    }
}

/* 添加游戏模式选择按钮 */
void AddChooseGameModeButton() {
    GUIStyle resetStyle = new GUIStyle("button");
    resetStyle.fontSize = 20;
    // 按下单人游戏模式按钮,游戏被初始化,游戏玩家人数变为1
    if (GUI.Button(new Rect(firstButtonX - 200, Screen.height - 2 * buttonHeight - 40, 180, 50), "1 Player Mode", resetStyle)) {
        InitGameWithTotalPlayer(1);
    }
    // 按下双人游戏模式按钮,游戏被初始化,游戏玩家人数变为2
    if (GUI.Button(new Rect(firstButtonX - 200, Screen.height - buttonHeight - 40, 180, 50), "2 Players Mode", resetStyle)) {
        InitGameWithTotalPlayer(2);
    }
}

/* 用游戏的玩家数初始化游戏 */
void InitGameWithTotalPlayer(int playersNum) {
    // 游戏回合从Player1开始
    gameTurn = PLAYER1;
    // 总进行回合数设为0
    totalMoves = 0;
    // 设置游戏的玩家数
    totalPlayer = playersNum;
    // 棋盘的每一格都还没被玩家占据
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            chessBoard[i, j] = NOPLAYER;
        }
    }
}

AddGameButton()方法中,按照坐标从左往右,从上往下设置棋格按钮的样式和功能,用到的是GetGameButtonText(int xIndex, int yIndex)SetGameButtonFunction(int xIndex, int yIndex)方法,这个方法根据游戏模式以及chessBoard中的棋格状态,设置按钮的功能和内容,代码如下:

/* 添加井字棋格 */
void AddGameButton() {
    // 按照坐标从左往右,从上往下设置棋格按钮的样式和功能
    for (int xIndex = 0; xIndex < 3; ++xIndex) {
        for (int yIndex = 0; yIndex < 3; ++yIndex) {
            // 按照横纵坐标找到棋格相应的位置
            int buttonX = firstButtonX + xIndex * buttonWidth;
            int buttonY = firstButtonY + yIndex * buttonHeight;
            string text = GetGameButtonText(xIndex, yIndex);
            if (GUI.Button(new Rect(buttonX, buttonY, buttonWidth, buttonHeight), text)) {
                SetGameButtonFunction(xIndex, yIndex);
            }
        }
    }
}

/* 根据横坐标和纵坐标获取棋盘相应位置从而获取井字棋格内容 */
string GetGameButtonText(int xIndex, int yIndex) {
    // 按照横纵坐标找到棋格相应的位置
    int buttonX = firstButtonX + xIndex * buttonWidth;
    int buttonY = firstButtonY + yIndex * buttonHeight;
    // 获取对应坐标中棋格的信息
    int Player = chessBoard[yIndex, xIndex];
    switch (Player) {
        // 如果这个棋格中为NOPLAYER
        case NOPLAYER:
            // 设置按钮中不显示任何内容
            return "";
        // 如果这个棋格中为PLAYER1
        case PLAYER1:
            // 棋格中显示内容为O
            return "O";
        // 如果这个棋格中为PLAYER2
        case PLAYER2:
            // 棋格中显示内容为X
            return "X";
        default:
            return "";
    }
}

/* 根据横坐标和纵坐标获取棋盘相应位置从而设置相应棋格功能 */
void SetGameButtonFunction(int xIndex, int yIndex) {
    int Player = chessBoard[yIndex, xIndex];
    switch (Player) {
        // 如果这个棋格中为NOPLAYER
        case NOPLAYER:
            // 如果还未选择游戏模式,那么进行提示,且点击按钮无反应
            if (totalPlayer == 0) {
                return;
            }
            // 选择游戏模式并点击棋格后,该棋格设置为这个游戏回合对应的玩家,游戏回合转换,总回合数加一
            chessBoard[yIndex, xIndex] = gameTurn;
            gameTurn = (gameTurn == PLAYER1 ? PLAYER2 : PLAYER1);
            totalMoves++;
            // 如果是单人游戏模式且游戏还未结束,那么电脑直接来走一步
            if (totalPlayer == 1 && totalMoves < 9 && CheckWinner() == NOPLAYER) {
                ComputerMove();
            }
            break;
        // 如果这个棋格中为PLAYER1,无反应
        case PLAYER1:
            break;
        // 如果这个棋格中为PLAYER2,无反应
        case PLAYER2:
            break;
    }
}

最后在PlayGameSystem()方法中添加背景、标题、提示框、游戏按钮,游戏就可以运行了,代码如下:

/* 进行游戏 */
void PlayGameSystem() {
    AddBackground();  // 添加游戏背景
    AddTitle();  // 添加游戏标题
    AddTip();  // 添加提示框
    AddButton();  // 添加游戏按钮
}

void OnGUI() {
    PlayGameSystem();
}

游戏截图:

玩家1胜出:

请添加图片描述

玩家2胜出:

请添加图片描述

打成平局:

请添加图片描述

  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在五花八门的游戏分类中,益智游戏是不可或缺的一大板块;而在益智游戏这个分类中,消除合成类游戏又是当之无愧的No.1。从考古级别的《俄罗斯方块》到现在风靡全国的《开心消消乐》,消除合成类游戏经久不衰,并且不断地涌现出新的经典作品,带给我们新的启发和乐趣。作为Unity3D的初级开发者,从头到尾独立完成一款简单的2D棋盘消除合成小游戏是你初级实战的最好选择。既能全面而详细的检验你的基础框架能力、C#编程能力、UGUI适配技巧,又可以在此基础上,较为深入的学习协程方法、DoTween插件和消除合成类的核心算法,一举数得。一言以蔽之,消除合成类游戏不难,但要写好却并不容易。它琐碎的功能逻辑、层出不穷的延时操作以及复杂的状态控制很容易让人顾此失彼,手忙脚乱,最后的结果往往是,费劲九牛二虎之力把功能实现好了,但代码已经乱成一团糟,定位Bug难、扩展新功能难,甚至自己读起来也难。本课程的最大目的,就是帮初级开发者梳理逻辑,写出规范而有条理的代码,避免陷入以上尴尬境地。条理捋清楚了,你就会觉得一切似乎变得简单了,很多地方都会顺理成章。本课程没有什么高深的编程技巧,更没有炫酷的视觉效果,只是一步一步教你如何踏踏实实、稳稳当当的完成一款《简单消消乐》。因此,资深大牛你就别进了,想学高深知识的你就别点了,自认为消除小case、分分钟搞定的高手你也趁早走人。在这门课程里,你能够学到的,只有以下几点:1、如何使用UGUI搭建棋盘游戏的基础阵列;2、如何快速的实现通用的三消逻辑;3、如何使用Dotween插件实现物体的位移、缩放、旋转动画;4、如何熟练的使用协程方法完成延时操作逻辑;5、数组、集合、引用类型、Prefab等基础知识的巩固;6、如何写出规范而有条理的代码;7、益智小游戏的通用模板。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值