尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

超级马里奥Unity源码全解析:碰撞、跳跃与构建实战

超级马里奥Unity源码全解析:碰撞、跳跃与构建实战 简介这份Unity源码包提供了经典“超级马里奥”在Unity引擎中的完整实现面向想通过真实项目入门2D游戏开发的学习者也适合有经验的开发者研究经典玩法的复刻思路。压缩包内共974个文件大小约4.89MB以C#脚本cs、动画anim、controller、预制体prefab、场景资源unity、asset及音频mp3等为主并包含文档说明md、txt和项目配置信息meta、info整体结构完整目录清晰易于按模块查阅。资源已吸引569人学习下载。通过源码可以深入理解Unity基于组件的架构、角色控制与碰撞检测、动画状态机切换、音频播放以及关卡场景搭建等关键实现。作者在配套解析中还梳理了马里奥角色类、世界管理、增强道具等核心逻辑对初学C#脚本编程和理解游戏循环也很有帮助是一份兼具学习与参考价值的经典复刻项目源码无论是学习组件式开发还是参考2D平台跳跃游戏的制作流程都能从中获得实用收获。1. 超级马里奥Unity源码.zip先看清这份资源能替你解决什么在资源站下载量靠前的游戏源码包里超级马里奥Unity源码.zip 是典型的能跑、能改、能抄的 2D 平台跳跃工程。常见内容是一整套 Unity 项目目录主场景、玩家控制脚本、敌人巡逻逻辑、砖块与金币预制体以及用 Tilemap 拼好的关卡。它的价值不在能玩到马里奥而在于把横版动作游戏绕不开的三件事——跳跃手感、碰撞分层的踩踏判定、Tilemap 关卡搭建——用短小的 C# 脚本摆在你面前。适合两类人想三天内跑通一个平台游戏雏形的 Unity 新手和想采样一套玩家控制与碰撞方案、再换皮做自己项目的一线开发。下面按处理这类工程的实际顺序推进解压对齐版本、调角色手感、改碰撞逻辑、构建与回包每一步给命令、给参数、给排错方向。2. 解压、版本对齐与项目结构打开unity源码包的第一步2.1 先别双击用命令行看 zip 里装了什么拿到压缩包先不要急着解压到当前目录用unzip -l只读列表能第一时间判断这份源码是完整工程还是片段# 列出包内容不解压 unzip -l 超级马里奥unity源码.zip # 解压到独立目录避免覆盖同名工程 unzip 超级马里奥unity源码.zip -d mario-project # 查看工程要求的 Unity 编辑器版本 cat mario-project/ProjectSettings/ProjectVersion.txt参数说明-l只列内容不落盘适合快速看包内有没有Assets/、ProjectSettings/、Packages/三个目录-d指定解压目标目录比在当前目录直接解压干净不会把一堆散文件摊进工作区ProjectVersion.txt是 Unity 工程的身份文件打开后第一行就是m_EditorVersion。如果列表里只有.cs和.png而没有.unity场景那多半是脚本片段或素材包不是完整工程只能做参考。完整工程里通常还会看到Assets/Tilemap或Assets/Sprites放关卡图块、Assets/Audio放音效、Assets/Prefabs放敌人和砖块预制体。先花两分钟把目录结构过一遍比直接打开场景再慢慢翻脚本要快得多。2.2 ProjectVersion.txt 决定你该装哪个 Unity 编辑器m_EditorVersion写的是作者当时使用的编辑器版本。Unity 打开旧版本工程通常允许但会自动做项目升级升级过程中可能改写物理设置和输入配置。最常见的坑是源工程用 2021.3 LTS 写的你机器上只有 Unity 6打开后控制台刷出一片Rigidbody2D.velocity is obsolete编译错误——Unity 6 把velocity改名为linearVelocity脚本全部失效场景里预制体跟着出现 Missing Script项目根本跑不起来。我的习惯是先在 Unity Hub 的安装列表里核对有没有与ProjectVersion.txt同小版本的编辑器没有就先安装匹配版本别指望打开时顺手升级能省事。源工程版本推荐做法需要留意的点2019.4 LTS匹配同版本旧包管理升级后检查输入轴是否丢失2020.3 LTS匹配同版本即可基本无迁移动作2021.3 LTS匹配同版本大多数此类工程的默认版本2022.3 LTS匹配同版本注意 Sprite Atlas 打包差异Unity 66000.x保持 Unity 6旧脚本 API 需要全局替换如果确实只有 Unity 6最快的处理是全局替换在Assets/Scripts下把rb.velocity统一替换成rb.linearVelocity再把isKinematic相关的过时警告一起处理掉。替换后让 Unity 重新编译控制台报错数归零再继续。2.3 场景、预制体与脚本源码引用的三层结构打开工程后先别急着点 Play。把Assets下的结构按场景—预制体—脚本三层过一遍场景里摆放的是预制体实例预制体上挂的MonoBehaviour组件引用脚本脚本里通过GetComponent、Find或序列化字段再引用其他对象。大部分源码包的报错都出在这一层对应关系上——脚本文件名与类名不一致、命名空间没写对、预制体里序列化字段指向的场景对象被删了。检查方式按下面三步走打开主场景看 Hierarchy 面板里有没有带(Missing Script)的节点选中一个预制体看 Inspector 里有没有类型为MonoBehaviour (Script Missing)的组件如果有去 Project 面板找到对应.cs文件重新拖进该组件的脚本槽位。C# 脚本要求文件名与类名一致源码包如果被重命名过传输会直接报类引用错误把文件名改回来就能恢复。这三层关系理顺之后后面的调参和改逻辑才有稳定基础。3. 角色控制与跳跃手感unity马里奥源码的核心调参区3.1 移动与跳跃的代码骨架这类工程的玩家角色几乎都是Rigidbody2D BoxCollider2D 自写 PlayerController的组合而不是 CharacterController。原因很简单2D 平台跳跃需要频繁叠加冲量、检测落地、控制重力Rigidbody2D 的物理回调正好够用。下面是带土狼时间和跳跃缓冲的移动跳跃脚本基本是此类工程的标准写法using UnityEngine; public class PlayerController : MonoBehaviour { [Header(移动)] public float moveSpeed 8f; // 最大水平速度 public float acceleration 55f; // 地面加速度 public float airAcceleration 35f; // 空中加速度 [Header(跳跃)] public float jumpVelocity 12f; // 起跳瞬间垂直速度 public float gravityScale 3.2f; // 上升阶段重力倍率 public float fallGravityScale 4.5f; // 下落阶段重力倍率 public float coyoteTime 0.1f; // 土狼时间离开平台仍可起跳的窗口 public float jumpBuffer 0.12f; // 跳跃缓冲落地前按跳不丢失输入 private Rigidbody2D rb; private bool isGrounded; private float coyoteTimer; private float jumpBufferTimer; private void Awake() { rb GetComponentRigidbody2D(); } private void Update() { if (Input.GetButtonDown(Jump)) jumpBufferTimer jumpBuffer; else jumpBufferTimer - Time.deltaTime; if (jumpBufferTimer 0f coyoteTimer 0f) { rb.velocity new Vector2(rb.velocity.x, jumpVelocity); jumpBufferTimer 0f; coyoteTimer 0f; } // 松开跳跃键砍掉一半上升速度实现轻按跳得矮 if (Input.GetButtonUp(Jump) rb.velocity.y 0f) { rb.velocity new Vector2(rb.velocity.x, rb.velocity.y * 0.5f); } } private void FixedUpdate() { float accel isGrounded ? acceleration : airAcceleration; float inputX Input.GetAxisRaw(Horizontal); float targetVx inputX * moveSpeed; float newVx Mathf.MoveTowards(rb.velocity.x, targetVx, accel * Time.fixedDeltaTime); rb.velocity new Vector2(newVx, rb.velocity.y); // 上升与下落用不同重力倍率下落更快更利落 rb.gravityScale rb.velocity.y 0f ? gravityScale : fallGravityScale; if (isGrounded) coyoteTimer coyoteTime; else coyoteTimer - Time.fixedDeltaTime; } private void OnCollisionEnter2D(Collision2D collision) { isGrounded false; foreach (ContactPoint2D contact in collision.contacts) { // 法线朝上说明脚下有支撑 if (contact.normal.y 0.5f) { isGrounded true; break; } } } private void OnCollisionExit2D(Collision2D collision) { isGrounded false; } }几个关键逻辑点水平方向没有直接给velocity赋值而是用Mathf.MoveTowards以固定加速度逼近目标速度起步有加速、急停不滑行手感比每帧硬赋速度稳得多。起跳不用AddForce而是直接改velocity好处是跳跃高度只由jumpVelocity和重力倍率决定不受上一帧残余速度影响复现性最好。gravityScale在上升与下落两个阶段取不同值是跳得高、落得快手感的核心。如果工程启用了 Input System 包Input.GetButtonDown会直接抛异常改成输入动作绑定的回调即可大多数马里奥源码包默认还是旧 Input Manager上文的旧 API 写法可直接用。3.2 土狼时间与跳跃缓冲两个必调参数coyoteTime和jumpBuffer是 2D 平台游戏里投入产出比最高的两个参数。前者解决明明踩在平台边缘却被判定不在地面的挫败感角色离开平台后的 0.1 秒内空中依然允许起跳后者解决我按了跳但角色还没落地的误触落地前 0.12 秒内的跳跃输入会被缓存落地瞬间立刻补跳。两个值都带进来之后玩家会觉得跳得比预期准实际是容错窗口变大了。调参时注意别贪土狼时间超过 0.15 秒会让角色显得飘跳跃缓冲超过 0.2 秒则容易连续误跳。常用区间见下表参数手感含义常用范围调过头是什么效果moveSpeed跑步速度7~10过大难以瞄准窄平台jumpVelocity起跳力度10~13与重力配合决定跳跃高度gravityScale / fallGravityScale上升与下落快慢3.0~3.5 / 4.0~5.0差值大则下落凌厉差值小则发飘coyoteTime离台容错0.08~0.12超过 0.15 手感发飘jumpBuffer按跳容错0.10~0.15超过 0.2 频繁误跳跳跃高度可以直接用公式估算h jumpVelocity² / (2 × 9.81 × gravityScale)。按默认值代入144 / (2 × 9.81 × 3.2) ≈ 2.29单位接近 Mario 经典模式下四格砖的高度。想调高跳跃优先动jumpVelocity动gravityScale会同时改变滞空时间和整体节奏影响面更大。3.3 用落地判定排查站住了却跳不起来源码包里最常见的 bug 是角色站在平台上却永远显示空中状态原因几乎都在落地判定。上面用的碰撞法线法在平地平台足够但遇到斜面和台阶会漏判法线不严格朝上normal.y 0.5f的阈值就过不了。此时改成脚下射线更稳private void Update() { RaycastHit2D hit Physics2D.Raycast(transform.position, Vector2.down, 0.1f, groundLayer); isGrounded hit.collider ! null; }注意三点射线起点放在角色脚底而不是中心否则 0.1 的检测距离根本不贴地groundLayer必须用 LayerMask 过滤否则敌人和道具也会被当成地面这里用单点Physics2D.Raycast就够不必做多点阵列。另外确认Rigidbody2D的Collision Detection在跑动速度超过 10 后仍选Continuous否则高速下落会直接穿过薄平台物理层根本没产生碰撞回调判定自然失效。这套排查做完跳跃手感才算真正稳定下来。4. 敌人踩踏与Tilemap关卡马里奥类源码的碰撞实现4.1 用碰撞法线区分踩踏与侧撞敌人脚本的实现源码包里常见两种做法一种在敌人头顶挂一个小的 trigger 碰撞体玩家碰到它才触发消灭另一种直接从OnCollisionEnter2D的接触点法线判断碰撞发生在哪一面。前者直观但多一个碰撞体要维护位置后者代码少且高速运动下结果更稳定。我一般用后者using UnityEngine; public class EnemyWalker : MonoBehaviour { public float moveSpeed 1.5f; public int direction -1; private Rigidbody2D rb; private void Awake() { rb GetComponentRigidbody2D(); } private void Update() { // 恒定速度巡逻撞墙翻转由另一层横向射线检测完成 rb.velocity new Vector2(direction * moveSpeed, rb.velocity.y); } private void OnCollisionEnter2D(Collision2D collision) { if (!collision.gameObject.CompareTag(Player)) return; bool isStomp false; foreach (ContactPoint2D contact in collision.contacts) { // normal 从被撞物体表面指向对方y 0.5 说明接触面在敌人顶部 if (contact.normal.y 0.5f) { isStomp true; break; } } if (isStomp) { // 玩家踩中给玩家反弹速度敌人销毁 Rigidbody2D playerRb collision.gameObject.GetComponentRigidbody2D(); playerRb.velocity new Vector2(playerRb.velocity.x, 8f); Destroy(gameObject); } else { // 侧向碰撞交给游戏管理器处理玩家受伤 GameManager.Instance.PlayerHurt(); } } }这里的关键是理解ContactPoint2D.normal的方向在敌人脚本的回调里法线永远从敌人的碰撞表面指向与之碰撞的对象。玩家踩在头顶时接触点位于敌人顶面法线 y 分量为正且接近 1玩家从侧面撞过来时法线几乎水平y 接近 0。所以只靠normal.y 0.5f就能区分踩踏和侧撞不需要额外挂碰撞体。反弹速度给的是垂直方向固定值与玩家水平速度无关保证踩中后立刻有嗒地把敌人压扁的反馈。撞墙翻转向量通常再单独加一条往移动方向打的Physics2D.Raycast检测前方一格有没有地面没地面就转向。4.2 用 Composite Collider 合并 Tilemap 碰撞体马里奥关卡本质上是一张 Tilemap地面、砖块、问号砖都画在同一个 Grid 上。如果每个 Tile 都独立生成碰撞体一个 20×10 的关卡就有几百个 BoxCollider2D物理引擎要维护的碰撞对数量暴涨这正是很多源码包跑起来掉帧的根源。标准解法是给 Tilemap 挂组合碰撞体把相邻 Tile 的碰撞轮廓合并成少量多边形——这本身也是最基本的 unity 游戏优化手段。操作路径是层级面板里新建GameObject 2D Object Tilemap Rectangular选中生成的 Tilemap 物体挂上TilemapCollider2D和CompositeCollider2D两个组件然后把TilemapCollider2D上的Used By Composite勾上运行前就会完成碰撞体合并。两个组件的设置按这张表核对组件关键设置作用TilemapCollider2DUsed By Composite true把单个 Tile 碰撞交给组合碰撞器合并CompositeCollider2DGeometry Type Polygons用多边形合并减少顶点数Rigidbody2D挂在 Tilemap 上Body Type Static组合碰撞器必须配静态刚体Tile 资源Collider Type 选 Grid规则砖块用格子碰撞别用 Sprite 像素轮廓最后一条最常见被忽略Tile 的Collider Type有None / Sprite / Grid三档。砖块地面是规则矩形选Grid即可选Sprite会按图片像素边缘生成物理形状性能差一个量级只有不规则地形才值得用。合并完成后再把玩家、敌人、砖块分别放到Player、Enemy、Block三个 Layer在 Physics 2D 的 Layer Collision Matrix 里关掉敌人之间的相互碰撞物理开销会再降一截。4.3 砖块顶撞与物品生成的常见做法问号砖被玩家从下方顶一下弹出金币或蘑菇是这类工程里另一个高频需求。判断从下方顶刚好复用上一节的法线逻辑在砖块的回调里玩家在砖块下方时接触面是砖块底面法线 y 分量为负。代码骨架如下private void OnCollisionEnter2D(Collision2D collision) { if (!collision.gameObject.CompareTag(Player)) return; foreach (ContactPoint2D contact in collision.contacts) { // 玩家从下往上顶接触面在砖块底面法线向下 if (contact.normal.y -0.5f) { GetComponentAnimator().SetTrigger(Bump); if (hasItem) { Instantiate(mushroomPrefab, spawnPoint.position, Quaternion.identity); hasItem false; // 只吐一次防止无限刷蘑菇 } else { GameManager.Instance.AddCoin(1); } break; } } }hasItem这个状态标记必须保留否则每次碰撞都会生成蘑菇。Instantiate的生成点建议用砖块子物体上的一个空Transform手动摆放别用砖块世界坐标直接加偏移否则以后调整砖块尺寸时生成位置全部要重调。金币和蘑菇生成后分别走各自的Rigidbody2D运动逻辑金币向上飞一段再消失蘑菇直接用和敌人一样的水平巡逻代码就能跑。源码包里通常就是复用EnemyWalker做蘑菇移动的这也说明敌人脚本写得通用一点后面换皮时能省一半工作量。5. 构建、WebGL排错与源码包回包技巧5.1 构建体积与压缩方式怎么选构建前先想清楚目标平台。Windows 构建在File Build Settings里直接选平台就行WebGL 要先用 Unity Hub 装 WebGL 模块否则平台列表是灰的。桌面与移动端看 Player Settings 里的 Compression MethodDefault走 LZ4、加载快LZ4HC压缩率高但构建慢适合追求包体小的分发场景WebGL 平台则看 Publishing Settings 里的 Compression FormatBrotli比Gzip压得更小但要求服务器开对应 Content-Encoding。5.2 WebGL 存档写入失败先查这两处unity 发布 webgl 使用 idbfs 写入失败是 WebGL 构建的真实高频问题。WebGL 没有真实文件系统运行时数据存在浏览器 IndexedDB 的虚拟文件系统里。直接System.IO.File.WriteAllText写到Application.persistentDataPath时如果文件系统没有同步控制台会报写盘失败。绕坑做法存档改走PlayerPrefs它在 WebGL 下会自动同步浏览器存储如果非写文件不可写完后调用一次浏览器文件系统同步接口并确认写入动作发生在用户手势回调里而不是加载流程里。5.3 交付源码包时只重打这三个目录调试完准备把工程发给别人别把整个文件夹拖进 zip。Library是本机资源导入缓存动辄几个 G 且含机器相关配置不该出现在交付包里。真正要打包的只有三个目录# 只打包工程必需内容排除缓存目录 zip -r mario-source.zip Assets Packages ProjectSettings -x */Library/* */Temp/* # 校验包完整性 unzip -t mario-source.zip-x排除通配符路径Assets Packages ProjectSettings三个显式参数保证不夹带垃圾文件unzip -t逐个解压到内存并校验 CRC返回无错误才说明包传输完整。对方拿到后在 Unity Hub 里 Add project from disk 指向解压出的目录即可自动导入。另外提醒一句交付包不必加密网上那些zip 压缩包密码破解工具很多是捆绑木马别为省一道工序去碰真要保护内容用zip -e设口令并当面告知口令比下载来路不明的工具可靠。本文还有配套的精品资源点击获取
返回列表