【Unity】动作游戏开发实战详细分析-15-可扩展的战斗系统

【Unity】动作游戏开发实战详细分析-15-可扩展的战斗系统


系统设计

攻击信息传递

通常情况下,伤害、属性、判定都会被封装到类中,在触发动画事件后将战斗信息发送给受击者。

我们可以结合Unity碰撞/触发,在发生事件后获取对应信息,而非主动将战斗信息发送给目标,这有利于后期受击判定的调试。

在这里插入图片描述

战斗系统设计

通过下图的组件模式设计战斗系统,并通过统一的回调函数进行战斗信息发送

只需要将所有组件的信息函数绑定到BattleObject的回调函数进行统一调用。

通过这样的组件模式构建可扩展的战斗系统,以及高度自定义的战斗系统。

设计所有组件的基类,并让所有组件通过它进行泛化,即可实现扩展。

在这里插入图片描述

战斗组件基类

基类是一个抽象类,它是所有组件的抽象。

具备基本字段,战斗对象(存储挂载的战斗对象本身),并在初始化函数中进行赋值。

public abstract class BattleObjectComponentBase : MonoBehaviour
{
  protected BattleObject mBattleObject;


  public virtual void Initialization(BattleObject battleObject)//初始化
  {
    mBattleObject = battleObject;
  }
}
战斗阵营

这是一个静态类,设计所有的战斗阵营,依次对队友与敌人进行区分。

public static class EasyFactionConst
{
  public const int PLAYER = 1;//玩家
  public const int ENEMY = 2;//敌人
}
战斗对象类

战斗对象类是个十分重要的类。

首先是基本字段

  • 战斗组件数组,存储该战斗对象存储的所有战斗组件
  • 战斗阵营
  • 接触其他阵营的回调事件,用于传递战斗信息
  • 常规回调事件,可用于处理同阵营的Buff等,这是一个基础回调事件。在本示例中,该事件会对所有阵营反馈调用。

其次是函数

  • 获取组件函数,这是一个泛型函数,用于获取该战斗对象上的任意战斗组件。
    • 遍历所有组件,返回对应的组件
  • 在Awake函数中初始化所有战斗组件
  • 触发器函数
    • 首先尝试获取战斗对象组件,并判断战斗阵营,其次就是回调事件的调用。
public class BattleObject : MonoBehaviour
{
  [SerializeField]
  BattleObjectComponentBase[] battleObjectComponents = new BattleObjectComponentBase[0];
  public int faction = EasyFactionConst.PLAYER;
  public event Action<BattleObject, BattleObject> OnContactOtherFaction;//接触到其他阵营
  public event Action<BattleObject, BattleObject> OnBattleTriggerEnter;//常规碰撞回调


  public T GetBattleObjectComponent<T>()//获取战斗对象组件
    where T : BattleObjectComponentBase
    {
      var result = default(T);
      for (int i = 0; i < battleObjectComponents.Length; i++)
      {
        var item = battleObjectComponents[i];
        if (item.GetType() == typeof(T))//匹配对应类型
        {
          result = item as T;
          break;
        }
      }
      return result;
    }
  protected virtual void Awake()
  {
    for (int i = 0; i < battleObjectComponents.Length; i++)
      battleObjectComponents[i].Initialization(this);//初始化战斗对象组件
  }

  void OnTriggerEnter(Collider collider)
  {
    var otherBattleObject = collider.transform.GetComponent<BattleObject>();
    if (otherBattleObject == null) return;//战斗对象过滤
    if (otherBattleObject.faction != faction)
    {
      if (OnContactOtherFaction != null)
        OnContactOtherFaction(this, otherBattleObject);//接触到其他阵营
    }
    if (OnBattleTriggerEnter != null)
      OnBattleTriggerEnter(this, otherBattleObject);//常规进入回调
  }
}
伤害传递组件

我们来添加伤害传递组件

该组件具有基本字段

  • 生命值,战斗数值比较复杂可自行定义类
  • 攻击状态判断
  • 伤害值
  • 上帝模式
  • 死亡判断
  • 事件(可根据自定义添加更多的回调事件)
    • HP改变回调事件
    • 死亡回调事件
    • 受伤回调事件
    • 攻击成功回调事件
    • 击杀回调事件

这些事件,只需要通过其他脚本进行回调绑定即可完成基本动作行为,包括:死亡,受伤,攻击,击杀等等,并且可以通过回调函数完成很多能力判定,例如,生命值改变触发能力增强,击杀触发生命值吸收等等。

