unity实现翻牌记忆匹配小游戏

一、游戏规则

  1. 游戏面板上有一定数量(偶数个)的方块,每个方块都有一个特定的图标或文字符号。
  2. 游戏开始时,所有方块都是背面朝上隐藏的。
  3. 玩家需要点击两个方块来翻开它们。如果这两个方块的标记相同,则这两个方块会保持翻 开状态;否则,在短暂展示后自动翻回来。
  4. 当所有方块都被成功匹配后,游戏结束。

二、游戏功能

  1. 游戏面板的动态创建和布局,可手动设置行列;
  2. 方块的翻转动画效果;
  3. 方块匹配逻辑的实现;
  4. 游戏结束的判定和处理;
  5. 可以根据需要进行进一步的功能扩展。
  6. 游戏包含一个主页面和游戏页面,可从首页点击开始游戏和点击游戏页按钮返回首页
  7. 进行数据持久化,并在游戏首页显示所记录的游戏次数、上次游戏成绩和一个数量上限为 10的历史成绩排行榜

三、实现结果

源码获取:unity记忆匹配小游戏资源-CSDN文库

四、实现过程

4.1 方块基类

这个类中用协程实现了方块翻转动画的方法。

public class Tile : MonoBehaviour
{
    private GameObject front;  // 正面,显示图标
    private GameObject back;   // 背面,隐藏图标
    private Sprite icon;      // 图标
    [SerializeField]
    private bool isFlipped = false;  // 标识方块是否翻转

    private void Awake()
    {
        front = transform.Find("Front").gameObject;
        back = transform.Find("Back").gameObject;
    }

    /// <summary>
    /// 设置图标
    /// </summary>
    /// <param name="newIcon"></param>
    public void SetIcon(Sprite newIcon)
    {
        icon = newIcon;
        front.GetComponent<Image>().sprite = icon;
    }

    /// <summary>
    /// 获取图标
    /// </summary>
    /// <returns></returns>
    public Sprite GetIcon()
    {
        return icon;
    }

    /// <summary>
    /// 初始化方块
    /// </summary>
    public void OnInit()
    {
        isFlipped = false;
        front.SetActive(isFlipped);
        back.SetActive(!isFlipped);
        transform.rotation = Quaternion.Euler(0, 0, 0);
    }

    /// <summary>
    /// 翻转方块
    /// </summary>
    public void Flip()
    {
        if(!isFlipped) StartCoroutine(FlipAnimation());
        else StartCoroutine(FlipBackAnimation());
    }

    /// <summary>
    /// 翻转动画,从背面翻转到正面
    /// </summary>
    /// <returns></returns>
    private IEnumerator FlipAnimation()
    {
        float duration = 0.5f;  // 动画持续时间
        float elapsed = 0f;     // 经过时间

        // 翻转动画过程
        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float angle = Mathf.Lerp(0, 90, elapsed / duration);
            transform.rotation = Quaternion.Euler(0, angle, 0);
            yield return null;
        }

        // 更新翻转状态
        isFlipped = !isFlipped;
        front.SetActive(isFlipped);
        back.SetActive(!isFlipped);

        // 继续翻转动画过程
        elapsed = 0f;
        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float angle = Mathf.Lerp(90, 180, elapsed / duration);
            transform.rotation = Quaternion.Euler(0, angle, 0);
            yield return null;
        }
    }

    /// <summary>
    /// 翻转动画,从正面翻转到背面
    /// </summary>
    /// <returns></returns>
    private IEnumerator FlipBackAnimation()
    {
        float duration = 0.5f;  // 动画持续时间
        float elapsed = 0f;     // 经过时间

        // 翻转动画过程
        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float angle = Mathf.Lerp(180, 90, elapsed / duration);
            transform.rotation = Quaternion.Euler(0, angle, 0);
            yield return null;
        }

        // 更新翻转状态
        isFlipped = !isFlipped;
        front.SetActive(isFlipped);
        back.SetActive(!isFlipped);

        // 继续翻转动画过程
        elapsed = 0f;
        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float angle = Mathf.Lerp(90, 0, elapsed / duration);
            transform.rotation = Quaternion.Euler(0, angle, 0);
            yield return null;
        }
    }

    /// <summary>
    /// 判断方块是否被翻转
    /// </summary>
    /// <returns></returns>
    public bool IsFlipped()
    {
        return isFlipped;
    }
}

4.2 面板基类

这个类为抽象类,每次创建新面板实例时,绑定组件和注册事件。每次调用展示面板方法时,进行初始化。

/// <summary>
/// 面板基类
/// </summary>
public abstract class PanelBase : MonoBehaviour
{
    /// <summary>
    /// 初始化面板
    /// </summary>
    public abstract void OnInit(params object[] args);

    /// <summary>
    /// 绑定面板组件
    /// </summary>
    public abstract void BindComponent();

