Unity简单攻击及受击实现

1759 字
9 分钟
Unity简单攻击及受击实现

通用开发思路#

在开发任何复杂系统(如战斗系统)时,建议遵循以下通用的三步走思路:

  1. 构建信息和数据的容器 :思考我们需要哪些数据?(例如:攻击力、生命值、技能列表)。
  2. 确定获取数据的方法 :这些数据从哪里来?(例如:数据库、配置文件、或者硬编码在脚本中)。
  3. 使用数据实现需求 :如何利用这些数据来执行逻辑?(例如:攻击时如何计算伤害 = 攻击力 - 防御力)。

基础设施搭建 (Data Layer)#

无论做实时还是回合制,角色背后的数值模型往往是通用的。我们先构建这层“地基”。

  1. 定义技能数据 (CombatSkill)#

首先,我们需要一个类来描述“技能”。

  • 属性 :技能名称、技能威力。
[System.Serializable]
public class CombatSkill
{
public string skillName;
public int attackPower;
public CombatSkill(string name, int power)
{
this.skillName = name;
this.attackPower = power;
}
}
  1. 定义角色数据 (CombatCharacterInfo)#

这是一个纯数据类(不继承 MonoBehaviour),用于存储角色的所有属性。它就像一张“角色卡”。

using System.Collections.Generic;
[System.Serializable]
public class CombatCharacterInfo
{
public string name;
public float maxHp;
public float currentHp;
public int attackPower;
public int defensePower;
public List<CombatSkill> skillList;
public CombatCharacterInfo(string name, float hp, int atk, int def)
{
this.name = name;
this.maxHp = hp;
this.currentHp = hp;
this.attackPower = atk;
this.defensePower = def;
this.skillList = new List<CombatSkill>();
}
}
  1. 模拟数据库 (ItemInfoCombat)#

为了方便测试,我们创建一个静态类来“伪造”数据库,提供初始化数据的方法。

using System.Collections.Generic;
public class ItemInfoCombat
{
private static Dictionary<int, CombatCharacterInfo> characterDatabase;
static ItemInfoCombat()
{
characterDatabase = new Dictionary<int, CombatCharacterInfo>();
// ID 0: 玩家 (高血量,低攻)
CombatCharacterInfo player = new CombatCharacterInfo("Alex", 200, 10, 5);
player.skillList.Add(new CombatSkill("Slash", 20));
characterDatabase.Add(0, player);
// ID 1: 敌人 (低血量,高攻)
CombatCharacterInfo enemy = new CombatCharacterInfo("Monster", 100, 25, 0);
enemy.skillList.Add(new CombatSkill("Bite", 30));
characterDatabase.Add(1, enemy);
}
public static CombatCharacterInfo GetCharacterInfo(int id)
{
if (characterDatabase.ContainsKey(id))
{
// 教学提示:实际项目中应返回 Deep Copy (深拷贝),防止修改源数据
return characterDatabase[id];
}
return null;
}
}

实时战斗系统#

  1. 什么是实时攻击#

实时攻击的核心是:

攻击的生效时机由动画的某一帧决定,而不是由玩家按键决定。同时更注重物理碰撞和时机。

也就是说,按下攻击键 ≠ 立刻造成伤害

真正的伤害时刻通常发生在动画中刀挥出去的某个瞬间。

为了实现这个,我们必须用到 动画事件(Animation Event)


  1. 攻击输入(Input)#

从最简单的开始:

按下某个键触发攻击。

在 Update 中写:

if (Input.GetKeyDown(KeyCode.J))
{
Attack();
}

Attack() 只是一个触发,不负责做攻击判定。 它只做一件事: 播放攻击动画


  1. 播放攻击动画(Animator)#

我们在 Animator 中准备一个 Attack 动画, 并使用 Trigger 参数 来控制它播放。

为什么要用 Trigger?

因为攻击动画播放一次就结束,不需要维持状态。

代码:

animator.SetTrigger("Attack");

这里要强调一个概念:

我们并不是在 Attack() 中去判断敌人的碰撞,而是在动画中指定合适的攻击帧,再调用判定函数。


  1. 动画事件(Animation Event)#

打开攻击动画,把时间轴拖到“刀挥出去那一瞬间”。 在 Inspector 中添加一个动画事件,调用一个函数,比如 DealDamage()

这个事件是极其重要的,它决定了攻击的“出招帧”。

为什么要用动画事件?

  • 如果在按键瞬间就造成伤害,会出现“动画还没打出去就命中了”的问题
  • 动画事件可以确保攻击判定与动画动作完全同步
  • 专业游戏也都是使用动画事件来触发攻击逻辑的

到这里,我们已经做好了“攻击的时机”。

接下来,我们要讲“攻击的范围”。


  1. 攻击判定(Hitbox / OverlapCircle)#

近战攻击最标准的做法是 Hitbox + OverlapCircle

我们给角色加一个空物体 AttackPoint,作为攻击的位置。 然后用一个范围,比如一个圆形,作为攻击范围。

代码:

Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(
attackPoint.position,
attackRange,
enemyLayer
);

这个函数返回范围内所有敌人,然后我们依次对他们造成伤害。

