How to store a type as an attribute in a scriptable object

Hi, nice to meet everyone!
I’m developing a proof of concept for a turn based rpg game, which will entail several battle abilities composed by similar building blocks (move to this spot, trigger this animation, and so on).

I’m not very familiar with C#'s options and best practices.

I’m creating a simple “node based” system based on what I’m calling “events”. Basically, each node is an object implementing an interface:

public interface IEvent
{
  Awaitable Run();
}

I then define specific events and allow passing parameters in the constructor, and I’m able to instantiate and chain events in code like this:

await new SomeEvent(...params).Run();
await new SomeOtherEvent(...params).Run();

The abilities available to characters are defined as scriptable objects, let’s call them AbilitySO. I would like an ability to be associated with a specific action, so that I can, given an action, create an instance of its “event type”. Something like (pseudocode):

class AbilitySO : ScriptableObject
{
  public Type<implements IEvent> actionType;
}

class Foo {
  AbilitySO ability;

  async void DoSomething()
  {
    await new ability.actionType().Run();
  }
}

This way I could create the ability SO instance and assign relevant parameters from the editor, including the action event to use, and then instantiate and run it.

I presume I could do this through reflection (something like string actionTypeName and then some C# voodoo to instantiate it?), but I’m not a fan of string referencing, I never had good experience with it. Is there some kind of compile time type safe alternative? The only way I found is to create one scriptable object definition per “action event”, inheriting from a common abstract SO definition I’m using as an interface, instantiate it in the editor and then assign it. This is pretty cumbersome though, and it creates at least 2 files per action event.

If this isn’t a good/idiomatic approach to the “final” result I want to achieve, feel free to recommend the proper way!

Thanks for the help!

Maybe [SerializeReference] with this library for the Editor part. You could also create an Enum+Attribute combo then use reflection:

// You could create a Unity Editor tool to autogenerate this enum using Reflection
public enum EventType
{
    Event1,
    Event2
}

[AttributeUsage(AttributeTargets.Class)]
public class EventTypeAttribute : Attribute
{
    public EventTypeAttribute(EventType eventType)
    {
        EventType = eventType;
    }

    public EventType EventType { get; }
}

// This will map an Event to a particular EventType enum value
[EventType(EventType.Event1)]
public class Event1 : IEvent
{
     // ...
}

public YourSO : ScriptableObject
{
    // Use the Enum in the SO
    public EventType EventType;
}


using System;
using System.Reflection;

public static class EventsRegistry
{
    private static readonly Dictionary<EventType, IEvent> _eventsDictionary = GetAllEvents();

    // Call this to get an event from an SO.EventType
    public static IEvent GetEvent(EventType eventType) => _eventsDictionary[eventType];

    // Automatically generates a dictionary (mapping EventType => IEvent) using Reflection
    private static Dictionary<EventType, IEvent> GetEventsDictionary()
    {
        return typeof(IEvent).Assembly.Types
            .Where(x => typeof(IEvent).IsAssignableFrom(x) && x.GetCustomAttribute<EventTypeAttribute>() != null)
            .ToDictionary(x => x.GetCustomAttribute<EventTypeAttribute>().EventType, x => (IEvent) Activator.CreateInstance(x));
    }
}

Code not tested, but you get the idea.

1 Like

Thank you for your input! I’ll try it out and compare it with my current approach to see which is more ergonomic in my case. Have a nice day!