    /// <summary>
    /// 注册事件
    /// </summary>
    public abstract void AddEvent();

    /// <summary>
    /// 显示面板
    /// </summary>
    public virtual void OnShow(params object[] args)
    {
        this.gameObject.SetActive(true);
        OnInit(args);
    }

    /// <summary>
    /// 隐藏面板
    /// </summary>
    public virtual void OnHide()
    {
        this.gameObject.SetActive(false);
    }

    public virtual void Start()
    {
        BindComponent();
        AddEvent();
        OnShow();
    }
}

4.3 面板管理类

这个类为单例模式,全局唯一管理已经创建的面板,使用了对象池,在关闭界面时隐藏界面的实例,而不是删除被GC回收,避免频繁创建和删除实例。

/// <summary>
/// 面板管理类
/// </summary>
public class PanelManager
{
    private Dictionary<string, PanelBase> panels = new Dictionary<string, PanelBase>();
    private Transform canvas = GameObject.Find("Canvas").transform;
    private static PanelManager _instance;
    public static PanelManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new PanelManager();
            }
            return _instance;
        }
    }

    /// <summary>
    /// 增加面板
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public void AddPanel<T>(params object[] args) where T : PanelBase
    {
        string name = typeof(T).Name;
        if (panels.ContainsKey(name))
        {
            panels[name].OnShow(args);
        }
        else
        {
            GameObject gameObject = Resources.Load<GameObject>("Prefabs/" + name);
            GameObject panel = GameObject.Instantiate(gameObject, canvas);
            PanelBase panelBase = panel.AddComponent<T>();
            panels.Add(typeof(T).Name, panelBase);
        }
    }

    /// <summary>
    /// 移除面板
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public void RemovePanel<T>() where T : PanelBase
    {
        string name = typeof(T).Name;
        if (panels.ContainsKey(name))
        {
            panels[name].OnHide();
        }
    }
}

4.4 主菜单界面类

继承面板基类,每次打开初始化游戏记录,有开始游戏和退出游戏两个按钮。

    /// <summary>
    /// 主菜单
    /// </summary>
    public class MainMenu : PanelBase
    {
        private Button beginBtn;
        private Button exitBtn;
        private Text scoreText;
        private Text countText;
        private Text rankText;
        public override void BindComponent()
        {
            beginBtn = transform.Find("Bg/BeginBtn").GetComponent<Button>();
            exitBtn = transform.Find("Bg/ExitBtn").GetComponent<Button>();
            scoreText = transform.Find("Bg/ScoreText").GetComponent<Text>();
            countText = transform.Find("Bg/CountText").GetComponent<Text>();
            rankText = transform.Find("Bg/RankText").GetComponent<Text>();
        }

        public override void AddEvent()
        {
            beginBtn.onClick.AddListener(() =>
            {
                PanelManager.Instance.RemovePanel<MainMenu>();
                PanelManager.Instance.AddPanel<GamePanel>();
            });
            exitBtn.onClick.AddListener(() =>
            {
#if UNITY_EDITOR
                UnityEditor.EditorApplication.isPlaying = false;
#else
    Application.Quit();
#endif
            });
        }

        public override void OnInit(params object[] args)
        {
            scoreText.text = "上次游玩分数:" + PlayerPrefs.GetInt("Score", 0).ToString();
            countText.text = "游玩次数:" + PlayerPrefs.GetInt("Count", 0).ToString();
            rankText.text = "";
            List<Record> records = JsonConvert.DeserializeObject<List<Record>>(PlayerPrefs.GetString("Records"));
            if (records != null)
            {
                for(int i = 0; i < 10; i++)
                {
                    if (i >= records.Count) break;
                    rankText.text += records[i].ToString() + "\n";
                }
            }
        }
    }

4.5 游戏界面类

继承面板基类,管理整个小游戏的进程,可在调用打开这个面板时设置四个参数,行数,列数,翻牌加分,错误扣分。使用了PlayerPrefs和Newtonsoft.Json进行数据持久化。

public class GamePanel : PanelBase
{
    private Button backBtn;

    private GameObject tilePrefab;
    private Transform group;
    [SerializeField]
    private int rows = 2;
    [SerializeField]
    private int columns = 6;
    private Sprite[] icons;
    private int iconNum = 10;

    private List<Tile> tiles = new List<Tile>();
    private Tile firstSelectedTile;
    private Tile secondSelectedTile;

    private int score = 0;
    private int sucScore = 10;
    private int failScore = -3;
    private Text scoreText;
    public override void BindComponent()
    {
        backBtn = transform.Find("Bg/BackBtn").GetComponent<Button>();
        tilePrefab = Resources.Load<GameObject>("Prefabs/Tile");
        group = transform.Find("Bg/Group").GetComponent<Transform>();
        scoreText = transform.Find("Bg/ScoreText").GetComponent<Text>();
        icons = new Sprite[iconNum];
        for (int i = 0; i < iconNum; i++)
            icons[i] = Resources.Load<Sprite>("Sprites/Tile" + (i + 1));
    }

