记忆翻牌游戏——使用unity完成的2D游戏

目录

前言

游戏循环基本原理

常见游戏事件

即时模式GUI(IMGUI)

Entity-Component-System (ECS)概念

Model-View-Controller (MVC)概念

游戏概述

具体的游戏实现

模型部分(Model)

初始化model

初始化各种参数以及Table和List

视图部分(View)

控制部分(Controller)

判断是否将两张卡牌消去的主要逻辑实现

查找元素的个数以及返回所在位置

判断游戏是否结束

ECS(Entity Component System)的应用

结语

代码汇总


前言

在这一篇博客中,我将介绍如何使用unity以及Entity-Component-System (ECS)和Model-View-Controller (MVC)概念来制作一个简单的2D游戏——记忆翻牌游戏。在这一个过程中,我们将会了解游戏循环的基本原理,掌握常见游戏事件的使用及其执行顺序,学会使用unity的简单操作与编程,掌握游戏世界的基本概念。同时还对ECS以及MVC概念有一定的深入了解。

游戏循环基本原理

游戏循环是指游戏引擎在每一帧中执行的一系列步骤,以更新游戏状态并呈现画面。下面是Unity游戏循环的基本原理:

1. 初始化阶段(Initialization Phase):
    游戏引擎初始化:创建游戏窗口、图形设备的初始化等。
    场景加载:加载游戏场景、资源等。

2. 输入处理阶段(Input Phase):
    处理输入:监听玩家的输入,例如键盘、鼠标、触摸屏等。
    更新输入状态:根据输入更新游戏中的相应状态,例如控制角色移动。

3. 更新阶段(Update Phase):
    更新游戏状态:执行游戏逻辑、物理模拟、碰撞检测等。
    更新游戏对象:更新游戏中的各个对象的状态,例如移动、旋转、动画等。
    处理游戏事件:触发和处理游戏中的事件,例如触发特定条件下的事件回调。

4. 渲染阶段(Render Phase):
    准备渲染:设置渲染目标、清除缓冲区等准备工作。
    渲染场景:将游戏对象的可视化表现绘制到屏幕上,包括几何形状、材质、光照等。
    后期处理:应用各种后期效果,例如颜色校正、模糊、阴影等。
    呈现画面:将渲染结果显示在屏幕上,完成一帧的渲染。

5. 循环重复:
    上述步骤会在每一帧中依次执行,形成游戏循环,不断更新游戏状态和渲染画面。

常见游戏事件

当了解了游戏循环的基本原理后,我们可以探讨一些常见的游戏事件以及它们的执行顺序。以下是一些常见的游戏事件及其执行顺序的概述:

1. Start(开始)事件:
    在游戏对象被激活或场景加载完成后触发。
    用于初始化游戏对象的状态、设置初始数值、获取引用等。
    只会在游戏对象的生命周期中执行一次。

2. Update(更新)事件:
    在每一帧的更新阶段执行。
    用于处理游戏逻辑、输入响应、移动、旋转等实时更新的操作。
    执行顺序从场景中的每个激活的游戏对象开始,按照它们在场景中的顺序依次执行。

3. FixedUpdate(固定更新)事件:
    在每一帧的物理更新阶段执行。
    用于处理物理模拟、碰撞检测等与物理相关的操作。
    时间间隔固定,不受帧率的影响,通常每秒执行几次(例如默认每秒执行 50 次)。

4. LateUpdate(延迟更新)事件:
    在 Update 事件之后执行。
    用于处理在 Update 事件中可能会影响到其他对象的操作。
    例如相机跟随、动画更新等需要在其他对象更新完毕后再执行的操作。

5. OnCollisionEnter(碰撞进入)事件:
    在游戏对象与其他对象发生碰撞时触发。
    用于处理碰撞事件的逻辑,例如触发游戏效果、播放音效等。

6. OnTriggerEnter(触发器进入)事件:
    在游戏对象进入触发器时触发。
    用于处理触发器事件的逻辑,例如触发任务、切换场景等。

7. OnGUI(绘制GUI)事件:
    在渲染阶段之后执行。
    用于绘制游戏界面、UI 元素等。

