简介:这是一份面向Unity初学者与课程设计学生的期末跑酷游戏完整工程,可直接用于课程作业提交、答辩演示或作为2D跑酷玩法的二次开发基础。项目实现了角色上下左右移动、J键发射子弹、K键跳跃等核心操作,并加入跳板限制机制——只有站在白色跳板上才能起跳,一旦掉落则无法跳跃并判定游戏结束,同时支持吃能量包累计分数,配有完整音效反馈。压缩包共9119个文件,约304MB,包含cs脚本、prefab预制体、anim动画、controller状态机、png贴图、mp3与wav音效、shader着色器及dll运行库等,覆盖工程源码、资源素材与可执行文件,目录结构完整。目前已有760人学习下载,适合想快速理解跑酷游戏逻辑、跳跃判定与分数统计实现方式的读者参考复用。
1. 从一份 Unity 跑酷作业说起:J 射击 K 跳跃,为什么跳跃要做平台判定
如果你正在搜「unity 游戏」相关的期末作业参考,大概率见过这类需求:角色能上下左右移动,J 键发射子弹,K 键跳跃,但跳跃不是随时能跳,只有站在跳板上才允许起跳,掉下白色跳板后跳跃功能直接失效、游戏结束,同时还要吃能量包加分、全程带音效。这份资源就是一套完整的 Unity 跑酷小游戏工程,把移动、射击、条件跳跃、能量包计分、音效播放这几块串成了一个能跑起来的闭环。
它适合两类人:一类是刚学 Unity、需要一份能直接打开对照的期末作业参考;另一类是想搞清楚「条件跳跃」这种非标准跳跃逻辑到底怎么落地的人。因为大多数跑酷教程里的跳跃是「按一下跳一下」,而这份工程的核心难点在于——跳跃被绑定在跳板碰撞体上,离开跳板就锁死,这背后是碰撞检测、状态标记、输入判定三件事的配合。下面我按「先看懂结构、再动手改、最后避坑」的顺序拆一遍。
2. 工程结构与核心脚本拆解:谁在管移动,谁在管跳跃开关
拿到一份 Unity 工程,别急着点运行,先看 Assets 目录下的脚本分工。这套跑酷游戏的逻辑不复杂,但脚本职责分得比较清楚,看懂分工后面改参数才不会牵一发动全身。
2.1 目录与脚本职责对照
常见做法是把脚本按功能拆成几个文件,挂在不同的 GameObject 上。下面是我按这类工程的一般组织方式整理的职责表,你打开工程后可以对照确认:
| 脚本/组件 | 挂载对象 | 核心职责 |
|---|---|---|
| PlayerController | 角色 Player | 读取水平/垂直输入,控制移动 |
| JumpController | 角色 Player | 判断是否在跳板上,执行跳跃 |
| ShootController | 角色 Player | J 键触发子弹实例化 |
| Bullet | 子弹预制体 | 自身位移与销毁 |
| EnergyPack | 能量包预制体 | 碰撞后加分并销毁 |
| GameManager | 空物体 | 分数统计、游戏结束判定 |
| AudioManager | 空物体 | 播放跳跃、射击、吃包音效 |
这张表的价值在于:当你发现「跳跃没反应」,你能立刻定位是 JumpController 的判定问题,而不是去翻移动脚本。很多人改跑酷游戏改到崩溃,就是因为所有逻辑塞在一个脚本里,改一处崩三处。
2.2 移动与射击的输入处理
移动和射击是这套工程里最标准的部分,用Input.GetAxis拿水平和垂直轴,用Input.GetKeyDown拿 J 键。下面是一段典型的移动加射击写法:
using UnityEngine; public class PlayerController : MonoBehaviour { public float moveSpeed = 5f; // 移动速度,跑酷手感主要靠它 public GameObject bulletPrefab; // 子弹预制体,在 Inspector 里拖入 public Transform firePoint; // 子弹生成位置,避免从角色中心飞出 void Update() { // 水平与垂直输入,GetAxisRaw 无平滑,跑酷更跟手 float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); Vector3 dir = new Vector3(h, v, 0).normalized; // 归一化防止斜向加速 transform.Translate(dir * moveSpeed * Time.deltaTime); // J 键发射子弹,GetKeyDown 保证按一次只发一颗 if (Input.GetKeyDown(KeyCode.J)) { Instantiate(bulletPrefab, firePoint.position, Quaternion.identity); } } }逻辑说明:normalized是关键,如果不归一化,同时按右和上时速度会变成约 1.414 倍,斜着走比直着走快,这是新手最常见的翻车点。GetKeyDown而不是GetKey,是为了避免按住 J 键时每帧都生成子弹,那样一秒能刷出几十颗,性能和手感都会崩。
参数说明:moveSpeed一般给 4 到 6,跑酷游戏太快会穿模、太慢没节奏感;firePoint建议放在角色前方一点,否则子弹和角色碰撞体会互相干扰。
2.3 条件跳跃的实现思路
这是整套工程最值得看的部分。跳跃不是无条件触发,而是要先判断「脚下有没有跳板」。常见做法有两种:一种是用碰撞体进入/离开事件打标记,另一种是用射线检测脚下。这份工程用的是前者,逻辑更直观。
using UnityEngine; public class JumpController : MonoBehaviour { public float jumpForce = 8f; // 跳跃力度 private bool canJump = false; // 是否站在跳板上 private Rigidbody2D rb; void Start() { rb = GetComponent<Rigidbody2D>(); } // 进入跳板碰撞体时解锁跳跃 void OnCollisionEnter2D(Collision2D col) { if (col.gameObject.CompareTag("JumpPad")) { canJump = true; } } // 离开跳板时锁死跳跃 void OnCollisionExit2D(Collision2D col) { if (col.gameObject.CompareTag("JumpPad")) { canJump = false; } } void Update() { // K 键跳跃,且必须站在跳板上 if (Input.GetKeyDown(KeyCode.K) && canJump) { rb.velocity = new Vector2(rb.velocity.x, jumpForce); canJump = false; // 起跳后立即锁死,防止空中连跳 } } }逻辑说明:canJump这个布尔量就是整个条件跳跃的开关。进入跳板置 true,离开置 false,起跳后立刻再置 false,三重保险保证「只有跳板上能跳、跳一次就锁」。CompareTag("JumpPad")要求你在 Unity 里给所有跳板物体打上 JumpPad 标签,标签打错是跳跃失效的头号原因。
参数说明:jumpForce给 7 到 10 之间,具体看重力大小;如果跳跃高度不够,先调 jumpForce,再考虑调 Rigidbody2D 的 Gravity Scale。
3. 能量包计分与音效接入:从碰撞到分数面板的完整链路
分数和音效是这类作业的加分项,也是答辩时最容易被问的地方。分数统计看着简单,但「什么时候加、加多少、加完怎么显示」三件事要串起来;音效则要注意播放时机和 AudioSource 的挂载位置。
3.1 能量包碰撞与分数累加
能量包本质是一个带 Collider2D 的预制体,角色碰到它时触发加分并销毁自己。分数存在 GameManager 里,用单例或静态变量暴露给其他脚本。
using UnityEngine; public class EnergyPack : MonoBehaviour { public int scoreValue = 10; // 每个能量包的分值 void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag("Player")) { // 累加分数并刷新 UI GameManager.Instance.AddScore(scoreValue); // 播放吃包音效 AudioManager.Instance.PlayPickup(); // 销毁能量包,避免重复触发 Destroy(gameObject); } } }逻辑说明:用OnTriggerEnter2D而不是OnCollisionEnter2D,是因为能量包通常勾选 Is Trigger,角色穿过去就吃掉,不产生物理反弹。Destroy(gameObject)必须写,否则角色停在能量包上会每帧触发一次,分数瞬间爆表。
参数说明:scoreValue按需给,一般 10 或 100;如果要做不同分值的能量包,把这个字段暴露到 Inspector 里,复制预制体改数值即可。
3.2 音效播放与 AudioSource 配置
音效这块,常见做法是建一个 AudioManager 单例,挂多个 AudioSource 或者用一个 AudioSource 配合PlayOneShot。PlayOneShot的好处是多个音效可以叠加播放,不会互相打断。
using UnityEngine; public class AudioManager : MonoBehaviour { public static AudioManager Instance; public AudioSource sfxSource; // 专门播短音效的 AudioSource public AudioClip jumpClip; // 跳跃音效 public AudioClip shootClip; // 射击音效 public AudioClip pickupClip; // 吃包音效 void Awake() { // 单例,保证全局只有一个音频管理器 if (Instance == null) Instance = this; else Destroy(gameObject); } public void PlayJump() => sfxSource.PlayOneShot(jumpClip); public void PlayShoot() => sfxSource.PlayOneShot(shootClip); public void PlayPickup() => sfxSource.PlayOneShot(pickupClip); }逻辑说明:Awake里做单例,保证场景里只有一个 AudioManager,切换场景时不会重复。PlayOneShot每次调用都完整播一遍,适合跳跃、射击这种短促音效。跳跃音效要在 JumpController 里起跳成功后调用,射击音效在 Instantiate 子弹后调用,吃包音效在加分后调用,时机对不上会出现「动作做了没声音」的玄学问题。
参数说明:sfxSource的 Volume 建议 0.5 到 0.8,Spatial Blend 设为 2D,否则音效会随角色位置忽大忽小。
3.3 游戏结束判定与跳跃锁死
「从白色跳板掉下去就不能跳、游戏结束」这条规则,本质是给角色加一个下落高度阈值或者触发区。常见做法是在场景下方放一个 Trigger 区域,角色掉进去就判定结束。
using UnityEngine; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviour { public static GameManager Instance; public int score = 0; public bool isGameOver = false; void Awake() { Instance = this; } public void AddScore(int value) { if (isGameOver) return; // 结束后不再加分 score += value; // 这里刷新 UI 文本 } public void GameOver() { isGameOver = true; // 可在此弹出结束面板或重载场景 } }逻辑说明:isGameOver标记一旦置 true,加分和跳跃都应该被拦住。JumpController 里可以在 Update 开头判断if (GameManager.Instance.isGameOver) return;,这样掉下去之后按 K 也没反应,符合需求描述。
参数说明:结束触发区的高度要低于所有跳板,否则角色正常下落时会被误判。
4. 避坑与排查:跳跃失效、分数不涨、音效不响的常见原因
这套工程跑不起来,九成问题出在标签、碰撞体、单例引用这三类。下面是我踩过或见别人踩过的几条,按「现象 → 原因 → 解决」列出来。
现象一:按 K 完全没反应,角色站着也跳不了。原因:跳板物体没打 JumpPad 标签,或者标签拼写不一致,CompareTag永远返回 false,canJump一直是初始的 false。 解决:选中所有跳板,在 Inspector 顶部 Tag 下拉里选 JumpPad;如果下拉里没有,先 Add Tag 新建。注意标签区分大小写。
现象二:能跳,但跳起来后在空中还能再跳,无限连跳。原因:canJump在起跳后没有立即置 false,或者 OnCollisionExit2D 没触发,导致离开跳板后标记还是 true。 解决:起跳代码里加canJump = false;,并确认跳板碰撞体不是 Trigger,Trigger 不会触发 OnCollisionExit2D。
现象三:吃了能量包分数不涨,或者一次涨好几百。原因:不涨多半是 GameManager 单例没初始化,Instance 为 null;涨好几百是能量包没销毁,每帧触发一次加分。 解决:确认 GameManager 挂在场景物体上且 Awake 里赋值了 Instance;能量包碰撞后必须Destroy(gameObject)。
现象四:音效在编辑器里响,打包后不响。原因:AudioClip 没有真正拖进 AudioSource 字段,或者打包时资源没被引用到。 解决:检查 Inspector 里每个 Clip 字段是否为空;空的话从 Assets 拖进去,别只写代码不赋值。
现象五:角色斜着走比直着走快。原因:输入向量没归一化,斜向合成速度是 1.414 倍。 解决:dir.normalized,这一行能省掉半小时的「手感不对」排查。
提示:改完标签或碰撞体后,先点运行看 Console 有没有报错,很多「没反应」其实是空引用异常把 Update 中断了。
5. 进阶技巧:用状态机重构跳跃判定,顺带把参数调到手感对
如果你已经能跑通基础版本,下一步值得做的是把canJump这种散落的布尔量收进一个简单的状态机。跑酷游戏的角色状态其实就几个:Idle、Run、Jump、Fall、Dead。用枚举管理状态,跳跃判定会清晰很多,答辩时也更好讲。
public enum PlayerState { Idle, Run, Jump, Fall, Dead } public class PlayerStateMachine : MonoBehaviour { public PlayerState currentState = PlayerState.Idle; private bool onJumpPad = false; void Update() { switch (currentState) { case PlayerState.Idle: case PlayerState.Run: // 只有在地面且站在跳板上才允许切到 Jump if (Input.GetKeyDown(KeyCode.K) && onJumpPad) currentState = PlayerState.Jump; break; case PlayerState.Jump: // 速度转为向下时进入下落 if (GetComponent<Rigidbody2D>().velocity.y < 0) currentState = PlayerState.Fall; break; case PlayerState.Fall: // 落回跳板恢复可跳 if (onJumpPad) currentState = PlayerState.Idle; break; case PlayerState.Dead: // 结束状态不做任何输入响应 break; } } }状态机的好处是「什么状态能做什么」一目了然,Dead 状态下所有输入自动失效,不用在每个脚本里写if (isGameOver) return;。参数调优上,我一般会固定一个调试流程:先调 moveSpeed 到移动跟手,再调 jumpForce 到能跳上下一个平台,最后调重力到下落不飘。这三者互相影响,一次只动一个,改完立刻运行验证,别三个一起改,否则出了问题根本不知道是谁的锅。
从那以后我每次拿到跑酷类工程,都强制先跑一遍「标签检查 → 碰撞体类型确认 → 单例引用确认」这三步,再谈改逻辑。这套 Unity 跑酷作业工程把条件跳跃、能量包计分、音效这几块都做成了可复用的模块,拿来当期末参考或者二次开发底子都够用,希望帮到你。
本文还有配套的精品资源,点击获取