Unity3D RPG Core | 15 人物基本属性
这节开始介绍游戏数据相关的内容。视频中介绍了 ScriptableObject 的方式,它能像其他 Unity 资产文件一样存储和管理,比如之前的 pipeline setting 文件也是 ScriptableObject 的形式。
1. 创建 ScriptableObject 脚本
我们创建一个 C# 脚本,命名为 CharacterDataScriptableObject。如代码清单 1 所示,我们让新建的类继承于 ScriptableObject 类。接着我们在类中定义和角色相关的属性,比如这边定义了最大生命值、当前生命值、基础防御力、当前防御力。
指定 CreateAssetMenu 特性,可以让我们在资产菜单中指定创建 ScriptableObject 对应的资产文件。menuName 设置菜单中的项名称,fileName 指定创建文件的默认名称。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- [CreateAssetMenu(fileName = "Character Data", menuName = "ScriptableObject/Character Data")]
- public class CharacterDataScriptableObject : ScriptableObject
- {
- public int m_maxHealth;
- public int m_currentHealth;
- public int m_baseDefence;
- public int m_currentDefence;
- }
视频中默认的目录是二级的。Unity2021 上如果想要设置多级目录,可以将 menuName 指定为 "ScriptableObject/Character Data" 这样。
2. 创建 ScriptableObject 资产文件
回到 Unity 界面,如图 1 所示,我们在资产窗口中点开菜单,可以发现有了新增的内容。

我们首先为人物角色创建对应的文件,命名为 PlayerData。如图 2 所示,我们可以在 Inspector 窗体中像之前一样设置属性值,非常方便。同样的,我们可以为史莱姆也创建一个对应的文件,也是使用相同的“模板”。

3. 创建访问脚本
直接在之前的人物控制代码中使用上述 CharacterDataScriptableObject 类是可以的。但是视频中应该是为了让数据这块功能更加独立,需要在对象上再单独挂一个脚本组件(需要继承 MonoBehaviour)。
我们新建 CharacterData 脚本。如代码清单 2 所示,主要就是封装 CharacterDataScriptableObject 类型的成员,我们通过访问器来访问其中的成员。
有一点需要说明,视频中的 CharacterDataScriptableObject 是公有的。这边把它设置为私有的原因是认为公有权限会破坏模块独立的初衷,同时也“浪费”了所写的访问器。但是我们还需要在 Unity 界面中指定对应的 CharacterDataScriptableObject 资产,所以使用 SerializeField 特性修饰。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- public class CharacterData : MonoBehaviour
- {
- [SerializeField]
- private CharacterDataScriptableObject m_characterData = null;
- public int MaxHealth
- {
- get { return m_characterData != null ? m_characterData.m_maxHealth : 0; }
- set { if (m_characterData != null) m_characterData.m_maxHealth = value; }
- }
- public int CurrentHealth
- {
- get { return m_characterData != null ? m_characterData.m_currentHealth : 0; }
- set { if (m_characterData != null) m_characterData.m_currentHealth = value; }
- }
- public int BaseDefence
- {
- get { return m_characterData != null ? m_characterData.m_baseDefence : 0; }
- set { if (m_characterData != null) m_characterData.m_baseDefence = value; }
- }
- public int CurrentDefence
- {
- get { return m_characterData != null ? m_characterData.m_currentDefence : 0; }
- set { if (m_characterData != null) m_characterData.m_currentDefence = value; }
- }
- }
SerializeField 特性可以使私有变量在 Inspector 窗体中指定。
4. 挂载访问脚本
如图 3 所示,我们把访问脚本挂载在人物角色对象上,并指定数据为 PlayerData。

回到之前的角色控制脚本中,如代码清单 3 所示,定义了 CharacterData 成员(第 3 行),并获取相关组件(第 9 行)。
- public class PlayerController : MonoBehaviour
- {
- CharacterData m_characterData;
- void Awake()
- {
- m_navMeshAgent = GetComponent<NavMeshAgent>();
- m_animator = GetComponent<Animator>();
- m_characterData = GetComponent<CharacterData>();
- }
- // Start is called before the first frame update
- void Start()
- {
- MouseManager.GetInstance().OnMoveMouseClick += DoMoveAction;
- MouseManager.GetInstance().OnAttackMouseClick += DoAttackAction;
- Debug.Log("Player MaxHealth=" + m_characterData.MaxHealth);
- m_characterData.MaxHealth = 200;
- Debug.Log("Player MaxHealth=" + m_characterData.MaxHealth);
- }
- }
第 18 至 20 行,我们编写了测试代码,测试数据的读写。如图 4 所示,结果符合预期。
