Unity3D简单小游戏毕设:从零实现一个可扩展的2D平台跳跃原型
摘要:许多计算机专业学生在毕业设计中选择Unity3D开发简单小游戏,却常因缺乏工程化思维导致项目结构混乱、功能难以扩展。本文以2D平台跳跃游戏为案例,系统讲解如何基于组件化设计与状态机模式构建清晰、可维护的Unity3D简单小游戏毕设原型。读者将掌握场景管理、角色控制、碰撞检测等核心模块的标准化实现,并获得一套可复用的代码模板,显著提升开发效率与答辩表现。
1. 背景痛点:毕设里最容易踩的五个坑
先把我当年踩过的坑摆出来,省得大家再掉进去。
- 脚本耦合:Player 里直接
Find("Enemy"),Enemy 里又GetComponent<Player>(),改一个变量,全线爆红。 - 无版本控制:文件夹命名“最终版”“最终版2”“最终版2-真的最终”,最后连自己都分不清。
- 性能盲区:手机一跑 15 FPS,才发现每张 2048×2048 的背景图都没压缩。
- 场景堆砌:Level1 到 Level10 全是
.unity场景,公共 UI 一改就要手动同步 10 次。 - 硬编码魔法数字:跳跃高度写死
jumpForce = 5.2f,老师一句“能不能调高点”就重新打包。
一句话:写完 demo 一时爽,答辩提问火葬场。
2. 技术选型:MonoBehaviour 一把梭 vs. ScriptableObject + 事件驱动
| 维度 | 纯 MonoBehaviour | ScriptableObject + 事件 |
|---|---|---|
| 耦合度 | 高,直接引用 | 低,靠事件或 SO 数据 |
| 配置灵活 | 要改代码 | 改.asset 文件即可 |
| 复用性 | 场景里拖来拖去 | 数据资产可跨项目 |
| 学习曲线 | 低 | 需理解 SO、Observer |
结论:毕设周期 4–6 周,建议“混合打法”——角色、关卡、UI 各写一个核心 Manager,配置数据全放 SO,既保证速度,又能体现“可扩展”亮点,答辩很加分。
3. 核心实现:三大模块标准化
下面所有代码均基于 Unity 2021 LTS,2D 模板新建工程即可跑通。
3.1 角色控制器(Rigidbody2D + CapsuleCollider2D)
- 把 Player 标签设为
Player,图层设为Default。 - 刚体类型选
Dynamic,重力缩放 3.5,碰撞检测Continuous。 - 用
Physics2D.queriesStartInColliders = false防止射线误判。
// PlayerController.cs using UnityEngine; [RequireComponent(typeof(Rigidbody2D))] public class PlayerController : MonoBehaviour { [Header("Movement")] [SerializeField] private float moveSpeed = 6f; [SerializeField] private float jumpForce = 11f; [SerializeField] private LayerMask groundMask; private Rigidbody2D rb; private float horizontal; private bool jumpReq; private bool isGrounded; private readonly Collider2D[] groundCols = new Collider2D[1]; void Awake() => rb = GetComponent<Rigidbody2D>(); void Update() { horizontal = Input.GetAxisRaw("Horizontal"); if (Input.GetButtonDown("Jump")) jumpReq = true; } void FixedUpdate() { CheckGround(); Move(); if (jumpReq) { Jump(); jumpReq = false; } } void CheckGround() { // 脚底短射线 isGrounded = Physics2D.OverlapCircleNonAlloc( transform.position + Vector3.down * 0.55f, 0.45f, groundCols, groundMask) > 0; } void Move() { rb.velocity = new Vector2(horizontal * moveSpeed, rb.velocity.y); } void Jump() { if (!isGrounded) return; rb.velocity = new Vector2(rb.velocity.x, jumpForce); } }单一职责:输入、地面检测、移动、跳跃四件事拆成私有方法,方便单元测试。
3.2 关卡加载机制(ScriptableObject 配置 + 单例管理)
- 新建
LevelDataSO.cs
[CreateAssetMenu(menuName = "SO/LevelData")] public class LevelDataSO : ScriptableObject { public string sceneName; public int buildIndex; public Sprite thumbnail; }- 新建
LevelManager.cs
public class LevelManager : MonoBehaviour { public static LevelManager Instance { get; private set; } [SerializeField] private LevelDataSO[] levels; private int currentIndex = 0; void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); } public void LoadNext() { currentIndex = (currentIndex + 1) % levels.Length; SceneManager.LoadScene(levels[currentIndex].buildIndex); } public void Reload() => SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); }好处:新增关卡只需在.asset 里拖一条数据,0 行代码改动。
3.3 UI 状态管理(基于事件,彻底解耦)
- 定义全局事件
public static class GameEvents { public static System.Action OnPlayerDeath; public static System.Action OnLevelComplete; }- UI 面板订阅事件即可,无需引用 Player。
public class GameOverPanel : MonoBehaviour { [SerializeField] private Button restartBtn; void OnEnable() => GameEvents.OnPlayerDeath += Show; void OnDisable() => GameEvents.OnPlayerDeath -= Show; void Show() => restartBtn.gameObject.SetActive(true); }4. 性能考量:让老手机也能 60 FPS
Draw Call 优化
- 把同图集的小图打Sprite Atlas,静态背景勾
Static,合批一次搞定。 - 粒子发射器最大粒子数 ≤ 50,关闭
Collision模块。
- 把同图集的小图打Sprite Atlas,静态背景勾
对象池应用
玩家死亡会爆三枚金币,金币预设体用对象池:
public class CoinPool : MonoBehaviour { [SerializeField] private Coin coinPrefab; private readonly Queue<Coin> pool = new Queue<Coin>(10); public Coin Get() { if (pool.Count == 0) return Instantiate(coinPrefab); return pool.Dequeue(); } public void Return(Coin c) { c.gameObject.SetActive(false); pool.Enqueue(c); } }- 移动端适配
- 分辨率适配选
Constant Pixel Size,基准 720×1280。 - 音频采样率统一 22 kHz,单声道,压缩格式 Vorbis,大小砍半。
- 分辨率适配选
5. 生产环境避坑指南
资源命名
统一类型_功能_编号,例如spr_platform_grass_01、snd_jump_01,检索时一眼定位。Git 忽略
在.gitignore末尾追加:
[Ll]ibrary/ [Tt]emp/ [Oo]bj/ [Bb]uild/ /UserSettings/ *.pidb *.pidb.meta- 打包陷阱
- 安卓最低 API 选 22,Target API 选 33,避免 Google Play 强制警告。
- 勾选
Strip Engine Code,IL2CPP 编译后包体减 20 MB。 - 场景列表里一定把
LevelDataSO用到的场景拖到Build Settings,否则加载会崩。
6. 可扩展方向 & 作业
- 存档系统:用
JsonUtility把LevelManager.currentIndex与玩家命数存到Application.persistentDataPath,下次打开自动续关。 - 粒子特效:给跳跃落地加一圈灰尘,死亡加星星飘散,资源商店搜“Cartoon FX”免费包即可。
- 把工程推到 GitHub,README 附一张 GIF 动图,issue 区互相 review,答辩时直接甩链接,老师点赞。
我的仓库目录结构,供参考。
7. 小结
整篇下来,你得到的是一套“能跑、能改、能吹”的 2D 平台跳跃模板:
- 脚本低耦合,事件驱动,老师问“如何扩展”时不再慌。
- 配置数据全 SO,改数值不用重新编译。
- 对象池 + Atlas 合批,低端机也能稳住 60 FPS。
下一步,把存档或粒子玩出新花样,再 Push 到 GitHub,欢迎贴链接到评论区一起交流。祝大家毕设答辩一次过,代码写着写着就毕业!