函数说明

  • 重写初始化事件,并绑定战斗对象的阵营接触回调事件

  • 生命值改变函数

  • 阵营接触回调函数

    • 伤害传递,调用其他阵营对象的生命值改变函数,受伤事件,判断死亡事件等等。

这里有一点需要重点说明,如果攻击方式触发动画事件创造了带有伤害信息的预制体来造成伤害,那么那个预制体上就必须要带有伤害传递组件,因为这是一个战斗对象。

其次还需要配置动画事件,初始化预制体的战斗阵营(可以在之前的动画事件接收脚本里完成),以及自我销毁脚本等等功能。

伤害传递组件具备了受伤功能,伤害功能,死亡,受伤等功能。

public class DamageBattleComponent : BattleObjectComponentBase
{
  public int hp;//生命值
  public bool isAttackState;//是否为攻击状态
  public int toEnemyDamage;//给予敌人伤害
  public bool godMode;//上帝模式
  bool mIsDied;

  /// <summary>
  /// 参数1 - 事件发送者, 参数2 - 旧的HP值, 参数3 - 新的HP值
  /// </summary>
  public event Action<BattleObject, int, int> OnHPChanged;//HP改变事件
  /// <summary>
  /// 参数1 - 事件发送者。
  /// </summary>
  public event Action<BattleObject> OnDied;//死亡事件
  /// <summary>
  /// 参数1 - 事件发送者,参数2 - 攻击者, 参数3 - 伤害值
  /// </summary>
  public event Action<BattleObject, BattleObject, int> OnHurt;//受伤事件
  /// <summary>
  /// 参数1 - 事件发送者, 参数2 - 受击者
  /// </summary>
  public event Action<BattleObject, BattleObject> OnAttackCompleted;//攻击成功事件
  /// <summary>
  /// 参数1 - 事件发送者, 参数2 - 受击者
  /// </summary>
  public event Action<BattleObject, BattleObject> OnKilled;//击杀事件


  public override void Initialization(BattleObject battleObject)//初始化函数
  {
    base.Initialization(battleObject);
    battleObject.OnContactOtherFaction += OnContactOtherFactionCallback;
  }

  public void ChangeHP(int newHP)
  {
    newHP = Math.Max(newHP, 0);

    if (hp == newHP) return;

    if (OnHPChanged != null)
      OnHPChanged(mBattleObject, hp, newHP);

    hp = newHP;

    if (hp <= 0 && !mIsDied)
    {
      if (OnDied != null)
        OnDied(mBattleObject);

      mIsDied = true;
    }
  }
  void OnContactOtherFactionCallback(BattleObject sender, BattleObject other)//接触不同阵营后的处理
  {
    var anotherDamageComponent = other.GetBattleObjectComponent<DamageBattleComponent>();
    if (anotherDamageComponent != null)
    {
      if (toEnemyDamage <= 0) return;//没有伤害信息则跳出
      if (anotherDamageComponent.godMode) return;//上帝模式则跳出

      var isAlive_Before = anotherDamageComponent.mIsDied;
      anotherDamageComponent.ChangeHP(anotherDamageComponent.hp - toEnemyDamage);//扣血处理
      var isAlive_After = anotherDamageComponent.mIsDied;

      if (anotherDamageComponent.OnHurt != null)//敌方触发受伤回调
        anotherDamageComponent.OnHurt(other, sender, toEnemyDamage);

      if (anotherDamageComponent.OnAttackCompleted != null)//己方触发攻击成功回调
        anotherDamageComponent.OnAttackCompleted(sender, other);

      if (!isAlive_Before && isAlive_After)//若扣血之后死亡则己方触发击杀回调
      {
        if (OnKilled != null)
          OnKilled(mBattleObject, other);
      }
    }
  }
}
受击僵直组件

受击僵直组件

基本字段

  • 僵直时间
  • 僵直回调事件

方法说明

  • 初始化函数
  • 回调函数

该组件具备造成僵直能力与受到僵直的功能。

public class HitStopBattleComponent : BattleObjectComponentBase
{
  public float toEnemyHitStopTime;//赋予敌人的僵直时间
  /// <summary>
  /// 进入僵直状态事件,参数1 - 僵直时间
  /// </summary>
  public event Action<float> OnHitStopTriggered;


  public override void Initialization(BattleObject battleObject)//初始化函数
  {
    base.Initialization(battleObject);
    battleObject.OnContactOtherFaction += OnContactOtherFactionCallback;
  }

