在这篇博客中,我们将介绍如何使用Entity-Component-System (ECS)和Model-View-Controller (MVC)概念制作一个数字消消乐小游戏。这个游戏将展示如何将游戏逻辑与界面分离,以实现更好的代码组织和可维护性。
游戏概述
我们要制作的游戏是一个数字消消乐游戏,玩家需要点击相同的数字卡片进行两两消除。游戏板上有多个卡片,每个卡片上都有一个数字。当两张卡片的数字相同时,它们将被移除,直到所有卡片都被消除干净。
游戏展示
ECS概念
Entity-Component-System (ECS) 是一种游戏开发模式,它将游戏对象分解为三个核心概念:实体(Entity)、组件(Component)和系统(System)。
-
实体(Entity):游戏对象的基本单元,通常只是一个标识符。
-
组件(Component):包含实体的数据,例如位置、外观、行为等。
-
系统(System):处理特定组件的逻辑,例如更新位置、渲染图形等。
MVC概念
Model-View-Controller (MVC) 是一种软件架构模式,用于将应用程序分为三个核心部分:
-
模型(Model):包含应用程序的核心逻辑和数据,独立于用户界面。
-
视图(View):负责呈现数据给用户,通常是用户界面元素。
-
控制器(Controller):接受用户输入并更新模型和视图以响应用户操作。
游戏实现
模型部分(Model)
private int[,] board = new int[4, 4];
private List<Vector2Int> selectedTiles = new List<Vector2Int>();
private bool gameEnded = false;
- `board`:这是一个2D整数数组,表示游戏板上的卡片。每个元素代表一个卡片的值,初始值为0。
- `selectedTiles`:这是一个`Vector2Int`的列表,用于跟踪玩家当前选择的卡片的位置。
- `gameEnded`:这是一个布尔值,用于标识游戏是否已经结束。
游戏初始化(InitBoard)
void InitBoard()
{
// 生成成对的随机数字
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j += 2)
{
int value = Random.Range(1, 9); // 生成1到8之间的随机数
board[i, j] = value;
board[i, j + 1] = value;
}
}
// 打乱卡片的顺序
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
int temp = board[i, j];
int randomRow = Random.Range(0, 4);
int randomCol = Random.Range(0, 4);
board[i, j] = board[randomRow, randomCol];
board[randomRow, randomCol] = temp;
}
}
}
- `InitBoard` 方法用于初始化游戏板。首先,它生成了成对的随机数字并将它们放置在游戏板上,确保每个数字都有两个相同的卡片。然后,它随机打乱了卡片的顺序,以确保卡片的位置是随机的。
游戏结束检测(GameOver)
bool GameOver()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (board[i, j] != 0)
{
return false;
}
}
}
return true;
}
- `GameOver` 方法用于检测游戏是否结束。它遍历游戏板上的所有卡片,如果发现还有未匹配的卡片(值不为0),则返回`false`,否则返回`true`表示游戏结束。
视图部分(View to render entities / models)
void OnGUI()
{
// 设置按钮文本颜色
GUI.skin.button.normal.textColor = Color.white;
GUI.skin.button.hover.textColor = Color.yellow;
GUI.Box(new Rect(210, 25, 350, 380), ""); // 绘制一个背景框
// 绘制“Restart”按钮
if (GUI.Button(new Rect(335, 355, 100, 30), "Restart"))
{
ResetGame(); // 点击按钮时重新开始游戏
return;
}
if (!gameEnded)
{
// 遍历游戏板上的卡片
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
int tileValue = board[i, j];
bool isSelected = selectedTiles.Contains(new Vector2Int(i, j));
// 设置按钮背景颜色,选择的卡片背景为青色
Color buttonColor = isSelected ? Color.cyan : Color.white;
GUI.backgroundColor = buttonColor;
// 绘制卡片按钮
if (tileValue != 0 && GUI.Button(new Rect(245 + j * 70, 55 + i * 70, 70, 70), tileValue.ToString()))
{
SelectTile(i, j); // 点击卡片时处理选择逻辑
}
// 重置按钮背景颜色
GUI.backgroundColor = Color.white;
}
}
// 检测游戏是否结束
if (GameOver())
{
gameEnded = true;
}
}
else
{
// 显示胜利信息
GUI.Box(new Rect(245, 55, 280, 280), "\n\n\n\n\nCongratulations!\nYou have won.");
}
}
- `OnGUI` 方法用于渲染游戏界面。它设置了按钮的文本颜色和绘制了一个背景框。
- 当点击"Restart"按钮时,会调用 `ResetGame` 方法来重新开始游戏。
- 在游戏未结束时,它遍历游戏板上的卡片,并根据卡片是否被选中设置按钮的背景颜色。玩家可以点击卡片来选择它们,点击后会调用 `SelectTile` 方法处理选择逻辑。
- 最后,如果游戏结束,它会显示胜利信息。
控制部分(Components /controls)
void SelectTile(int row, int col)
{
Vector2Int currentTile = new Vector2Int(row, col);
selectedTiles.Add(currentTile);
if (selectedTiles.Count == 2)
{
int tile1Value = board[selectedTiles[0].x, selectedTiles[0].y];
int tile2Value = board[selectedTiles[1].x, selectedTiles[1].y];
if (tile1Value == tile2Value)
{
board[selectedTiles[0].x, selectedTiles[0].y] = 0;
board[selectedTiles[1].x, selectedTiles[1].y] = 0;
}
selectedTiles.Clear();
}
}
- `SelectTile` 方法用于处理卡片的选择逻辑。当玩家点击一个卡片时,它将其位置添加到 `selectedTiles` 列表中。如果已选择了两张卡片,它会检查它们的值是否相同,如果相同则将它们从游戏板上移除。
游戏重置(Restart)
private void ResetGame()
{
InitBoard();
gameEnded = false;
}
- `ResetGame` 方法用于重新开始游戏。它调用 `InitBoard` 方法来初始化游戏板,然后将游戏结束状态设为`false`。
通过上述代码,我们实现了一个简单的数字消消乐游戏,并使用了Unity的GUI系统来创建游戏界面。玩家的任务是找到所有匹配的卡片并在游戏结束时获得胜利。
ECS的应用
虽然上述代码中没有明确实现ECS模式,但我们可以考虑如何将它引入游戏中。例如,我们可以创建一个 Card
组件,其中包含卡片的值、状态等信息,然后编写系统来处理卡片的翻转和匹配。这样,我们可以更好地分离游戏逻辑和数据。
结语
通过结合ECS和MVC概念,我们可以更清晰地组织游戏代码,并实现良好的可维护性和扩展性。这使得游戏开发过程更加顺畅,也有助于更好地协作和维护游戏项目。希望这篇博客对你理解以及如何应用ECS和MVC概念有所帮助。
代码汇总
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MatchGame : MonoBehaviour
{ // Entities and their states / Model
private int[,] board = new int[4, 4];
private List<Vector2Int> selectedTiles = new List<Vector2Int>();
private bool gameEnded = false;
// System Handlers
void Start()
{
InitBoard();
}
// View to render entities / models
// Here! you cannot modify model directly, use components/controls to do it
void OnGUI()
{
GUI.skin.button.normal.textColor = Color.white; // 设置按钮文本颜色为白色
GUI.skin.button.hover.textColor = Color.yellow; // 设置按钮鼠标悬停时文本颜色为黄色
GUI.Box(new Rect(210, 25, 350, 380), "");
if (GUI.Button(new Rect(335, 355, 100, 30), "Restart"))
{
ResetGame();
return;
}
if (!gameEnded)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
int tileValue = board[i, j];
bool isSelected = selectedTiles.Contains(new Vector2Int(i, j));
// 设置按钮背景颜色
Color buttonColor = isSelected ? Color.cyan : Color.white;
GUI.backgroundColor = buttonColor;
if (tileValue != 0 && GUI.Button(new Rect(245 + j * 70, 55 + i * 70, 70, 70), tileValue.ToString()))
{
SelectTile(i, j);
}
// 重置按钮背景颜色
GUI.backgroundColor = Color.white;
}
}
if (GameOver())
{
gameEnded = true;
}
}
else
{
GUI.Box(new Rect(245, 55, 280, 280), "\n\n\n\n\nCongratulations!\nYou have won.");
}
}
// Components /controls
void SelectTile(int row, int col)
{
Vector2Int currentTile = new Vector2Int(row, col);
selectedTiles.Add(currentTile);
if (selectedTiles.Count == 2)
{
int tile1Value = board[selectedTiles[0].x, selectedTiles[0].y];
int tile2Value = board[selectedTiles[1].x, selectedTiles[1].y];
if (tile1Value == tile2Value)
{
board[selectedTiles[0].x, selectedTiles[0].y] = 0;
board[selectedTiles[1].x, selectedTiles[1].y] = 0;
}
selectedTiles.Clear();
}
}
void InitBoard()
{
// Generate random numbers in pairs
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j += 2)
{
int value = Random.Range(1, 9);
board[i, j] = value;
board[i, j + 1] = value;
}
}
// Shuffle the board
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
int temp = board[i, j];
int randomRow = Random.Range(0, 4);
int randomCol = Random.Range(0, 4);
board[i, j] = board[randomRow, randomCol];
board[randomRow, randomCol] = temp;
}
}
}
bool GameOver()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (board[i, j] != 0)
{
return false;
}
}
}
return true;
}
private void ResetGame()
{
InitBoard();
gameEnded = false;
}
}