Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question A way to specify a ScriptableObject's generic types upon creation

Discussion in 'Scripting' started by PlayCreatively, Nov 2, 2020.

  1. PlayCreatively

    PlayCreatively

    Joined:
    Jan 25, 2016
    Posts:
    94
    Does anyone have any idea how I could be able to specify a ScriptableObject's generic types upon creation? The ScriptableObject itself doesn't have to be generic but more importantly the class inside needs to have specified generic types, so that I can have access to that information when I'm referencing the ScriptableObject in code.
    If anyone knows of any repositories I could use or know if this is even possible in theory please let me know.
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,722
    What are you trying to accomplish?

    ScriptableObjects are not special in terms of the rules and behavior of Generic Types in C#.
     
    Bunny83 likes this.
  3. PlayCreatively

    PlayCreatively

    Joined:
    Jan 25, 2016
    Posts:
    94
    I'm using ScriptableObjects to create quests and other conditionals. A ScriptableObject has a function which needs to have the specified generic types. So for an example I'll create a ScriptableObject called "Talk to Bob" which has a function which generically takes in a string and returns a bool, so the method checks if who I'm talking to is "Bob".
    I'm using ScriptableObjects because I'll have hundreds of quests and conditions, having a ScriptableObject as one objective.
     
  4. Boo-Let

    Boo-Let

    Joined:
    Jan 21, 2019
    Posts:
    150
    Wouldn’t it be better to use interfaces for this?
     
  5. PlayCreatively

    PlayCreatively

    Joined:
    Jan 25, 2016
    Posts:
    94
    Maybe. Could you tell me how I'd use interfaces for this?
     
  6. Boo-Let

    Boo-Let

    Joined:
    Jan 21, 2019
    Posts:
    150
    You can use an “Iintractable” with a “interact” function. In that Function you could use that to return the scriptable object in which you’re interacting with and use it to display specific options to that character. I mean there are many ways to achieve what you’re wanting.. I would start with a simple solution and then work on expanding it as you need. Don’t over think it or you might end up wasting time creating a state processor that’s unnecessary. I’ve been guilty of that myself when there has been much easier solutions I’ve over looked because of “over thinking”
     
  7. PlayCreatively

    PlayCreatively

    Joined:
    Jan 25, 2016
    Posts:
    94
    I agree with you on the overthinking part and my generic conditional class was the simples way of doing it. But when I realized I needed to be able to create the conditions in the editor itself I stumbled on the huge headache of serializing generics and methods which Unity does nothing to assist you with.

    I'm not sure how your idea lets me skip making the conditions generic though, every condition needs different parameters which is why it needs to be generic.

    However even though what I've been doing has been taking me A LOT of time the reason I'm doing it is so that I can use this implementation in all of my future projects. It's generic, flexible and can be used in any situation.
     
  8. Boo-Let

    Boo-Let

    Joined:
    Jan 21, 2019
    Posts:
    150
    I think we had a misunderstanding..

    you’re wanting to create a scriptable objects of say “npc” right? And in that object you’ll have his name and what not, right? And you’re wanting a way of saying “hey I’m interacting with such and such npc”. Right?
     
  9. PlayCreatively

    PlayCreatively

    Joined:
    Jan 25, 2016
    Posts:
    94
    No no, each scriptable object is an unattached thing. It only has 2 methods, the condition and the onSuccess method. The parameters they both take needs to be generic so that I can have a large selection of possible methods. One example would be: a condition that takes in a string and returns bool, so I'd do condition(characterName) and if it returns true then the onSuccess fires and runs whatever method I selected for it.

    But for that example I needed to have created a scriptable object which its generic parameters is specified to string.
    I'm sure there is a way to let you specify what generic types the scriptable object you're creating will have.
     
  10. Dextozz

    Dextozz

    Joined:
    Apr 8, 2018
    Posts:
    488
    I've done something similar in the past. Here's how I would do it if I was in your shoes.

    I would start by creating a generic Condition class. It will be inherited by all special edge cases that you want to implement. For example, talking to someone, going somewhere, etc.
    Code (CSharp):
    1. public class Condition : ScriptableObject
    2. {
    3.     // Generic code for this root class
    4.     protected virtual void Success()
    5.     {
    6.         // Generic implementation for this. Most likely you move to your onSuccess thing from here.
    7.         // Every subclass below has to call Success at some point
    8.     }
    9. }
    10.  
    11. public class TalkTo : Condition
    12. {
    13.     // Special Overrides. For example:
    14.     public Enum CharacterName
    15.     {
    16.         None = 0,
    17.         Bob = 1,
    18.         ...
    19.     }
    20. }
    21.  
    22. public class ConditionXY : Condition
    23. {
    24.     ...
    25. }
    Once your condition has been completed (ie Success() has been called), you are then able to move to OnSuccess
    Code (CSharp):
    1. public class OnSuccess : ScriptableObject
    2. {
    3.     public void Execute() => ExecuteThisAction();
    4.  
    5.     protected virtual void ExecuteThisAction()
    6.     {
    7.         // Generic implementation for this. All subclasses implement different outcomes
    8.     }
    9. }
    10.  
    11. public class GiveReward : OnSuccess
    12. {
    13.     protected override void ExecuteThisAction()
    14.     {
    15.         // TODO: Give reward ... etc.
    16.     }
    17. }
    Now, you should be able to specify exactly which condition needs to be completed and what happens on its success.

    Some notes:
    - My parent classes (Condition and OnSuccess) can be abstract, but there's really no point in them being so.
    - They derive from ScriptableObject because it allows for polymorphism. You'll be able to display them in the editor.
    - Once you have all of this in place, you should be able to create a custom inspector that enables you to choose which subclass of Condition and OnSuccess you want to use. You'll almost certainly either implement an ugly enum to class conversion with 100 line switch or go for a more elegant solution with the reflection that enables you to get all child classes of a class. Once you've done that, you'll be saving the data in a .json file almost certainly.
    - If you need help serializing scriptable objects, this is most likely a solution that works: https://gist.github.com/RedDude
     
    PlayCreatively likes this.
  11. PlayCreatively

    PlayCreatively

    Joined:
    Jan 25, 2016
    Posts:
    94
    I really like your idea, it's modular and simple.

    The only 3 gripes I have with it at the moment are that every single condition and onSuccess will be really specific and hardcoded meaning I'll be making multitudes of these modules for every single possible condition, instead of what I'm trying to do which is to make use of the already existing methods I've made in the project.

    on the other hand making the conditions and OnSuccess' more specific and focused can be a good thing as video games always have specific rules and routines so having those rules reflect in the modules themselves could be a good thing.

    The second thing is I won't be able to use the parameters from the condition in the onSuccess function which is a must.

    and the last thing is that after I'm done with my project I won't be reusing any of the conditions and onSuccess' in my next project since they're so specific to my project.

    Tell me if I'm understanding you correctly. I might fall on to your solution if things don't work out. Thanks for the detailed post btw.
     
  12. Dextozz

    Dextozz

    Joined:
    Apr 8, 2018
    Posts:
    488
    Well, it doesn't have to be hardcoded to that extent. For example, the "TalkTo" condition can accept a parameter with the name of your character you are referring to, thus being completely reusable for all characters + whenever you add a new character, it magically continues working. The same thing applies to OnSuccess and everything that follows.
    Think of these as components. You make small simple behaviors and you implement them where needed. It takes a small amount of work when you want to create a new one, but from there onwards it works like a charm.

    There must be a way to accomplish this. You could always use a custom class as a return type from Condition and extract data OnSuccess. Should be quite simple to set up actually.

    I don't think I can help you with this one. It's a monumental task to create a completely modular system that is fully reusable between all projects. What I've outlined here will work great for all projects, but specific conditions and similar would have to be implemented on a per-project basis. I can't think of a solution that avoids this in any way.
     
    PlayCreatively likes this.