Unity中Serializable类HealthController初始化生命值的更优实现咨询
我编写了一个用于GameObject生命值控制的HealthController类,它并非MonoBehavior,但会被MonoBehavior对象调用。相关代码如下:
using System; using UnityEngine; [Serializable] public class HealthController{ [SerializeField] private int max_health; private int health; public HealthController() { this.health = this.max_health; // 这行无效,因为构造函数在序列化前执行 } }
为了能在Unity编辑器中设置最大生命值,我将max_health标记为[SerializeField],同时希望初始时将health设为max_health,且不为health添加额外的SerializeField。尝试用构造函数完成赋值,但构造函数在序列化前执行,导致操作无效。
目前的解决方案是为HealthController添加Initialize方法,并在所属MonoBehavior的Start方法中显式调用:
using System; using UnityEngine; [Serializable] public class HealthController{ [SerializeField] private int max_health; private int health; public void Initialize() { this.health = this.max_health; } } public class Player : MonoBehaviour { public HealthController health; public void Start() { health.Initialize(); } }
但这个方案过于繁琐,想咨询是否存在更优的实现方式?
更优实现方式
方法1:实现ISerializationCallbackReceiver接口
Unity提供的ISerializationCallbackReceiver接口,正好能解决序列化时机问题——它的OnAfterDeserialize方法会在Unity完成序列化后执行,完美替代构造函数的初始化逻辑:
using UnityEngine; [Serializable] public class HealthController : ISerializationCallbackReceiver{ [SerializeField] private int max_health = 100; // 给默认值避免编辑器显示0 private int health; // 序列化完成后自动调用,初始化生命值 public void OnAfterDeserialize() { health = max_health; } // 序列化前调用,可做参数校验 public void OnBeforeSerialize() { max_health = Mathf.Max(0, max_health); // 确保最大生命值不为负 } }
这种方式无需手动调用任何初始化方法,Unity会自动完成health的赋值,完全贴合需求。
方法2:利用OnValidate方法
OnValidate会在编辑器修改属性、脚本加载时自动执行,同时运行时启动前也会触发,能兼顾编辑器和运行时的初始化需求:
using UnityEngine; [Serializable] public class HealthController{ [SerializeField] private int max_health = 100; private int health; private void OnValidate() { max_health = Mathf.Max(0, max_health); // 仅在未初始化或生命值异常时赋值 if (health == 0 || health > max_health) { health = max_health; } } }
不仅能自动初始化生命值,还能在后续修改max_health时,自动修正超出范围的当前生命值。
方法3:属性封装max_health
通过属性封装监听max_health的变化,同步更新health,同时配合OnValidate确保编辑器修改时触发逻辑:
using UnityEngine; [Serializable] public class HealthController{ [SerializeField] private int max_health = 100; private int health; public int MaxHealth { get => max_health; set { max_health = Mathf.Max(0, value); // 初始化或修正当前生命值 if (health == 0 || health > max_health) { health = max_health; } } } public int CurrentHealth => health; private void OnValidate() { MaxHealth = max_health; // 触发setter逻辑完成初始化 } }
这种方式不仅解决初始化问题,还能在后续动态修改最大生命值时,自动维护当前生命值的合理性。
内容的提问来源于stack exchange,提问作者man zet

