Unity3D RPG Core | 28 玩家升级系统

本篇介绍玩家角色的升级,都是和代码相关的内容。

相较于视频中的代码,这边重新组织了一下数据信息相关的代码。这块的流程和 《Unity3D RPG Core | 15 人物基本属性》 一样。

1. 角色数据信息

如代码清单 1 所示,我们新增角色人物的 ScriptableObject 类,继承之前实现的 CharacterDataScriptableObject 类。其中增加了当前等级、最高等级、基础经验值、当前经验值以及升级加成这些属性。

代码清单 1 新增角色 ScriptableObject
  1. [CreateAssetMenu(fileName = "Character Data", menuName = "ScriptableObject/Player Character Data")]
  2. public class PlayerDataScriptableObject : CharacterDataScriptableObject
  3. {
  4.     [Header("Level")]
  5.     public int   m_currentLevel;
  6.     public int   m_maxLevel;
  7.     public int   m_baseExp;
  8.     public int   m_currentExp;
  9.     public float m_levelBuff;
  10. }

代码清单 2 中的 PlayerData 类是对上述 ScriptableObject 的控制封装,继承之前实现的 CharacterData。关注 UpdateExp()、LevelUp() 和 GetLevelMultiplier() 这几个函数。

UpdateExp() 为角色加上获取的经验值。如果当前经验值大于基础经验值,则进行升级操作,对应 LevelUp() 函数。

LevelUp() 修改当前的等级、基础经验值和最大生命值。基础经验值和最大生命值的加成数值见 GetLevelMultiplier() 函数,它逐级递增。

代码清单 2 角色 ScriptableObject 封装
  1. public class PlayerData : CharacterData
  2. {
  3.     PlayerDataScriptableObject m_playerData;
  4.  
  5.     private void Awake()
  6.     {
  7.         if (m_templateCharacterData != null)
  8.         {
  9.             m_characterData = m_templateCharacterData;
  10.             m_playerData = (PlayerDataScriptableObject)m_characterData;
  11.         }
  12.     }
  13.  
  14.     public void UpdateExp(int point)
  15.     {
  16.         CurrentExp += point;
  17.         while (CurrentExp >= BaseExp)
  18.             LevelUp();
  19.     }
  20.  
  21.     private void LevelUp()
  22.     {
  23.         CurrentLevel = Math.Min(CurrentLevel + 1, MaxLevel);
  24.         BaseExp += (int)(BaseExp * GetLevelMultiplier());
  25.         MaxHealth = (int)(MaxHealth * GetLevelMultiplier());
  26.         CurrentHealth = MaxHealth;
  27.  
  28.         Debug.Log("LevelUp! CurrentLevel:" + CurrentLevel + ";MaxHealth:" + MaxHealth);
  29.     }
  30.  
  31.     private float GetLevelMultiplier()
  32.     {
  33.         return 1 + (CurrentLevel - 1) * LevelBuff;
  34.     }
  35.  
  36.     public int CurrentLevel
  37.     {
  38.         get { return m_playerData != null ? m_playerData.m_currentLevel : 0; }
  39.         set { if (m_playerData != null) m_playerData.m_currentLevel = value; }
  40.     }
  41.  
  42.     public int MaxLevel
  43.     {
  44.         get { return m_playerData != null ? m_playerData.m_maxLevel : 0; }
  45.         set { if (m_playerData != null) m_playerData.m_maxLevel = value; }
  46.     }
  47.  
  48.     public int BaseExp
  49.     {
  50.         get { return m_playerData != null ? m_playerData.m_baseExp : 0; }
  51.         set { if (m_playerData != null) m_playerData.m_baseExp = value; }
  52.     }
  53.  
  54.     public int CurrentExp
  55.     {
  56.         get { return m_playerData != null ? m_playerData.m_currentExp : 0; }
  57.         set { if (m_playerData != null) m_playerData.m_currentExp = value; }
  58.     }
  59.  
  60.     public float LevelBuff
  61.     {
  62.         get { return m_playerData != null ? m_playerData.m_levelBuff : 0; }
  63.         set { if (m_playerData != null) m_playerData.m_levelBuff = value; }
  64.     }
  65. }

2. 敌人数据信息

如代码清单 3 所示,敌人的信息我们新增了一个被击杀获取的经验点数字段。访问相关的逻辑和角色人物的一致,这边不再赘述。

代码清单 3 新增敌人 ScriptableObject
  1. [CreateAssetMenu(fileName = "Character Data", menuName = "ScriptableObject/Enemy Character Data")]
  2. public class EnemyDataScriptableObject : CharacterDataScriptableObject
  3. {
  4.     [Header("Kill")]
  5.     public int m_killPoint;
  6. }

3. 升级逻辑

我们将升级的逻辑放在敌人数据这边,即之前的 CharacterData,第 2 节中重新组织到敌人数据这边了。见代码清单 4 所示的第 29 至 33 行,如果当前敌人的生命值小于等于 0,我们就把敌人所属的经验值加到攻击它的角色身上。为了防止重复经验值添加的意外发生,特别添加了 hasGivenPoint 变量用来区分。

代码清单 4 升级逻辑
  1. public class EnemyData : CharacterData
  2. {
  3.     EnemyDataScriptableObject m_enemyData;
  4.     bool hasGivenPoint = false;
  5.  
  6.     void Awake()
  7.     {
  8.         base.Awake();
  9.         m_enemyData = (EnemyDataScriptableObject)m_characterData;
  10.     }
  11.     public int KillPoint
  12.     {
  13.         get { return m_characterData != null ? m_enemyData.m_killPoint : 0; }
  14.         set { if (m_characterData != null) m_enemyData.m_killPoint = value; }
  15.     }
  16.  
  17.     public void ReceiveAttackDamage(AttackData attackerAttackData, PlayerData attackerData)
  18.     {
  19.         if (attackerAttackData.m_isCriticalHit)
  20.         {
  21.             GetComponent<Animator>().SetTrigger("Hurt");
  22.         }
  23.  
  24.         int damage = Mathf.Max(CalculateDamage(attackerAttackData) - CurrentDefence, 1);
  25.         CurrentHealth = Mathf.Max(CurrentHealth - damage, 0);
  26.         // 更新UI
  27.         RaiseHealthChanged();
  28.         //TODO:更新经验
  29.         if (CurrentHealth <= 0 && hasGivenPoint == false)
  30.         {
  31.             hasGivenPoint = true;
  32.             attackerData.UpdateExp(KillPoint);
  33.         }
  34.     }
  35.  
  36.     int CalculateDamage(AttackData attacker)
  37.     {
  38.         float damage = UnityEngine.Random.Range(attacker.MinDamage, attacker.MaxDamage);
  39.         if (attacker.m_isCriticalHit)
  40.             damage *= attacker.CriticalMultiplier;
  41.         return (int)damage;
  42.     }
  43. }

4. 测试

在测试之前,我们需要重新创建人物角色和敌人的 ScriptableObject。然后移除之前的角色数据脚本,替换成角色或敌人的数据控制脚本。最后在控制脚本上添加新的 ScriptableObject。

我们打三只史莱姆就可以升级了,显示如图 1 所示的日志就代表逻辑跑通了,角色升了一级,最大血量加了 10%。

图1 最终结果