设计敌人血条模块

在游戏中我们经常需要动态控制血条的渲染,比如在黑魂中遇到Boss时屏幕下方会显示Boss血量条或者是在只狼中Boss对战时屏幕左上角的属性条。在原神中攻击敌人后显示敌人血量条,停止攻击一段时间或敌人失去攻击状态后血量条消失。它们的血量条都是不同的。

假如我们按照上面的例子,给Boss和小兵设计出不一样表现的血量条。分析可以看出它们在渲染显示部分存在差异、而在逻辑上是存在共性部分的。可以抽象出HpBarEntity血条实体的概念,抽象会变化的部分,提取出共性部分,具体实现渲染表现部分。代码如下:

public class HpBarEntity : MonoBehaviour
{
    [SerializeField]
    private UILabel _nameLabel;
    [SerializeField]
    private UIProgressBar _UISlider;
    private Transform followTarget;
    public void SetFollowHead(Transform trans)
    {
        followTarget=trans;
    }
    public void Update()
    {
        if (followTarget)
        {
            transform.position = CameraMgr.WorldPointToNGUIWorldPoint(followTarget.position+Vector3.up*0.5f);
        }
    }

    public void SetInfo(string entityName,IDamageable damageable)
    {
        _nameLabel.text = entityName;
        UpdateHpBar(damageable);
    }
    public void UpdateHpBar(IDamageable damageable)
    {
        _UISlider.value = damageable.hp*1.0f / damageable.MaxHP;
    }
}

上面代码中HpBarEntity是血量条的基类提取出了设置属性、更新血条、血条跟随目标对象的行为。血条跟随目标对象这个可能不是每个血条共有的行为,但是考虑再抽象出一个类型会使结构变得复杂,抽象的意义也不是很大,显得很鸡肋。 所以血条是否跟随目标对象取决于 是否设置了目标对象。

对于特殊的血量条,我们可以继续扩展,如主角玩家血量条。主角玩家血量条一般除了血量属性,还存在体力、能量等表现。可以考虑直接在基础上扩展、或者是抽象出属性条、体力条,使用组合模式来控制各个属性的表现。

除了设计血条的类结构,我们还需要考虑血条的复用。游戏中一般每个敌人头上都有血量条,我们其实可以复用血量条表现一致的游戏对象,来降低创建和销毁带来的性能开销。接下来要说的对象池想必大家也很熟悉了,关于对象池可以浏览文章:通用化的对象池设计了解。应用对象池并结合我的项目来看看我们如何复用血量条,代码如下:

public Dictionary<IDamageable, HpBarEntity> hpbarEntityDics = new Dictionary<IDamageable, HpBarEntity>();//血量条实体字典

//获取当前角色对象挂载的血量条实体
public HpBarEntity GetHpBarEntity(IDamageable damageable)
    {
        HpBarEntity hpBarEntity;
        hpbarEntityDics.TryGetValue(damageable, out hpBarEntity);
        return hpBarEntity;
    }
//获取当前角色对象挂载的血量条实体或申请得到一个新的血量条
    public HpBarEntity ApplyHpBarEntity(IDamageable damageable)
    {
        HpBarEntity hpBarEntity;
        hpbarEntityDics.TryGetValue(damageable,out hpBarEntity);
        if (hpBarEntity==null)
        {
//从对象池中取出
            hpBarEntity = GameObjectFactory.Instance.PopItem(PathDefine.UIItemPath+Constants.HpBarEntity).GetComponent<HpBarEntity>();

            if (hpBarEntity!=null)
            {
                hpbarEntityDics.Add(damageable,hpBarEntity);
//获取头顶位置
                hpBarEntity.SetFollowHead(damageable.characterFacade.actorPhyiscal.GetCheckPoint(CheckPointType.Head));
            }
        }

       return hpBarEntity;
    }
//从一个角色对象上移除血量条
    public void RemoveHpBarEntity(IDamageable damageable)
    {
        if (hpbarEntityDics.ContainsKey(damageable))
        {
            HpBarEntity hpBarEntity=hpbarEntityDics[damageable];
            hpBarEntity.SetFollowHead(null);
            hpbarEntityDics.Remove(damageable);
//压入对象池
            GameObjectFactory.Instance.PushItem(hpBarEntity.gameObject);
        }
    }

控制血量条的显示

敌人受伤时显示血量条-IDamageable类Damage方法

protected virtual void Damage(DamageData damageData)
    {
        if (IsDie)
        {
            return;
        }
        
        attackerList.Clear();
        attackerList.Add(damageData.attacker.GetComponent<IDamageable>());
        hp -= damageData.damage;

        HpBarEntity _HpBarEntity = characterFacade.ApplyHpBarEntity();//获取血量条
        _HpBarEntity?.SetInfo(actorSystem.roleDefinition.object_Name, this);//设置血量信息
        _HpBarEntity?.gameObject?.SetActive(true);//激活
        _HpBarEntity?.UpdateHpBar(this);//更新

        if (hp <= 0)
        {
           
            Die(damageData);
        }
        PlayHitEffectAudio(IsDie);
    }

敌人死亡

public virtual void Die(DamageData damageData)
    {
        hp = 0;
        BattleManager.Instance.RemoveHpBarEntity(this);//关闭血量条
        dieAction?.Invoke(damageData);
    }

敌人网格体-渲染移入移除时控制血量条

关于渲染移入移除接口,可见文章进入或退出相机渲染范围内-血条动态显示

public class EnemyController: NPCController
{
    private int inVisible_CheckBarTid=-1;
    //public MyTimer hitAfterLookAttackWait=new MyTimer();//多少秒后锁定攻击者
    public override void Awake()
    {
        base.Awake();
    }
//进入相机渲染范围
    public override void OnRenderVisible()
    {
        base.OnRenderVisible();
        HpBarEntity barEntity = BattleManager.Instance.GetHpBarEntity(damageable);
        if (barEntity)
        {
            barEntity.SetInfo(actorSystem.roleDefinition.object_Name,damageable);
            barEntity.gameObject.SetActive(true);
        }
    }
//移出相机渲染范围
    public override void OnRenderInVisible()
    {
        base.OnRenderInVisible();
        HpBarEntity barEntity = BattleManager.Instance.GetHpBarEntity(damageable);
        if (barEntity)
        {
            barEntity.gameObject.SetActive(false);
            if (inVisible_CheckBarTid!=-1)
            {
                GameRoot.Instance.DelTimeTask(inVisible_CheckBarTid);
            }
            
            inVisible_CheckBarTid=GameRoot.Instance.AddTimeTask(5000, () =>
            {
                if (renderStateListener.isRenderObject==false)
                {
                    BattleManager.Instance.RemoveHpBarEntity(damageable);
                    inVisible_CheckBarTid = - 1;
                }
            });
        }
    }
}
作者:Miracle
来源:麦瑞克博客
链接:https://www.unitymake.com/archives/unity/159
本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议,转载请注明!
THE END
分享
打赏
海报
设计敌人血条模块
在游戏中我们经常需要动态控制血条的渲染,比如在黑魂中遇到Boss时屏幕下方会显示Boss血量条或者是在只狼中Boss对战时屏幕左上角的属性条。在原神中攻击敌人……
<<上一篇
下一篇>>