Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

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

Discussion in 'Scripting' started by template, Apr 14, 2024.

  1. template


    Jul 24, 2017
    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:
    Code (cSharp):
    2. public interface IEvent
    3. {
    4.   Awaitable Run();
    5. }
    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:
    Code (CSharp):
    1. await new SomeEvent(...params).Run();
    2. 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):

    Code (CSharp):
    2. class AbilitySO : ScriptableObject
    3. {
    4.   public Type<implements IEvent> actionType;
    5. }
    7. class Foo {
    8.   AbilitySO ability;
    10.   async void DoSomething()
    11.   {
    12.     await new ability.actionType().Run();
    13.   }
    14. }
    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!
    Last edited: Apr 14, 2024
  2. Nad_B


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

    Code (CSharp):
    1. // You could create a Unity Editor tool to autogenerate this enum using Reflection
    2. public enum EventType
    3. {
    4.     Event1,
    5.     Event2
    6. }
    8. [AttributeUsage(AttributeTargets.Class)]
    9. public class EventTypeAttribute : Attribute
    10. {
    11.     public EventTypeAttribute(EventType eventType)
    12.     {
    13.         EventType = eventType;
    14.     }
    16.     public EventType EventType { get; }
    17. }
    19. // This will map an Event to a particular EventType enum value
    20. [EventType(EventType.Event1)]
    21. public class Event1 : IEvent
    22. {
    23.      // ...
    24. }
    26. public YourSO : ScriptableObject
    27. {
    28.     // Use the Enum in the SO
    29.     public EventType EventType;
    30. }
    33. using System;
    34. using System.Reflection;
    36. public static class EventsRegistry
    37. {
    38.     private static readonly Dictionary<EventType, IEvent> _eventsDictionary = GetAllEvents();
    40.     // Call this to get an event from an SO.EventType
    41.     public static IEvent GetEvent(EventType eventType) => _eventsDictionary[eventType];
    43.     // Automatically generates a dictionary (mapping EventType => IEvent) using Reflection
    44.     private static Dictionary<EventType, IEvent> GetEventsDictionary()
    45.     {
    46.         return typeof(IEvent).Assembly.Types
    47.             .Where(x => typeof(IEvent).IsAssignableFrom(x) && x.GetCustomAttribute<EventTypeAttribute>() != null)
    48.             .ToDictionary(x => x.GetCustomAttribute<EventTypeAttribute>().EventType, x => (IEvent) Activator.CreateInstance(x));
    49.     }
    50. }
    Code not tested, but you get the idea.
    Last edited: Apr 14, 2024
    template likes this.
  3. template


    Jul 24, 2017
    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!