需要注意的是,不同的游戏事件在执行顺序上可能会有差异,具体取决于事件的类型和注册方式。例如,MonoBehaviour 中的事件会按照脚本的顺序进行执行,而物理事件则与物理引擎的更新步骤相关联。

即时模式GUI(IMGUI)

即时模式 GUI(IMGUI)是 Unity 引擎提供的一种简单而直接的图形用户界面(GUI)系统。IMGUI 允许你在游戏循环的 OnGUI 事件中直接编写代码来创建和更新用户界面元素。

IMGUI 是基于立即绘制的概念,每当 OnGUI 事件被触发时,GUI 元素将被立即绘制到屏幕上。IMGUI 使用一系列 GUI 函数来创建不同类型的控件,例如按钮、文本框、滑块等。你可以通过调用这些函数来设置控件的属性、处理用户输入和响应事件。

以下是使用 IMGUI 的基本示例:

using UnityEngine;

public class MyGUI : MonoBehaviour
{
    private string playerName = "Player";
    private int score = 0;

    private void OnGUI()
    {
        // 创建一个标签显示玩家名称和分数
        GUI.Label(new Rect(10, 10, 200, 20), "Player Name: " + playerName);
        GUI.Label(new Rect(10, 30, 200, 20), "Score: " + score);

        // 创建一个按钮,点击后增加分数
        if (GUI.Button(new Rect(10, 60, 80, 20), "Add Score"))
        {
            score += 10;
        }

        // 创建一个文本框,用于输入玩家名称
        playerName = GUI.TextField(new Rect(10, 90, 120, 20), playerName);
    }
}

需要注意的是,IMGUI 是一种立即绘制的方式,每一帧都会重新绘制所有的 GUI 元素,这可能会对性能产生一定影响。因此,对于复杂的 GUI 界面或需要频繁更新的情况,Unity 推荐使用新的 UI 系统(例如 Canvas 和 RectTransform)来构建用户界面。

Entity-Component-System (ECS)概念

ECS(Entity Component System)是一种用于构建游戏和应用程序的软件架构模式。它的核心思想是将游戏对象(Entities)拆分为组件(Components)和系统(Systems),以提供更高效、可扩展和可维护的开发方式。

在传统的面向对象编程中,游戏对象通常是由一个包含各种属性和行为的单个类表示。但是,当游戏对象变得复杂时,这种设计模式可能导致类的膨胀和难以管理。ECS 通过分离数据和行为,提供了一种更灵活的开发方式。

以下是 ECS 的三个核心概念:

1. 实体(Entity):
    实体代表游戏中的对象,可以是角色、敌人、道具等。
    实体本身只是一个标识符或唯一标识(ID),不包含任何数据或行为。

2. 组件(Component):
    组件是实体的数据部分,用于描述实体的特性和属性。
    组件是纯数据,通常是结构体或类,只包含属性和数据,没有任何行为。
    一个实体可以拥有多个组件,不同的组件可以用于描述实体的不同方面,例如位置、渲染、碰撞等。

3. 系统(System):
    系统是处理组件的行为部分,用于操作和处理具有特定组件集合的实体。
    系统根据组件的数据执行逻辑和操作,例如更新位置、渲染图形、处理碰撞等。
    系统是独立的,可以根据需要创建多个系统来处理不同类型的组件。

ECS 的核心思想是通过将实体解耦为组件和系统,使得系统可以高效地处理具有相同组件集合的实体,提高了性能和可扩展性。此外,ECS 还支持数据驱动的设计,使得开发者可以更容易地优化和并行化代码,以适应现代多核处理器的优势。

Model-View-Controller (MVC)概念

Model-View-Controller(MVC)是一种软件设计模式,用于组织和管理应用程序的结构和交互逻辑。它将应用程序划分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。每个组件具有不同的责任和功能,以实现分离关注点和提高代码的可维护性。

下面是 MVC 模式中各个组件的功能和职责:

1. 模型(Model):
    模型代表应用程序的数据和业务逻辑。
    它负责管理数据的读取、存储、处理和验证。
    模型通常包含数据结构、数据库操作、业务规则和算法等。