    public override void AddEvent()
    {
        backBtn.onClick.AddListener(()=>
        {
            RecordScore();
            PanelManager.Instance.AddPanel<MainMenu>();
            PanelManager.Instance.RemovePanel<GamePanel>();
        });
    }

    public override void OnInit(params object[] args)
    {
        if(args.Length >= 2)
        {
            rows = (int)args[0];
            columns = (int)args[1];
            if(args.Length >= 4)
            {
                sucScore = (int)args[2];
                failScore = (int)args[3];
            }
        }

        SetScore(-score);
        if (rows * columns / 2 > iconNum)
        {
            Debug.LogError("图标数量不足");
            OnHide();
            PanelManager.Instance.AddPanel<MainMenu>();
            return;
        }
        if (rows * columns % 2 != 0)
        {
            Debug.LogError("方块数量必须为偶数");
            OnHide();
            PanelManager.Instance.AddPanel<MainMenu>();
            return;
        }
        SetGrid();
        CreateGameBoard();
        AssignIcons();
        firstSelectedTile = null;
        secondSelectedTile = null;

    }

    /// <summary>
    /// 设置排列
    /// </summary>
    private void SetGrid()
    {
        GridLayoutGroup g = group.GetComponent<GridLayoutGroup>();
        g.constraint = GridLayoutGroup.Constraint.FixedRowCount;
        g.constraintCount = rows;
    }

    /// <summary>
    /// 创建多个方块
    /// </summary>
    private void CreateGameBoard()
    {
        if(tiles.Count == 0)
        {
            for (int i = 0; i < rows * columns; i++)
            {
                CreateTile();
            }
        }
        else
        {
            //如果要创建的方块数量大于当前方块数量,则需要创建新的方块
            if(rows * columns > tiles.Count)
            {
                for(int i = tiles.Count; i < rows * columns; i++)
                {
                    CreateTile();
                }
            }
            else
            {
                //如果创建的方块数量小于当前方块数量,则需要令多余的方块隐藏
                for(int i = tiles.Count - 1; i >= rows * columns; i--)
                {
                    tiles[i].gameObject.SetActive(false);
                }
            }
            for(int i = 0; i < rows * columns; i++)
            {
                tiles[i].gameObject.SetActive(true);
                tiles[i].OnInit();
            }
        }
    }

    /// <summary>
    /// 创建方块
    /// </summary>
    private void CreateTile()
    {
        GameObject tileObject = Instantiate(tilePrefab, group);
        Tile tile = tileObject.AddComponent<Tile>();
        tile.GetComponent<Button>().onClick.AddListener(() => OnTileClicked(tile));
        tiles.Add(tile);
    }

    /// <summary>
    /// 分配方块图标
    /// </summary>
    private void AssignIcons()
    {
        List<Sprite> iconsList = new List<Sprite>();
        for (int i = 0;i<rows*columns/2;i++)
        {
            iconsList.Add(icons[i]);
            iconsList.Add(icons[i]);
        }
        
        ListRandom<Sprite>(iconsList);

        for (int i = 0; i < rows * columns; i++)
        {
            tiles[i].SetIcon(iconsList[i]);
        }
    }

    /// <summary>
    /// 点击方块
    /// </summary>
    /// <param name="clickedTile"></param>
    private void OnTileClicked(Tile clickedTile)
    {
        if (firstSelectedTile == null)
        {
            firstSelectedTile = clickedTile;
            firstSelectedTile.Flip();
        }
        else if (secondSelectedTile == null && clickedTile != firstSelectedTile)
        {
            secondSelectedTile = clickedTile;
            secondSelectedTile.Flip();
            StartCoroutine(CheckMatch());
        }
    }

    /// <summary>
    /// 检查匹配
    /// </summary>
    /// <returns></returns>
    private IEnumerator CheckMatch()
    {
        yield return new WaitForSeconds(1f);

        if (firstSelectedTile.GetIcon() == secondSelectedTile.GetIcon())
        {
            SetScore(sucScore);
            firstSelectedTile = null;
            secondSelectedTile = null;
        }
        else
        {
            SetScore(failScore);
            firstSelectedTile.Flip();
            secondSelectedTile.Flip();
            firstSelectedTile = null;
            secondSelectedTile = null;
        }

        if (CheckGameEnd())
        {
            RecordScore();
            PanelManager.Instance.AddPanel<WinPanel>();
            OnHide();
        }
    }

