这次我们来看一个完整的 Unity 2D 游戏开发实战项目:从零开始构建一个飞行棋游戏。对于想入门 Unity 2D 开发,或者想通过一个具体项目来串联 C# 脚本、UI 交互、游戏逻辑和状态管理的开发者来说,飞行棋是一个绝佳的练手项目。它涵盖了游戏开发的核心流程,从场景搭建、角色控制到规则实现,每一步都有明确的技术点。
这个项目的重点不是概念有多复杂,而是如何将零散的知识点整合成一个可运行、可扩展的完整游戏。你会接触到 Unity 编辑器的基础操作、Sprite 和 UI 系统的使用、C# 脚本的编写与调试,以及如何设计一个清晰的游戏状态机。整个过程对硬件几乎没有特殊要求,一台能流畅运行 Unity 编辑器的电脑即可,主要考验的是逻辑思维和代码组织能力。
本文将以“上篇”的形式,带你完成飞行棋项目的基础框架搭建和核心玩法实现。我们会从创建项目、导入资源开始,一步步实现棋盘生成、棋子移动、回合制逻辑以及基础的 UI 交互。读完本文,你将掌握一个可运行的飞行棋 Demo,并理解其背后的代码结构,为后续添加更多功能(如动画、音效、网络对战等)打下坚实基础。
1. 核心能力速览
在深入代码之前,我们先快速了解通过这个实战项目你将获得哪些核心能力。
| 能力项 | 说明 |
|---|---|
| 项目类型 | Unity 2D 游戏开发实战教程 |
| 技术栈 | Unity Engine (2021/2022 LTS 版本)、C# 编程语言、UGUI 界面系统 |
| 硬件门槛 | 极低。主要依赖 CPU 和内存,集成显卡即可流畅进行开发。Unity 编辑器本身对硬件有要求,但飞行棋项目资源轻量。 |
| 核心功能实现 | 1. 2D 棋盘动态生成与渲染 2. 棋子移动逻辑与路径规划 3. 回合制游戏状态管理 4. 骰子随机数生成与交互 5. 基础 UI 系统(开始界面、回合提示、胜负判定) |
| 启动与运行 | 在 Unity 编辑器中直接点击 Play 按钮运行,最终可打包为 PC、WebGL 或移动端应用。 |
| 代码组织 | 采用面向对象设计,模块化分离(如 GameManager, Dice, Player, Piece),便于理解和扩展。 |
| 适合场景 | Unity 与 C# 初学者练手、巩固 2D 游戏开发核心概念、毕业设计或小型游戏开发。 |
2. 适用场景与使用边界
这个飞行棋项目主要服务于以下几类开发者:
- Unity 新手:如果你已经看过一些 Unity 和 C# 的教程,但不知道如何将知识点串联起来做一个完整的游戏,这个项目提供了清晰的路径。
- 在校学生:项目结构完整,代码注释清晰,非常适合作为课程设计或毕业设计的蓝本,你可以在此基础上增加自己的创意。
- 转行或技能提升者:想进入游戏行业或丰富个人作品集,一个从零到一的可运行项目是非常有说服力的证明。
- 兴趣开发者:单纯想用 Unity 复刻一个童年经典游戏,体验游戏开发的乐趣。
使用边界与注意事项:
- 教学性质:本项目侧重于教学和原理实现,美术资源(棋子、棋盘贴图)可能比较简单,商业级项目需要更精美的美术资源和更完善的异常处理。
- 规则简化:为了聚焦核心开发流程,可能会对传统飞行棋规则(如迭子、跳子、飞回基地等)进行一定程度的简化或分阶段实现。高级规则可以在“下篇”或自行扩展。
- 版权与原创:项目中使用的代码和逻辑思路可以自由学习与修改。但如果使用第三方美术、音效资源,请确保你拥有相应的版权或使用许可,特别是计划公开发布或商用的情况。
- 性能:作为 2D 项目,性能压力很小。但代码编写方式(如 Update 中的频繁查找、不必要的实例化)会影响效率,良好的编程习惯需要从一开始就培养。
3. 环境准备与前置条件
开始之前,请确保你的开发环境已经就绪。
1. 操作系统
- Windows 10/11 64位
- macOS 10.14+
- Linux (Ubuntu 等,需查看 Unity 官方支持情况)
2. Unity Hub 与 Unity 编辑器
- 必需:安装 Unity Hub 。
- Unity 版本:推荐使用2021.3 LTS或2022.3 LTS等长期支持版本。它们稳定且社区资源丰富。在 Unity Hub 中“安装”页面添加对应版本。
- 安装模块:在安装 Unity 编辑器时,确保勾选“Windows Build Support”(或对应平台的构建支持) 和“WebGL Build Support”(可选)。对于 2D 开发,默认模块已足够。
3. 代码编辑器
- Visual Studio 2022(Windows):安装 Unity 时通常会捆绑安装,或使用 Community 版本。
- Visual Studio Code:轻量级选择,需安装 C# 扩展和 Unity 扩展。
- Rider:强大的第三方 IDE,对 Unity 和 C# 支持极佳(付费,有学生许可)。
4. 基础概念准备
- 了解 C# 基础:变量、函数、类、循环、条件判断。
- 了解 Unity 基础:GameObject, Component, Transform, Sprite Renderer, Canvas 等概念。
- 硬件:8GB 及以上内存,剩余磁盘空间 10GB 以上(用于安装 Unity 和项目)。
4. 项目创建与资源准备
现在,我们开始创建飞行棋项目。
步骤 1:创建新项目
- 打开 Unity Hub,点击“新建项目”。
- 选择“2D”核心模板。
- 设置项目名称,例如
FlightChess2D。 - 选择项目保存路径。
- 点击“创建项目”。Unity 会自动打开新项目。
步骤 2:规划项目文件夹结构清晰的文件夹结构是项目可维护性的关键。在 Project 窗口的 Assets 文件夹下,创建以下子文件夹:
Assets/ ├── Scripts/ # 存放所有 C# 脚本 ├── Sprites/ # 存放棋盘、棋子、骰子等图片资源 ├── Prefabs/ # 存放预制体(棋子、格子等) ├── Scenes/ # 存放游戏场景 ├── UI/ # 存放 UI 相关的图片、字体等 └── Settings/ # 存放脚本化对象等配置(可选)步骤 3:准备美术资源(或使用占位符)你可以从免费资源网站(如 Kenney.nl, OpenGameArt.org)寻找简单的棋盘和棋子素材,或者直接使用 Unity 自带的 2D Sprite 形状(如 Square, Circle)作为占位符。
- 将棋盘背景图导入
Assets/Sprites。 - 准备四种颜色的棋子 Sprite(红、黄、蓝、绿),每种颜色4个。
- 准备一个骰子 Sprite,或者用6个数字图片表示骰子面。
步骤 4:创建游戏场景
- 在
Assets/Scenes文件夹右键,选择Create -> Scene,命名为MainGame。 - 双击打开
MainGame场景。 - 删除场景中默认的
SampleScene目录(如果存在),并确保主摄像机为Orthographic(正交)模式,这是 2D 游戏的标准。
5. 构建游戏核心管理器 (GameManager)
GameManager 是游戏的大脑,负责管理游戏状态、回合切换、胜负判定等全局逻辑。
步骤 1:创建 GameManager 脚本
- 在
Assets/Scripts文件夹右键,选择Create -> C# Script,命名为GameManager。 - 双击用代码编辑器打开,编写核心逻辑。
using UnityEngine; using UnityEngine.UI; // 引入UI命名空间 public class GameManager : MonoBehaviour { // 单例模式,方便其他脚本访问 public static GameManager Instance { get; private set; } // 游戏状态枚举 public enum GameState { Waiting, Rolling, Moving, GameOver } public GameState CurrentState { get; private set; } // 玩家相关 public int currentPlayerIndex = 0; // 当前行动玩家索引 (0-3) public Color[] playerColors; // 玩家颜色,用于UI显示 public string[] playerNames = { "红方", "黄方", "蓝方", "绿方" }; // UI 引用 public Text currentPlayerText; public Button rollDiceButton; public Text diceResultText; // 骰子结果 private int currentDiceValue = 0; private void Awake() { // 实现单例 if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); // 跨场景不销毁 } else { Destroy(gameObject); } } private void Start() { // 初始化游戏状态 CurrentState = GameState.Waiting; UpdateUI(); } // 更新UI显示 void UpdateUI() { if (currentPlayerText != null) currentPlayerText.text = $"当前回合: {playerNames[currentPlayerIndex]}"; if (diceResultText != null) diceResultText.text = $"骰子: {currentDiceValue}"; // 根据状态控制按钮 rollDiceButton.interactable = (CurrentState == GameState.Waiting); } // 点击掷骰子按钮时调用 public void OnRollDiceButtonClicked() { if (CurrentState != GameState.Waiting) return; CurrentState = GameState.Rolling; RollDice(); } // 模拟掷骰子 void RollDice() { currentDiceValue = Random.Range(1, 7); // 生成1-6的随机数 Debug.Log($"{playerNames[currentPlayerIndex]} 掷出了 {currentDiceValue}"); CurrentState = GameState.Moving; UpdateUI(); // 通知当前玩家的棋子可以开始移动 // 这里需要调用棋子移动逻辑,我们稍后实现 // PieceManager.Instance.StartMove(currentPlayerIndex, currentDiceValue); } // 当一个玩家的移动结束后调用 public void OnPlayerMoveFinished() { // 切换到下一个玩家 currentPlayerIndex = (currentPlayerIndex + 1) % 4; CurrentState = GameState.Waiting; currentDiceValue = 0; UpdateUI(); // 这里可以添加一些额外的回合结束逻辑,如检查胜负 CheckWinCondition(); } void CheckWinCondition() { // 胜负判断逻辑(简化版:当有玩家所有棋子到达终点) // 具体实现需要与棋子状态联动 // if (somePlayerAllPiecesFinished) { CurrentState = GameState.GameOver; } } }步骤 2:创建 GameManager 游戏对象
- 在 Hierarchy 窗口右键,选择
Create Empty,重命名为GameManager。 - 选中
GameManager对象,在 Inspector 窗口点击Add Component,搜索并添加刚创建的GameManager脚本。 - 在 Inspector 中,将
Player Colors数组大小设为4,并分配红、黄、蓝、绿四种颜色。 - 我们稍后创建UI后,再把对应的
Text和Button组件拖拽赋值。
6. 设计棋盘与格子系统
飞行棋的棋盘由一系列格子组成,棋子沿着格子路径移动。
步骤 1:创建棋盘预制体
- 在
Assets/Sprites中导入或创建棋盘背景图。在 Hierarchy 中右键2D Object -> Sprite,将背景图拖入,调整大小覆盖摄像机视野。重命名为Board。 - 创建格子预制体:
- 在 Hierarchy 中右键
Create Empty,重命名为GridCell。 - 为其添加
Sprite Renderer组件,选择一个简单的方形 Sprite(如白色小方块),调整颜色和透明度,使其在编辑器中可见,游戏运行时可以隐藏。 - 添加一个
Box Collider 2D(用于后续点击检测,可选)。 - 将
GridCell拖入Assets/Prefabs文件夹,生成预制体。然后删除 Hierarchy 中的实例。
- 在 Hierarchy 中右键
步骤 2:创建棋盘管理器脚本 (BoardManager)这个脚本负责在运行时根据设计好的路径,动态生成或配置棋盘格子。
using System.Collections.Generic; using UnityEngine; public class BoardManager : MonoBehaviour { public static BoardManager Instance { get; private set; } // 格子预制体 public GameObject gridCellPrefab; // 存储所有格子的列表,索引即位置编号 public List<Transform> gridPositions = new List<Transform>(); // 棋盘路径数据(简化版,共52个常规格+4个基地起点+4个终点路径) // 这是一个示例路径,你需要根据实际飞行棋棋盘布局来定义坐标 public Vector2[] pathPositions; private void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } } void Start() { GenerateBoard(); } void GenerateBoard() { if (gridCellPrefab == null || pathPositions.Length == 0) { Debug.LogError("BoardManager: 预制体或路径数据未设置!"); return; } gridPositions.Clear(); for (int i = 0; i < pathPositions.Length; i++) { GameObject cell = Instantiate(gridCellPrefab, this.transform); cell.transform.position = pathPositions[i]; cell.name = $"Grid_{i}"; // 可以在这里根据格子类型(起点、终点、安全格等)设置不同的颜色或Sprite gridPositions.Add(cell.transform); } Debug.Log($"棋盘生成完毕,共 {gridPositions.Count} 个格子。"); } // 根据格子索引获取世界坐标 public Vector3 GetGridWorldPosition(int gridIndex) { if (gridIndex >= 0 && gridIndex < gridPositions.Count) { return gridPositions[gridIndex].position; } return Vector3.zero; } // 获取下一个格子索引(考虑路径循环) public int GetNextGridIndex(int currentIndex, int step) { int nextIndex = currentIndex + step; // 简化处理:如果超出最大索引,则循环(实际飞行棋规则更复杂,可能涉及终点判断) if (nextIndex >= pathPositions.Length) { nextIndex = nextIndex % pathPositions.Length; } return nextIndex; } }步骤 3:配置棋盘路径
- 在 Hierarchy 中创建一个空对象,命名为
BoardManager,挂载BoardManager脚本。 - 将之前创建的
GridCell预制体拖拽到脚本的Grid Cell Prefab字段。 - 在 Inspector 中,找到
Path Positions数组。你需要根据你设计的棋盘布局,手动设置或通过脚本计算每个格子的坐标。这是一个关键且繁琐的步骤。你可以先在 Scene 视图中摆放好GridCell预制体的实例,记录下它们的坐标,然后填入数组。数组索引0代表起点,之后按飞行路径顺序排列。
7. 实现棋子与移动逻辑
棋子是游戏中的可移动单位,每个玩家拥有多枚棋子。
步骤 1:创建棋子预制体与脚本
- 在
Assets/Sprites中准备棋子 Sprite。在 Hierarchy 中创建 Sprite,命名为Piece_Red_1,设置好 Sprite 和颜色。为其添加Circle Collider 2D(用于交互)。 - 创建 C# 脚本
Piece:
using UnityEngine; public class Piece : MonoBehaviour { // 棋子所属玩家索引 (0-3) public int playerIndex; // 棋子在该玩家中的编号 (0-3) public int pieceIndex; // 当前所在的棋盘格子索引 public int currentGridIndex = -1; // -1 表示在基地未出发 // 是否已到达终点 public bool isFinished = false; // 移动速度 public float moveSpeed = 5f; // 是否正在移动 private bool isMoving = false; private Vector3 targetPosition; private int moveStepsRemaining; private int targetGridIndex; void Update() { if (isMoving) { // 平滑移动到目标位置 transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime); if (Vector3.Distance(transform.position, targetPosition) < 0.01f) { transform.position = targetPosition; OnReachedOneStep(); } } } // 开始移动 public void StartMove(int steps) { if (isFinished || isMoving) return; // 计算目标格子 if (currentGridIndex == -1) { // 从基地出发,需要掷得6(或其他规则) // 这里简化:只要移动,就从起点开始 targetGridIndex = GetStartGridIndex(playerIndex); } else { targetGridIndex = BoardManager.Instance.GetNextGridIndex(currentGridIndex, steps); } moveStepsRemaining = steps; isMoving = true; MoveToNextGrid(); } void MoveToNextGrid() { if (moveStepsRemaining <= 0) { // 移动结束 isMoving = false; currentGridIndex = targetGridIndex; // 通知 GameManager 该棋子移动结束 GameManager.Instance.OnPlayerMoveFinished(); return; } // 计算下一个格子的位置 int nextGridIndex; if (currentGridIndex == -1) { nextGridIndex = GetStartGridIndex(playerIndex); } else { nextGridIndex = BoardManager.Instance.GetNextGridIndex(currentGridIndex, 1); } targetPosition = BoardManager.Instance.GetGridWorldPosition(nextGridIndex); currentGridIndex = nextGridIndex; moveStepsRemaining--; } void OnReachedOneStep() { // 到达一个格子后,继续向下一格移动,直到步数用完 MoveToNextGrid(); } // 获取玩家的起点格子索引(需要与 BoardManager 中的路径定义对应) int GetStartGridIndex(int pIndex) { // 示例:假设4个玩家的起点在路径上的第0, 13, 26, 39格 int[] startIndices = { 0, 13, 26, 39 }; if (pIndex >= 0 && pIndex < startIndices.Length) return startIndices[pIndex]; return 0; } // 鼠标点击选择棋子(可选) private void OnMouseDown() { if (GameManager.Instance.CurrentState == GameManager.GameState.Moving && GameManager.Instance.currentPlayerIndex == playerIndex) { Debug.Log($"选中棋子: 玩家{playerIndex} - 棋子{pieceIndex}"); // 这里可以触发移动逻辑,例如:PieceManager.Instance.SelectPiece(this); } } }- 将
Piece脚本挂载到棋子 GameObject 上。配置Player Index和Piece Index。 - 将这个配置好的棋子拖入
Assets/Prefabs文件夹,创建为预制体,例如Piece_Prefab。然后为其他颜色和编号创建不同的预制体或通过脚本动态实例化时设置颜色。
步骤 2:创建棋子管理器 (PieceManager)管理所有棋子的生成、选择和移动协调。
using System.Collections.Generic; using UnityEngine; public class PieceManager : MonoBehaviour { public static PieceManager Instance { get; private set; } // 棋子预制体 public GameObject piecePrefab; // 存储所有棋子的列表 [玩家索引][棋子索引] public List<List<Piece>> allPieces = new List<List<Piece>>(); // 每个玩家的棋子起始位置(基地位置) public Vector3[] playerBasePositions; // 棋子间的偏移,避免完全重叠 public Vector3 pieceOffset = new Vector3(0.3f, 0.3f, 0); private void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } } void Start() { InitializePieces(); } void InitializePieces() { int playerCount = 4; int piecesPerPlayer = 4; for (int p = 0; p < playerCount; p++) { List<Piece> playerPieces = new List<Piece>(); for (int i = 0; i < piecesPerPlayer; i++) { Vector3 spawnPos = playerBasePositions[p] + new Vector3(pieceOffset.x * i, pieceOffset.y * i, 0); GameObject pieceObj = Instantiate(piecePrefab, spawnPos, Quaternion.identity, this.transform); Piece piece = pieceObj.GetComponent<Piece>(); piece.playerIndex = p; piece.pieceIndex = i; piece.currentGridIndex = -1; // 在基地 // 设置棋子颜色(可以通过Material或SpriteRenderer) SpriteRenderer sr = pieceObj.GetComponent<SpriteRenderer>(); if (sr != null) { // 这里简单使用 GameManager 中定义的颜色 sr.color = GameManager.Instance.playerColors[p]; } pieceObj.name = $"Piece_P{p}_N{i}"; playerPieces.Add(piece); } allPieces.Add(playerPieces); } Debug.Log($"棋子初始化完成,共 {playerCount} 名玩家,每名 {piecesPerPlayer} 枚棋子。"); } // 开始移动当前玩家的一个棋子(简化:移动第一枚可移动的棋子) public void StartMoveForCurrentPlayer(int diceValue) { int currentPlayer = GameManager.Instance.currentPlayerIndex; var pieces = allPieces[currentPlayer]; // 遍历找到第一枚未到达终点的棋子 foreach (var piece in pieces) { if (!piece.isFinished) { piece.StartMove(diceValue); break; // 简化逻辑:每次只移动一枚棋子 } } } }步骤 3:连接 GameManager 与 PieceManager回到GameManager脚本,在RollDice方法中,取消注释并修改调用:
// 在 GameManager 的 RollDice 方法末尾添加 // 通知棋子管理器开始移动 PieceManager.Instance.StartMoveForCurrentPlayer(currentDiceValue);8. 搭建游戏用户界面 (UI)
使用 Unity 的 UGUI 系统创建简单的游戏界面。
步骤 1:创建 UI Canvas
- 在 Hierarchy 中右键
UI -> Canvas。这会自动创建一个Canvas、EventSystem和Canvas Scaler等组件。 - 设置
Canvas Scaler的UI Scale Mode为Scale With Screen Size,参考分辨率设为 1920x1080。
步骤 2:创建 UI 元素在 Canvas 下创建以下 UI 对象:
- Text - CurrentPlayerText:显示当前回合玩家。锚点设为左上角。
- Button - RollDiceButton:掷骰子按钮。锚点设为底部中心。将其子物体
Text的内容改为“掷骰子”。 - Text - DiceResultText:显示骰子结果。可以放在按钮上方或旁边。
步骤 3:关联 UI 与 GameManager
- 选中 Hierarchy 中的
GameManager对象。 - 在 Inspector 的
GameManager脚本组件中,将对应的 UI 元素拖拽到公共字段中:Current Player Text-> 拖入CurrentPlayerText的 Text 组件。Roll Dice Button-> 拖入RollDiceButton的 Button 组件。Dice Result Text-> 拖入DiceResultText的 Text 组件。
- 选中
RollDiceButton,在 Inspector 的Button组件下方,点击On Click()事件列表的+号。 - 将
GameManager对象拖入事件对象框。 - 在下拉菜单中选择
GameManager -> OnRollDiceButtonClicked方法。
步骤 4:测试 UI 交互点击 Unity 编辑器上的 Play 按钮。你应该能看到 UI 显示“当前回合: 红方”,点击“掷骰子”按钮,DiceResultText会显示随机骰子数,并且控制台会输出日志。此时棋子还不会移动,因为移动逻辑需要与棋盘路径联动。
9. 功能整合与测试运行
现在,我们需要将棋盘、棋子、管理器和 UI 全部连接起来,进行第一次整合测试。
步骤 1:配置场景中的管理器
- 确保
BoardManager对象已正确配置Path Positions。 - 确保
PieceManager对象已正确配置Piece Prefab和Player Base Positions(四个基地的坐标)。 - 确保
GameManager对象已正确配置Player Colors和 UI 引用。
步骤 2:编写一个简单的调试脚本为了直观看到移动,我们可以在PieceManager的StartMoveForCurrentPlayer方法中,先实现一个简单的直线移动测试,暂时绕过复杂的棋盘路径。
// 在 PieceManager 中临时修改 StartMoveForCurrentPlayer 方法 public void StartMoveForCurrentPlayer(int diceValue) { int currentPlayer = GameManager.Instance.currentPlayerIndex; var pieces = allPieces[currentPlayer]; foreach (var piece in pieces) { if (!piece.isFinished) { // 临时测试:让棋子向右移动一段距离 StartCoroutine(SimpleMoveTest(piece.transform, diceValue)); break; } } } System.Collections.IEnumerator SimpleMoveTest(Transform pieceTransform, int steps) { float distancePerStep = 1.0f; for (int i = 0; i < steps; i++) { Vector3 startPos = pieceTransform.position; Vector3 endPos = startPos + Vector3.right * distancePerStep; float duration = 0.3f; float elapsed = 0f; while (elapsed < duration) { pieceTransform.position = Vector3.Lerp(startPos, endPos, elapsed / duration); elapsed += Time.deltaTime; yield return null; } pieceTransform.position = endPos; yield return new WaitForSeconds(0.1f); // 每步间隔 } // 移动结束后通知 GameManager GameManager.Instance.OnPlayerMoveFinished(); }记得在文件顶部添加using System.Collections;。这个协程会让选中的棋子向右平移,模拟移动步数。
步骤 3:运行测试
- 点击 Play。
- 点击“掷骰子”按钮,观察 DiceResultText 变化和 Console 日志。
- 观察场景中当前玩家的第一枚棋子是否向右移动了相应的步数。
- 回合结束后,当前玩家指示应切换到下一个玩家。
如果测试成功,说明游戏的核心循环(掷骰子 -> 移动 -> 切换回合)已经打通。
步骤 4:接入真实棋盘移动将PieceManager中的SimpleMoveTest临时代码注释掉或删除,确保Piece脚本中的StartMove方法以及BoardManager的路径系统能正常工作。这需要你仔细配置好BoardManager中的pathPositions数组,确保坐标连续且符合飞行棋棋盘布局。
10. 常见问题与排查方法
在开发过程中,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 点击 Play 后无任何反应 | 1. 场景未保存或未设置为激活场景。 2. GameManager 或其他管理器脚本未挂载到场景中的 GameObject 上。 | 1. 检查 File -> Build Settings 中当前场景是否在 Scenes In Build 列表中且被勾选。 2. 在 Hierarchy 中搜索关键管理器对象。 | 1. 保存场景并将其添加到 Build Settings。 2. 将缺失的脚本挂载到空对象上。 |
| UI 按钮点击无效 | 1. Button 的 OnClick 事件未绑定方法。 2. EventSystem 被禁用或缺失。 3. UI 元素被其他元素遮挡。 | 1. 检查 Button 组件的 OnClick 列表。 2. 检查 Hierarchy 中是否有 EventSystem。 3. 检查 Canvas 的渲染顺序和 Raycast Target。 | 1. 重新绑定事件。 2. 右键 UI -> EventSystem 创建。 3. 调整 UI 层级。 |
| 棋子不移动 | 1.PieceManager.Instance为空。2. BoardManager.Instance或gridPositions为空。3. pathPositions数组未正确配置。4. Piece脚本中的moveSpeed为0或isMoving逻辑错误。 | 1. 在Awake或Start中打印 Instance 是否初始化。2. 检查 BoardManager的Start方法中GenerateBoard是否被调用。3. 在 Scene 视图查看生成的格子位置。 4. 在 Piece.Update中打印isMoving状态。 | 1. 确保管理器脚本的Awake单例逻辑正确。2. 确保预制体和路径数据已赋值。 3. 仔细核对 pathPositions的每个坐标。4. 调试 StartMove和MoveToNextGrid的调用流程。 |
| 移动后回合不切换 | GameManager.Instance.OnPlayerMoveFinished()未被调用。 | 在Piece移动结束的逻辑处和GameManager.OnPlayerMoveFinished方法开始处添加 Debug.Log。 | 确保移动协程或逻辑结束后,能正确调用回合结束方法。 |
| 多个棋子重叠 | 1. 所有棋子实例化在了同一位置。 2. pieceOffset设置过小或为0。 | 检查PieceManager.InitializePieces中生成棋子时的位置计算。 | 调整playerBasePositions和pieceOffset的值,使棋子分散开。 |
| 脚本编译错误 | C# 语法错误,缺少分号、括号,或变量未定义。 | 查看 Console 窗口的错误信息,双击错误定位到代码行。 | 根据错误提示修正代码。注意 Visual Studio 可能需要刷新项目(Regenerate Project Files)。 |
11. 项目优化与扩展思路
完成基础版本后,你可以从以下方向优化和扩展你的飞行棋项目:
完善棋盘规则:
- 起飞规则:只有掷得6点(或其他特定点数)才能将棋子从基地移出。
- 跳跃规则:当棋子走到与自己颜色相同的格子时,可以跳跃到下一个同色格。
- 撞击规则:如果棋子走到已有敌方棋子的格子,可将敌方棋子撞回基地。
- 终点规则:棋子必须精确到达终点,否则需要倒退。
增强视觉表现:
- 棋子动画:为棋子移动添加平滑的 Lerp 或 DoTween 动画。
- 骰子动画:创建3D或2D骰子模型,添加旋转和定格动画。
- 粒子效果:在移动、撞击、胜利时添加粒子特效。
- 音效:添加掷骰子、移动棋子、撞击、胜利等音效。
改进UI/UX:
- 棋子选择:当掷骰子后,高亮显示当前玩家可移动的棋子,由玩家点击选择移动哪一枚。
- 状态面板:显示每位玩家尚未出发、正在路上、已达终点的棋子数量。
- 规则说明:添加游戏规则说明界面。
代码重构:
- 状态机:使用更严谨的状态机(如枚举或 State Pattern)管理游戏状态。
- 事件系统:使用 C# 事件或 UnityEvent 解耦模块间的通信,避免 Manager 之间直接强引用。
- 数据与表现分离:将棋盘数据、棋子数据与它们的 GameObject 表现层分离。
多人游戏:
- 本地多人:在同一台设备上轮流操作,当前实现已支持。
- 网络对战:使用 Unity Netcode、Photon 或 Mirror 等网络框架,实现在线对战。
通过这个“上篇”项目,你已经成功搭建了 Unity 2D 飞行棋的核心骨架。你拥有了一个可运行的游戏循环,理解了管理器、对象池、UI 交互和基础动画的协作方式。接下来,你可以根据“优化与扩展思路”逐步完善细节,或者开始构思“下篇”——加入更复杂的规则、动画和音效,甚至网络功能。记住,游戏开发是一个迭代过程,先让核心跑起来,再不断打磨和添加内容,是最高效的学习路径。建议将当前项目进行版本管理(如 Git),以便在尝试新功能时能安全地回溯。