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?

1 Answer 1

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();
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

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<EnemyAIBase> 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<EnemyAIBase> doesn't work: forum.unity.com/threads/…
Actually, looks like I can't serialize any of these: public EnemyData<EnemyAIBase>[] enemyArray; public EnemyData<EnemyAIBase> enemy; public EnemyData<PirateShipAI> pirateShip;
Yes that's why you need the explicit implementation PirateShip as shown. You could however have a non-genric parent class you can serialize

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.