  void OnContactOtherFactionCallback(BattleObject sender, BattleObject other)//接触不同阵营后的处理
  {
    var anotherHitStopComponent = other.GetBattleObjectComponent<HitStopBattleComponent>();
    if (anotherHitStopComponent != null)
    {
      if (toEnemyHitStopTime <= 0) return;//没有僵直信息则跳出
      if (anotherHitStopComponent.OnHitStopTriggered != null)//触发僵直事件,并传入僵直时间
        anotherHitStopComponent.OnHitStopTriggered(toEnemyHitStopTime);
    }
  }
}

这是一个与受击僵直组件耦合的脚本,它具有会绑定受击僵直组件的回调事件,它会单独处理僵直逻辑。

public class GenericHitStopProcess : MonoBehaviour
{
  public HitStopBattleComponent hitStopBattleComponent;
  public Animator animator;
  float mHitRecoverTimer;
  int mIsHitAnimatorHash;
  public bool HitStop { get { return mHitRecoverTimer > 0; } }//是否处于僵直状态


  void Awake()
  {
    hitStopBattleComponent.OnHitStopTriggered += OnHitStopTriggered;
    mIsHitAnimatorHash = Animator.StringToHash("IsHit");
  }

  void Update()
  {
    if (mHitRecoverTimer > 0)//简易的僵直度反馈
      animator.SetBool(mIsHitAnimatorHash, true);
    else
      animator.SetBool(mIsHitAnimatorHash, false);

    mHitRecoverTimer = Mathf.Max(0f, mHitRecoverTimer - Time.deltaTime);//僵直恢复
  }

  void OnHitStopTriggered(float hitStopValue)
  {
    mHitRecoverTimer = Mathf.Max(mHitRecoverTimer, hitStopValue);//更新僵直时间
  }
}
物理位移组件

物理位移组件,它负责所有的战斗相关的物理移动能力:击退,浮空等等。

基本字段

  • 力的类型
  • 力度大小
  • 角色驱动
  • 物理行为回调事件

方法说明

  • 初始化函数
  • 施加力量函数(这是被动调用的函数,也就是受到物理位移攻击)
  • 阵营接触回调函数
public class PhysicsBattleComponent : BattleObjectComponentBase
{
  public enum EType { Push, VerticalPush, AirPush, Max }
  public EType type;//力的类型
  public Vector3 forceValue;//力的值

  public CharacterMotor characterMotor;

  /// <summary>
  /// 当物理行为触发,参数1 - 类型
  /// </summary>
  public event Action<EType> OnPhysicBehaviourTriggered;


  public override void Initialization(BattleObject battleObject)//初始化函数
  {
    base.Initialization(battleObject);
    battleObject.OnContactOtherFaction += OnContactOtherFactionCallback;
  }

  public void SetForce(Vector3 forceVector, EType type)//设置力的传入
  {
    if (characterMotor == null) return;

    var upAxis = -Physics.gravity.normalized;
    switch (type)
    {
      case EType.Push:
        characterMotor.SetForce(Vector3.ProjectOnPlane(forceVector, upAxis));//消除Y轴力
        break;
      case EType.VerticalPush:
        characterMotor.SetForce(Vector3.Project(forceVector, upAxis));//消除平面方向力
        break;
      case EType.AirPush:
        characterMotor.SetForce(forceVector);//直接将力赋予Motor
        break;
    }

    OnPhysicBehaviourTriggered(type);
  }

