Search Unity

A skill concept, advice on how to implement?

Discussion in 'Scripting' started by Wolfie5493, Sep 14, 2015.

  1. Wolfie5493

    Wolfie5493

    Joined:
    Sep 12, 2015
    Posts:
    4
    Is this the right place for this thread?

    I want to create a skill system with a central "database that stores the skills (and respective base information) and allows me to update, add and remove skills without actually affecting the code of my concept. This gets coupled with Attributes and Stats in a Base character class to be instanced by each character, player and non-player alike.

    Basically, its a skill-based leveling system with a huge variety of skills. Skills get discovered along the player's journey, and improve with use. Each skill has its own experience meter and levels up when enough experience has been gained. Character experience is only affected by skill experience, and characters level up when the minimum experience requirement is met. Skills are also affected by character stats, etc. Skills need to be equipped before you can use them, and the hopes is that they will eventually expand to hundreds of skills. Some skills will also have prerequisite skills before they can be discovered and used.

    As you can imagine, hard coding the individual skills for such an idea will inevitably get tedious, especially when the skills increase in number. And being that I'm generally lazy and doing this solo for the most part, I want to build a system from the ground up that lets me add the skills to a central database which the concept will call from, and have it integrated as a foundation for my game. This is so that I don't have to change masses of code just to get a single skill working. I can just add it to the database, and the scripts do the rest, sort of thing.

    And although I'm only prototyping at the moment, the eventual goal is to make some sort of MMO out of it.

    However, I'm still inexperienced in programming, and the methods I've tried so far have failed to produce the results I'm looking for. So, in my desperation, I'm turning to the online community for advice and some sort of direction to get this going.

    At the moment I've got the following questions:

    What would I need to do to implement this idea? Or rather, what steps would i need to take in regards to coding, to achieve this?

    What advice do you have that will point me in the right direction?

    What would the better option be to use for this database, XML or ScriptableObject? Or do you have a better suggestions than either?

    What things can I try to not only improve my programming knowledge, but to let me see first hand the effects of your suggestions?
     
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    If you post this in the Scripting forum, you'll get more implementation-minded eyes on it. The Game Design forum is better for higher level questions such as what types of skills you'll have in your game, how progression will work, stuff like that.

    That said, I consider the discussion of data-driven design (versus hard-coding) to be a "design" topic. For implementation, I recommend ScriptableObjects. Jacob Pennock wrote a good tutorial. Make each skill a separate ScriptableObject asset file. You can create subclasses for different types of skills. ScriptableObjects are nice because Unity handles serialization for you, and you can edit them in the Inspector view just like any other Unity asset. Then you can have a MonoBehaviour or another ScriptableObject that has an array or list of skill assets.

    Try to generalize things as much as possible. Instead of a "fire projectile" class (subclassing ScriptableObject), design a generic "spawn prefab" class for which you can specify an optional velocity. Then you can use this class to create lots of different types of skills such as:
    • Fireball: spawn a fireball prefab with a strong forward velocity
    • Web: spawn a web prefab with a low forward velocity that traps enemies
    • Bardsong: spawn a prefab with no velocity that plays an AudioSource and affects entities within its trigger area
    • and so on
     
    theANMATOR2b and BackwoodsGaming like this.
  3. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    I went ahead and put in a Report to get this moved to Scripting.

    I've got some code that does this sort of thing in my "dead" project Sara the Shieldmage. Here's the 10,000 foot view for you.

    As you say - hardcoding values for a game is a bad idea (you have to re-compile to iterate an idea.) "Softcoding" values via the editor is similarly bad (the same restriction applies, it's just a better interface for hardcoding.) The only other option is to put them in an external file of some sort.

    Luckily, JSON is well-suited to this sort of thing. All you need is some library for managing JSON (I prefer SimpleJSON), and then you can get started coding.

    "But wait!" you ask, "What do I code?" I'll help you with that too.

    Generally what you want are layers. The way I like to architect this sort of setup, is to start with a Repository (a static object, possibly even a static class) that has the sole and explicit responsibility of reading or saving data. The way I choose to do it, is create a MonoBehavior that goes on an object that is never unloaded between scenes, that has a reference in-project to the config file I want to use - this is better, because this way, only the config file-to-code relationship is hardcoded. I prefer to go the extra mile for my players, and put it under a Resources folder so that the player can mod their game if they so choose.

    The next thing you need, though, is something to hold the data. I call these sorts of objects Models. These are not monobehaviors; instead, chances are good you'll want these to be serializable objects, so you can inspect them during your development. Generally the template looks like this:

    Code (csharp):
    1. using System;
    2. using SimpleJSON;
    3.  
    4. [Serializable]
    5. public class SomeModel
    6. {
    7.     #region Fields
    8.  
    9.     // Fields...
    10.  
    11.     #endregion Fields
    12.  
    13.     #region Constructors
    14.  
    15.     public SomeModel(JSONClass state)
    16.     {
    17.         ImportState(state);
    18.     }
    19.  
    20.     #endregion Constructors
    21.  
    22.     #region Methods
    23.  
    24.     public JSONClass ExportState()
    25.     {
    26.         // Export the object contents to a JSONClass, so you can save to a file.
    27.     }
    28.  
    29.     public void ImportState(JSONClass state)
    30.     {
    31.         // Import named properties from the state, into this instance of the class.
    32.     }
    33.  
    34.     #endregion Methods
    35. }
    The next thing you need is the ability to use a model...this is where things get murky based on your implementation. It could be a behavior on a live game object that's doing things in your game. It could be something that controls GUIs. The applications start to diverge from my web development experience here, so I'll talk about it the way I see it; feel free to correct me if there's a better term or way of phrasing the idea.

    The thing I just mentioned - a live object doing things that the player is supposed to respond to in the game world - I instead like to call an actuator. Actuators take the model and make the consequences of the data that you just read in through the stack actually apply to the game world. This can be stat systems, prefab paths (As @TonyLi said above)...anything.

    Conversely, if the data is going to be used to feed a GUI, you may consider creating a Controller. The controller tells certain objects called Views how to present information to the player. Views collectively are just groups of buttons, labels, images, all that sort of thing.

    Man, Asvarduil, that's a lot of stuff! Why do I want to do it that way?
    Because it's maintainable. If you're unsatisfied with your GUI or need to change something, you just change your GUI - you don't have to change the whole stack, necessarily.

    That's good, what are the upsides, not in nerd-speak?
    Here you go...
    1. It's easy to change things when you need to. Specifically, changing one thing should have minimal impact on other stuff, provided you keep the interface between layers.

    2. You don't have to worry about inheritance chains so much - those are hard to understand. Instead, you can create good GUIs and game behaviors by composing objects of various models.

    3. You're adhering to a convention. Data always comes in the Repository and is stored to a Model, which is used by an Actuator or Controller. The controller always uses the model to update a View.

    OK. What's the catch?
    Good on you for thinking that - there's always a catch in computer science. The catches are:
    1. More code to keep track of. Above and beyond having to read/understand more, it mean...

    2. More possible points of failure. You'll need to have a way of debugging each layer if something goes wrong.

    3. More RAM is required for the end-user. It should be said that this should generally be a small amount, but if you're needing to squeeze, say, an XBox 360 for every microsecond of performance, you may be in for some refactoring down the line. You're storing each layer as well as all instances of all models in memory, after all.

    4. Because you're adhering to a convention, it's up to you to have the discipline to adhere to that, in your own self-interest. At the same time, it's a pattern; there may a case where you need to break this pattern. For code-understandability reasons, it's worth noting you do this at your own risk.
    I hope this helps you. Just remember - Repository -> Model -> Actuator OR Controller -> View. 3-4 layers for a more data-driven bit of software.

    Yes, this applies outside games. In fact this is more or less the enforced architecture of Microsoft's MVC.NET. You've learned something that just made you more employable!
     
  4. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    I'm curious - what's the difference between softcoding in ScriptableObject format versus JSON format (or XML for that matter)?

    I will grant you that JSON is more portable if you jump ship to another game engine. But ScriptableObjects have fewer moving parts. (Simpler = less opportunity for bugs.)
     
  5. BackwoodsGaming

    BackwoodsGaming

    Joined:
    Jan 2, 2014
    Posts:
    2,229
    I was actually wondering the same thing. I opted to go the scriptable object route with my development since it was already integrated into Unity over getting something to work with Unity.
     
  6. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    I've never heard of "ScriptableObject" as a data format prior to this. Any references you'd recommend?

    As far as the actual differences - if by "ScriptableObject" you're referring to game objects in Unity - the fact remains that the game still has to be built for value changes to take effect; there's no external way of affecting those values, unless you do something exotic.

    A JSON file, conversely, doesn't get built with the project - it's fully external, it's just data. If you change it and re-run your game, you get the changes instantly. Heck, if you implement your data stack correctly, it may be possible to just change the data, and swap back to your game, and the game itself instantly pick up the changes.

    TL;DR - Iteration times. If you have to compile your game, it means you're spending more time building, less time checking the result.
     
  7. BackwoodsGaming

    BackwoodsGaming

    Joined:
    Jan 2, 2014
    Posts:
    2,229
    Check out Tony's link above. Unity also did a scripting video on it and you can find a number of videos by other devs by searching it on YouTube. Here is the link to the Unity Live Training one -
     
  8. Serdan

    Serdan

    Joined:
    Sep 1, 2015
    Posts:
    10
    I think Asvarduil's point is that ScriptableObjects get packaged by Unity, so end-users can't edit them. This also means that to patch a skill you have to build the project and send the entire assets file to the user, while if you're using JSON or XML you can just edit and send the relevant xml/json file.
     
    AndrewGrayGames likes this.
  9. BackwoodsGaming

    BackwoodsGaming

    Joined:
    Jan 2, 2014
    Posts:
    2,229
    But do you really want users to be able to edit their files? In most cases I would prefer they couldn't, dependent on what type of data it is. But that is an excellent point. It probably depends a lot of type of game as well. Most of the games I play, I'm used to patching as a standard process and as such have accepted it as something that will be part of my game's process as well.
     
  10. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    Ok, that's pretty cool - from both sources.

    I still see some pretty significant tradeoffs, though, as someone who doesn't yet use this technique.

    Benefits that I see:
    1. Can edit directly in the editor, without needing to open VS, or custom middleware, or any of that stuff.
    2. Can see details that a text format like JSON doesn't convey - for instance, thumbnails of images, etc.
    3. Can set up a GUI that other team members or even you can use to efficiently do this sort of thing.
    Disadvantages that I see:
    1. The API exposing this capability is very obtuse, as an outsider - I'm not sure exactly how this setup allows you to "just change some data" and see your changes propagate to your game. The pipeline for saving the data is significantly clearer. For "Joe Grammer", the new, clueless, but otherwise skilled developer on the team, I will have to take time to explain the pipeline to them.
    2. You're still creating middleware - specifically, the Editor interface with which to modify the ScriptableObject file.
    3. There's a lot of action happening here outside of the game, proper, which further hampers being able to trace problems that arise.
    This is some cool stuff that I need to pay more attention to. Still, I think it could be argued either way. It seems like a "MVC-like" architecture provides more control, at the expense of more code and having to know implementation details. This setup provides ease of setup and use, at the cost of less clear code, and having to understand the engine's implementation of the pipeline in this specific case.
     
  11. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    Yes, I do. I want people to be able to mod my games. Modding is not a bad thing.
    1. Modding communities are a thing - some players like to customize, others like to reap the benefits. Also, watching them mod my game tells me things about what they may want. It's sort of like research, only they're pretty much just telling me.
    2. It makes it easy for me to fix "Stupid Asvarduil" mistakes.
     
    mattssheep4 and TonyLi like this.
  12. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    ScriptableObjects are just another tool in the toolbox. They'll be more appropriate than JSON for some uses, less appropriate for others.

    I think most people do their iterative development in the Unity editor. ScriptableObject assets are just data; you don't need to recompile after changes. Internally, they're in YAML format (versus XML or JSON). Some advantages:
    • You don't need to worry about the format. Unity natively serializes and deserializes them. (Unity serialization details for anyone who's curious)
    • You can edit their contents using Unity's Inspector view, without having to write any extra editor code, which provides a very consistent UI.
    • You can edit them during play, and the game will recognize the changes.
    They're also easy to pack into asset bundles if you want to send patches or DLC.

    That said, they're not good for letting players edit the files.
     
    AndrewGrayGames likes this.
  13. Serdan

    Serdan

    Joined:
    Sep 1, 2015
    Posts:
    10
    The problem is not with patching itself, but with the size of the patches. Patches for Unity games on Steam can be several gigs, even if only minor changes were made.
     
  14. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    I bet @Wolfie5493 wasn't expecting quite so much discussion. :)
     
    AndrewGrayGames likes this.
  15. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    That's just a design flaw by the developer. If you design a game to be patchable with asset bundles, patches can be very small.
     
  16. BackwoodsGaming

    BackwoodsGaming

    Joined:
    Jan 2, 2014
    Posts:
    2,229
    That is what I was going to say. Designer really has to put some thought into the game's structure to avoid having to do a full patch when the patch is released. I haven't figured that part out myself and exactly what will be bundled with what. Truth is, I'm a long ways away from releasing anything. But that is one of the things I have on my table of things to do when that time draws closer.. lol
     
    AndrewGrayGames likes this.
  17. Wolfie5493

    Wolfie5493

    Joined:
    Sep 12, 2015
    Posts:
    4
    Okay, excellent discussion. Learning a lot already.

    Maybe a small insight into my goal might be in order:

    As I mentioned already, its a skill based leveling system. Skills are implemented differently to require players to "discover" them to use them. Some skills will have a chance to be accidentally used, thus flagging it as discovered. Skills will be separated into various groups, eg: Combat Skills, Defense Skills, Profession Skills, etc. Skills are anything that can generally be learned, where as Stats are what make up the character physically, so to speak.

    To use a skill once discovered, the character equips it, and just uses it, which will add experience to that particular skill, which will eventually cause it to level up, thus increase the base stats for the skill. Characters level up if the total COMBINED experience of all skills reaches a certain amount. So character progression is entirely dependent on the level of the various skills. AI will utilize this concept as well, but will be automated where players get choice.

    Now, the big thing about this is that the end result will make use of server-side rendering, as opposed to the local client rendering. Just think of a broadband connection between your PC box and you monitor. Thus, patching won't be necessary. When the idea is eventually rolled out, and the game needs updating, I can just update the files on the server and clients will see the update the next time they log in (at least that's what i've got in mind). No downloading, no patching, and of course, I'm not sure if modding is possible with this idea. But everything is done server side. However, this is beyond the scope of my experience and this topic, but its to give a better idea of what i'm wanting to eventually achieve with this.

    At the moment i'm just prototyping the individual concepts, and have begun with creating the Base character, which the Player character and NPCs will inherit to gain the basic "skeleton" of their character. The rest will obviously be done per object (AI will be done in the AI branch, and Player will be done on the Player branch) to give them the code necessary to do what needs done.

    Anyway, the idea is to have a central database of skills. One place to update the skill catalog, that the game automatically picks up, etc etc. What I struggle with is the logical progression of the whole concept, and thus the implementation of it. Perhaps its still too advance for me? Regardless, the coding itself is easy, i just need the logic behind it worked out, which is why I've come here: To better understand that logic behind a game, and character creation.

    Now below is the XML I started to try out the most recent method, and it will give you a better idea of what I'm after in terms of the skills database:

    Code (XML):
    1. <Skills_Collection>
    2.   <Offensive>
    3.     <Punch>
    4.       <Description>Punch the foe's very hard</Description>
    5.       <Required_Stamina>15</Required_Stamina>
    6.       <Base_Effectiveness>100</Base_Effectiveness>
    7.       <Exp_Modifier>1.75</Exp_Modifier>
    8.     </Punch>
    9.  
    10.     <Spit>
    11.       <Description>Spit at your foes</Description>
    12.       <Required_Stamina>5</Required_Stamina>
    13.       <Base_Effectiveness>10</Base_Effectiveness>
    14.       <Exp_Modifier>1.5</Exp_Modifier>
    15.     </Spit>
    16.  
    17.     <Kick>
    18.       <Description>Kick them</Description>
    19.       <Required_Stamina>50</Required_Stamina>
    20.       <Base_Effectiveness>9000</Base_Effectiveness>
    21.       <Exp_Modifier>1.25</Exp_Modifier>  
    22.     </Kick>
    23.  
    24.     <Insult>
    25.       <Description>Hurt you foe's feelings</Description>
    26.       <Required_Stamina>7</Required_Stamina>
    27.       <Base_Effectiveness>23</Base_Effectiveness>
    28.       <Exp_Modifier>2.3</Exp_Modifier>  
    29.     </Insult>
    30.   </Offensive>
    31.  
    32.   <Defensive>
    33.     <Block>
    34.       <Description>Block an Attack</Description>
    35.       <Required_Stamina>2</Required_Stamina>
    36.       <Base_Effectiveness>12</Base_Effectiveness>
    37.       <Exp_Modifier>1.33</Exp_Modifier>
    38.     </Block>
    39.  
    40.   </Defensive>
    41. </Skills_Collection>
    42.  
    So a few more questions have sprung to mind, and some are based on what is mentioned in the discussion:

    @TonyLi Using scriptable objects, doesn't that mean lots of files if each skill has its own asset? Or doesn't that really matter?

    And i've watched tons of tutorials on Scriptable Objects, but I'm still struggling to grasp it. I understand it can be used to make custom inspectors and editor elements, but how does the logic behind using it as a database work?

    @Asvarduil I'm liking the concept of Layers. Could you help me visualize the logic behind that?

    What I'm doing to build my Base Character Script is:

    AttributesScript + StatsScript + VitalsScript + SkillsScript = Base CharacterScript where everything is put together.

    Would that be some sort of layering concept? Please explain more if you can. Until I can visualize the concept, I will struggle with understanding how it works.

    Which of the two options given, and possibly XML, would be better suited to this sort of programming? In fact, which of them would you use if you designed such a project?

    And most important of all, and maybe slightly off topic: I started with creating the character, but is there any unwritten or written rule about where to actually start when creating a game?
     
    MrPaparoz likes this.
  18. BackwoodsGaming

    BackwoodsGaming

    Joined:
    Jan 2, 2014
    Posts:
    2,229
    Think of the scriptable object as a database. You probably wouldn't want a scriptable object for every skill. In looking at your XML, I would probably either use a single scriptable object to manage the whole list with one of the elements of each item being SkillType which could either be Offensive or Defensive. Or I might break it down into two scriptable objects and have one for each type. Really, since it looks like all of the data is essentially the same, I'd probably lean more towards a single scriptable object to house all the info. Something like having a scriptable object named SkillsCollection (or whatever you want to name it) based off of an item that contains a skill type, skill name, description, required stamina, base effectiveness, and exp modifier.
     
    AndrewGrayGames likes this.
  19. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    So far, I've used only JSON. I've no experience with ScriptableObjects (though, I plan to experiment with it sooner or later), and I haven't found a good XML library/framework.

    If you go with something that's not ScriptableObject, you'll find that a layered architecture makes it easy. If you decide XML isn't a good fit, and would like to use JSON instead, it's just a matter of swapping out the framework, updating the mechanism(s) that let you save/load data, and going from there. To be fully honest, I'd say SimpleJSON might not be the best setup for JSON parsing - I do find I have to do manual mapping in the model. I could create 'mapper' classes that operate off to the side...I forgot about that.

    As far as ScriptableObject, @TonyLi is correct - there are fewer moving parts. It's perfectly viable to have all your abilities in a single scriptable asset, or each one broken out into a separate asset.

    Ultimately, this is a tech choice you have to make. All you can be given is information, we should not choose for you.
     
    BackwoodsGaming likes this.
  20. BackwoodsGaming

    BackwoodsGaming

    Joined:
    Jan 2, 2014
    Posts:
    2,229
    Definitely agree. Both are good solutions. I know I have some videos bookmarked on using JSON as well and still may end up diving into it for some other parts of my project. I do like keeping possibilities of modding available but there are some things that I wouldn't want players to be able to mod easily. So a combination of the two methods may be what I do in the end. Keep things which could be used to effect gameplay advantages in scriptable objects and cosmetic stuff in XML/JSON/something similar. Or something along those lines.
     
    AndrewGrayGames likes this.
  21. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    I agree, not everything needs to be moddable.

    Case in point, in Sara the Shieldmage, one of the things I did with abilities was select an ability effect. The ability effect? Prefab, usually a particle system. Granted, I could have provided for a particle base, gave a path to the particle material, blah blah blah...but, it simply wasn't necessary. I still gave a path to the effect (hey if you want a fire spell that uses water particles, I'm all for violations of common sense in video games.), but there is an upper bound to customizability before stuff gets whack.
     
    BackwoodsGaming likes this.
  22. Wolfie5493

    Wolfie5493

    Joined:
    Sep 12, 2015
    Posts:
    4
    I appreciate your guys time in taking part with this discussion!
    Wouldn't a layer architecture work the same with a ScriptableObject? Especially if the Repository stores the data in this case, as opposed to an external file?

    And I'm trying to get a better visual understanding of your Layering system. Below is a quick flow chart i made for reference, but I'm not sure about the controller and view's positions. Do they both come off the Actuator, or is it as below?


    So you're saying that the Repository will save and load data from an external source, or in the case of a ScriptableObject become the data (in my concept it would save and load to the XML file?). The Model will be an object built around the data in, or called by, the repository (in my case it will be the individual skills?) And is this another class?

    The Actuator is what manipulates this data. In my case it would be the stats altering the variables of each skill?

    The Controller then gets the data ready for viewing, which the View does utilizing GUI elements? And each of these are essentially their own script?
     
    Last edited: Sep 15, 2015
  23. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    You have it correct, for the most part. From the Model, split it two ways - one path has the actuator, the other has the controller then view.

    Pretty much, the model feeds actuators and controllers. Actuators and controllers are equals.

    In the interest of showing what I mean, here's files from Sara the Shieldmage.

    Data: Ability-List.txt - This is the list of abilities that existed when I gave up on Sara the Shieldmage. Note it's in JSON format, and that the blob hosts both the Ability Effects and the Abilities themselves. Fun Unity quirk - if you name the file .json you can't use it as a text asset.

    Repository: AbilityDatabase.cs - This code reads Abilities from a JSON file, and stores models. It stores AbilityEffects in a similar fashion; both exist in the same file. It also allows for other things to ask for a model, or series of models as necessary.

    Model: Ability.cs - This is the model. It stores all the things needed for a model to work in the game. Observe how it has name references to AbilityEffects within it - models can host other models, as the shape of the data requires. In this case, instead of brute-force replicating effects, I just stored ability effects by name, since we can just clone off an ability effect whenever. When loading abilities, I spin off effects into the object, but on the save path we just save the name.

    Actuator/Controller: BattleReferee.cs - This is where stuff gets real, the battle system. A JRPG battle is pretty much altering the values of other stats, on a timer. You can see that we interface with a GUI controller for characters whose turns occur, we apply stats to the ability in action, and we call methods to cause the effects to manifest.

    Because this code is talking to the View, this is a great example of a controller. Because this game is operating mechanics to change the game state, this is a great example of an actuator as well. The difference is largely artificial, but by calling something an 'Actuator', I know it's executing game logic; by calling something a 'Controller', I know it's controlling a view.

    View: BattleCommandPresenter.cs - This code reads a character's abilities when a turn occurs. The player selects an ability, and this view code returns that to the controller/actuator. The actuator makes it happen.

    @TonyLi and any other competent coders - The organization here is a little bit on the fritz. I'm mentally critiquing this now that I've posted it. Go with the spirit of what I'm showing, not the specifics, there is some gnarliness in here.
     
    Last edited: Sep 15, 2015
    MrPaparoz likes this.
  24. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I have been currently thinking of how to do my skill system and seen many posts by you explaining data driven, as well as composition design patterns. They were very helpful, however, I still do not completely understand how things are done in a data driven environment.

    For example, lets have a skill scriptableobject like this..
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public enum InputType {Hold, Down}
    4. public enum AimType {Ray, OverlapSphere}
    5.  
    6. public class Skill : ScriptableObject
    7. {
    8.     public string skillName;
    9.     public int damage;
    10.     public InputType inputType;
    11.     public IsInput isInput;
    12.     public KeyCode key;
    13.     public AimType aimType;
    14.     public Ray aimRay;
    15. }

    Now, I set up the ScriptableObject as desired.
    The IsInput is a abstract class with a public Check(KeyCode key). Since all of my input checks will just need the keycode parameter and return a bool, I can easily use polymorphism.
    The problem, and my confusion, arises when we get to the aiming type.
    There can be many different ways to gather up the people you are able to hit, but here I just have two ways, by Ray or by the OverlapSphere.
    These two methods are totally different, each having their own needs. I cannot define them in a polymorphic way like I did with the IsInput. So, I end up doing checks with switch statements and handling things as needed.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. public class SkillBehaviour : MonoBehaviour
    5. {
    6.     public Skill skill;
    7.     AimRay aimRay = new AimRay();
    8.     AimOverlapSphere aimOverlapShere = new AimOverlapSphere();
    9.  
    10.     void Awake()
    11.     {
    12.         if(skill.inputType == InputType.Down) skill.isInput = new IsInputDown();
    13.         if(skill.inputType == InputType.Hold) skill.isInput = new IsInputHold();
    14.     }
    15.  
    16.     void Update()
    17.     {
    18.         if(!IsInput()) return;
    19.         Debug.Log(1);
    20.         List<GameObject> aimingAt = AimingAt();
    21.         //do more stuff....
    22.     }
    23.  
    24.     bool IsInput()
    25.     {
    26.         return skill.isInput.Check(skill.key);
    27.     }
    28.  
    29.     List<GameObject> AimingAt()
    30.     {
    31.         List<GameObject> aimingAt = new List<GameObject>();
    32.         switch(skill.aimType)
    33.         {
    34.             case AimType.Ray : aimingAt.Add(aimRay.AimingAt(skill.aimRay)); break;
    35.             case AimType.OverlapSphere : aimingAt.AddRange(aimOverlapShere.AimingAt(transform.position, .5f)); break;
    36.         }
    37.         return aimingAt;
    38.     }
    39. }

    This seems very messy and I am probably doing something very wrong.
    I am curious what the correct way is to doing the data driven design. I am also wondering how it can be done without adding any components to a gameobject other then the main SkillBehaviour to do the update. I am not saying to avoid the component design, just components on a gameobject (No reason for avoiding gameobject components other then me being curious how it would be done).

    I appreciate any light on the subject as I feel I am in the dark =)
     
  25. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    @HiddenMonk - Bearing in mind that I've never used ScriptableObject before, I think you are probably doing it right. It's not messy, at all, but I think your confusion is because you can't see what's going on. You're setting up a model, and setting a reference in an actuator, where it's doing stuff. This is what I was talking about with the syntax being obtuse. Some programmers I've worked with would call it "PFM" or just "Automagical."

    You should see my JSON post above where I link files from my most recent failed project. That's some messy stuff - it's more code, and at the time my organizational skills were slipping from mental fatigue. That being said, I doubt you'll be more confused than by the code you're showing - you can see where data enters the system, you can see it propagate upward, you can see it used.
     
  26. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I was afraid of that :p
    Im wondering how the "Anti if" people deal with a data driven approach.
     
  27. BackwoodsGaming

    BackwoodsGaming

    Joined:
    Jan 2, 2014
    Posts:
    2,229
    In looking at it, I'm not really sure how they would accomplish that without some sort of conditional somewhere.. I've seen some people create a method and put those in the Awake/Update/whatever other MonoBehavior method.. But then all they do is do the if statement there. If there is another way to get around the ifs, I'd love them to show us. heheh

    I wonder if you could drive it from whatever character script is controlling the player/npc? Somewhere down the line I think there probably has to be a script attached to the gameobject, but maybe that would be an Character Manager script that would be the component and it would deal with all the scripts the character needs in order to avoid a ton of components for each script the character uses. I think there are ways to accomplish not requiring them as components. But somewhere down the line they may have to be able to communicate with a component on the gameobject. Not 100% sure, but I think that is how it works.
     
  28. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Here's a ScriptableObject example: ScriptableObjectSkills_2015-09-14.unitypackage

    To play it, import it into Unity 5.0.1+, import Unity's standard first person controller, and play ScriptableObject Skills/Scene. Press the '1' key to fire once you have the tall cube in your sights. I don't have time to make a big library of skills, but you can do it without any additional scripting. I just added a "Fire Bullet" skill.


    The Player GameObject has a SkillBehaviour script. It has:
    • A Unity UI Text field to show the name of the current target.
    • An array of Skills, which are ScriptableObjects that inherit from Skill.


    The Skill Fire Bullet asset is a SkillSpawnPrefab, which is a subclass of Skill. It has:
    • A reference to a SkillAim asset (a ScriptableObject).
    • A reference to a SkillTrigger asset (a ScriptableObject).
    • Settings specific to SkillSpawnPrefab (prefab and velocity).


    The Aim Raycast asset is a SkillAimRaycast, which is a subclass of SkillAim. It runs a raycast to aim. The inheritance tree is:
    • SkillAim (abstract)
      • SkillAimPhysics (abstract): adds a LayerMask property.
        • SkillAimRaycast: aims via Raycast.
        • SkillAimOverlap: aims via OverlapSphere.
    (See - I'm not entirely against inheritance trees! ;))



    The Trigger Key Alpha1 asset is a SkillTriggerKey, which is a subclass of SkillTrigger. The inheritance tree is:
    • SkillTrigger (abstract)
      • SkillTriggerKey: triggers if a specified key is pressed.
        • SkillTriggerKeyHeld: triggers if a specified key is held.

    Although the setup seems complicated at first, it has three big advantages:

    1. Data-driven: Behavior is defined in data, so level designers can tweak them without touching code. And Unity provides the editing interface for free. I didn't write a single line of custom editor code.

    2. Extensible: There are no "switch" statements. To apply a certain behaviour (such as raycast aiming), just add a raycast aiming "module" (a ScriptableObject asset). You can define a new module in the future, such as a timed trigger that fires every 10 seconds, assign it to any existing skill, and the skill will automatically work with it.

    3. Small, Decoupled Scripts: Each script is extremely short and self-contained, with no strange dependencies to worry about.
    Here are the scripts:

    Skill.cs:
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public abstract class Skill : ScriptableObject
    4. {
    5.  
    6.     public SkillAim aim;
    7.     public SkillTrigger trigger;
    8.  
    9.     public abstract void Use(Transform origin, Transform target);
    10.  
    11. }
    SkillSpawnPrefab.cs:
    Code (csharp):
    1. using UnityEngine;
    2. #if UNITY_EDITOR
    3. using UnityEditor;
    4. #endif
    5.  
    6. public class SkillSpawnPrefab : Skill
    7. {
    8.  
    9.     public GameObject prefabToSpawn;
    10.     public float velocity;
    11.  
    12.     public override void Use(Transform origin, Transform target)
    13.     {
    14.         var instance = Instantiate(prefabToSpawn, origin.position + origin.forward, origin.rotation) as GameObject;
    15.         var rb = instance.GetComponent<Rigidbody>();
    16.         if (rb != null) rb.AddForce(origin.forward * velocity);
    17.     }
    18.  
    19.     #if UNITY_EDITOR
    20.     [MenuItem("Assets/Create/Skill Example/Skills/Skill Spawn Prefab")]
    21.     public static void CreateSkillSpawnPrefabAsset()
    22.     {
    23.         ScriptableObjectUtility.CreateAsset<SkillSpawnPrefab>();
    24.     }
    25.     #endif
    26.  
    27. }
    SkillAim.cs:
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public abstract class SkillAim : ScriptableObject
    4. {
    5.  
    6.     public abstract Transform GetTarget(Transform origin);
    7.  
    8. }
    SkillAimOverlap.cs:
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public abstract class SkillAimPhysics : SkillAim
    4. {
    5.  
    6.     public LayerMask layerMask;
    7.  
    8. }
    SkillAimRaycast.cs:
    Code (csharp):
    1. using UnityEngine;
    2. #if UNITY_EDITOR
    3. using UnityEditor;
    4. #endif
    5.  
    6. public class SkillAimRaycast : SkillAimPhysics
    7. {
    8.  
    9.     public float maxDistance;
    10.  
    11.     public override Transform GetTarget(Transform origin)
    12.     {
    13.         RaycastHit hitInfo;
    14.         if (Physics.Raycast(origin.position, origin.forward, out hitInfo, maxDistance, layerMask))
    15.         {
    16.             var targetable = hitInfo.collider.GetComponent<Targetable>();
    17.             if (targetable != null) return targetable.transform;
    18.         }
    19.         return null;
    20.     }
    21.  
    22.     #if UNITY_EDITOR
    23.     [MenuItem("Assets/Create/Skill Example/Aiming/Skill Aim Raycast")]
    24.     public static void CreateSkillAimRaycastAsset()
    25.     {
    26.         ScriptableObjectUtility.CreateAsset<SkillAimRaycast>();
    27.     }
    28.     #endif
    29.  
    30. }
    SkillAimOverlap.cs:
    Code (csharp):
    1. using UnityEngine;
    2. #if UNITY_EDITOR
    3. using UnityEditor;
    4. #endif
    5.  
    6. public class SkillAimOverlap : SkillAimPhysics
    7. {
    8.  
    9.     public float radius;
    10.  
    11.     public override Transform GetTarget(Transform origin)
    12.     {
    13.         var colliders = Physics.OverlapSphere(origin.position, radius);
    14.         foreach (var other in colliders)
    15.         {
    16.             var targetable = other.GetComponent<Targetable>();
    17.             if (targetable != null) return targetable.transform;
    18.         }
    19.         return null;
    20.     }
    21.  
    22.     #if UNITY_EDITOR
    23.     [MenuItem("Assets/Create/Skill Example/Aiming/Skill Aim Overlap")]
    24.     public static void CreateSkillAimOverlapAsset()
    25.     {
    26.         ScriptableObjectUtility.CreateAsset<SkillAimOverlap>();
    27.     }
    28.     #endif
    29.  
    30. }
    SkillTrigger.cs:
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public abstract class SkillTrigger : ScriptableObject
    4. {
    5.  
    6.     public abstract bool IsTriggered();
    7.  
    8. }
    SkillTriggerKey.cs:
    Code (csharp):
    1. using UnityEngine;
    2. #if UNITY_EDITOR
    3. using UnityEditor;
    4. #endif
    5.  
    6. public class SkillTriggerKey : SkillTrigger
    7. {
    8.  
    9.     public KeyCode key;
    10.  
    11.     public override bool IsTriggered()
    12.     {
    13.         return Input.GetKeyDown(key);
    14.     }
    15.  
    16.     #if UNITY_EDITOR
    17.     [MenuItem("Assets/Create/Skill Example/Triggers/Skill Trigger Key")]
    18.     public static void CreateSkillTriggerKeyAsset()
    19.     {
    20.         ScriptableObjectUtility.CreateAsset<SkillTriggerKey>();
    21.     }
    22.     #endif
    23.  
    24. }
    SkillTriggerKeyHeld.cs:
    Code (csharp):
    1. using UnityEngine;
    2. #if UNITY_EDITOR
    3. using UnityEditor;
    4. #endif
    5.  
    6. public class SkillTriggerKeyHeld : SkillTriggerKey
    7. {
    8.  
    9.     public override bool IsTriggered()
    10.     {
    11.         return Input.GetKey(key);
    12.     }
    13.  
    14.     #if UNITY_EDITOR
    15.     [MenuItem("Assets/Create/Skill Example/Triggers/Skill Trigger Key Held")]
    16.     public static void CreateSkillTriggerKeyHeldAsset()
    17.     {
    18.         ScriptableObjectUtility.CreateAsset<SkillTriggerKeyHeld>();
    19.     }
    20.     #endif
    21.  
    22. }
    SkillBehaviour.cs
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class SkillBehaviour : MonoBehaviour
    4. {
    5.  
    6.     public UnityEngine.UI.Text targetName;
    7.     public Skill[] skills;
    8.  
    9.     public void Update()
    10.     {
    11.         targetName.text = string.Empty;
    12.         foreach (var skill in skills)
    13.         {
    14.             var target = skill.aim.GetTarget(transform);
    15.             if (target != null)
    16.             {
    17.                 targetName.text = target.name;
    18.                 if (skill.trigger.IsTriggered())
    19.                 {
    20.                     skill.Use(transform, target);
    21.                 }
    22.             }
    23.         }
    24.     }
    25. }
     
    Last edited: Sep 15, 2015
  29. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Wow! ^^
    In another thread you gave a nice component based example
    http://forum.unity3d.com/threads/skill-casting-method-and-secondary-effects.279190/#post-1844525
    And now we got a nice scriptableobject example =)

    However, I still am unsure about a few things.
    1 - In your SkillAim you are requiring a Transform to be used. To me this seems like a messy workaround to getting what we really need. The SkillAimRaycast only needs the origin and direction, and the SkillAimOverlap only needs the origin. By using a transform for this, to me its like saying "Each of my methods only need a certain amount of data, but I still want to use them in a polymorphic way, so I will just create a SuperObject that contains a whole bunch of info, pass that to the aim method and let it choose what it wants". What happens in the future if I add something to this "SuperObject" that requires a lot of processing? Now I am wastefully doing things just to avoid an if statement.

    2 - The Skill has a Use method that takes in a Transform for origin, and Transform for target. Not only are we going to run into the "SuperObject" problem again (for example, my skill requires a vector3 position, a vector3 normal, maybe even a vector2 textureCoord, now I need to not use a Transform for ALL of my skills, but create a "SuperObject" that now all of my skills must use),
    but what happens when my skill is a AOE type? This skills method only works for a single target. Do I now need to wastefully change that Use method to just use an array of transforms, even when I dont always need arrays? Or do I now need to get into if / switch statements?

    So while, yes, the code you show is "...Extensible: There are no "switch" statements...." it seems to me that its more so just an illusion. There will eventually have to be if/switch statements, and a lot more if you want to avoid the "SuperObject" problem.
    Or am I thinking of this wrong?
    How would you go about adding the ability to target multiple enemies? Or avoiding the "SuperObject"?

    Thank you for taking the time to create that wonderful example, you have been a great help in regards to these skill systems =)
    Its surprising to me how little info there seems to be on this subject, when its like the base of any game. Or maybe I am just not searching correctly.
     
  30. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    The example just demonstrates the concept. I used transforms to keep the code simple. It's not a comprehensive, production-level system. In a real system, each ScriptableObject could advertise the services it provides and how to utilitize them. In the example, skills only provide one service: Use(origin,target).

    However, even as it is, there's actually a huge conceptual difference between this and if/switch statements. When you hard-code if/switch statements, you define behavior in code. To add a new behavior, you have to modify the code to add a new case to your switch statement.

    In contrast, the example above provides generic "sockets" into which you can plug services such as "trigger with key" or "aim with raycast". You can define a new type of service (e.g., "trigger when a stat such as health is low"), plug it into a socket, and it will automatically work. It's similar to C# interfaces, but data-driven. I realize there are a few steps from the example to a more comprehensive system, but my goal was to demonstrate this principle without adding too much extraneous stuff.
     
    AndrewGrayGames likes this.
  31. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    @HiddenMonk - It occurred to me that I glossed over and didn't really answer your question about different types of target selections. Off the top of my head, to extend this example I'd probably use an abstract base Target class which would, yes, essentially be an efficient "SuperObject." SkillAim.GetTarget would return a Target object. Skill.Use would accept a Target object.

    The difference is that the Target class might not contain any data at all. You could define subclasses such as GameObjectTarget, WorldPointTarget, etc., that return the actual targets, such as a list of GameObject(s) or a Vector3 in world space. The subclass could specify what kind of targets it returns. So you're not passing around any extraneous data; the WorldPointTarget only has a Vector3, no GameObjects etc.

    Ultimately you're going to have to tie some of it into code. But the principle is that you're defining a simple, decoupled bit of functionality in a very short script. Then you can combine these bits in various ways to generate more complex behavior. If you look at the example code above, most of the scripts have only about 1-5 lines of actual code, with extra "bloat" only from helper menu items, "using" statements at the top, etc. This makes them easy to understand and less bug prone.
     
    Deleted User and AndrewGrayGames like this.
  32. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I am a bit confused on how you can have the Target object not hold all the data.
    We can lower the amount of data it has by using getters and setters, but the Target class must have all variables for us to access if we want to use it with polymorphism. And since many of our variables would be value types, they will automatically contain values.

    Unless if you mean the Target object holds a collection of TargetTypes such as GameObjectTarget, WorldPointTarget, etc... and they can essentially be null.
    However, the way you are saying it is like being able to only have a vector3 within this "SuperObject", but maybe I am misunderstanding.

    Using a "SuperObject" feels kind of wrong, but maybe this is just how data driven designs are done?

    How would we then choose which service we use in the Skills scriptableobject? I am assuming this is where we would get dirty with if / switch statements?

    I am not sure if this was a reply to my "So while, yes, the code you show is "...Extensible: There are no "switch" statements...." it seems to me that its more so just an illusion.", but I wasn't saying your example was an illusion and that it was actually an if / switch statement. What I meant was, while yes, the code you show is a great way to avoid conditionals and use polymorphism, the issue is that it is, as you said, generic. It could only do so much, and to do more you would need to get into conditionals (or at least that is what I am thinking). So the illusion was how your system was trying to say "Look! I can do it all and there are no if / switches!", but really it could only do so much. Using a "SuperObject" would extend its ability, but as I said, it felt wrong to do that.
     
  33. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    For the target object, I was thinking of something like this:
    Code (csharp):
    1. public abstract class Target { }
    In actuality, you'd be working with Target subclasses such as:
    Code (csharp):
    1. public class GameObjectTarget : Target {
    2.     public GameObject[] gameObjects;
    3. }
    4.  
    5. public class Vector3Target : Target {
    6.     public Vector3 position;
    7. }
    8. // etc...
    So each instance only has the data it needs. Vector3Target has a Vector3, but it doesn't have an array of GameObjects.

    But you'd pass the base Target type to Use:
    Code (csharp):
    1. public class Skill : ScriptableObject {
    2.     public abstract void Use(Target target); ...
    3. }
    I omitted the origin (i.e., skill user). How you specify the origin might be project-specific. Maybe you want to pass the skill user's GameObject, maybe your own Character class, whatever's convenient.

    And this is where it's probably most useful to add some branching logic (if/switch). You're not going to avoid branching logic entirely, but it's much simpler in this case because you can branch specifically for each SkillXXX class's Use method without having to worry about dependencies with other code.

    Maybe SkillSpawnPrefab.Use checks the type of target it's been given:
    Code (csharp):
    1. public override void Use(Target target) {
    2.     if (target is Vector3Target) {
    3.         // Spawn prefab at (target as Vector3Target).position.
    4.     } else if (target is GameObjectTarget) {
    5.         // Spawn prefabs at (target as GameObjectTarget).gameObjects.
    6.     } else {
    7.         Debug.Log("Can't spawn to target type " + target.GetType().Name);
    8.     }
    9. }
    The idea is that the SkillSpawnPrefab script does one isolated thing: given a target, spawn prefabs. The branching logic is simple because it only branches specifically for the purpose of spawning prefabs. It doesn't care how the target was acquired or triggered. It could have been acquired using some new subclass of SkillAim that was dreamed up well after SkillSpawnPrefab was written. In addition to reducing dependencies, short scripts like this also make it easier to verify correctness and write unit tests.
     
  34. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987