该五子棋游戏基于 Unity 引擎开发,实现了 15x15 标准棋盘的 2D 对战功能,包含棋盘渲染、落子交互、胜负判定、悔棋和重新开始等核心功能。系统由两个主要脚本组成:
1. 加载棋盘背景图像,设置RectTransform尺寸为(cellSize*(boardSize-1))x(cellSize*(boardSize-1))
2. 生成水平和垂直网格线:
- 水平线:从顶部到底部,共boardSize条,每条线横跨棋盘
- 垂直线:从左到右,共boardSize条,每条线纵贯棋盘
- 坐标计算:以棋盘中心为原点,通过(cellSize * 索引 - 中心偏移量)确定线端点位置
3. 创建可交互单元格:
- 每个单元格绑定点击事件,映射二维坐标(row, col)
- 单元格位置与网格线对齐,形成15x15的交互网格
1. 初始化游戏状态:
- 棋盘数据gameBoard初始化为0(空),currentPlayer=1(黑棋先手)
- 重置时间、步数等统计数据,隐藏胜利面板
2. 落子交互流程:
- 点击单元格触发OnCellClicked,检查位置合法性(游戏进行中且空位)
- 更新棋盘数据,记录落子历史,生成对应颜色的棋子
- 执行胜利检测,若五连子则高亮获胜棋子并显示胜利面板
- 否则检查平局,若棋盘已满则显示平局面板
- 切换玩家回合,更新UI状态
3. 事件响应流程:
- 悔棋:回退最后一步,清空棋盘位置、销毁棋子、恢复玩家状态
- 重新开始:重置所有游戏数据,重新初始化棋盘
难点:Unity UI 坐标与棋盘逻辑坐标的映射(UI 以左上角为原点,棋盘逻辑以中心为原点)
解决方案:
// 逻辑坐标(row, col)转UI坐标
float xPos = col * cellSize - centerOffset;
float yPos = centerOffset - row * cellSize;
难点:如何快速检测四个方向(水平、垂直、对角线、反对角线)的五连子
解决方案:
难点:悔棋功能需要准确回退棋盘状态、棋子显示和玩家回合
解决方案:
难点:落子动画和胜利高亮需要流畅的视觉体验
解决方案:
using UnityEngine;
using UnityEngine.UI;
///
/// 负责在Unity中创建和配置2D棋盘及其网格线
///
public class Board2DSetup : MonoBehaviour
{
[Header("棋盘设置")]
public Image boardImage; // 棋盘背景图像组件
public Sprite boardSprite; // 棋盘背景精灵
public float cellSize = 40f; // 每个格子的大小(像素)
public int boardSize = 15; // 棋盘网格尺寸(行列数)
[Header("网格线设置")]
public LineRenderer gridLinePrefab; // 网格线预制体
void Start()
{
// 初始化棋盘
CreateBoard();
// 创建网格线
CreateGridLines();
}
///
/// 设置棋盘背景图像的大小和精灵
///
void CreateBoard()
{
// 获取棋盘图像的矩形变换组件
RectTransform rectTransform = boardImage.GetComponent
// 计算棋盘总尺寸(根据格子大小和数量)
float totalSize = cellSize * (boardSize - 1);
// 设置棋盘图像尺寸
rectTransform.sizeDelta = new Vector2(totalSize, totalSize);
// 应用棋盘精灵
boardImage.sprite = boardSprite;
}
///
/// 创建棋盘的水平和垂直网格线
///
void CreateGridLines()
{
// 计算棋盘中心偏移量
float centerOffset = (boardSize - 1) * cellSize / 2;
// 创建水平线
for (int i = 0; i < boardSize; i++)
{
// 实例化网格线预制体
LineRenderer line = Instantiate(gridLinePrefab, transform);
line.positionCount = 2; // 设置线的端点数量
// 计算当前水平线的Y坐标(从上到下)
float yPos = centerOffset - i * cellSize;
// 设置水平线的起点和终点
line.SetPosition(0, new Vector3(-centerOffset, yPos, 0));
line.SetPosition(1, new Vector3(centerOffset, yPos, 0));
}
// 创建垂直线
for (int i = 0; i < boardSize; i++)
{
// 实例化网格线预制体
LineRenderer line = Instantiate(gridLinePrefab, transform);
line.positionCount = 2; // 设置线的端点数量
// 计算当前垂直线的X坐标(从左到右)
float xPos = i * cellSize - centerOffset;
// 设置垂直线的起点和终点
line.SetPosition(0, new Vector3(xPos, centerOffset, 0));
line.SetPosition(1, new Vector3(xPos, -centerOffset, 0));
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
///
/// 五子棋游戏核心管理类,负责游戏逻辑、状态管理和UI交互
///
public class Gobang2DGameManager : MonoBehaviour
{
// 游戏常量定义
public const int BOARD_SIZE = 15; // 定义15x15的标准棋盘大小
public float cellSize = 40f; // 单个棋盘格子的物理尺寸(像素)
// 游戏状态数据
private int[,] gameBoard = new int[BOARD_SIZE, BOARD_SIZE]; // 棋盘数据存储(0=空,1=黑棋,2=白棋)
private int currentPlayer = 1; // 当前落子玩家(1=黑棋,2=白棋)
private bool gameActive = true; // 游戏是否正在进行
private List
private float gameTime = 0f; // 游戏持续时间计时
// UI组件引用
public RectTransform boardParent; // 棋盘父容器(用于定位单元格)
public GameObject cellPrefab; // 棋盘格子预制体
public TextMeshProUGUI statusText; // 游戏状态文本
public TextMeshProUGUI currentPlayerText; // 当前玩家文本
public TextMeshProUGUI timeText; // 游戏时间文本
public TextMeshProUGUI moveCountText; // 步数统计文本
public Button restartButton; // 重新开始按钮
public Button undoButton; // 悔棋按钮
public GameObject winPanel; // 胜利面板
public TextMeshProUGUI winnerText; // 获胜者文本
// 棋子预制体
public GameObject blackPiecePrefab; // 黑棋棋子预制体
public GameObject whitePiecePrefab; // 白棋棋子预制体
// 游戏对象数组
private GameObject[,] cells = new GameObject[BOARD_SIZE, BOARD_SIZE]; // 棋盘格子对象数组
private GameObject[,] pieces = new GameObject[BOARD_SIZE, BOARD_SIZE]; // 棋子对象数组
void Start()
{
// 游戏初始化
InitializeGame();
}
void Update()
{
// 游戏进行中时更新计时
if (gameActive)
{
gameTime += Time.deltaTime;
UpdateTimeDisplay();
}
}
///
/// 初始化游戏状态,包括棋盘、UI和玩家信息
///
void InitializeGame()
{
// 清空棋盘数据
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
gameBoard[i, j] = 0; // 0表示空位
// 销毁已存在的单元格和棋子
if (cells[i, j] != null) Destroy(cells[i, j]);
if (pieces[i, j] != null) Destroy(pieces[i, j]);
// 创建新的棋盘单元格
CreateCell(i, j);
}
}
// 重置游戏状态
currentPlayer = 1; // 黑棋先手
gameActive = true; // 游戏激活
moveHistory.Clear(); // 清空历史记录
gameTime = 0f; // 重置游戏时间
// 更新UI显示
UpdateGameStatus();
UpdateTimeDisplay();
// 隐藏胜利面板
winPanel.SetActive(false);
}
///
/// 创建单个棋盘单元格并设置交互事件
///
/// 单元格行索引
/// 单元格列索引
void CreateCell(int row, int col)
{
// 实例化单元格预制体
GameObject cell = Instantiate(cellPrefab, boardParent);
// 计算单元格在棋盘上的位置(居中布局)
Vector2 position = new Vector2(
col * cellSize - (BOARD_SIZE - 1) * cellSize / 2, // X坐标(从左到右)
(BOARD_SIZE - 1) * cellSize / 2 - row * cellSize // Y坐标(从上到下)
);
// 设置单元格位置
cell.GetComponent
cell.name = $"Cell_{row}_{col}"; // 命名便于调试
// 添加点击事件处理
Button cellButton = cell.GetComponent
// 存储单元格引用
cells[row, col] = cell;
}
///
/// 处理单元格点击事件,执行落子逻辑
///
/// 点击的行索引
/// 点击的列索引
void OnCellClicked(int row, int col)
{
// 检查游戏状态(是否进行中/格子是否已落子)
if (!gameActive || gameBoard[row, col] != 0) return;
// 执行落子操作
gameBoard[row, col] = currentPlayer; // 更新棋盘数据
moveHistory.Add(new Move { Row = row, Col = col, Player = currentPlayer }); // 记录落子历史
// 创建并显示棋子
CreatePiece(row, col, currentPlayer);
// 检查是否获胜
if (CheckWin(row, col, currentPlayer))
{
gameActive = false; // 游戏结束
statusText.text = $"{(currentPlayer == 1 ? "黑棋" : "白棋")}获胜!";
ShowWinPanel(currentPlayer); // 显示胜利面板
return;
}
// 检查是否平局
if (CheckDraw())
{
gameActive = false; // 游戏结束
statusText.text = "游戏结束 - 平局!";
ShowDrawPanel(); // 显示平局面板
return;
}
// 切换到下一玩家
currentPlayer = currentPlayer == 1 ? 2 : 1;
UpdateGameStatus(); // 更新UI状态
}
///
/// 创建棋子并添加动画效果
///
/// 棋子行位置
/// 棋子列位置
/// 玩家标识(1/2)
void CreatePiece(int row, int col, int player)
{
// 根据玩家选择棋子预制体
GameObject piecePrefab = player == 1 ? blackPiecePrefab : whitePiecePrefab;
GameObject piece = Instantiate(piecePrefab, boardParent);
// 计算棋子位置(与单元格位置一致)
Vector2 position = new Vector2(
col * cellSize - (BOARD_SIZE - 1) * cellSize / 2,
(BOARD_SIZE - 1) * cellSize / 2 - row * cellSize
);
// 设置棋子位置
piece.GetComponent
piece.name = $"{(player == 1 ? "Black" : "White")}Piece_{row}_{col}";
// 添加缩放动画(从0到1)
piece.transform.localScale = Vector3.zero;
StartCoroutine(ScalePiece(piece));
// 存储棋子引用
pieces[row, col] = piece;
}
///
/// 棋子缩放动画协程
///
/// 要动画的棋子对象
IEnumerator ScalePiece(GameObject piece)
{
float duration = 0.3f; // 动画持续时间(秒)
float elapsed = 0f; // 已用时间
// 动画过程
while (elapsed < duration)
{
float t = elapsed / duration; // 动画进度(0-1)
piece.transform.localScale = Vector3.Lerp(Vector3.zero, Vector3.one, t); // 线性插值缩放
elapsed += Time.deltaTime;
yield return null;
}
// 确保最终状态
piece.transform.localScale = Vector3.one;
}
///
/// 检查指定位置落子后是否获胜
///
/// 落子行
/// 落子列
/// 落子玩家
///
bool CheckWin(int row, int col, int player)
{
// 定义四个检查方向(水平/垂直/对角线/反对角线)
int[,] directions = {
{1, 0}, // 水平向右
{0, 1}, // 垂直向下
{1, 1}, // 对角线右下
{1, -1} // 反对角线左下
};
// 遍历四个方向检查
for (int i = 0; i < 4; i++)
{
int dx = directions[i, 0]; // X方向增量
int dy = directions[i, 1]; // Y方向增量
int count = 1; // 当前位置已有一个棋子,计数从1开始
// 正向检查(沿方向延伸)
for (int j = 1; j < 5; j++)
{
int newRow = row + j * dy;
int newCol = col + j * dx;
// 检查边界
if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE)
break;
// 同色棋子则计数增加
if (gameBoard[newRow, newCol] == player)
count++;
else
break;
}
// 反向检查(沿反方向延伸)
for (int j = 1; j < 5; j++)
{
int newRow = row - j * dy;
int newCol = col - j * dx;
// 检查边界
if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE)
break;
// 同色棋子则计数增加
if (gameBoard[newRow, newCol] == player)
count++;
else
break;
}
// 五连子则获胜
if (count >= 5)
{
// 高亮显示获胜的棋子序列
HighlightWinningPieces(row, col, dx, dy, player);
return true;
}
}
return false;
}
///
/// 高亮显示获胜的棋子序列
///
/// 起始行
/// 起始列
/// X方向增量
/// Y方向增量
/// 获胜玩家
void HighlightWinningPieces(int row, int col, int dx, int dy, int player)
{
// 高亮当前落子位置
HighlightPiece(row, col);
// 正向高亮
for (int j = 1; j < 5; j++)
{
int newRow = row + j * dy;
int newCol = col + j * dx;
// 检查边界
if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE)
break;
// 同色棋子则高亮
if (gameBoard[newRow, newCol] == player)
HighlightPiece(newRow, newCol);
else
break;
}
// 反向高亮
for (int j = 1; j < 5; j++)
{
int newRow = row - j * dy;
int newCol = col - j * dx;
// 检查边界
if (newRow < 0 || newRow >= BOARD_SIZE || newCol < 0 || newCol >= BOARD_SIZE)
break;
// 同色棋子则高亮
if (gameBoard[newRow, newCol] == player)
HighlightPiece(newRow, newCol);
else
break;
}
}
///
/// 为单个棋子添加高亮效果
///
/// 棋子行
/// 棋子列
void HighlightPiece(int row, int col)
{
if (pieces[row, col] != null)
{
// 获取棋子图像组件并设置高亮颜色
Image pieceImage = pieces[row, col].GetComponent
if (pieceImage != null)
{
pieceImage.color = Color.yellow; // 黄色高亮
}
}
}
///
/// 检查游戏是否平局(棋盘已满)
///
///
bool CheckDraw()
{
// 遍历棋盘检查是否有空位
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
if (gameBoard[i, j] == 0)
return false; // 存在空位,不是平局
}
}
return true; // 棋盘已满,平局
}
///
/// 更新游戏状态UI显示
///
void UpdateGameStatus()
{
if (gameActive)
{
// 显示当前玩家回合
statusText.text = $"游戏进行中 - {(currentPlayer == 1 ? "黑棋" : "白棋")}回合";
currentPlayerText.text = $"当前回合: {(currentPlayer == 1 ? "黑棋" : "白棋")}";
}
// 显示步数统计
moveCountText.text = $"步数: {moveHistory.Count}";
}
///
/// 更新游戏时间显示
///
void UpdateTimeDisplay()
{
// 计算分钟和秒数
int minutes = Mathf.FloorToInt(gameTime / 60);
int seconds = Mathf.FloorToInt(gameTime % 60);
// 格式化为00:00格式
timeText.text = $"游戏时间: {minutes:D2}:{seconds:D2}";
}
///
/// 显示胜利面板
///
/// 获胜玩家
void ShowWinPanel(int winner)
{
winnerText.text = $"{(winner == 1 ? "黑棋" : "白棋")}获胜!";
winPanel.SetActive(true); // 激活胜利面板
}
///
/// 显示平局面板
///
void ShowDrawPanel()
{
winnerText.text = "游戏结束 - 平局!";
winPanel.SetActive(true); // 激活平局面板
}
///
/// 重新开始按钮点击事件处理
///
public void OnRestartButtonClicked()
{
InitializeGame(); // 重新初始化游戏
}
///
/// 悔棋按钮点击事件处理
///
public void OnUndoButtonClicked()
{
// 检查是否可以悔棋(有历史记录且游戏进行中)
if (moveHistory.Count == 0 || !gameActive)
{
Debug.Log("无法悔棋!");
return;
}
// 获取最后一步记录
Move lastMove = moveHistory[moveHistory.Count - 1];
gameBoard[lastMove.Row, lastMove.Col] = 0; // 清空棋盘位置
// 销毁最后放置的棋子
if (pieces[lastMove.Row, lastMove.Col] != null)
{
Destroy(pieces[lastMove.Row, lastMove.Col]);
pieces[lastMove.Row, lastMove.Col] = null;
}
// 移除历史记录
moveHistory.RemoveAt(moveHistory.Count - 1);
// 回到上一个玩家
currentPlayer = lastMove.Player;
// 更新UI状态
UpdateGameStatus();
}
}
///
/// 落子记录类,用于存储落子历史
///
public class Move
{
public int Row { get; set; } // 落子行索引
public int Col { get; set; } // 落子列索引
public int Player { get; set; } // 落子玩家(1/2)
}