Search Unity

Skill casting method and secondary effects

Discussion in 'Scripting' started by Crouching-Tuna, Nov 10, 2014.

  1. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    Hi, first of all I'm probably aiming way too far for a game idea. I did successfully make what I want, but I can't help but always think I'm doing things wrong. The main intention of posting this here is to let others comment on how I designed the whole system.
    Prototype is here: http://dl.dropboxusercontent.com/u/81093784/GotGWeb.html

    The game I'm making is a fully 3d shooter. It's online, and I'll have it read/write to external file about the player's character like stats, skills, items(not in the game yet), etc.

    Basically there are several ways to cast spells. Shot, Projectile, Cursor, Target, Self.
    Shot is typical gun shot ish, sending raycast.
    Projectile spawns a projectile object with velocity, and upon hitting it'll apply damage.
    Cursor means you spawn(or do the skill) where your mouse is. Say, you're charging an AoE spell, or EarthSpike from the selected ground.
    Target means it needs a target locked on(there's a targetting system)
    Self, you just hit the button and it'll do the spell(or charge it first or something, depends).

    I thought this is all I need to define how all the spells will fly around and be shot. Apparently not.
    Some spells I already have:
    FireBolt - Projectile. Shoots a projectile and goes boom on hit. I want the boom to do an area splash.
    StormGust - Cursor to select the area. Upon releasing the cast, spawns a sphere with yet another script called FieldEffect that applies water element damage over time in that area.
    EarthSpike - Also cursor, and when releasing(if mouse raycast hits ground), spawns a projectile from ground up that hits everything along its path(doesn't go boom and disappear on first contact).

    As I make the spells, the more things I have to hardcode. I don't mind hardcoding its effects(each spell has a list of effects, on hit, applies all of them). What I'm having problem is how messy each spell acts, casting-wise and, say, how it behaves when it hits. I ended up with a lot of switch-case statements telling how each spell should act. What if I shoot a projectile spell, and it spawns 6 more projectiles on all direction when hit?
    Is there a way to make a category on all of them?

    Any suggestions?

    I have:

    1. PlayerInput class - Detects input.

    2. Hotkey class - What's currently in the hotkey. I make this because it relates to the hotkey GUI, and other things that can also be assigned to the hotkey slot. This together with PlayerInput determines what each hotkey does when pressed. (I think this is my first mistake. PlayerInput shouldn't depend on what's in Hotkey. PlayerInput should only care about what is pressed, and sends the event to the scripts that needs to know it, right?)

    3. ActiveSkills class - It has the name, enum SpiritType element, enum CastType, and a
    Code (CSharp):
    1.  
    2. public static Dictionary<string, ActiveSkill> activesDB = new Dictionary<string, ActiveSkill> ();
    3.  
    4. PlayerSkills script - One of the variable in ActiveSkills, the castType is this
    Code (CSharp):
    1.  public enum CastType{Shot, Projectile, Cursor, Target, Self};
    This script goes to the player, it has
    Code (CSharp):
    1.  
    2. public List<ActiveSkill> learnedActives = new List<ActiveSkill>();
    3. public void Shot(string sk, float charge){
    4. // Basically the typical gun type. Sends raycast, etc. Works properly.
    5. }
    6. public void Projectile(string skillname, float charge){
    7. // Instantiates, or get from pool, some sphere with velocity. The projectile has a script called projectile
    8. }
    9. public void Target(string skillname, GameObject target, float charge){
    10. // Skills that need target. Directly calculates damage on the target
    11. }
    12. public void Self(string skillname, float charge){
    13. // Doesn't need target. Aura spells, self buff, etc.
    14. }
    15.  
    5. Projectiles script - Attached to projectiles that is shot. It contains the skill name, the charge when it is shot
    Again, in here, depending on the skill of the projectile, I have to hardcode each behavior. FireBolt disappears upon hit, while the projectile for EarthSpike doesn't(can multi hit along its path).

    6. Attribute class - Contains float variables like str, agi, castSpeed, movementSpeed, with appropriate get/set. For example, like so:
    Code (CSharp):
    1. private float _HpRegen;
    2.     public float HpRegen{
    3.         get{ return _HpRegen + (float)(_Vit / 15); }
    4.         set{ _HpRegen = value - (float)(_Vit / 15); }
    5.    
    7. Entity class - which has Attribute myStat = new Attribute();
    This is also where most of the damage gets calculated. I'm also having a hard time figuring out how to make, say, an aura effect. Lets say it's a burning aura effect. Does it go here, and apply damage to everything around the player, because this is also where I keep track of status effect like burn, frozen, etc?


    I only started coding casually around beginning of this year. I'm aware that I'm missing a lot of basic, fundamental knowledge about coding. I've been reading around this skill stuff(haven't found the one specifically related to my problem. Mostly about skill composition and effects, which I think I can figure out), and have been trying to apply using interface, but I just can't figure out where's a good place to do that.

    For instance, I thought I could put an interface called ICast, and have each skill be categorized by their castType(so I'll have a class called Projectile, Shot, etc, then in the skillDB.add("FireBolt", new Projectile("FireBolt", Fire, etc)). And then have all of those class implement ICast. But then I'll need a target GameObject parameter for Target skills, vector3 for Aoe, etc, so I don't know where the interface could come in handy.

    I could clean up a bit here and there, but I really think I'm approaching this in a very amateur-ish way. I'm ready to learn something new, and possibly remake everything from scratch if only someone can point me on the right direction.

    Thanks!
     
    Pharaoh_ likes this.
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
  3. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    Thanks for the reply!

    After checking them out, I still think some skill behaviors need a very unique code that I have to hard-code them.
    I guess I understand data-driven is awesome. I've played around with them in the past. There's even some cases where I just use a bunch of floats in a skill (param1, param2, param3, etc), and let the code figure what each represents. 1 of the float handles the duration, where > 0 is damage*time(basic duration), 0 is instant damage, < 0 is the damage/time, etc.

    However, the kind of skills I want to make is something like Dota 2's skill(there are several other games with complex skills too of course).
    For instance, a hero called Witch Doctor has a skill called Paralyzing Cask. It requires target(enemy), and upon casting, it'll throw a projectile to the target. The projectile will stun the enemy for 1 second. The projectile will bounce to nearby enemies within 200 range for a maximum of ~8 bounce(depending on skill) or if there's no other valid target within range.

    There's 100+ heroes in Dota, each has 4 skills. Out of all those skills, I think there's only ~3 skills that has this bouncing mechanics.
    If I hard code this, then these ~3 skills will benefit from the function I'd be writing. But if I don't hard-code this, then I should have a way to re-call a SpawnProjectile function after the skill hits a target.
    If I really go with the data-driven approach, how will the code look like so that it can also understand that some skills will call SpawnProjectile again after hitting a target, and have an int i-- after every bounce or something?

    Cheers!
     
    Pharaoh_ likes this.
  4. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    The key is to define as little as possible in code. Unity provides a good way to do this using ScriptableObjects. I provided some example code in this post: Skill systems and other data-rich structures: inheritance or database?

    The way to minimize code is to split out functionality into small, abstract pieces. You're already doing this to some degree. For example, instead of hard-coding in your Projectiles script the different things that can happen when the projectile hits, give it a slot to which you can assign another component that does something on hit. This way, your Projectiles script doesn't need to know anything about hit behavior. When the projectile hits, it just passes the message along to the hit handler. This lets you add new types of hit handlers without having to touch any code in the Projectiles script.

    Then apply this principle to every part of a skill -- how it targets, how the player triggers it, etc.

    Also, don't create subclasses. I don't see that you are; this is a just a suggestion to keep in mind. Instead, try to make things modular like described in the paragraph above.
     
  5. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    I'm still not getting it 100%.

    The way I understand it, lets just say I have a skill called WeirdShot. Maybe I already created it, it's already in the DB, etc.
    Lets say I want this skill to be cast like a gunshot(raycast in mid of screen), but upon hit it'll spawn a projectile upwards.

    Skill WeirdShot's variables:
    CastType.Shot
    Param1 = 5;
    Param2 = 2;

    Then I have a GameObject called Skiller that handles all raycasting, all instantiating of projectiles, etc. When the raycast hits, it calls Skiller's OnHitTarget and sends param1 and param2.
    The function's like so:
    Code (CSharp):
    1.  
    2. OnHitTarget(int param1, int param2){
    3.         switch(param1){
    4.         case 1: do another shot
    5.         case 2: spawn explosion area
    6.         case 3 4 5: spawn projectile
    7.             switch(param2){
    8.             case 1: vector3.left
    9.             case 2: vector3.up
    10.             }
    11.         }
    12.     }
    13.  
    Or am I completely missing the point? This is not how I'm gonna actually code it, but just to show that what will determine how the projectile will behave after hit depends on the data it brings(in this case, 5 and 2, whatever that means), and the one that'll understand what 5 and 2 means is the OnHitTarget function in Skiller(and Skiller probably also have other functions like OnDestroyed, etc)

    On another example, so the Projectile script would only have functions such as OnCasted(calls Skiller's OnCasted with corresponding parameters), OnColliderEnter(calls Skiller's OnHitTarget), etc.

    Is this what you mean?
    Sorry, I get skills having weird effects, but somehow having skills doing weird behaviors is hard for me to understand. Especially because some skill doesn't actually spawn a projectile(it's not a projectile skill), hits a target instantly, and so on. The only way a, say, Self skill end up casting 20 projectiles around, or a Target skill end up casting something else on skill(like, Drain, that after damaging the target, casts some kind of heal back to caster) can work, in my understanding, with data-driven things, is something like this.
    Cheers,
     
  6. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    Recently, i was looking at this issue, and found Dependency Injection.

    With this, you can do:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class WeaponControl : MonoBehaviour {
    4.  
    5.     public class Dependency{
    6.      
    7.         public virtual void Start(){}
    8.         public virtual void Update(){}
    9.         public virtual void FixedUpdate(){}
    10.         public virtual void LateUpdate(){}
    11.         public virtual void OnCollisionEnter(){}
    12.         public virtual void Destroy(){}
    13.         //etc...
    14.      
    15.     }
    16.  
    17.     [System.Serializable]
    18.     public class FireBold : Dependency{
    19.      
    20.         //Variables
    21.         public float damage;
    22.         public GameObject prefab;
    23.         public Transform parent;
    24.         public Vector3 position;
    25.         public Quaternion rotation;
    26.         public KeyCode fireKey;
    27.         // etc...
    28.      
    29.         public override void Start(){
    30.          
    31.             GameObject obj = (GameObject)Instantiate(this.prefab);
    32.             obj.transform.parent = this.parent;
    33.             obj.transform.localRotation = this.rotation;
    34.             obj.transform.localPosition = this.position;
    35.          
    36.         }
    37.      
    38.         public override void Update(){
    39.          
    40.             //Update Stuff;
    41.          
    42.         }
    43.      
    44.         //etc...
    45.      
    46.     }
    47.  
    48.     [System.Serializable]
    49.     public class StormGust : Dependency{
    50.      
    51.         //Create Dependency, as FireBold
    52.         //Variables
    53.         public float damage;
    54.         public GameObject prefab;
    55.         public Transform parent;
    56.         public Vector3 position;
    57.         public Quaternion rotation;
    58.         public KeyCode fireKey;
    59.         // etc...
    60.      
    61.         public override void Start(){
    62.          
    63.             GameObject obj = (GameObject)Instantiate(this.prefab);
    64.             obj.transform.parent = this.parent;
    65.             obj.transform.localRotation = this.rotation;
    66.             obj.transform.localPosition = this.position;
    67.          
    68.         }
    69.      
    70.         public override void Update(){
    71.          
    72.             //Update Stuff;
    73.          
    74.         }
    75.      
    76.         //etc...
    77.      
    78.     }
    79.  
    80.     /*
    81.     [System.Serializable]
    82.     public class EarthSpike : Dependency{
    83.  
    84.         //Create Dependency, as Firebold
    85.  
    86.     }
    87.     */
    88.  
    89.     [Header("FireBold Skills")]
    90.     [Space(10f)]
    91.     public FireBold fireBold;
    92.     [Header("StormGust Skills")]
    93.     [Space(10f)]
    94.     public StormGust stormGust;
    95.  
    96.  
    97.     Dependency[] weapon;
    98.  
    99.     [HideInInspector]
    100.     public int currentWeapon = 0;
    101.  
    102.     int oldWeapon;
    103.  
    104.  
    105.     void Start(){
    106.      
    107.         this.weapon = new Dependency[]{
    108.          
    109.             new Dependency(),//0.None
    110.             this.fireBold,//1.Firebold
    111.             this.stormGust//2.StormGurst
    112.             //etc...
    113.          
    114.         };
    115.      
    116.         this.oldWeapon = currentWeapon;
    117.      
    118.         this.weapon[this.currentWeapon].Start();
    119.      
    120.     }
    121.  
    122.     void Update(){
    123.      
    124.         //Verify weapon switch
    125.         if(this.oldWeapon != this.currentWeapon){
    126.          
    127.             //Destroy previous weapon
    128.             this.weapon[this.oldWeapon].Destroy();
    129.             //Create new weapon
    130.             this.weapon[this.currentWeapon].Start();
    131.          
    132.             this.oldWeapon = this.currentWeapon;
    133.          
    134.         }
    135.      
    136.         this.weapon[currentWeapon].Update();
    137.      
    138.     }
    139.  
    140.     void FixedUpdate(){ this.weapon[currentWeapon].FixedUpdate(); }
    141.     void LateUpdate(){ this.weapon[currentWeapon].LateUpdate(); }
    142.     //etc...
    143.  
    144. }
    From another script, you access and modified the currentWeapon variable.
     
    Last edited: Nov 13, 2014
  7. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Here's an example project: Skill System Example

    I'm not claiming this is the only way, or even the best way, but I think it follows the Unity philosophy of using prefabs and component-based design.

    The player has a GameObject named Skillbar. Skills are defined as children:


    The skillbar and skills use scripts in these folders:


    Each skill has different scripts that, as a whole, define the skill's behavior. The scripts are generic, nothing hard-coded for specific skills. The Launcher (rocket launcher) skill is below.
    • For aiming, it uses "AimCenter", which aims at targets at the center of the screen.
    • For triggering, it uses "ClickToCast", which casts the skill when the player mouse-clicks. This sends an OnFire message to the skill.
    • For shooting, it uses "SpawnWithVelocity", which listens for OnFire and spawns a prefab with a velocity.


    The prefab that it spawns is a Rocket. The rocket has three behaviors:
    • "Temporary" despawns the rocket after 5 seconds.
    • "CollisionDamage" causes damage to whatever it hits, by sending an OnTakeDamage message.
    • "SpawnCollisionReplacement" destroys the rocket immediately and replaces it with an Explosion prefab. (The Explosion prefab also has a Temporary that despawns it after 1 second.)


    So you can see that none of the code is skill-specific. All skill-specific data is managed in the inspector. You can create prefabs of these skills and add and remove them as the player gains and loses abilities.

    Here are some of the main scripts, if you don't want to download the package. (The package has a working scene, too.)

    Skillbar.cs draws the skill bar and lets you select the active skill.
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class Skillbar : MonoBehaviour {
    4.  
    5.     private Skill[] skills;
    6.  
    7.     private Skill activeSkill;
    8.  
    9.     void Start() {
    10.         skills = GetComponentsInChildren<Skill>();
    11.         SetActiveSkill((skills.Length > 0) ? skills[0] : null);
    12.     }
    13.  
    14.     public void SetActiveSkill(Skill newActiveSkill) {
    15.         activeSkill = newActiveSkill;
    16.         foreach (var skill in skills) {
    17.             skill.gameObject.SetActive(skill == activeSkill);
    18.         }
    19.     }
    20.  
    21.     void OnGUI() {
    22.         GUILayout.BeginHorizontal();
    23.         foreach (var skill in skills) {
    24.             GUI.color = (skill == activeSkill) ? Color.yellow : Color.gray;
    25.             if (GUILayout.Button(skill.name, GUILayout.Width(64), GUILayout.Height(64))) {
    26.                 SetActiveSkill(skill);
    27.             }
    28.         }
    29.         GUILayout.EndHorizontal();
    30.     }
    31. }
    Skill.cs is really just a placeholder in this example. The other scripts compose the actual functionality.
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class Skill : MonoBehaviour {
    4.  
    5.     public GameObject target;
    6.  
    7. }
    AimBase.cs provides basic targeting functionality.
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class AimBase : MonoBehaviour {
    4.  
    5.     public LayerMask layerMask = 1;
    6.     public Texture2D reticle;
    7.  
    8.     private Skill skill;
    9.  
    10.     void Awake() {
    11.         skill = GetComponent<Skill>();
    12.     }
    13.  
    14.     public virtual Ray GetAimRay() {
    15.         return Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2f, Screen.height / 2f));
    16.     }
    17.  
    18.     void Update() {
    19.         RaycastHit hit;
    20.         if (Physics.Raycast(GetAimRay(), out hit, 100f, layerMask)) {
    21.             skill.target = hit.collider.gameObject;
    22.         }
    23.     }
    24.  
    25.     void OnGUI() {
    26.         DrawReticle();
    27.         DrawTargetName();
    28.     }
    29.  
    30.     public virtual void DrawReticle() {
    31.         if (reticle == null) return;
    32.         var rect = new Rect((Screen.width - reticle.width) / 2, (Screen.height - reticle.height) / 2, reticle.width, reticle.height);
    33.         GUI.DrawTexture(rect, reticle);
    34.     }
    35.  
    36.     public virtual void DrawTargetName() {
    37.         if (skill.target == null) return;
    38.         var size = GUI.skin.label.CalcSize(new GUIContent(skill.target.name));
    39.         var rect = new Rect((Screen.width - size.x) / 2, ((Screen.height - size.y) / 2) - 50, size.x, size.y);
    40.         GUI.Label(rect, skill.target.name);
    41.     }
    42. }
    AimCenter.cs is really just another name for AimBase, since AimBase by default aims in the center of the screen.
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class AimCenter : AimBase {
    4. }
    AimCursor.cs overrides the targeting position and the reticle GUI position. This is for skills that aim where the mouse cursor is pointing.
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class AimCursor : AimBase {
    4.  
    5.     public override Ray GetAimRay() {
    6.         return Camera.main.ScreenPointToRay(Input.mousePosition);
    7.     }
    8.  
    9.     public override void DrawReticle() {
    10.         if (reticle == null) return;
    11.         var rect = new Rect(Input.mousePosition.x - (reticle.width / 2), (Screen.height - Input.mousePosition.y) - (reticle.height / 2), reticle.width, reticle.height);
    12.         GUI.DrawTexture(rect, reticle);
    13.     }
    14. }
    ClickToCast.cs implements one way to cast the skill (that is, send "OnFire").
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class ClickToCast : MonoBehaviour {
    4.  
    5.     void Update() {
    6.         if (Input.GetMouseButtonDown(0)) {
    7.             SendMessage("OnFire");
    8.         }
    9.     }
    10. }
    SpawnWithVelocity.cs responds to OnFire by firing a projectile.
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class SpawnWithVelocity : MonoBehaviour {
    4.  
    5.     public GameObject prefab;
    6.     public GameObject origin;
    7.     public float force = 500f;
    8.  
    9.     void OnFire() {
    10.         var projectile = Instantiate(prefab) as GameObject;
    11.         projectile.transform.position = origin.transform.position;
    12.         projectile.transform.rotation = origin.transform.rotation;
    13.         projectile.GetComponent<Rigidbody>().velocity = origin.transform.TransformDirection(Vector3.forward * force);
    14.     }
    15. }
    If you added RaycastEffect.cs or SpawnAtCursor.cs instead, it would change the way the skill handles OnFire.

    CollisionDamage.cs gets added to projectiles (not to skills). When it collides, it sends "OnTakeDamage". You could make this more generic by allowing the designer to specify the message that it sends, instead of hard-coding "OnTakeDamage".
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class CollisionDamage : MonoBehaviour {
    4.  
    5.     public float damage = 1;
    6.  
    7.     void OnCollisionEnter(Collision collision) {
    8.         collision.collider.SendMessage("OnTakeDamage", damage, SendMessageOptions.DontRequireReceiver);
    9.     }
    10. }
    The rocket has CollisionDamage.cs and SpawnCollisionReplacement.cs, which replaces the rocket with another prefab -- in the rocket's case, an explosion. It also has Temporary.cs, which despawns the rocket after 5 seconds, in case it doesn't hit anything.

    The StormGust spell uses AimCursor and SpawnAtCursor. It spawns a Storm prefab, which has a Temporary to despawn it after 5 seconds.
     
    Last edited: Nov 12, 2014
  8. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    Wow. That looks like what I'm looking for. I'm currently re-making everything from scratch, as I was building things as I learn things(huge mess everywhere).
    Good thing I haven't completely re-made the skill system when I saw this. Still playing with the database/scriptable object thing and I think I know what I'm gonna do to finish it.

    Thanks!
    I guess the hard thing about newbies learning things we google up is, we're just not completely familiar with some terms and "common practices" and stuff.
    For instance,
    I never heard about this, and that this is (one of) a recommended way of designing codes.
    Maybe I'll have to look into other people's finished projects and learn from those if I wanna improve more...
    But for now... I need to finish something, at least a prototype!

    Thanks again. I'll reply here again if I run into some problems.
     
  9. AlecGamble

    AlecGamble

    Joined:
    Dec 28, 2015
    Posts:
    6
    Hi,

    Sorry to dredge this up again but I'm at the point where I have an instance of the spell object attached to one of the skill slots on my player. So the way I've interpreted this is essentially as an inspector driven cherrypicker of scripts? So in this example the structure of the skill should be to give options to the player and then add the script components based on those choices? So in your example would this manifest itself as a method in the script which Initialises as a GameObject and then uses a series of if / switch statements to add script components to that object or is there a more eloquent method to achieve that part?
     
  10. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Hi - The purpose of this design is to avoid if / switch statements and instead compose complex behavior out of several small, relatively-independent components. Take the Rocket Launcher skill above, for example. The screenshot of the Inspector shows that it's composed of three small components:
    • AimCenter
    • ClickToCast
    • SpawnWithVelocity
    Each of these components basically does its job in isolation. The AimCenter component just aims at the center of the screen; it doesn't care what happens before or after that. The SpawnWithVelocity component just spawns a prefab at the aim point; it doesn't care how the aim point was determined or how the shot is triggered.

    That said, a lot of this was just spitballing and proof of concept from a couple years ago. Nowadays I think a lot of people would opt for ScriptableObjects instead, as in Richard Fine's recent video:



    However, that's just a GameObject vs. ScriptableObject thing. The important idea is composing larger behavior out of small, self-contained, reusable logic, and leveraging the Unity editor to do it without having to hard-code things.
     
    EvilGremlin likes this.
  11. AlecGamble

    AlecGamble

    Joined:
    Dec 28, 2015
    Posts:
    6
    Thanks Tony,

    I understood that as far as it trying to remove the idea of complex behaviour in that way so I was unsure on how to use the scriptable object to add / utilise the component monobehaviours. I'll watch the video shortly. I just also wanted to say thank you so much for posting all of this, it's been a great help to me.
     
  12. AlecGamble

    AlecGamble

    Joined:
    Dec 28, 2015
    Posts:
    6
    Oh one more question. How would you go about asigning variables with this method (i.e. amount of damage or similar)?
     
  13. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Each ScriptableObject asset will be a separate instance.

    If they represent things like bullets, this makes sense:
    Code (csharp):
    1. public class Bullet : ScriptableObject {
    2.     public float damage;
    3. }
    You could have a PistolBullet asset that does damage 2, ShotgunShell asset that does damage 4, Rocket that does damage 10, etc. When you create each ScriptableObject or GameObject, just assign a value to damage.

    But you can also go overboard with the granularity of composition. This would probably be a bad idea:
    Code (csharp):
    1. public class Damage : ScriptableObject {
    2.     public float amount;
    3. }
    4.  
    5. public class Bullet : ScriptableObject {
    6.     public Damage damage; // Points to another ScriptableObject representing damage.
    7. }
    You'd have to create separate assets for damage 1, damage 2, damage 3, etc., and assign them to the bullet assets, which would be overkill.
     
  14. AlecGamble

    AlecGamble

    Joined:
    Dec 28, 2015
    Posts:
    6
    So you're saying that I still add these to the Skill scriptable object, assign them in the inspector and then pass that variable to the script after it's instantiated? Also I've been trying to write the custom editor so that I can have an objectfield for each script type I need to add to my scripts but I want to be able to limit it to only picking scripts which inherit from the parent class. Is there a way to do that as far as you know? It would feel a bit incomplete to me if I had variables for each script but it allows me to pick from any monoscript... I've tried things such as:

    Code (CSharp):
    1. //Skill Script
    2. GenericTarget targetScript;
    3.  
    4. //Editor Script
    5. mySkill.targetScript = EditorGUILayout.ObjectField(mySkill.targetScript,typeof(GenericTarget),false)asGenericTarget;
    but obviously this just shows no results. Any ideas how I should approach this? Or am I just being too picky in wanting it to be neat like that?
     
  15. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    That should work. It should show a field that only allows you to assign ScriptableObject assets of the GenericTarget type or subclasses.
     
  16. AlecGamble

    AlecGamble

    Joined:
    Dec 28, 2015
    Posts:
    6
    They're MonoBehaviours (because they're the components) not ScriptableObjects. The field in the inspector says None(GenericTarget) but when I click it there is nothing under assets. I have several MonoBehaviours which inherit from it and then the original GenericTarget script as well. Sorry, I'm not just looking to be given the answer, I did a lot of googling trying to see how I could do this but I haven't found anything that works for me yet.
     
  17. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    I'm not sure I follow you 100%. If you're talking about using the bullseye picker on the right side of the ObjectField, it will only show in-scene GameObjects that have a GenericTarget (or subclass) component. It won't show prefab asset files. However, it will only allow you to assign prefab asset files that have a GenericTarget. So I think the answer is that you can't get exactly the behavior you want.

    However, another approach would be to call FindObjectsOfTypeAll<GenericTarget>() in OnEnable to generate a list. Then show it in an EditorGUILayout.Popup instead of ObjectField.
     
  18. AlecGamble

    AlecGamble

    Joined:
    Dec 28, 2015
    Posts:
    6
    Fair enough, sorry I should've uploaded images, this is what I was referring to. Maybe I misunderstood but I thought the point of this design pattern was to create the snippets of code which represent the component parts of a skill. Then attaching the scripts to the scriptable object which would then (on demand) instantiate a gameObject instance of the skill and then add the script components to it? I was expecting (using my code) to be able to look at the asset instances of the scripts and then assign them to the variables on the scriptable object. I've probably misunderstood something fundamental though. Here's what I'm getting anyway:


    I'll try your other method of populating them, that sounds interesting. Thanks! :)
     
  19. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    For some reason I had very similar discussions with a couple other people yesterday, too, and I'm afraid I don't remember which approach you're taking -- ScriptableObjects, GameObjects (prefabs), or some mix of the two.

    The nice thing about GameObjects (and thus prefabs) is that you can easily add as many components (i.e., instances of MonoBehaviour) as you want. For a Fireball skill, you can start with an empty GameObject, and then add small, narrowly-focused scripts for each element of the skill (targeting, triggering, reducing mana, instantiating a fireball projectile, etc.)

    The challenging thing about prefabs is that, apart from simply referencing them, you usually need to have an instance in the scene to invoke its methods.

    The nice thing about ScriptableObjects, on the other hand, is that you don't have to instantiate a copy to invoke methods.

    However, it's not as easy to add elements. You could manually maintain a list of skill elements, or you could create variables for the different types of elements. For the latter, it might be something like:
    Code (csharp):
    1. [CreateAssetMenu]
    2. public class Skill : ScriptableObject {
    3.     public Targeting targeting;
    4.     public Triggering triggering;
    5.     public Firing firing;
    6. }
    7.  
    8. public class Targeting : ScriptableObject {}
    9. public class MouseTargeting : Targeting {}
    10. public class ScreenTargeting : Targeting {
    11.     public Vector2 screenPosition;
    12. }
    13.  
    14. public class Triggering : ScriptableObject {}
    15. public class KeyTriggering : Triggering {
    16.     public KeyCode fireKey;
    17. }
    18.  
    19. public class Casting : ScriptableObject {}
    20. public class ProjectileCasting : Casting {
    21.     public GameObject projectilePrefab;
    22.     public float speed;
    23. }
    To create a Fireball skill that launches a fireball projectile from the center of the screen when you press 'F', you might:
    • Create a new Skill asset named "Fireball".
    • Create a new ScreenPositionTargeting asset named "ScreenCenter" and set screenPosition to (0.5,0.5).
    • Create a new KeyTriggering asset named "KeyF" and set fireKey to KeyCode.F.
    • Create a new ProjectileCasting asset named LaunchFireball, assign the fireball prefab to projectilePrefab, and set the speed.
    • Finally, assign ScreenCenter, KeyF, and LaunchFireball to the Fireball asset.
    If you didn't want separate asset files for the skill elements (ScreenCenter, KeyF, LaunchFireball), you could write a custom editor to create ScriptableObjects without saving them as asset files, assign them to the Fireball asset, and use AssetDatabase.AddObjectToAsset to add them to the Fireball asset itself. This way you have only one asset file instead of four. However, this also means you can't re-use ScreenCenter or KeyF. You'd have to reproduce them for every skill that uses them.
     
  20. Piotrone

    Piotrone

    Joined:
    Mar 6, 2015
    Posts:
    36
    @TonyLi Thanks for outlining the composition approach. It sounds promosing but there aren't many other examples or tutorials out there.
    Anyway I'm giving it a go and have 3 questions:

    1/ How'd you manage spell's mana cost and player mana usage?
    Should every button/spell have a separate script with a 'manaCost' property that is subtracted from the player's static property manaPool every time the button is clicked?

    2/ How'd you manage healing (+health) and damage (-health) friendly units?
    Let's say the player can heal friendly units. I now have a Healable.cs (like your Damagealbe) on very unit with a Heal() class that waits for messages to be sent form CollisionHeal (like your CollisionDamage) to add health points to health. How it needed to be modified to access health when friendly units take damage?

    3/ How'd implement something like a spell cast time or spell cooldowns ?

    Thanks
     
  21. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    A good example is Richard Fine's presentation at Unite:



    He uses ScriptableObjects, but the composition principles are the same if you're using MonoBehaviours.

    I try to avoid global variables. If you have more than one spellcaster in your game, a single global variable won't do. Your spellcaster could have a ManaPool MonoBehaviour that has an amount property, something like:
    Code (csharp):
    1. public class ManaPool : MonoBehaviour {
    2.     public float maxAmount;
    3.     public float amount;
    4.     public float regenRate;
    5.  
    6.     void Update() {
    7.         amount = Mathf.Clamp(amount + Time.deltaTime * regenRate, 0, maxAmount);
    8.     }
    9. }
    Then your spells can use GetComponent/GetComponentInChildren/GetComponentInParent to find it and check/modify the amount:
    Code (csharp):
    1. public abstract class CastingRequirement : MonoBehaviour {
    2.     public abstract bool IsMet();
    3. }
    4.  
    5. public class ManaRequirement : CastingRequirement {
    6.     public float requiredAmount;
    7.  
    8.     public override void IsMet() {
    9.         return GetComponentInParent<ManaPool>().amount >= requiredAmount;
    10.     }
    11. }
    12.  
    13. public class Spell : MonoBehaviour {
    14.     public CastingRequirement[] castingRequirements;
    15.  
    16.     public bool AreRequirementsMet() {
    17.         foreach (var requirement in castingRequirements) {
    18.             if (!requirement.IsMet()) return false;
    19.         }
    20.         return true;
    21.     }
    22. }
    I suppose your Heal class could check what it collided with. If it's friendly, take effect.

    Working off the code above, you could add, for example, a cooldown requirement. It might make the code more elegant to define C# interfaces (e.g., ICastingRequirement) or the Unity Messaging System (IEventSystemHandler) liberally, but I didn't want to complicate the example code above by including them. I'll include it here though. The code below lets CooldownRequirement listen for Cast() messages and start a cooldown timer.
    Code (csharp):
    1. public interface ICastMessageTarget : IEventSystemHandler {
    2.     void Cast();
    3. }
    4.  
    5. public class CooldownRequirement : CastingRequirement, ICastMessageTarget {
    6.     public float cooldownTime;
    7.     public float cooldownLeft;
    8.  
    9.     public override bool IsMet() {
    10.         return cooldownLeft <= 0;
    11.     }
    12.  
    13.     public void Cast() {
    14.         cooldownLeft = cooldownTime;
    15.     }
    16.  
    17.     void Update() {
    18.         cooldownLeft = Mathf.Clamp(cooldownLeft - Time.deltaTime, 0, cooldownTime);
    19.     }
    20. }
    Here's how the spell could tell its ICastMessageTargets that it's being cast (e.g., to start the cooldown timer):
    Code (csharp):
    1. public class Spell : MonoBehaviour {
    2.     ...
    3.     public void Cast() {
    4.         ExecuteEvents.Execute<ICastMessageTarget>(target, null, (x,y)=>x.Cast());
    5.     }
    6. }
     
    Piotrone likes this.
  22. Piotrone

    Piotrone

    Joined:
    Mar 6, 2015
    Posts:
    36
    Thanks! Looking forward to try it!

    I see that there's a spell class, so I guess also having a global spell book class that collects all the available spells and a player spell book, with all spells the player knows, will not be so difficult. I had the impression that by composing spells in the inspector the goal was to avoid such spell class, and therefor a spell book would be then difficult to build.

    p.s.: I had a static property because I'm planning to have only one healer per game.
     
  23. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    The need for a Spell class really depends on how you structure everything else. It was convenient in this example, but it's not necessary. Note that even in this example the Spell class doesn't define anything about how a spell works. It simply assumes that the GameObject has a bunch of other components (casting requirements, effects, etc.) and invokes those components without having to know how they work.

    Regarding the static property, you won't have any NPC casters?
     
    Piotrone likes this.
  24. Piotrone

    Piotrone

    Joined:
    Mar 6, 2015
    Posts:
    36
    Thanks for clarifying. Makes sense.
    Yep, forgot about those NPCs. I will not make it static.

    Thanks!
     
  25. Piotrone

    Piotrone

    Joined:
    Mar 6, 2015
    Posts:
    36
    @TonyLi I'm implementing your code, and I if you have time, I have some questions regarding the Spell class.

    1/ I'm getting familiar with the new Unity Messaging System, and it's not clear to me what object should I assign as the target of the ExecuteEvents.Execute function. You say that the event could start the cooldown timer, but shouldn't that script be already attached to the spell and fire because it implements the right interface? Also assuming that enemies or other players may be interested in the Cast() event, I guess that further down road I'll probably need a list of listener objects, to loop through and execute the event on, right?

    2/ Casting requirements is a public array of type "casting requirements" exposed in the inspector. I guess I don't need to assign (also because I can't) the mana and cooldown requirements scripts in the inspector to make the spell script work, or should I?

    3/ In your example Casting Requirement is an abstract class but it could also be an interface (ICastingRequirement). Any hint on why did you prefer the first to the second?

    Thanks
     

    Attached Files:

  26. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    This is not a system that I've actually implemented in production, so any insights you have while actually putting it into use will be probably be the right way to go.

    In my post above, it looks like that's how it's set up. To cast a spell, you run ExecuteEvents.Execute<ICastMessageTarget>(Cast()). Since CooldownRequirement implements ICastMessageTarget, it runs Cast() to start the timer. You might also have a SpawnProjectile script whose Cast() spawns a projectile, and an AudioEffect script whose Cast() plays an audio clip, etc. BTW, the target for ExecuteEvents.Execute is the GameObject that will receive the event (e.g., ICastMessageTarget.Cast), not the victim of the spell.

    Right. You might want to add another script that implements ICastMessageTarget.Cast() to notify listeners (enemies/players).

    If you add instances of ManaRequirement and CooldownRequirement to the GameObject, you should be able to assign them.

    I think I did that just to make it easier to assign in the inspector, which works better with MonoBehaviour subclasses than with C# interfaces.
     
    Piotrone likes this.
  27. Piotrone

    Piotrone

    Joined:
    Mar 6, 2015
    Posts:
    36
    @TonyLi Thank you again for your replies! I really appreciate your help.

    I was confused because in your example ExecuteEvent was in the Spell.cs class and it didn't work. Everything works if the call to the event is in ClickToCast.cs, attached to the UI element that fires the spell and target is set to that game object. Here's the working code:

    ClickToCast.cs
    Code (CSharp):
    1. public class ClickToCast : MonoBehaviour {
    2.  
    3.     void Update() {
    4.         if (Input.GetMouseButtonDown(0)) {
    5.             ExecuteEvents.Execute<ICastMessageTarget>(gameObject, null, (x,y)=>x.Cast());
    6.         }
    7.     }
    8. }
    Spell.cs
    Code (CSharp):
    1. public class Spell : MonoBehaviour {
    2.  
    3.     public CastingRequirement[] castingRequirements;
    4.  
    5.     public bool AreRequirementsMet() {
    6.         foreach (var requirement in castingRequirements) {
    7.             if (!requirement.IsMet()) return false;
    8.         }
    9.         return true;
    10.     }      
    11. }
     
    TonyLi likes this.
  28. Melo_JR

    Melo_JR

    Joined:
    Oct 13, 2017
    Posts:
    4
    Hello people, first of all I want to thank you guys for the information given in this post. I'm building a SRPG that follows a component approach for the skills but I have encountered a problem that I don't know how to solve. I would appreciate if anyone could help me with this.

    In my game, each character has a GameObject named SkillBar that has the different skills. Each skill has one component for the range of the skill (SkillRange), one for the type of target (SkillTarget) and one for the area of effect of the skill (SkillArea). In order to apply different skill effects for different targets in the area of effect I has to create direct children of the skill's GameObject for each type of target. Here is an example of a typical heal skill that heals allies and damages undead:

    - Unit
    -- SkillBar
    --- OP Heal [components: RadialRange(radius: 3), EmptyCellTarget, RadialArea(radius: 2)]
    ---- AllyEffect [components: AllyTarget, Heal(amount: 3)]
    ---- UndeadEffect [components: UndeadTarget, Damage(amount: 3)]

    With this approach I could create some interesting skills but the problem came when I wanted to introduce the status concept in the game. In my game each status has a status effect and a status condition. And here is the question: How can I create a SkillEffect that applies a Status in a way that allows me to define the StatusEffect and the StatusCondition in the Inspector?

    Obviosly I can create one SkillEffect for each StatusEffect and each StatusCondition, something like ApplyPoisonEffect, ApplyHasteEffect, ... But this solution produce an explosion of classes that I don't like at all.

    I've researched a lot in forums but I haven’t been able to get appropriate solutions. Here you can see some of my thoughts about possible solutions:
    • Make StatusEffect and StatusCondition sublcass of ScriptableObject and make ApplyStatusEffect with two public fields, one for a StatusEffect and another for a StatusCondition. I don't like this so much because of the use of ScritableObjects (read my thoughts about ScriptableObjects in the end).
    • Same as the previous solution, but in this case, instead of creating the ScriptableObject as assets one by one, I would build a custom inspector that allows me to create those ScriptableObjects dynamically from the SkillEffects view. I could use the concepts used in this post or this other. I'm not sure if it is possible and if it is a good practice. I suppose it isn’t.
    • Another solutioncould be to create a SkillEffect that "searches" for StatusEffects and StatusConditions among its descendants, a pair for each descendant. Problems: right now StatusEffect and StatusCondition are not MonoBehavior and I don't like that some SkillEffect requires other type of GameObject hierarchy.
    • The last solution could be to build a custom inspector and to use some sort of Reflection techniques in order to create the effect and the duration and set their fields. But I don't like the use of Reflection :/
    Ideal solution
    I have a ApplyStatusEffect that I can add to the GameObject, I can set the specific StatusEffect and StatusCondition in the Inspector, and I can also set the public fields of the chosen effect and condition.

    My problem with the ScriptableObject approach (because I probably don't get the point)
    I don't like the ScriptableObject approach because I don't like the processof creating a new skill. If I understood this approach well, I would have to do something like this:
    - Create an asset for the skill range with fixed parameters (I don't think it will be very reusable).
    - Create an asset for the type of target (it can be very reusable)
    - Create an asset for the type of area of effect with a fixed parameters (I don't think it will be very reusable).
    - Assign these assets to the Skill.

    I should probably try the solution that @TonyLi proposed in this post:
    The problem I see is that with this approach you are losing the ability to reuse the ScriptableObject and if you have different subclasess I think you have to code some sort of if clauses... (something that you want to avoid with these approachs)

    Finally, sorry for this long post and thank you in advance.
     
  29. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    You're absolutely right on losing the ability to reuse the same ScriptableObject. But is that really much of a problem? Quest Machine uses this technique, and it seems to work well. Quest Machine treats it as a way to add polymorphism to Unity's built-in serialization. But each subasset really is unique to each quest, whereas different skills might want to share the same subasset data.

    In any case, you don't have to code any 'if' statements for subclasses. Polymorphism (e.g., virtual and override) should handle everything for you.

    Another option is to define everything in an external file format such as XML or even an Excel spreadsheet. Then write a script that processes the file to automatically build the skills. It's still not perfect because you'll need to define the base capabilities (e.g., launch projectile), including their runtime behavior and how to reference them in your external file format. But it's another option at least.
     
  30. Melo_JR

    Melo_JR

    Joined:
    Oct 13, 2017
    Posts:
    4
    Thank you for your quick answer!

    If there is no performance issue and it isn't a bad practice then there is no problem.
    So you recommend to build a custom inspector that build the assets directly and assign them to the skill, right?
    I am going to research Quest Machine and give it a try. I will post my advances on this topic.
     
  31. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    It doesn't necessarily need to be a custom inspector. When designing a system like this, it helps to approach it from 3 angles simultaneously:
    • Execution: What implementation makes it easy for the game to execute skills at runtime?
    • Design: What's a simple, error-free, easy-to-tweak way to define skills at design time?
    • Implementation: What's the simplest way to code this?
    and then try to get all 3 approaches to meet at the center.

    For example, if your team has a dedicated game designer, they may feel more comfortable designing skills in Excel. If you're a solo dev and you want to tinker with skill values while the game is running to balance them, you might want to use ScriptableObject assets.

    Personally, especially if you're a solo dev, I'd just create separate loose ScriptableObject asset files (rather than adding sub-assets) and organize them in folders in the project. It's more files to manage, but it's flexible and fairly simple to implement. You don't even need to fiddle with custom inspectors because they can just use the default inspector.

    Or take a look at how Quest Machine works. There's a free trial version, but unlike the paid version it doesn't include source code. You can still get something of an idea of how it works. The main ScriptableObject asset is a Quest. It can contain subassets whose types are QuestCondition, QuestAction, and QuestContent.
     
  32. Melo_JR

    Melo_JR

    Joined:
    Oct 13, 2017
    Posts:
    4
    I'm a solo dev and this is a personal project (maybe a complex one) to improve my skills in Unity making a game that I would like to play. So I do not care to research a lot of information in order to try to get the "best" solution for me. After all I have no deadlines.

    As far as I know the solution with components are pretty similar to the solution with ScriptableObject in terms of performance. Maybe the first one is a bit more performant. Is it correct? So the execution doesn't matter so much. My first goal is build a system that enables me to create lot of skills in a easy and confortable way. In my game I will have to create dozens of skills because it is a game with lot of unique heroes with 4-5 interesting skills per hero.

    I have researched a bit about AssetDatabase.AddObjectToAsset and Quest Machine and I have prepared some sources to test this approach. Until now, the problem that I have found is that in order to create a concrete subclass (e.g. ScreenTargeting that you said above) the user has to write the name of this subclass in some input of the Inspector, it hasn't? I can create a "select input" with the legal values but this is no a scalable solution. Am I missing something important?

    Thank you for all the information, I'm learning a lot.
     
  33. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Yes, about the same.

    This is really getting down the rabbit hole, but Quest Machine has custom inspectors that use reflection to identify all subclasses and show them in a dropdown menu. When the user selects a subclass from the dropdown menu, the editor creates a ScriptableObject instance of that type and adds it to the Quest asset using AssetDatabase.AddObjectToAsset.

    The custom editors also use Editor.CreateEditor to create nested editors for the instances of the subclasses.

    For example, let's say this is your main Skill class:
    Code (csharp):
    1. public class Skill : ScriptableObject {
    2.     public TargetingMethod[] targetingMethods;
    3. }
    And these are your targeting methods:
    Code (csharp):
    1. public abstract class TargetingMethod : ScriptableObject {
    2.     public abstract Transform GetCurrentTarget();
    3. }
    4.  
    5. public class ScreenTargeting : TargetingMethod {
    6.     public override Transform GetCurrentTarget() { /*some code*/ }
    7. }
    8.  
    9. public class MouseTargeting : TargetingMethod {
    10.     public override Transform GetCurrentTarget() { /*some code*/ }
    11. }
    Then the custom editor for Skill can make a list of { ScreenTargeting, MouseTargeting } using reflection as in this stackoverflow answer and show it in a dropdown.

    When it comes time for the custom editor for Skill to draw the elements of targetingMethods, you can create and use an editor for each one:
    Code (csharp):
    1. var skill = target as Skill;
    2. foreach (var targetingMethod in skill.targetingMethods) {
    3.     var editor = Editor.CreateEditor(targetingMethod);
    4.     targetingMethod.OnInspectorGUI();
    5. }
    The code above is not the way to really do it because it allocates a new editors every repaint. I just wrote it like that to keep the code shorter. You should create and cache the editors for re-use.

    Hence the reason why I suggested throwing loose assets into subfolders might be simpler in the long run. :)
     
  34. Melo_JR

    Melo_JR

    Joined:
    Oct 13, 2017
    Posts:
    4
    Hi @TonyLi, sorry for not replying to your last message, I was playing around with custom editors and dynamic creation of ScriptableObjects, but I had lot of work the last quarter of the year and I had no time for this project. But I returned few weeks ago with new ideas for my heroes and their skills =D

    Everything was good with the last system we had discussed, I could create lots of skills easily but I have recently found some troubles... classic.
    I would like to share it with you in order to receive some helpful feedback.
    1. I discovered that the system works well with active skills but regarding passive skills I have found out that each one requires the creation of new ScriptableObject classes (sometimes 3-4) to devise the skill by composition. Does this workflow make sense? Lately, I have been giving some thought to this and it is probably easier if I create a subclass of "PassiveSkill" for each new passive skills.
    2. I created some new skills that require the player to make a choice from a set of available options, e.g., a skill that uses an object from the character's inventory. I don't know how I can create a SkillComponent that changes my game state to show this selection screen. I have more skills that require a similar screen but with some differences between them. I don't neither know if my game state manager is the best for my game :/
    3. With these skills I need that the skill changes its behaviour depending on the player's choice. I have identified 2 types of these skills:
    • An "improvable" skill, this is a skill that can change its behaviour (range distance, range type, skill effect, cost, etc.) according to the player's choice. In addition, the player, in some circunstances, can select more than 1 option, so multiple improvements apply together.
    • Skill that change its behavior when an event occurs. A good example of this type of skill is one that costs 0 in odd turns or another that applies 'root' when it's raining.
      My ideal solution would be similiar to the following (simplified):
      Skill
      - Skill Range
      -- Apply Damage 5
      -- SwitchComponentX(ApplyPoison, ApplyRoot, None)
      This was an example of skill that adds an effect depending on the player's choice.
      I was thinking a lot and I didn't find an ideal solution because these 'switchs' may be available for almost every skill component (SkillCost, SkillRange, SkillEffect, etc.) and there are different types of switch (so, explosions of subclasses again T.T)... I think that it happens because of the type of game I want to develop: unique heroes with very different skills from each others but I would not like to change the concept.
    I would like to do the things right in Unity but recently I am thinking about making a subclass por each hero skill, both passive and active skills... Something that, as far as I know, is not the "Unity style".

    Can you give me some advice? Thanks in advance.
     
  35. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Do what works best for you. One person's interpretation of "Unity style" may not be another's. Unity, and game development in general, favor composition over inheritance in most cases. But you'll usually find a healthy mix of both. Unity itself even continues to evolve its interpretation of the Unity way of doing things. They're moving toward an entity-component-system mode, for example.
     
  36. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    Oh wow, it's been a while since I first asked here.
    I have my own skill system for a while, the Gameobject-component style one based on Tony, and had since worked it to manage a few Dota-like skills, like how I wanted to.
    Some weirdest effects:
    Necromastery - gains +2 Atk per monsters killed, loses charge on death.
    Blizzard - release an AoE that damages per second and slows. After 3 seconds, freeze. While freeze, not be damaged anymore until out of freeze(damaged).
    Aqua Shield - Spellblock, like Dota's Linkens Sphere
    Blink Strike - Like Dota's Rikimaru
    Some elemental enchant sword, some gives on hit effect like burn, stun

    Not gonna post code because it's got some extra mess due to authoritative multiplayer stuff. But basically, have a bunch of events. Every skill object only need 2 important reference: the player caster, and the skill handler. Every of the skill Component only need a ref to the skill Handler (if needed, or the handler can send event/collect info from the Components abstract-ly), and the Handler only needs ref of the player caster.

    I've returned here because I'm planning to do a remake. Some of my other system are already going towards ScriptableObjects, the skills included, but the data in the skillDB doesn't control enough data definition, only things such as Allowed Class, bool Weapon variance, bool require Charge, bool Offensive, and ActivationStyle{Single, ToggleHold, ToggleOnOff, Passive}
    Thinking of letting the SO data to define more of the skill, or at least, the default stats of the skill, so the skill instance itself can be more expressive in terms of player stat influence, weapon used, ammunition, etc.

    The ammunition part is the main reason why I feel a remake (or rework) is really necessary.
    When I shoot arrow, my arrow projectile is generic. I can attach effects to the arrow, but I want to simply attach actual ammunition data AS the arrow as well.
    When I equip an arrow that inflicts curse, I'm giving the player that effect, which attached on hit effect to the projectile.
    That's the most I can do with this system with arrows, on hit effect. Can definitely make new behavior (spawn AoE on destroy, etc) but...

    Anyways, the arrow. How do people deal with arrow effects, without dumping all bonus stats/effects into the player's stats?
    What if later I want to be able to have a weapon/arrow set switch?
    Other than tidying up what I already have (which I must first do... this is a few years old system I got), I think I can figure it out, but just wanna know how others make skill system with their arrow effects, mainly.
    Some arrows I wanted to make and continuing with the current system feels messy:
    Exploding arrow. (Spawn AoE object on destroy? Can work)
    Piercing arrow (Can't use the generic arrow. Or all arrow projectile should be trigger, and current equipped arrow contributes the hit count on release)

    Hmm.. as I was typing, realized maybe it's not too bad? Definitely needs tidying up tho...