2. 视图(View):
    视图负责呈现模型中的数据给用户,并接收用户的输入。
    它通常是用户界面(UI)的一部分,例如图形界面、网页或命令行界面等。
    视图是对模型数据的可视化呈现,但本身不负责处理数据或逻辑。

3. 控制器(Controller):
    控制器充当模型和视图之间的中介,负责协调它们之间的交互。
    它接收用户的输入或请求,并根据需要更新模型和视图。
    控制器处理用户事件、调用适当的模型操作,并更新响应的视图。

MVC 模式的核心思想是分离关注点,使每个组件专注于自己的任务。模型负责数据和业务逻辑,视图负责用户界面的呈现,而控制器负责处理用户输入和协调模型与视图之间的交互。

游戏概述

好啦,在前面各种基础知识的了解下,我们可以开始制作我们的2D小游戏啦。我们现在准备制作的游戏是记忆翻牌游戏,具体玩法是:开始游戏时,所有卡牌是不可以见到数字的,只有当我们点击的时候,卡牌才会翻转过来,即可以看到卡牌上的数字。当我们点击了两张卡牌翻转后,如果这两张卡牌上的数字不相等,那么卡牌会再次翻转过去;如果这两张卡牌上的数字相等,那么这两张卡牌就会消失。一直这样子不断点击,使得卡牌两两消去,直到所有卡牌被消去为止,即游戏结束,可以点击Restart重新开始游戏。

我们先来看一下这一个游戏的实现效果吧!

先来看一下我们的游戏界面:

这是我们的游戏展示链接:

记忆翻牌游戏

具体的游戏实现

模型部分(Model)

初始化model

    //1.模型Model部分
    //初始化model
    private List<int> numbers;
    private int[,] table;
    private int line_column=6;
    private int[,] Is_Click;
    private bool CanClick=true;

numbers:这是一个List,用于存放可能在表格中出现的数字,同时为了实现数字之间的匹配从而实现消去。

table:这是一个2D整数数组,表示表格中每一个位置的数字。

line_column:表示这一个矩阵(表格)的行和列是多少,为了实现数字之间的两两消去,这一个变量应该为偶数。

Is_Click:这是一个2D整数数组,表示表格中的哪一个位置被点击,同时表示哪一个位置上的数字已经被消去。

CanClick:这是一个布尔值的变量,表示当点击的卡牌有两个时,将不能再点击其它卡牌,直到这两张卡牌再次翻转过去。

初始化各种参数以及Table和List

    //初始化各种参数以及List和Table
    void Init(){
        //首先初始化table、List
        table=new int[line_column,line_column];
        Is_Click=new int[line_column,line_column];
        numbers=new List<int>();

        //将数字初始化到list中
        for(int i=1;i<=line_column*line_column/2;i++){
            numbers.Add(i);
            numbers.Add(i);
        }

        //随机洗牌,将列表中的数据随机打乱
        System.Random random = new System.Random();

        for (int i = 0; i < numbers.Count - 1; i++){
            int j = random.Next(i, numbers.Count);
            int temp = numbers[i];
            numbers[i] = numbers[j];
            numbers[j] = temp;
        }

        //将列表中的数据转换到表格中来
        //同时将Is_Click的所有数据初始化为0
        for(int i=0;i<line_column;i++){
            for(int j=0;j<line_column;j++){
                table[i,j]=numbers[i*line_column+j];
                Is_Click[i,j]=0;
            }
        }
    }

我们需要初始化table、Is_Click以及numbers,首先赋值给numbers,然后将这一个List里面的数据进行随机打乱,最后再将这一个List的数字映射到table中,同时还需要将Is_Click的所有数据初始化为0。

视图部分(View)

    //2.视图View部分
    void OnGUI(){
        GUI.Box(new Rect(255,50,70*line_column,70*line_column),"");
        if(GUI.Button(new Rect(215+line_column/2*70,65+line_column*70,100,30),"Restart")){
            Init();
        }
        for(int i=0;i<line_column;i++){
            for(int j=0;j<line_column;j++){
                if(Is_Click[i,j]==0&&GUI.Button(new Rect(255+j*70,50+i*70,70,70),"")&&CanClick){
                        Is_Click[i,j]=1;
                        StartCoroutine(Is_Fade_Away());
                }
                else{
                    if(Is_Click[i,j]==1){
                        GUI.Button(new Rect(255+j*70,50+i*70,70,70),table[i,j].ToString());
                    }
                }
            }
        }
        if(GameOver()){
            GUI.Box(new Rect(260,50,70*line_column,70*line_column),"\n\n\n\n\n\n\n\nCongratulations!\n");
        }
    }

