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

    template

    Joined:
    Jul 24, 2017
    Posts:
    2
    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):
    1.  
    2. public interface IEvent
    3. {
    4.   Awaitable Run();
    5. }
    6.  
    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):
    1.  
    2. class AbilitySO : ScriptableObject
    3. {
    4.   public Type<implements IEvent> actionType;
    5. }
    6.  
    7. class Foo {
    8.   AbilitySO ability;
    9.  
    10.   async void DoSomething()
    11.   {
    12.     await new ability.actionType().Run();
    13.   }
    14. }
    15.  
    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

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    730
    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. }
    7.  
    8. [AttributeUsage(AttributeTargets.Class)]
    9. public class EventTypeAttribute : Attribute
    10. {
    11.     public EventTypeAttribute(EventType eventType)
    12.     {
    13.         EventType = eventType;
    14.     }
    15.  
    16.     public EventType EventType { get; }
    17. }
    18.  
    19. // This will map an Event to a particular EventType enum value
    20. [EventType(EventType.Event1)]
    21. public class Event1 : IEvent
    22. {
    23.      // ...
    24. }
    25.  
    26. public YourSO : ScriptableObject
    27. {
    28.     // Use the Enum in the SO
    29.     public EventType EventType;
    30. }
    31.  
    32.  
    33. using System;
    34. using System.Reflection;
    35.  
    36. public static class EventsRegistry
    37. {
    38.     private static readonly Dictionary<EventType, IEvent> _eventsDictionary = GetAllEvents();
    39.  
    40.     // Call this to get an event from an SO.EventType
    41.     public static IEvent GetEvent(EventType eventType) => _eventsDictionary[eventType];
    42.  
    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

    template

    Joined:
    Jul 24, 2017
    Posts:
    2
    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!