用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胜出:
打成平局: