资讯动态

Unity2D翻牌记忆游戏完整工程与C#脚本实现详解

发布时间:2026/10/15 1:21:49 来源:尧图企业网站定制
简介这份资源是面向Unity初学者与2D小游戏开发爱好者的记忆翻牌游戏完整源代码基于Unity 2019.4.18f1c1版本与C#语言实现可帮助读者理解翻牌配对类休闲游戏的完整开发流程。项目围绕牌面翻转与匹配展开涵盖OnMouseDown点击事件处理、枚举状态管理、Sprite渲染切换、Collider碰撞检测、脚本间通信、UI界面搭建以及Animator翻转动画等核心知识点同时涉及单元测试框架与Plastic SCM版本管理等工程化内容。压缩包整体约62.47MB文件类型以C#脚本、Unity场景与资源文件为主配合项目配置与测试相关文件结构完整便于直接导入学习。目前已有2022人学习下载适合希望从零掌握Unity 2D开发、提升C#编程与引擎使用能力的入门及进阶开发者参考。1. 翻牌记忆游戏从一堆散乱脚本到能跑通的 Unity2D 工程很多人第一次接触 Unity2D 小游戏开发都是从翻牌记忆这类项目入手的。它规则简单——点开两张牌图案一致就消除不一致就翻回去——但真正动手写的时候你会发现卡住新手的从来不是玩法逻辑而是「牌怎么摆」「点击怎么响应」「翻回去的时机怎么控制」这些琐碎问题。这份记忆翻牌源代码C#就是冲着这些痛点来的它把一套完整的、能直接跑起来的 Unity2D 工程拆成了若干职责清晰的 C# 脚本涵盖卡牌生成、点击检测、配对判定、动画翻转和关卡重置。适合两类人一是刚学完 Unity 基础、想找一个完整小项目练手的入门开发者二是需要快速搭一个翻牌玩法原型、再往上叠美术和数值的从业者。下面我按「拿到工程怎么跑 → 核心脚本怎么读 → 参数怎么调 → 哪里容易翻车」的顺序把这份资源拆开讲一遍。2. 工程结构与运行链路先让场景跑起来再谈改代码2.1 目录里有什么各自负责哪一段拿到这份源代码第一件事不是急着打开脚本而是先认清工程目录的职责划分。一个能跑的记忆翻牌工程通常包含这几个关键部分Scenes文件夹放主场景Scripts放全部 C# 逻辑Prefabs放卡牌预制体Sprites或Textures放卡面图案资源Audio放翻牌和配对的音效。卡牌预制体是整个玩法的物理载体它身上挂着碰撞体用于点击检测和至少一个控制脚本用于翻转和状态记录。我一般会先打开主场景看 Hierarchy 里的对象层级通常有一个空物体作为棋盘容器Board下面动态生成 N 张卡牌还有一个 GameManager 之类的常驻对象负责全局状态。理解这条链路比逐行读代码重要得多——因为翻牌游戏的 bug 十有八九出在「谁在什么时候通知谁」上。提示如果打开场景后 Hierarchy 里只有零星几个对象卡牌是运行时生成的这是正常设计不要以为工程缺东西。2.2 从点击到配对一条完整的调用链把工程跑起来之前先在心里过一遍调用链后面读代码会顺很多。典型流程是这样的玩家点击某张卡牌 → 卡牌的碰撞体触发OnMouseDown或射线检测 → 脚本把这张牌加入「已翻开」列表 → 判断列表长度是否达到 2 → 达到就比对两张牌的 ID → 相同则标记为已配对并播放消除效果不同则延迟一小段时间后翻回去。这条链路里有两个容易忽略的点。第一判断「已翻开」不能只看动画状态要用一个独立的布尔字段记录逻辑状态否则动画播放中途连点会出问题。第二比对完成后必须清空临时列表否则第三次点击会拿旧数据去比。这两点在下面的代码里都会体现。2.3 让工程跑起来的最小步骤在 Unity Hub 里用对应版本打开工程文件夹等待资源导入完成。如果版本差异导致报错优先看 Console 里的第一条红字通常是 API 命名或包缺失。导入完成后在 Project 窗口找到主场景双击打开点播放按钮即可试玩。# 工程根目录的典型结构用命令行快速确认关键文件夹是否齐全 ls -R Assets | head -40 # 预期能看到 Scenes / Scripts / Prefabs / Sprites 这几类目录这段命令只是帮你确认资源完整性不涉及任何构建操作。如果Scripts目录为空说明工程可能只打包了场景没带脚本需要重新核对资源包。确认目录齐全后检查ProjectSettings里的输入设置确保鼠标点击能被正常捕获——有些工程默认只开了触摸输入PC 上点不动就是这个原因。3. 核心脚本拆解卡牌状态机与配对判定怎么写3.1 卡牌脚本一个字段管状态一个方法管翻转卡牌脚本是整个工程的心脏。它要维护的核心状态其实就三个这张牌的图案 ID、它当前是否正面朝上、它是否已经被配对消除。很多人图省事用旋转角度去反推是否翻开结果动画一卡就判断错。正确做法是用独立的布尔字段。using UnityEngine; using System.Collections; public class Card : MonoBehaviour { public int cardId; // 图案 ID配对时比对这个值 public Sprite frontSprite; // 正面图案 public Sprite backSprite; // 背面图案 private bool isFaceUp false; // 逻辑状态不依赖动画 private bool isMatched false; // 已配对标记防止重复点击 private SpriteRenderer sr; void Start() { sr GetComponentSpriteRenderer(); sr.sprite backSprite; } // 由 GameManager 调用而不是自己处理点击 public void Flip() { if (isMatched || isFaceUp) return; // 已配对或已翻开直接忽略 StartCoroutine(FlipRoutine()); } private IEnumerator FlipRoutine() { isFaceUp true; // 这里用缩放模拟翻转实际工程可能用旋转或动画 for (float t 0; t 0.15f; t Time.deltaTime) { transform.localScale new Vector3( Mathf.Lerp(1f, 0f, t / 0.15f), 1f, 1f); yield return null; } sr.sprite frontSprite; for (float t 0; t 0.15f; t Time.deltaTime) { transform.localScale new Vector3( Mathf.Lerp(0f, 1f, t / 0.15f), 1f, 1f); yield return null; } } public void FlipBack() { if (isMatched) return; isFaceUp false; sr.sprite backSprite; } public void SetMatched() { isMatched true; // 配对成功后的表现比如淡出或缩小消失 StartCoroutine(FadeOut()); } private IEnumerator FadeOut() { for (float a 1f; a 0f; a - Time.deltaTime * 3f) { var c sr.color; c.a a; sr.color c; yield return null; } gameObject.SetActive(false); } }逻辑说明Flip方法里先做双重拦截——已配对或已翻开就 return这是防止连点翻车的核心。翻转用协程分两段做前半段缩到 0 换图后半段再展开视觉上就是翻牌。isFaceUp在协程一开始就置为 true而不是等动画结束这样在动画播放期间再次点击会被正确拦截。参数说明cardId是配对比对的唯一依据生成卡牌时必须成对赋值frontSprite和backSprite在 Inspector 里拖入或者由 GameManager 运行时赋值翻转时长 0.15 秒是手感经验值调大到 0.3 会显得拖沓调小到 0.05 又看不清翻转过程。3.2 游戏管理脚本生成棋盘与配对判定GameManager 负责三件事开局生成打乱的卡牌、接收卡牌点击、判定配对。它持有一个「当前已翻开卡牌」的列表长度到 2 就触发比对。using UnityEngine; using System.Collections; using System.Collections.Generic; public class GameManager : MonoBehaviour { public GameObject cardPrefab; public Sprite[] cardSprites; // 所有图案长度应为偶数 public Transform boardRoot; public int rows 4; public int cols 4; private ListCard flippedCards new ListCard(); private bool isChecking false; // 比对期间锁住输入 void Start() { GenerateBoard(); } void GenerateBoard() { int total rows * cols; // 每个图案出现两次凑成 total 张 Listint ids new Listint(); for (int i 0; i total / 2; i) { ids.Add(i); ids.Add(i); } // 洗牌 for (int i 0; i ids.Count; i) { int r Random.Range(i, ids.Count); (ids[i], ids[r]) (ids[r], ids[i]); } for (int i 0; i total; i) { var go Instantiate(cardPrefab, boardRoot); var card go.GetComponentCard(); card.cardId ids[i]; card.frontSprite cardSprites[ids[i]]; // 按行列摆放间距根据卡牌尺寸调整 int row i / cols; int col i % cols; go.transform.localPosition new Vector3(col * 1.2f, -row * 1.2f, 0); } } // 卡牌点击后调用此方法 public void OnCardClicked(Card card) { if (isChecking) return; if (flippedCards.Contains(card)) return; card.Flip(); flippedCards.Add(card); if (flippedCards.Count 2) { StartCoroutine(CheckMatch()); } } private IEnumerator CheckMatch() { isChecking true; yield return new WaitForSeconds(0.6f); // 留时间给玩家看第二张牌 if (flippedCards[0].cardId flippedCards[1].cardId) { flippedCards[0].SetMatched(); flippedCards[1].SetMatched(); } else { flippedCards[0].FlipBack(); flippedCards[1].FlipBack(); } flippedCards.Clear(); isChecking false; } }逻辑说明GenerateBoard先把图案 ID 成对塞进列表再洗牌保证每种图案恰好两张。OnCardClicked里用flippedCards.Contains防止同一张牌被点两次。CheckMatch用isChecking锁住输入避免比对动画期间玩家又点第三张牌导致列表错乱。参数说明rows和cols决定棋盘规模4×4 是入门默认值改成 4×6 需要保证cardSprites长度至少为 12WaitForSeconds(0.6f)是翻回去前的停顿太短玩家看不清第二张牌太长会显得卡顿卡牌间距 1.2 是相对卡牌尺寸的经验值卡牌变大要同步调大。3.3 点击检测射线还是碰撞体选哪个Unity2D 里检测点击有两种常见做法。一是给卡牌挂BoxCollider2D用OnMouseDown回调二是从摄像机发射射线用Physics2D.Raycast命中判断。前者写起来快适合原型后者更可控适合需要区分 UI 和场景点击的场合。这份工程用的是碰撞体方案因为卡牌本身就是独立对象OnMouseDown直接挂在卡牌脚本上最省事。但要注意OnMouseDown依赖主摄像机如果场景里有多个摄像机或者摄像机没开Physics2D相关设置点击会失效。我一般会在卡牌脚本里加一句转发把点击事件交给 GameManager 统一处理而不是让卡牌自己判断配对——职责分离后改玩法时不用动卡牌脚本。// 挂在卡牌预制体上转发点击给管理器 void OnMouseDown() { if (GameManager.Instance ! null) GameManager.Instance.OnCardClicked(this); }这里用单例Instance是为了让卡牌能拿到管理器引用。如果你的工程不想用单例也可以在生成卡牌时把管理器引用注入进去效果一样。4. 参数调优与手感打磨让翻牌不像在点按钮4.1 翻转动画的时长与曲线翻牌手感好不好八成看动画。上面代码用的是线性缩放实际调的时候建议换成缓动曲线。Unity 的AnimationCurve可以在 Inspector 里可视化编辑把翻转前半段调快、后半段调慢会有「啪」一下翻过去的感觉。翻转总时长控制在 0.2 到 0.3 秒之间比较舒服低于 0.15 秒玩家来不及反应高于 0.4 秒会显得拖沓。配对成功的消除动画同理。直接SetActive(false)太生硬加一个 0.2 秒的缩小加淡出玩家会有明确的「消掉了」的反馈。失败翻回去的动画可以比翻开稍快一点暗示「这张不对赶紧翻回去」。4.2 棋盘布局与卡牌尺寸的匹配棋盘布局不是随便摆的。卡牌间距太小会误触太大又显得松散。经验公式是间距 卡牌宽度 × 1.2 到 1.4。如果卡牌是 1×1 单位间距取 1.2 到 1.4卡牌是 2×2间距就取 2.4 到 2.8。行列数也要考虑屏幕比例4×4 在竖屏上偏挤横屏上刚好如果目标平台是手机竖屏3×4 或 4×5 更合适。另外棋盘整体要居中。生成时算一下总宽高把boardRoot的位置偏移到屏幕中心否则卡牌会从左上角开始铺右边和下面留一大片空白。4.3 音效与反馈的时机音效不是锦上添花是手感的一部分。翻牌音效要在翻转动画开始的瞬间播放配对成功音效在判定成立的瞬间播放失败音效在翻回去之前播放。三个音效的时机差个零点几秒玩家的感受完全不同。常见做法是把音效播放也放进协程和动画节点对齐而不是在逻辑判断里随手播。注意音效文件不要直接拖到场景里播用AudioSource.PlayOneShot在脚本里控制时机否则容易出现音效和动画对不上的情况。5. 避坑与排查翻牌游戏最容易翻车的五个地方5.1 连点导致翻开三张牌现象玩家手快在两张牌还没比对完的时候点了第三张结果三张牌都翻开了配对逻辑彻底乱掉。原因OnCardClicked里没有输入锁或者锁的时机不对。isChecking只在CheckMatch协程里置位但协程启动前有个空档。解决在flippedCards.Count 2成立的瞬间就置isChecking true而不是等协程里再置。同时OnCardClicked开头就判断isChecking双保险。5.2 同一张牌被点两次算作一对现象玩家双击同一张牌系统判定为「翻开两张」然后拿这张牌和自己比对居然配对了。原因flippedCards里没有去重或者去重判断写在Flip之后。解决在OnCardClicked里先判断flippedCards.Contains(card)包含就直接 return不执行任何翻转和添加操作。5.3 翻回去的动画和逻辑状态不同步现象牌翻回去了但isFaceUp还是 true导致这张牌再也点不开。原因FlipBack只改了图片没改状态字段或者状态字段在协程里改但协程被打断了。解决FlipBack里第一件事就是把isFaceUp false再改图片。所有状态变更都放在逻辑层动画层只负责表现不要用动画状态反推逻辑状态。5.4 卡牌生成后位置重叠现象所有卡牌都堆在同一个位置只能看到最上面一张。原因生成时没有按行列计算偏移localPosition全是默认值。解决在生成循环里用i / cols和i % cols算出行列再乘以间距。注意Instantiate时父物体设为boardRoot这样localPosition才是相对棋盘的坐标。5.5 图案数量不够导致索引越界现象Console 报IndexOutOfRangeException卡牌生成到一半就停了。原因cardSprites数组长度小于rows * cols / 2cardSprites[ids[i]]取不到值。解决生成前先校验cardSprites.Length rows * cols / 2不满足就在 Console 里打一条明确的错误提示而不是让它默默崩掉。这个校验我每次都会加血泪经验。6. 进阶技巧把翻牌工程改成可配置的关卡系统基础版跑通之后下一步通常是让它支持多关卡。与其复制多份场景不如把棋盘参数抽成配置。常见做法是定义一个LevelConfig类用ScriptableObject存每关的行列数、图案集合、限时和步数上限GameManager 开局读配置生成棋盘。using UnityEngine; [CreateAssetMenu(fileName LevelConfig, menuName Memory/LevelConfig)] public class LevelConfig : ScriptableObject { public int rows 4; public int cols 4; public Sprite[] sprites; // 本关可用图案 public float timeLimit 60f; // 限时0 表示不限 public int maxMoves 0; // 步数上限0 表示不限 }逻辑说明CreateAssetMenu让你能在 Project 窗口右键直接创建配置文件每关一个 asset。GameManager 里把rows、cols、cardSprites替换成从LevelConfig读取就能做到不改代码换关卡。参数说明timeLimit和maxMoves是两种不同的压力设计限时考验反应限步考验记忆策略一般二选一即可同时开会让难度陡增。sprites数组长度同样要满足rows * cols / 2的约束这个校验建议写进OnValidate里在 Inspector 改参数时就报错而不是运行时才崩。验证关卡配置是否生效最简单的办法是在 GameManager 的Start里打一行日志输出当前关卡的行列和图案数量跑起来看 Console 对不对。我习惯每加一个配置项就加一条这样的日志确认无误后再删掉。从那以后我每次拿到一个能跑的工程都强制先跑通再改代码绝不在一堆报错的情况下动逻辑。希望这份拆解能帮你少走点弯路把翻牌这个小玩法真正吃透。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