    /// <summary>
    /// 判断游戏是否结束
    /// </summary>
    /// <returns></returns>
    private bool CheckGameEnd()
    {
        for(int i=0;i<rows*columns;i++)
        {
            if (!tiles[i].IsFlipped())
            {
                return false;
            }
        }
        return true;
    }

    /// <summary>
    /// 设置得分
    /// </summary>
    /// <param name="num"></param>
    private void SetScore(int num)
    {
        score += num;
        scoreText.text = "得分:" + score;
    }

    /// <summary>
    /// 记录分数
    /// </summary>
    private void RecordScore()
    {
        PlayerPrefs.SetInt("Score", score);
        int count = PlayerPrefs.GetInt("Count", 0);
        count++;
        PlayerPrefs.SetInt("Count", count);
        List<Record> records = JsonConvert.DeserializeObject<List<Record>>(PlayerPrefs.GetString("Records"));
        if (records == null) records = new List<Record>();
        records.Add(new Record(score));
        records.Sort((a, b) => b.score - a.score);
        PlayerPrefs.SetString("Records", JsonConvert.SerializeObject(records));
    }

    /// <summary>
    /// 随机打乱顺序
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sources"></param>
    private static void ListRandom<T>(List<T> sources)
    {
        System.Random rd = new System.Random();
        int index = 0;
        T temp;
        for (int i = 0; i < sources.Count; i++)
        {
            index = rd.Next(0, sources.Count - 1);
            if (index != i)
            {
                temp = sources[i];
                sources[i] = sources[index];
                sources[index] = temp;
            }
        }
    }
}

4.6 获胜界面类

继承了面板基类,总共两个按钮,继续游戏和返回主菜单。

public class WinPanel : PanelBase
{
    private Button continueBtn;
    private Button backBtn;

    public override void BindComponent()
    {
        continueBtn = transform.Find("Bg/ContinueBtn").GetComponent<Button>();
        backBtn = transform.Find("Bg/BackBtn").GetComponent<Button>();
    }

    public override void AddEvent()
    {
        continueBtn.onClick.AddListener(()=>
        {
            PanelManager.Instance.AddPanel<GamePanel>();
            OnHide();
        });
        backBtn.onClick.AddListener(()=>
        {
            PanelManager.Instance.AddPanel<MainMenu>();
            OnHide();
        });
    }

    public override void OnInit(params object[] args)
    {
        
    }
}

4.7 记录数据类

里面有一个int型参数的构造函数,时间自动获取当前时间,重写了ToString方法。

/// <summary>
/// 记录类,用于存储时间和分数
/// </summary>
public class Record
{
    public string time;
    public int score;

    public Record(int score)
    {
        time = System.DateTime.Now.ToString("G");
        this.score = score;
    }

    public override string ToString()
    {
        return $"{time} | {score}";
    }
}

五、测试过程

5.1 场景

5.2 美术资源

5.3 测试方法

获取到面板管理类的实例来打开主菜单的界面。

public class ThirdTest : TestBase
{
    public override void TestMethod()
    {
        PanelManager.Instance.AddPanel<MainMenu>();

        //扩展(row=2,col=4)
        //PanelManager.Instance.AddPanel<GamePanel>(2,4);
        //扩展(row=2,col=4,sucScore=5,failScore=-2)
        //PanelManager.Instance.AddPanel<GamePanel>(2,4,5,-2);
    }

}
  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要使用Unity实现俄罗斯方块的小游戏,您可以按照以下步骤进行: 步骤1:创建游戏场景 在Unity中创建一个新的场景,并设置适当的摄像机和灯光。您可以使用2D或3D的方式来实现俄罗斯方块,具体取决于您的需求和偏好。 步骤2:创建游戏对象和脚本 创建俄罗斯方块的各种游戏对象,如方块、游戏区域、下落点等。然后,为每个游戏对象创建相应的脚本来控制它们的行为。 步骤3:实现方块的下落和移动 在游戏脚本中,实现方块的下落和移动逻辑。您可以使用定时器或帧更新来控制方块的下落速度,以及使用输入控制方块的左右移动和旋转。 步骤4:检测碰撞和消除行 实现方块与游戏区域的碰撞检测,以及行的消除逻辑。当方块落到底部或与其他方块碰撞时,将其固定在游戏区域中,并检查是否有完整的行可以消除。 步骤5:游戏结束和重置 实现游戏结束和重置逻辑。当方块堆积到达游戏区域的顶部时,游戏结束。您可以显示分数或其他游戏结束的界面,并提供重新开始游戏的选项。 步骤6:美化和音效 添加适当的图形和音效来提升游戏的体验。您可以使用精灵或模型来渲染方块,添加背景音乐和音效来增强游戏的氛围。 以上是一个基本的实现俄罗斯方块小游戏的步骤。您可以根据自己的需求和创意来扩展和改进游戏。祝您实现一个有趣而成功的俄罗斯方块小游戏

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

winlife_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值