Currently I'm using ScriptableObjects as a template for things like character's faction, weapons, etc. Using faction as an example the FactionSO is a container for CharacterStats class for a character like move speed/ damage multipliers and so on. The Character class references to the FactionSO and it looks like so:
[System.Serializable]
public class Character
{
public string characterName;
public int level;
public FactionSO factionSO;
public CharacterStats stats;
}
This way I can create new Factions easily and assign reference to FactionSO when a character is created. When you start a new game with a chosen character the Stats are initialized from FactionSO's CharacterStats so the FactionSO is untouched.
It worked but when I want to save Character class with ToJSON utility, the reference to ScriptableObject is serialized as InstanceID, which means each time I restart the editor the reference is lost. In a built application it instead serializes to m_fileID and m_pathID which I don't know how vulnerable it is. Cannot using the same save file between editor and build is another issue.
I tried using Addressables' AssetReference which works but then I need to create a singleton database to load assets and create a separate SavableCharacter class which has AssetReferene FactionRef instead of FactionSO.
public class Database : MonoBehaviour
{
public List<AssetReference> factionRef;
public List<FactionDatabase> factionDatabase;
public static Database instance {get; private set;}
void Awake()
{
if(instance!=null){
Destroy(this.gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this.gameObject);
factionDatabase.Clear();
foreach(var asset in factionRef){
asset.LoadAssetAsync<FactionSO>().Completed += (aHandle)=>{
FactionDatabase newFaction = new FactionDatabase(aHandle.Result, asset);
factionDatabase.Add(newFaction);
};
}
}
public FactionSO GetFactionSOFromRef(AssetReference asset){
return factionDatabase.FirstOrDefault(i=> i.factionRef.AssetGUID==asset.AssetGUID).factionSO;
}
public AssetReference GetFactionRefFromSO(WeaponSO weaponSO){
return factionDatabase.FirstOrDefault(i=> i.factionSO==factionSO).factionRef;
}
}
[System.Serializable]
public class FactionDatabase{
public FactionSO factionSO;
public AssetReference factionRef;
public FactionDatabase(FactionSO factionSO, AssetReference factionRef)
{
this.factionSO = factionSO;
this.factionRef = factionRef;
}
}
and add constructor each time I load/save the game that converts Character to SavableCharacter and vice versa:
[System.Serializable]
public class SavableCharacter
{
public string characterName;
public int level;
public AssetReference factionSORef;
public CharacterStats stats;
public SavableCharacter(Character character){
this.characterName = character.characterName;
this.level = character.level;
this.factionSORef = AssetSOLookup.instance.GetFactionRefFromSO(character.factionSO);;
}
}
which works as intended but I feel it's really messy and what if I want to also have List saved to the character which has reference to ScriptableObject of weapons nested inside?
[System.Serializable]
public class WeaponProficiency{
public WeaponSO weaponSO;
public int proficiencyLevel;
}
}
I feel like I'm having to much fun putting everything into SOs and I'm paying for it. To summarize the questions:
- Is there a way to make ScriptableObjects turns into AssetReference when you serialize it probably with ISerializationCallbackReceiver? so that I don't have to manually create another savable class
- Is my approach even the right one? There is option of assigning IDs to everything but then I need to change all references. Currently I can simply reference character.factionSO.factionName to get which faction the character belongs in.