这是一个OnGUI的主函数,我们首先需要创建一个Box,然后在这一个Box中生成一个Button展示字符串“Restart!”,实现重新开始游戏的功能。随后开始生成line_column*line_column的矩阵,生成line_column*line_column个Button。然后开始进行逻辑判断,当点击该卡牌,同时Is_Click为0时,将这一个Is_Click的值赋为1,然后将所有Is_Click为1的卡牌翻转,展示数字。而当点击了两张卡牌后,就需要进行判断,这两张卡牌是否相等,如果相等,就将Is_Click的值赋为2,如果不相等,就将这两张卡牌所在的位置的Is_Click赋为0。最后,直到所有的卡牌对应的位置的Is_Click的值为2,即所有的卡牌都已经被消去了,那么游戏结束,打印“Congratulations!”。

控制部分(Controller)

判断是否将两张卡牌消去的主要逻辑实现

    //3.控制Components/Controller部分
    //判断是否将两张卡牌消去的主要逻辑实现
    IEnumerator Is_Fade_Away(){
        CanClick=false;
        List<int> counts=Find_element(1);
        if(counts.Count==2){
            int i_1=counts[0]/line_column;
            int j_1=counts[0]-i_1*line_column;
            int i_2=counts[1]/line_column;
            int j_2=counts[1]-i_2*line_column;

            if(table[i_1,j_1]==table[i_2,j_2]){
                yield return new WaitForSeconds(0.5f);
                Is_Click[i_1,j_1]=2;
                Is_Click[i_2,j_2]=2;
            }
            else{
                yield return new WaitForSeconds(0.5f);
                Is_Click[i_1,j_1]=0;
                Is_Click[i_2,j_2]=0;
            }
        }
        CanClick=true;
    }

判断卡牌是否消去,首先需要获取Is_Click中值为1的元素的位置,然后判断为1的元素有多少个,如果只有1个1,将不做处理,如果有2个1,那么将进行判断这两个位置上的数字是否相等。如果相等,就将Is_Click的值赋为2;如果不相等,就将Is_Click的值重新赋为0。同时注意到,为了限制在只能点击两张卡牌,即只能同时看到两张卡牌的数字,我们在进入到这一个函数的时候将CanClick设为了false,然后在退出这一个函数的时候将CanClick设为了true。同时,为了实现当两张卡牌不等时,为了增加记忆时间,我们将使用协程的方法使得函数在这里等待0.5秒的时间。

查找元素的个数以及返回所在位置

    //查找Is_Click里面值为1的元素有多少个,将位置返回
    List<int> Find_element(int element){
        List<int> counts=new List<int>();
        for(int i=0;i<line_column;i++){
            for(int j=0;j<line_column;j++){
                if(Is_Click[i,j]==element){
                    counts.Add(i*line_column+j);
                }
            }
        }
        return counts;
    }

这个函数用于查找在Is_Click中有多少个1以及返回每一个1所在的位置,我们将这些所在位置存在一个List中,并且将这一个List作为返回值返回。

判断游戏是否结束

    //判断游戏是否结束
    bool GameOver(){
        bool temp=true;
        for(int i=0;i<line_column;i++){
            for(int j=0;j<line_column;j++){
                if(Is_Click[i,j]!=2){
                    temp=false;
                    break;
                }
            }
        }
        return temp;
    }

当且仅当Is_Click里面的所有数据都是2时才会结束游戏,就算有一个不为2也会是返回false。

ECS(Entity Component System)的应用

虽然上述代码中没有明确实现ECS模式,但我们可以考虑如何将它引入游戏中。例如,我们可以创建一个Card组件,其中包含卡片的值、状态等信息,然后编写系统来处理卡片的翻转和匹配。这样,我们可以更好地分离游戏逻辑和数据。

结语

