ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity打怪物格斗游戏期末大作业:从零搭建战斗闭环与AI状态机

Unity打怪物格斗游戏期末大作业:从零搭建战斗闭环与AI状态机 简介这是一份面向Unity初学者与在校学生的2D格斗小游戏完整工程可直接作为期末大作业或课程设计参考。项目实现人物上下左右跳跃移动、攻击怪物、血量条扣减、分数统计与游戏背景切换等核心玩法并支持背景音乐调节与多按钮UI交互帮助新手理解角色控制、碰撞检测、状态管理与UI事件绑定的基本流程。资源以zip压缩包形式提供整体约50.78MB免积分下载解压后可直接导入Unity编辑器运行调试。目前已有2400人学习下载适合刚接触Unity3D、需要一份可运行案例来对照练习场景搭建与脚本编写的同学。通过拆解该工程读者能掌握从角色移动、怪物AI到血量与计分系统的实现思路并在此基础上自行扩展关卡与音效快速完成一份结构清晰的课程作业。1. 从零拆一个 Unity 打怪物格斗工程初学者能跑起来的期末大作业长什么样如果你正在搜 unity打怪物格斗游戏 或者 unity期末大作业大概率是两种情况要么课程设计 deadline 逼近需要一个能跑、能改、能讲清楚逻辑的完整工程要么刚装完 Unity想找一个比打砖块复杂一点、又不至于看不懂的项目练手。我拿到这个资源包的第一反应是——它没有堆花哨的 Shader 和联网同步核心就是一套「玩家状态机 怪物 AI 攻击判定 血量结算」的闭环所有脚本都拆得比较碎适合逐行读。它解决的不是「做一个商业格斗游戏」而是让你在 2D/3D 场景里把碰撞检测、动画事件、协程冷却、UI 血条这几件事串起来。适合谁刚学完 C# 基础语法、知道MonoBehaviour生命周期、但还没独立写过完整战斗流程的初学者。如果你已经能徒手写行为树这个包对你偏简单但可以拿它的判定逻辑当参考。2. 工程结构与核心脚本先搞清楚谁在控制谁2.1 目录拆解与脚本职责划分拿到压缩包解压后常见做法是先看Assets下的文件夹命名。这个工程一般会分成Scripts、Prefabs、Animations、Scenes、UI几块。Scripts里通常能看到PlayerController、EnemyAI、HealthSystem、AttackHitbox、GameManager这几个核心文件。别急着打开场景运行先花十分钟把脚本依赖关系画出来——谁引用谁、谁在Update里轮询、谁靠事件触发。我一般会按这个顺序读GameManager看它持有哪些全局引用是否用单例是否管理游戏状态开始/暂停/结束。HealthSystem看血量字段、受伤方法、死亡事件这是所有战斗逻辑的汇聚点。PlayerController看输入读取、移动、跳跃、攻击触发的入口。EnemyAI看状态切换条件、追击范围、攻击间隔。AttackHitbox看碰撞体启用/禁用时机这是判定是否「打得中」的关键。// HealthSystem.cs 典型结构 using UnityEngine; using System; public class HealthSystem : MonoBehaviour { public int maxHealth 100; private int currentHealth; // 用事件解耦UI 和 AI 都订阅不用互相引用 public event Actionint, int OnHealthChanged; public event Action OnDeath; void Awake() { currentHealth maxHealth; } public void TakeDamage(int amount) { if (currentHealth 0) return; // 防止死亡后继续扣血 currentHealth Mathf.Max(currentHealth - amount, 0); OnHealthChanged?.Invoke(currentHealth, maxHealth); if (currentHealth 0) OnDeath?.Invoke(); } public int GetCurrentHealth() currentHealth; }这段代码的逻辑说明Awake里初始化血量保证场景加载后数值正确TakeDamage先做死亡判断避免怪物死后还被鞭尸导致事件重复触发用event把血量变化广播出去UI 血条和怪物 AI 各自订阅不用在PlayerController里写FindObjectOfTypeUIManager()这种硬引用。参数方面maxHealth建议在 Inspector 里暴露方便不同怪物配不同数值amount由攻击方传入通常和动画帧挂钩。2.2 攻击判定与动画事件绑定格斗游戏最容易被问「为什么我明明打到了却没伤害」。这个工程常见做法是用动画事件Animation Event在攻击动画的特定帧开启AttackHitbox的碰撞体持续几帧后再关闭。这样判定窗口和视觉表现是对齐的比在Update里按时间硬编码靠谱。// AttackHitbox.cs using UnityEngine; public class AttackHitbox : MonoBehaviour { public int damage 10; private Collider hitCollider; void Awake() { hitCollider GetComponentCollider(); hitCollider.enabled false; // 默认关闭等动画事件开启 } // 动画事件调用攻击帧开始 public void EnableHitbox() { hitCollider.enabled true; } // 动画事件调用攻击帧结束 public void DisableHitbox() { hitCollider.enabled false; } void OnTriggerEnter(Collider other) { if (other.CompareTag(Enemy)) { var health other.GetComponentHealthSystem(); if (health ! null) health.TakeDamage(damage); } } }逻辑说明Awake里先把碰撞体关掉防止角色站着不动也判定命中EnableHitbox和DisableHitbox挂在动画剪辑的对应帧上在 Animation 窗口右键添加事件即可。参数上damage可以按攻击类型分档轻击 5、重击 15hitCollider建议用BoxCollider或SphereCollider并勾选Is Trigger避免物理推挤。注意OnTriggerEnter里要判 Tag否则怪物打怪物也会掉血。2.3 怪物 AI 的状态切换与追击逻辑初学者最容易把 AI 写成一大坨if-else。这个工程一般用简单状态枚举Idle、Chase、Attack、Dead。每帧根据玩家距离切换状态不引入行为树够用且好懂。// EnemyAI.cs 核心片段 using UnityEngine; public class EnemyAI : MonoBehaviour { public float chaseRange 5f; public float attackRange 1.5f; public float moveSpeed 2f; public Transform player; public HealthSystem health; private enum State { Idle, Chase, Attack, Dead } private State currentState State.Idle; void Update() { if (currentState State.Dead) return; float dist Vector3.Distance(transform.position, player.position); switch (currentState) { case State.Idle: if (dist chaseRange) currentState State.Chase; break; case State.Chase: if (dist attackRange) currentState State.Attack; else if (dist chaseRange) currentState State.Idle; else MoveTowardsPlayer(); break; case State.Attack: if (dist attackRange) currentState State.Chase; break; } } void MoveTowardsPlayer() { Vector3 dir (player.position - transform.position).normalized; transform.position dir * moveSpeed * Time.deltaTime; } void OnEnable() health.OnDeath () currentState State.Dead; void OnDisable() health.OnDeath - () currentState State.Dead; }逻辑说明Update里先做死亡短路避免死后还在追switch按距离切换状态Chase状态下如果进入攻击范围就转Attack超出追击范围就回Idle。MoveTowardsPlayer用normalized保证斜向速度不会变快。参数上chaseRange和attackRange要在 Scene 视图里用 Gizmos 画出来调别盲猜。订阅OnDeath时注意在OnDisable里取消订阅否则场景重载会报空引用。3. 从空场景到能打手把手复现战斗闭环3.1 场景搭建与组件挂载顺序如果你拿到的是完整工程直接打开Scenes下的主场景就能跑。但如果你想自己从空场景搭一遍按这个顺序来新建 3D 场景地面用Plane玩家和怪物用Capsule临时替代。玩家挂CharacterController、PlayerController、HealthSystem子物体加一个空物体命名为AttackPoint挂AttackHitbox和BoxColliderIs Trigger 勾上。怪物挂CharacterController、EnemyAI、HealthSystemTag 设为Enemy。UI 加一个Slider做血条挂脚本订阅OnHealthChanged。动画控制器里把攻击动画的事件帧对齐到EnableHitbox/DisableHitbox。常见做法是先把玩家和怪物的HealthSystem都拖到 Inspector 里互相引用但更稳的方式是用事件解耦减少拖拽遗漏。3.2 输入配置与攻击冷却Unity 默认的Input.GetKeyDown(KeyCode.J)就够初学者用。攻击冷却用协程或时间戳都行我一般用时间戳少一次协程开销。// PlayerController.cs 攻击部分 public float attackCooldown 0.5f; private float lastAttackTime -999f; void Update() { if (Input.GetKeyDown(KeyCode.J) Time.time - lastAttackTime attackCooldown) { lastAttackTime Time.time; animator.SetTrigger(Attack); } }逻辑说明lastAttackTime初始设为 -999 保证第一次能立刻攻击Time.time - lastAttackTime判断冷却是否结束。参数上attackCooldown要和攻击动画长度匹配一般略短于动画总时长让连招手感顺一点。如果发现按了没反应先看animator里有没有Attack这个 Trigger再看动画状态机有没有从Any State过渡回来。3.3 血条 UI 与死亡处理血条用Slider最简单OnHealthChanged回调里改value就行。死亡处理分两块怪物死亡播放倒地动画并销毁玩家死亡弹出重开按钮。// UIManager.cs using UnityEngine; using UnityEngine.UI; public class UIManager : MonoBehaviour { public Slider healthSlider; public HealthSystem playerHealth; void OnEnable() { playerHealth.OnHealthChanged UpdateHealthUI; } void OnDisable() { playerHealth.OnHealthChanged - UpdateHealthUI; } void UpdateHealthUI(int current, int max) { healthSlider.value (float)current / max; } }逻辑说明订阅放在OnEnable取消放在OnDisable这是 Unity 里避免空引用的标准写法。参数上healthSlider的Min Value设 0、Max Value设 1代码里传归一化比值。如果血条不动先确认playerHealth有没有在 Inspector 里赋值再看OnHealthChanged有没有被TakeDamage触发。4. 避坑与排查初学者最容易翻车的五个点4.1 攻击判定不生效现象动画播了怪物不掉血。原因AttackHitbox的碰撞体没在动画事件里开启或者怪物 Tag 不是Enemy。解决在 Animation 窗口检查事件帧是否绑定了EnableHitbox再确认怪物 Tag 和OnTriggerEnter里的判断一致。4.2 怪物死后仍然追击现象血量归零怪物还在移动。原因OnDeath事件没把状态切到Dead或者Update里没做死亡短路。解决在EnemyAI里订阅OnDeath并设currentState State.Dead同时在Update开头加if (currentState State.Dead) return;。4.3 血条不更新或报空引用现象Console 报NullReferenceException血条不动。原因UIManager里的playerHealth没赋值或者订阅时机在Awake之前。解决在 Inspector 里拖引用订阅写在OnEnable而不是Awake取消订阅写在OnDisable。4.4 动画事件丢失现象换模型或重新导入动画后攻击判定失效。原因动画事件是绑在剪辑上的重新导入可能覆盖。解决每次替换动画后手动检查事件帧或者把判定逻辑改成用StateMachineBehaviour挂在状态上减少对剪辑事件的依赖。4.5 角色穿墙或掉出地图现象玩家或怪物移动时穿过地面。原因CharacterController的Move没乘Time.deltaTime或者碰撞体尺寸不对。解决检查移动代码里有没有Time.deltaTimeCharacterController的Radius和Height要和模型匹配地面加MeshCollider或BoxCollider。5. 进阶技巧用 LayerMask 和 Gizmos 把判定调准5.1 用 LayerMask 隔离攻击判定层当场景里怪物多了OnTriggerEnter会频繁触发无关碰撞。常见做法是建一个Attack层只让攻击碰撞体和Enemy层交互。在Physics设置里取消Attack和Attack的勾选避免玩家攻击互相干扰。// 在 AttackHitbox 里用 LayerMask 过滤 public LayerMask targetLayer; void OnTriggerEnter(Collider other) { if (((1 other.gameObject.layer) targetLayer) 0) return; var health other.GetComponentHealthSystem(); if (health ! null) health.TakeDamage(damage); }逻辑说明1 other.gameObject.layer把层索引转成位掩码和targetLayer做与运算不在目标层就直接返回。参数上targetLayer在 Inspector 里勾选Enemy即可。这样比CompareTag更灵活也少一次字符串比较。5.2 用 Gizmos 可视化追击和攻击范围调 AI 参数时盲猜chaseRange很痛苦。在EnemyAI里加OnDrawGizmosSelected选中怪物时画出范围圈。void OnDrawGizmosSelected() { Gizmos.color Color.yellow; Gizmos.DrawWireSphere(transform.position, chaseRange); Gizmos.color Color.red; Gizmos.DrawWireSphere(transform.position, attackRange); }逻辑说明OnDrawGizmosSelected只在选中物体时绘制不影响运行性能。黄色圈是追击范围红色圈是攻击范围在 Scene 视图里直接拖chaseRange数值就能看到圈变大变小。我一般会把attackRange调到比怪物手臂略长一点避免视觉上没碰到就掉血。5.3 用 Animator 的 StateMachineBehaviour 替代动画事件动画事件在换模型时容易丢进阶做法是写一个StateMachineBehaviour挂在攻击状态上在OnStateEnter开启判定、OnStateExit关闭判定。这样判定逻辑跟着状态机走不依赖剪辑事件。using UnityEngine; public class AttackStateBehaviour : StateMachineBehaviour { public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { var hitbox animator.GetComponentInChildrenAttackHitbox(); if (hitbox ! null) hitbox.EnableHitbox(); } public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { var hitbox animator.GetComponentInChildrenAttackHitbox(); if (hitbox ! null) hitbox.DisableHitbox(); } }逻辑说明OnStateEnter在进入攻击状态时开启碰撞体OnStateExit在退出时关闭。参数上AttackHitbox要挂在 Animator 所在物体的子物体上GetComponentInChildren才能找到。这个方案比动画事件稳但要求你的攻击动画单独占一个状态不能和待机混在一起。从那以后我每次拿到新的格斗工程都强制先跑一遍「空场景搭闭环」的流程先让玩家能打中一个不动的怪物再加 AI再加 UI最后才调动画和特效。这样出问题时能快速定位是哪一层断了。希望帮到你。本文还有配套的精品资源点击获取
返回列表