  void OnContactOtherFactionCallback(BattleObject sender, BattleObject other)//接触不同阵营处理
  {
    var anotherPhysicsBattleComponent = other.GetBattleObjectComponent<PhysicsBattleComponent>();
    if (anotherPhysicsBattleComponent != null)
    {
      forceValue = mBattleObject.transform.rotation * forceValue;
      anotherPhysicsBattleComponent.SetForce(forceValue, type);//传入力
    }
  }
}
  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回合制自己写的 战斗DEMO 加动画状态 Q键移动并攻击。 代码很简单。不要抱有太大希望 。作为新手学习使用。 public enum HeroStatus { idle = 0, //空闲 attack, //攻击 other, //其他 hit, //受击 die, //死亡 defense, //防御 cast, //施法 miss, //闪避 seriousInjury, //重伤 move, //移动 exit, //获取下一个状态 MAX, } public class hero : MonoBehaviour { public UISpriteAnimation m_spriteAnimation = null; public HeroStatus m_staus = HeroStatus.exit; //身体碰撞盒 public BoxCollider m_bodyBC = null; public string m_name = ""; public AttackCrash m_attackCrash = null; public byte m_posIndex = 0; void Awake() { m_spriteAnimation = transform.GetComponent(); m_bodyBC = transform.GetComponent(); } // Use this for initialization void Start () { m_spriteAnimation.AddFrameCallBack("attack", 1, AttackCallBack); m_spriteAnimation.AddFrameCallBack("attack", 4, AttackCallBack); m_spriteAnimation.AddFrameCallBack("attack", 7, AttackCallBack); } public float m_speed = 1f; public float m_offset = 0.5f; public Vector3 m_destination = Vector3.zero; public List m_actionList = new List(); // Update is called once per frame void Update () { switch (m_staus) { case HeroStatus.idle: //空闲状态 if (m_actionList.Count > 0) m_staus = HeroStatus.exit; break; case HeroStatus.attack: if (!m_spriteAnimation.isPlaying) { //攻击动画播放完毕 m_staus = HeroStatus.exit; } break; case HeroStatus.defense: transform.position = Vector3.MoveTowards(transform.position, m_destination, m_speed * Time.deltaTime); if (transform.position == m_destination) m_staus = HeroStatus.exit; break; case HeroStatus.hit: transform.position = Vector3.MoveTowards(transform.position, m_destination, m_speed * Time.deltaTime); if (transform.position == m_destination) m_staus = HeroStatus.exit; break; case HeroStatus.die: break; case HeroStatus.cast: break; case HeroStatus.miss: break; case HeroStatus.seriousInjury: break; case HeroStatus.move: transform.position = Vector3.MoveTowards(transform.position, m_destination, m_speed * Time.deltaTime); if (transform.position == m_destination) m_staus = HeroStatus.exit; break; case HeroStatus.exit: //获取下一个状态 if (m_actionList.Count > 0) { string str = "idle"; switch(m_actionList[0].status) { case HeroStatus.move: str = "idle"; break; default: str = Enum.GetName(typeof(HeroStatus), m_actionList[0].status); break; } m_spriteAnimation.namePrefix = str; m_spriteAnimation.loop = m_actionList[0].loop; m_destination = m_actionList[0].destinationMove; m_speed = m_actionList[0].speed; m_spriteAnimation.ResetToBeginning(); m_staus = m_actionList[0].status; m_actionList.RemoveAt(0); } else { m_spriteAnimation.namePrefix = Enum.GetName(typeof(HeroStatus), HeroStatus.idle); m_spriteAnimation.loop = true; m_spriteAnimation.ResetToBeginning(); m_staus = HeroStatus.idle; } break; // case HeroStatus.moveback: // //transform.position = Vector3.SmoothDamp(transform.position, destinationMove, ref cameraVelocity, smoothTime); // transform.position = Vector3.MoveTowards(transform.position, destinationMove, m_speed * Time.deltaTime); // if (transform.position == destinationMove) // m_staus = HeroStatus.idle; // break; } } public void SetPosition(byte pos, float x, float y) { m_posIndex = pos; transform.localPosition = new Vector3(x, y); } public void AttackCallBack() { //创建攻击特效 A攻击B B掉血222 B反击A闪避 UnityEngine.Object sourceObj = Resources.Load("AttackCrash"); GameObject go = UnityEngine.Object.Instantiate(sourceObj) as GameObject; go.transform.parent = transform; go.transform.localScale = Vector3.one; go.transform.localPosition = new Vector3(-70, 0, 0); } private void OnCollisionEnter(Collision co) { //进入碰撞 Debug.Log("进入碰撞!"); UnityEngine.Object sourceObj = Resources.Load("Effect"); GameObject go = UnityEngine.Object.Instantiate(sourceObj) as GameObject; go.transform.parent = transform; go.transform.localScale = Vector3.one; go.transform.localPosition = new Vector3(0, 0, 0); //Defense(); Hit(); } public void Attack(GameObject aims) { ActionData tmpAD = new ActionData(); tmpAD.status = HeroStatus.move; tmpAD.loop = true; UISprite tmpS = transform.GetComponent(); tmpAD.destinationMove = GameObject.Find("UI Root/Camera").transform.TransformPoint(new Vector3(aims.transform.localPosition.x + (tmpS.width/2), aims.transform.localPosition.y)); tmpAD.speed = Vector3.Distance(transform.position, tmpAD.destinationMove) * 4; //4/1秒到达目的地 m_actionList.Add(tmpAD); ActionData tmpAD1 = new ActionData(); tmpAD1.status = HeroStatus.attack; tmpAD1.loop = false; tmpAD1.destinationMove = Vector3.zero; tmpAD1.speed = 0; m_actionList.Add(tmpAD1); ActionData tmpAD2 = new ActionData(); tmpAD2.status = HeroStatus.move; tmpAD2.loop = true; tmpAD2.destinationMove = transform.position; tmpAD2.speed = Vector3.Distance(tmpAD.destinationMove, tmpAD2.destinationMove) * 4; //4/1秒到达目的地 m_actionList.Add(tmpAD2); } public void Defense() { ActionData tmpAD = new ActionData(); tmpAD.status = HeroStatus.defense; tmpAD.loop = false; tmpAD.destinationMove = GameObject.Find("UI Root/Camera").transform.TransformPoint(new Vector3(transform.localPosition.x - 25, transform.localPosition.y)); tmpAD.speed = 0.25f; //4/1秒到达目的地 m_actionList.Add(tmpAD); ActionData tmpAD1 = new ActionData(); tmpAD1.status = HeroStatus.move; tmpAD1.loop = false; tmpAD1.destinationMove = transform.position; tmpAD1.speed = 0.8f; //4/1秒到达目的地 m_actionList.Add(tmpAD1); } public void Hit() { ActionData tmpAD = new ActionData(); tmpAD.status = HeroStatus.hit; tmpAD.loop = false; tmpAD.destinationMove = GameObject.Find("UI Root/Camera").transform.TransformPoint(new Vector3(transform.localPosition.x - 25, transform.localPosition.y)); tmpAD.speed = 0.3f; //4/1秒到达目的地 m_actionList.Add(tmpAD); ActionData tmpAD1 = new ActionData(); tmpAD1.status = HeroStatus.move; tmpAD1.loop = false; tmpAD1.destinationMove = transform.position; tmpAD1.speed = 0.8f; //4/1秒到达目的地 m_actionList.Add(tmpAD1); } public void Idle() { m_spriteAnimation.namePrefix = Enum.GetName(typeof(HeroStatus), HeroStatus.idle); m_spriteAnimation.loop = true; m_spriteAnimation.ResetToBeginning(); m_staus = HeroStatus.idle; } public void Move() { // //transform.GetComponent().depth = 99; // GameObject go = GameObject.Find("enemy").gameObject; // destinationMove = GameObject.Find("UI Root/Camera").transform.TransformPoint(new Vector3(go.transform.localPosition.x + 98,go.transform.localPosition.y,go.transform.localPosition.z)); // m_speed = Vector3.Distance(transform.position, destinationMove) * 4; //4/1秒到达目的地 // m_staus = HeroStatus.moveto; // m_spriteAnimation.namePrefix = Enum.GetName(typeof(HeroStatus), HeroStatus.idle); // m_spriteAnimation.loop = true; // m_spriteAnimation.ResetToBeginning(); }
Unity3D的动作游戏战斗系统是一种为游戏开发者提供的一套功能强大的工具和技术,用于创建充满刺激和动感的战斗场景。它提供了各种功能和组件,帮助开发者实现复杂的战斗机制,让玩家能够享受到真实感和挑战性。 首先,Unity3D提供了丰富的动画系统,使得角色在战斗中能够执行各种流畅的动作。开发者可以通过导入模型和动画,使用Animator组件来控制角色动画的播放和过渡。这样,玩家在游戏中的行动和操作会得到立即的反馈,增加了战斗的紧张感和乐趣。 其次,Unity3D提供了强大的碰撞检测和物理引擎,用于处理角色之间的碰撞和交互。开发者可以使用Collider和Rigidbody等组件,通过设定碰撞和力的属性来模拟真实的战斗效果。这样,在游戏中的击打、挥动武器等动作会根据物理规律产生逼真的效果,使战斗更加有趣和真实。 此外,Unity3D还提供了AI系统,可以为敌人和NPC角色添加自动控制和行为。开发者可以编写AI脚本,使敌人具备攻击、躲避、追击等行为,增加战斗的策略性和挑战性。AI系统的灵活性和可定制性,使得开发者可以根据游戏需求自由设计各种智能的战斗场景。 总之,Unity3D的动作游戏战斗系统开发者提供了丰富的工具和技术,使得他们能够轻松地创建出各种充满动感和挑战性的战斗场景。通过使用动画系统、碰撞检测和物理引擎以及AI系统开发者可以创造出真实、流畅而有趣的游戏体验,使玩家沉浸在刺激的战斗世界中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值