1

I have an EnemyData ScriptableObject that holds data about enemies. I'd like to have a field on EnemyData that references some logic about how this enemy behaves on its turn (in turn-based card game). My current attempt at this is to structure that as a ScriptableObject too, basically like this:

public class EnemyData : ScriptableObject
{
    public int health;
    public EnemyAIBase enemyAI;
}

public abstract class EnemyAIBase : ScriptableObject
{
    public abstract void PlayTurn(Enemy thisEnemy);
}

public class PirateShipAI : EnemyAIBase
{
    public override void PlayTurn(Enemy thisEnemy)
    {
        thisEnemy.Heal();
        AttackPlayer();
    }
}

So as an example, I've got a "PirateShip" asset of type EnemyData, whose enemyAI field points to a "PirateShipAI" asset of type PirateShipAI.

But this feels wrong, every time I code up a new enemy's AI I have to also instantiate an asset just so it can be referenced by an EnemyData. I feel like EnemyAIBase shouldn't even be an SO, it's not like it has any variables that different assets will override. There will be a 1-to-1 mapping between EnemyData assets and custom AI for that enemy. So this SO is just a container for some logic, which doesn't feel right. But I don't know any other way to reference that logic in my EnemyData SO.

I guess I wish an SO could reference a C# class directly, but I don't think this is possible.

One option is that I could build an Editor that hides the EnemyAI asset as a sub-asset of the EnemyData asset, kinda like I did over here: Building an Editor for nested ScriptableObjects to compose abilities in a card game

But that feels really wrong here, because I don't intend to make any of this AI generic.

How can I attach behavior/logic to a ScriptableObject?

ack
  • 14,285
  • 22
  • 55
  • 73

1 Answers1

2

You can indeed simply make it not a ScriptableObject. To define different behaviors I would use a generic here:

public abstract class EnemyData<T> : ScriptableObject where T : EnemyAIBase
{
    public int health;
    public T enemyAI;
}

public abstract class EnemyAIBase
{
    public abstract void PlayTurn(Enemy thisEnemy);
}

And then from these create your actual implementations

[CreateAssetMenu]
public class PirateShip : EnemyData<PirateShipAI>{ }

public class PirateShipAI : EnemyAIBase
{
    public override void PlayTurn(Enemy thisEnemy)
    {
        thisEnemy.Heal();
        AttackPlayer();
    }
}
derHugo
  • 83,094
  • 9
  • 75
  • 115
  • thanks @derHugo, this is interesting! i'm playing with it, hitting two things that make this feel not very "first class": (A) references to `EnemyData` become `EnemyData` which i guess i could alias globally for readability. (B) i don't think i can serialize a list of generics i.e. a GameObject that holds a list of `EnemyData` doesn't work: https://forum.unity.com/threads/case-1188478-list-of-generic-type-not-serialized.754442/ – ack Jul 30 '20 at 17:38
  • Actually, looks like I can't serialize any of these: `public EnemyData[] enemyArray; public EnemyData enemy; public EnemyData pirateShip;` – ack Jul 30 '20 at 17:47
  • Yes that's why you need the explicit implementation `PirateShip` as shown. You could however have a non-genric parent class you can serialize – derHugo Jul 30 '20 at 19:22