foreach (Collider2D enemy in hitEnemies)
{
enemy.GetComponent<Enemy>().TakeDamage(attackDamage);
}

重点是:

  • 攻击不是碰撞器撞上敌人
  • 攻击是我们主动检测一个区域内的敌人

这是所有动作游戏最基本的攻击方式。


  1. 敌人受伤(Enemy TakeDamage)#

既然敌人可以被攻击,那他一定得有个血条

同时他还得能被攻击到

public void TakeDamage(int damage)
{
currentHP -= damage;
animator.SetTrigger("Hurt");
if (currentHP <= 0)
Die();
}

说明:

  • 扣血
  • 播放受击动画
  • 播放死亡动画或销毁对象

如果你想让攻击更有“手感”,可以加一些伪物理效果,比如击退(Knockback)。


回合制战斗系统#

回合制战斗(如《宝可梦》、《崩坏:星穹铁道》)的核心在于流程控制UI交互

  1. 核心机制#

  • 回合管理 :谁先动?谁后动?
  • UI 驱动 :玩家通过点击菜单来行动,而不是按键。
  • 状态流转 :等待输入 -> 执行动画 -> 结算伤害 -> 切换回合。
  1. 战斗管理器 (CombatManagerTurnBased)#

这是最复杂的脚本,它需要管理整个战场的状态。

2.1 定义辅助结构#

public enum AttackDirection { PlayerToEnemy, EnemyToPlayer }

2.2 管理器核心逻辑#

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class CombatManagerTurnBased : MonoBehaviour
{
public static CombatManagerTurnBased Instance;
[Header("UI 引用")]
public Transform skillFrameContainer; // 技能按钮的父物体
public GameObject skillFramePrefab; // 技能按钮预制体
public Slider playerHpBar;
public Slider enemyHpBar;
[Header("运行时数据")]
public CombatCharacterInfo playerInfo;
public CombatCharacterInfo enemyInfo;
private AttackDirection currentTurn; // 当前回合
private void Awake() { Instance = this; }
private void Start()
{
// 游戏开始,初始化一场 ID 0 vs ID 1 的战斗
InitializeBattle(0, 1);
}
public void InitializeBattle(int playerId, int enemyId)
{
playerInfo = ItemInfoCombat.GetCharacterInfo(playerId);
enemyInfo = ItemInfoCombat.GetCharacterInfo(enemyId);
InitializeUI();
currentTurn = AttackDirection.PlayerToEnemy;
}
// ... (InitializeUI 代码省略,主要是实例化按钮和设置Slider) ...
// 玩家点击技能按钮后调用此方法
public void ExecuteAttack(CombatSkill skill, AttackDirection direction)
{
if (direction != currentTurn) return; // 不是你的回合不能动
StartCoroutine(ProcessTurn(skill, direction));
}
// 使用协程来处理回合流程(攻击 -> 动画 -> 结算 -> 延迟 -> 换人)
private IEnumerator ProcessTurn(CombatSkill skill, AttackDirection direction)
{
var attacker = (direction == AttackDirection.PlayerToEnemy) ? playerInfo : enemyInfo;
var defender = (direction == AttackDirection.PlayerToEnemy) ? enemyInfo : playerInfo;
int damage = Mathf.Max(0, (skill.attackPower + attacker.attackPower) - defender.defensePower);
defender.currentHp -= damage;
Debug.Log($"{attacker.name} 使用 {skill.skillName} 造成了 {damage} 点伤害!");
UpdateHpBars();
if (defender.currentHp <= 0)
{
Debug.Log("战斗结束!");
yield break;
}
SwitchTurn();
}
private void SwitchTurn()
{
if (currentTurn == AttackDirection.PlayerToEnemy)
{
currentTurn = AttackDirection.EnemyToPlayer;
// 轮到敌人:启动AI协程
StartCoroutine(EnemyTurnRoutine());
}
else
{
currentTurn = AttackDirection.PlayerToEnemy;
// 轮到玩家:等待点击,无需操作
}
}
// 简单的敌人AI
private IEnumerator EnemyTurnRoutine()
{
yield return new WaitForSeconds(1.5f); // 模拟思考/发呆
// 随机选一个技能打玩家
var skill = enemyInfo.skillList[Random.Range(0, enemyInfo.skillList.Count)];
ExecuteAttack(skill, AttackDirection.EnemyToPlayer);
}
}
  1. UI 交互 (SkillFrame)#

每个技能按钮上挂载的脚本,用于连接数据和点击事件。

public class SkillFrame : MonoBehaviour
{
private CombatSkill mySkill;
public void Setup(CombatSkill skill)
{
mySkill = skill;
// 更新 Text 显示技能名和伤害...
}
public void OnClick()
{
// 通知管理器:玩家想用这个技能攻击
CombatManagerTurnBased.Instance.ExecuteAttack(mySkill, AttackDirection.PlayerToEnemy);
}
}
Unity简单攻击及受击实现
https://blog.rouming-fei.top/posts/unity-implements-simple-attack-and-hit-effects/
作者
废江流
发布于
2025-12-08
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
废江流
提灯寻影,灯到影灭。
分类
标签
站点统计
文章
15
分类
4
标签
18
总字数
37,248
运行时长
0
最后活动
0 天前

文章目录