- Home /
Non-MonoBehaviour object with SerializableField
Hi, I'm trying to implement health for my player character and enemies. My class looks like this:
[Serializable]
public class Health {
[SerializeField] private int totalHealthPoints;
public Health() {
HealthPoints = totalHealthPoints;
}
public int TotalHealthPoints => totalHealthPoints;
public int HealthPoints { get; private set; }
public bool IsEmpty => HealthPoints <= 0;
public void TakeDamage(int damagePoints) {
HealthPoints -= damagePoints;
}
}
I have another class that checks the HealthPoints
in their Update
method (with a reference to my PlayerCharacter
class which is a MonoBehaviour
with a IsAlive
property) and ends the game if the player has no health left. My problem is that the game ends immediately and if I log the value of totalHealthPoints
in the constructor, it's always 0, no matter what I input in the editor.
Is there no way to access a serialized value in the constructor without making the class a MonoBehaviour
? The only other way to solve this problem would be to introduce an event to the Health
, which the "observer" can listen to.
public class PlayerCharacter : MonoBehaviour {
[SerializeField] private Health health;
public bool IsAlive => !health.IsEmpty;
}
public class PlayerHealthObserver : MonoBehaviour {
[SerializeField] private PlayerCharacter playerCharacter;
private void Update() {
if (playerCharacter.IsAlive)
return;
EndGame();
}
private void EndGame() {
// Does stuff to end the game.
}
}
Answer by G_hi3 · Nov 25, 2020 at 06:23 PM
I was able to find a solution to my problem: Unity 4.5 introduced the ISerializationCallbackReceiver
interface. It declares two methods called OnBeforeSerialize
and OnAfterDeserialize
. I implemented the interface on my Health
class to set the healthPoints
variable after deserialization.
[Serializable]
public class Health : ISerializationCallbackReceiver {
[SerializeField] private int totalHealthPoints;
public int TotalHealthPoints => totalHealthPoints;
public int HealthPoints { get; private set; }
public bool IsEmpty => HealthPoints <= 0;
public void TakeDamage(int damagePoints) {
HealthPoints -= damagePoints;
}
public void OnBeforeSerialize() {
}
public void OnAfterDeserialize() {
HealthPoints = totalHealthPoints;
}
}
The only down-side in my case is the empty OnBeforeSerialize
method.
Answer by xxmariofer · Nov 25, 2020 at 02:25 PM
Hello, have you consider using ScriptableObject for that task? simply create the scriptable object and use it for serialization
using UnityEngine;
[CreateAssetMenu(fileName = "Health", menuName = "ScriptableObjects/Health", order = 1)]
public class HealthScriptable : ScriptableObject
{
[SerializeField] private int totalHealthPoints;
public int TotalHealthPoints => totalHealthPoints;
public int HealthPoints { get; private set; }
public bool IsEmpty => HealthPoints <= 0;
private void OnEnable()
{
HealthPoints = totalHealthPoints;
}
public void TakeDamage(int damagePoints)
{
HealthPoints -= damagePoints;
}
}
Yes, but as I understand it, the ScriptableObject
is used for things you want to create an asset for. It seems a bit overkill to create an asset for every Health
instance I want to use in my game. Especially, since it's such a lightweight class.
It only creates one asset, and the instances are components. It's like the box collider component for example. You can find it in the menu (which are part of the default Unity assets) and when you add a new instance of a box collider component onto your object, it doesnt create a new asset. Its definitely not overkill and it's probably a good idea to use xxmariofer's solution above.
I've been working a bit with ScriptableObject
lately. An asset created from a ScriptableObject
can be referenced by different objects in the scene. In my case it made more sense to have Health
be a ISerializationCallbackReceiver
, because it will only ever be accessed by a PlayerCharacter
or Enemy
object that owns it.
I did end up using ScriptableObject
in my project as something completely unrelated. I'm able to create a Dialog
object and write dialog pages and reference the speaking characters.