目录
前言
在这一篇博客中,我将介绍如何使用unity和Model-View-Controller (MVC)概念来制作一个简单的2D游戏——连连看游戏。游戏中所有UI使用IMGUI来制作。
概念介绍
即时模式GUI
即时模式 GUI(IMGUI)是 Unity 引擎提供的一种简单而直接的图形用户界面(GUI)系统。IMGUI 允许你在游戏循环的 OnGUI 事件中直接编写代码来创建和更新用户界面元素。
IMGUI 是基于立即绘制的概念,每当 OnGUI 事件被触发时,GUI 元素将被立即绘制到屏幕上。IMGUI 使用一系列 GUI 函数来创建不同类型的控件,例如按钮、文本框、滑块等。你可以通过调用这些函数来设置控件的属性、处理用户输入和响应事件。
Model-View-Controller (MVC)
MVC模式由三个核心组件组成:模型(Model)、视图(View)、和控制器(Controller)。它们分别负责不同的职责,通过相互协作来完成整个应用的功能。
-
模型(Model)
模型是应用程序的核心数据和业务逻辑层,它负责处理应用的数据及其相关的操作。模型通常独立于用户界面和输入方式,因此它可以独立于用户交互运行,并且可以在后台处理业务逻辑。 -
视图(View)
视图是应用程序的用户界面层,负责呈现数据并与用户进行交互。视图根据模型的数据进行更新和显示,并且可以响应用户的输入行为。视图与用户直接交互,但不会处理业务逻辑或数据存储。 -
控制器(Controller)
控制器是连接视图和模型的桥梁,负责接收用户输入并调用模型处理逻辑,同时更新视图。控制器解耦了视图和模型之间的直接依赖,使得应用架构更加灵活。
游戏概述
这个连连看游戏的具体玩法是:点击数字相同的两个按钮,即可完成消除,当点击不同数字的按钮时,会显示不匹配的结果。一直这样子不断点击,使得按钮两两消去,直到所有按钮被消去为止,即游戏结束。可以点击restart按钮重新开始游戏。
游戏界面展示
游戏视频展示链接: link.
具体的游戏实现
GameModel部分
//GameModel的属性
public int[,] Grid { get; private set; }
public bool[,] IsCleared { get; private set; }
private int gridSize = 6;
private int iconTypes = 5;
Grid:是一个图案矩阵,记录每个方格的图案
IsCleared:消除状态矩阵,记录方格是否已被消除
gridSize:矩阵大小
iconTypes:图案类型数量
// 判断两个位置是否相同且未消除
public bool AreMatching(int x1, int y1, int x2, int y2)
{
if (Grid[x1, y1] == Grid[x2, y2] && !IsCleared[x1, y1] && !IsCleared[x2, y2])
{
IsCleared[x1, y1] = true;
IsCleared[x2, y2] = true;
return true;
}
return false;
}
GameModel中的AreMatching方法用于判断两个图案是否匹配且未被消除,并在匹配时更新消除状态。
// 检查游戏是否结束
public bool IsGameOver()
{
for (int i = 0; i < Grid.GetLength(0); i++)
{
for (int j = 0; j < Grid.GetLength(1); j++)
{
if (!IsCleared[i, j])
return false;
}
}
return true;
}
GameModel中的IsGameOver方法通过遍历IsCleared矩阵检查游戏是否结束。
GameView部分
public delegate void IconClickHandler(int x, int y); // 点击图标事件
public event IconClickHandler OnIconClicked;
IconClickHandler 是用来定义一个接受两个整数(坐标 x 和 y)且没有返回值的方法签名的委托。该委托在点击图标时,传递被点击图标的坐标 x 和 y,用于通知其他部分(例如 Controller)哪个图标被点击了。
OnIconClicked 是一个事件,使用 IconClickHandler 作为事件类型,当图标被点击时,会触发该事件,并将点击的坐标信息传递给所有订阅该事件的方法。
这段代码的目的是在图标点击时触发事件,让其他部分知道发生了点击,并做出相应的处理。
public void DrawGrid(int[,] grid, bool[,] isCleared)
{
for (int i = 0; i < grid.GetLength(0); i++)
{
GUILayout.BeginHorizontal();
for (int j = 0; j < grid.GetLength(1); j++)
{
if (!isCleared[i, j])
{
if (GUILayout.Button(grid[i, j].ToString(), GUILayout.Width(buttonSize), GUILayout.Height(buttonSize)))
{
OnIconClicked?.Invoke(i, j);
}
}
else
{
GUILayout.Button("", GUILayout.Width(buttonSize), GUILayout.Height(buttonSize));
}
}
GUILayout.EndHorizontal();
}
DrawGrid方法用于绘制整个图案矩阵。在每一行开始时,调用 GUILayout.BeginHorizontal()。这是 IMGUI 的一种布局方式,表示在同一行上绘制多个元素(如按钮、文本等)。如果图案未被消除,绘制按钮。当按钮被点击时,会触发 OnIconClicked 事件,将按钮所在的坐标 i, j 传递出去。
GameController部分
this.view.OnIconClicked += HandleIconClick; // 订阅View的点击事件
this.view.OnRestartRequested += RestartGame; // 订阅重启事件
private void HandleIconClick(int x, int y)
{
if (selectedX == null && selectedY == null)
{
// 如果没有选择图案,记录第一次点击的位置
selectedX = x;
selectedY = y;
}
else
{
// 如果已经选择了第一个图案,处理第二次点击
if (model.AreMatching(selectedX.Value, selectedY.Value, x, y))
{
Debug.Log("Match Found!");
}
else
{
Debug.Log("Not a Match!");
}
// 重置选择
selectedX = null;
selectedY = null;
}
// 检查游戏是否结束
if (model.IsGameOver())
{
view.GameOver = true; // 设置游戏结束状态
}
}
HandleIconClick的功能是处理游戏中的点击事件逻辑,并检查游戏是否结束。如果这是玩家的第一次点击(即 selectedX 和 selectedY 为空),则记录当前点击的图案位置 (x, y)。如果已经有了第一次点击,当玩家再次点击第二个图案时,检查这两个图案是否匹配。
调用 model.AreMatching(selectedX.Value, selectedY.Value, x, y) 方法判断两个位置的图案是否匹配且未被消除。
public void UpdateView()
{
view.DrawGrid(model.Grid, model.IsCleared);
}
调用GameView中的DrawGrid来更新View
GameManager部分
public class GameManager : MonoBehaviour
{
private GameModel model;
private GameView view;
private GameController controller;
private void Start()
{
model = new GameModel();
view = new GameView();
controller = new GameController(model, view);
}
private void OnGUI()
{
controller.UpdateView();
}
}
GameManager 类是游戏的管理器,负责初始化实例化游戏的 MVC 架构组件(GameModel, GameView, GameController)
Start 方法是 Unity 中的一个生命周期方法,在游戏开始时自动调用。这里用于初始化 MVC 架构的三个核心部分。
OnGUI 是 Unity 内置的 GUI 更新方法,每帧都会调用,用于绘制和更新游戏的界面。controller.UpdateView() 方法负责通过 GameController 调用 GameView 来更新游戏界面。
controller.UpdateView() 会通过 view.DrawGrid() 渲染当前的游戏图案矩阵,并根据 model.IsCleared 数组,决定哪些图案需要显示,哪些已经被消除。用户点击按钮时触发 OnIconClicked 事件,传递给 controller,再通过控制器处理用户的交互逻辑。
总结
以上就是我使用unity并结合MVC概念制作而成的一个2D连连看小游戏,通过这个游戏,我清晰地学习认识到了MVC模式的具体含义和应用,也对于入门unity有了更加深刻的理解和帮助。
数据与游戏逻辑分离有价值吗?
答:有重要的价值,分离数据与游戏逻辑有以下的优点:1.能够提高代码的可维护性,数据结构的修改不会直接影响逻辑部分,大幅减少代码变动的连锁反应,使得代码更容易维护和修改。2.提高数据复用性,通过将游戏逻辑与数据分离,数据可以在不同的逻辑场景下复用。3. 便于调试与测试,开发者可以使用模拟数据进行单元测试或集成测试,独立验证逻辑的正确性,而不必依赖复杂的数据库或数据文件。4. 增强扩展性,游戏开发中,需求经常变化,例如新增游戏功能、调整游戏规则等。通过分离数据与逻辑,可以更轻松地扩展游戏功能。游戏逻辑模块可以被独立更新,而不需要修改底层的数据存储或管理方法。5. 支持多人开发协作,通过将数据与逻辑分离,团队成员可以并行开发,负责逻辑的开发者不必依赖数据的具体实现,而数据设计者也可以专注于数据层的优化与设计,从而提升协作效率。6. 优化性能,当数据与逻辑分离后,可以更容易实现性能优化。例如,数据层可以针对读取、存储的效率进行优化(如缓存、数据库查询优化等),而游戏逻辑则可以进行算法上的优化。这样,数据和逻辑可以分别针对不同的瓶颈进行性能调优,避免了性能问题相互干扰。