以上就是我们使用unity以及结合ECS和MVC概念制作而成的一个2D小游戏,其中MVC的三个部分明确详细,并且相互制约,相互联系。通过这一个小游戏,我们可以更加清晰地学会了ECS以及MVC概念的具体含义以及应用,也对于入门unity有了更加深刻的理解和帮助。那么,就让我们一起动起手来,使用unity制作一个属于你们的游戏吧!

代码汇总

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ConcerationGame : MonoBehaviour
{

    //1.模型Model部分
    //初始化model
    private List<int> numbers;
    private int[,] table;
    private int line_column=6;
    private int[,] Is_Click;
    private bool CanClick=true;

    // Start is called before the first frame update
    void Start()
    {   
        Init();
    }

    //初始化各种参数以及List和Table
    void Init(){
        //首先初始化table、List
        table=new int[line_column,line_column];
        Is_Click=new int[line_column,line_column];
        numbers=new List<int>();

        //将数字初始化到list中
        for(int i=1;i<=line_column*line_column/2;i++){
            numbers.Add(i);
            numbers.Add(i);
        }

        //随机洗牌,将列表中的数据随机打乱
        System.Random random = new System.Random();

        for (int i = 0; i < numbers.Count - 1; i++){
            int j = random.Next(i, numbers.Count);
            int temp = numbers[i];
            numbers[i] = numbers[j];
            numbers[j] = temp;
        }

        //将列表中的数据转换到表格中来
        //同时将Is_Click的所有数据初始化为0
        for(int i=0;i<line_column;i++){
            for(int j=0;j<line_column;j++){
                table[i,j]=numbers[i*line_column+j];
                Is_Click[i,j]=0;
            }
        }
    }


    //2.视图View部分
    void OnGUI(){
        GUI.Box(new Rect(255,50,70*line_column,70*line_column),"");
        if(GUI.Button(new Rect(215+line_column/2*70,65+line_column*70,100,30),"Restart")){
            Init();
        }
        for(int i=0;i<line_column;i++){
            for(int j=0;j<line_column;j++){
                if(Is_Click[i,j]==0&&GUI.Button(new Rect(255+j*70,50+i*70,70,70),"")&&CanClick){
                        Is_Click[i,j]=1;
                        StartCoroutine(Is_Fade_Away());
                }
                else{
                    if(Is_Click[i,j]==1){
                        GUI.Button(new Rect(255+j*70,50+i*70,70,70),table[i,j].ToString());
                    }
                }
            }
        }
        if(GameOver()){
            GUI.Box(new Rect(260,50,70*line_column,70*line_column),"\n\n\n\n\n\n\n\nCongratulations!\n");
        }
    }


    //3.控制Components/Controller部分
    //判断是否将两张卡牌消去的主要逻辑实现
    IEnumerator Is_Fade_Away(){
        CanClick=false;
        List<int> counts=Find_element(1);
        if(counts.Count==2){
            int i_1=counts[0]/line_column;
            int j_1=counts[0]-i_1*line_column;
            int i_2=counts[1]/line_column;
            int j_2=counts[1]-i_2*line_column;

            if(table[i_1,j_1]==table[i_2,j_2]){
                yield return new WaitForSeconds(0.5f);
                Is_Click[i_1,j_1]=2;
                Is_Click[i_2,j_2]=2;
            }
            else{
                yield return new WaitForSeconds(0.5f);
                Is_Click[i_1,j_1]=0;
                Is_Click[i_2,j_2]=0;
            }
        }
        CanClick=true;
    }

    //查找Is_Click里面值为1的元素有多少个,将位置返回
    List<int> Find_element(int element){
        List<int> counts=new List<int>();
        for(int i=0;i<line_column;i++){
            for(int j=0;j<line_column;j++){
                if(Is_Click[i,j]==element){
                    counts.Add(i*line_column+j);
                }
            }
        }
        return counts;
    }

    //判断游戏是否结束
    bool GameOver(){
        bool temp=true;
        for(int i=0;i<line_column;i++){
            for(int j=0;j<line_column;j++){
                if(Is_Click[i,j]!=2){
                    temp=false;
                    break;
                }
            }
        }
        return temp;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值