Search Unity

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

Resolved "Use" Functions for items

Discussion in 'Scripting' started by Myst1cS04p, Aug 3, 2023.

  1. Myst1cS04p

    Myst1cS04p

    Joined:
    Oct 10, 2022
    Posts:
    16
    So I have eggs and I want them to do different things when you throw them. E.g.: a bomb egg that blows everything up.

    I dont understand how I should implement this. First I tried using scriptables that would have all the attributes of the egg, and then, at the end an event to which I could subscribe my "use" function in the inspector. The idea was that I would just do "Egg.effect.Invole();" but because its a scriptable object I cant give it functions from my game scene. So then I decided to just use prefabs with the same basic idea in mind. But its the same problem. Prefabs are in my prefabs folder and I cant assign specific functions to them from a script that is running in my game scene. I even tried to set up the functions and then make the egg prefabs but it just removes the functions I subscribed to.
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    If these eggs should have different abilities when they're used, that should just be designed/coded into the egg itself.

    Yes you can't assign references from asset level objects to scene stuff, but there's no need to. That can just be subscribed on the fly through normal C# code.
     
  3. Myst1cS04p

    Myst1cS04p

    Joined:
    Oct 10, 2022
    Posts:
    16
    The eggs have all got the same attributes I dont wanna have to create different scripts for each individual egg. Isnt there a way to make it just execute its assigned function whenever it lands?
     
  4. SeerSucker69

    SeerSucker69

    Joined:
    Mar 6, 2021
    Posts:
    65
    Code (CSharp):
    1.         switch EggType
    2.         {
    3.         case 1:
    4.             // Show sprite for Egg Type 1
    5.            // explosion effect  Egg Type 1
    6.            // play sound Egg Type 1
    7.            // Subtract hit points Egg Type 1
    8.            // Push back player Egg Type 1
    9.             break;
    10.         case 2:
    11.             // Show sprite for Egg Type 2
    12.            // explosion effect  Egg Type 2
    13.            // play sound Egg Type 2
    14.            // Subtract hit points Egg Type 2
    15.            // Push back player Egg Type 2
    16.             break;
    17.         case 3:
    18.             // Show sprite for Egg Type 3
    19.            // explosion effect  Egg Type 3
    20.            // play sound Egg Type 3
    21.            // Subtract hit points Egg Type 3
    22.            // Push back player Egg Type 3
    23.             break;
    24.         case 4:
    25.             // Show sprite for Egg Type 4
    26.            // explosion effect  Egg Type 4
    27.            // play sound Egg Type 4
    28.            // Subtract hit points Egg Type 4
    29.            // Push back player Egg Type 4
    30.             break;
    31.         default:
    32.             Debug.log("Unkown Egg Type Passed to Egg effect function!")
    33.         }
     
    Myst1cS04p likes this.
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Then just subscribe that functionality on the fly. If you don't know how, read up on how to work with C# delegates.
     
  6. ijmmai

    ijmmai

    Joined:
    Jun 9, 2023
    Posts:
    188
    You wouldn't have to create a different script for every egg, just a different script/function for every type of egg. ScriptableObjects seem perfect for this scenario.
     
    Bunny83 likes this.
  7. Myst1cS04p

    Myst1cS04p

    Joined:
    Oct 10, 2022
    Posts:
    16
    OH TYSM THATS INGENIOUS!
     
    SeerSucker69 likes this.
  8. ijmmai

    ijmmai

    Joined:
    Jun 9, 2023
    Posts:
    188
    It will work, but is it your best option. There is a lot of repetition in your code, and if you want to add a new type of egg later on you will have to add more cases as well.

    With ScriptableObjects, when used the right way, you set it up once, and can easily add or remove types later on. All you have to change would be the code where types are defined, the rest of the code will just follow along.

    A deep dive into ScriptableObjects is worth your time.
     
    Chubzdoomer likes this.
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    God please don't endorse giant switch statements. That's a terrible anti-pattern and will not scale at all.

    The data of your eggs can just be scriptable objects. Then these can be represented in game with prefabs, drawing from a scriptable object data source. Any use abilities can be composed onto these prefabs via components, probably expressing a common interface that defines the Use function.

    A more advanced approach would be to use SerializeReference and define this into the scriptable object themselves.

    "I don't want to write different scripts" is never a valid excuse. This is game dev. You will need to write tons of different scripts. Once you understand the power of polymorphism you will be writing all the more separate scripts.
     
    mopthrow, MaskedMouse, ijmmai and 4 others like this.
  10. SeerSucker69

    SeerSucker69

    Joined:
    Mar 6, 2021
    Posts:
    65
    Heh, Switch statements have their uses, and OOP is bad.

    No need to crack an egg with a sledgehammer.
    Adding an egg is a cut and paste, 5 second job, pretty scaleable. He wants to write games and see his eggs explode, not do a five year course in OOP theory...

    He can work his way up to bigger and better things in the future, if he wants and more importantly if he learns that a case statement as above really sabotaged his game/scalability/life. I haven't got there yet....
     
    Stardog likes this.
  11. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    It's not that much of a sledgehammer. It's just standard clean coding. All I'm suggesting is a few classes, and an interface.

    Lets not promote bad habits here. You can't go 'big' unless you learn to code small properly.
     
  12. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    I would have a parent class of
    Egg.cs
    , then each child class be
    ExplosiveEgg.cs
    , etc...

    Have Egg.cs make a master list of each child class(assuming you want random at some point), so you can easily call an index from
    Random.Range
    .

    If each child were to have any duplicate code/calls, it would be in a public function within Egg.cs, so Awake/Start/Update(within child) just needs said function/call pasted in it.

    Then make a generic call from Egg.cs to Instantiate said child prefab, something like
    Egg current = Egg.GetEgg(explosive, damage, lifeTime);
    ..

    But that's just me, I'm sure there's better ways. :)
     
  13. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,057
    I've been there, multiple times. There's the possibility that you expand so much that it isn't maintainable anymore.
    There are cases where it can be avoided easily by using scriptable objects and interfaces.
    it doesn't take a 5 year course to learn that. But having experience does make things easier to come up with something. It's not rocket science though, just carefully thinking it through.

    We don't know the game that the TS is making. Sure it may just be 3-4 cases. But what if it were to be 20 over a year?
    Eggs that blow up, heal, burn, splash water, deal nature damage, bleed, make you move faster, go upside down, anti-gravity, teleport... whatever you can think of.
    You don't want 1 method to handle all those cases. It would create an enormous dependency freak script. An interface can be used to apply any effect that has been programmed.

    plain classes, interfaces and scriptable objects aren't scary things.
    If you want to keep things small because it is a game you won't expand on? Sure you can go dirty if you want.
    If you want to keep expanding, you'll have to make code scalable, maintainable and reusable.
    It'll save you a lot of headaches later on because code can outgrow its purpose very quickly.
    Code is a lot of iteration. You can't iterate quickly if you constantly have to go back to all switch cases you've made to only notice you've forgotten to add the Flying Pet Egg. Now it can't fly and reaches a default which does nothing.
    You might notice it during testing but if your game is big and forgot to test it. Well your players will notice.
    Perhaps through errors breaking other systems. Just because Egg X hasn't been programmed in.

    Here is an example of what can be used. It's a bit rough though as I've made it up in a short amount of time.
    The
    Use
    method is set to apply the effects, but it may as well be instantiating an egg into the world which can be thrown at a target where a collision component on the egg prefab will apply effects.
    Quite flexible to change easily when you need it to.
    If you want an additional effect, just create a new effect that inherits from
    IEffect
    .
    Assign it in the Egg asset and you've got yourself a new egg.
    You can even mix several effects if you want to give it some more oomph.
    You can make it more complex in layers by doing little changes.

    Code (CSharp):
    1. public class Egg : ScriptableObject, IUsable
    2. {
    3.     [field: SerializeField]
    4.     public Sprite EggSprite { get; private set; }
    5.  
    6.     [field: SerializeReference]
    7.     public List<IEffect> Effects { get; private set; }
    8.  
    9.     public void Use(ITargetable target)
    10.     {
    11.         foreach(var effect in Effects)
    12.         {
    13.             effect.Apply(target);
    14.         }
    15.     }
    16. }
    17.  
    18. public interface IUsable
    19. {
    20.     void Use(ITargetable target);
    21. }
    22.  
    23. public interface ITargetable
    24. {
    25.     GameObject gameObject { get; }
    26.     Transform transform { get; }
    27. }
    28.  
    29. public interface IEffect
    30. {
    31.     void Apply(ITargetable target);
    32. }
    33.  
    34. public class Targetable : MonoBehaviour, ITargetable
    35. { }
    36.  
    37. public static class GameObjectExtensions
    38. {
    39.     public static void InstantiateAt(this GameObject prefab, ITargetable target)
    40.     {
    41.         var position = target.transform.position;
    42.         Object.Instantiate(prefab, position, Quaternion.identity);
    43.     }
    44. }
    45.  
    46. public static class TargetableExtensions
    47. {
    48.     public static bool TryGetComponent<T>(this ITargetable target, out T component)
    49.     {
    50.         return target.gameObject.TryGetComponent(out component);
    51.     }
    52. }
    53.  
    54. public class HealEffect : IEffect
    55. {
    56.     public float Value;
    57.  
    58.     public void Apply(ITargetable target)
    59.     {
    60.         if(target.TryGetComponent<Health>(out var healthComponent))
    61.             healthComponent.Heal(Value);
    62.     }
    63. }
    64.  
    65. public class HealOverTimeEffect : IEffect
    66. {
    67.     public float Value;
    68.  
    69.     public void Apply(ITargetable target)
    70.     {
    71.         if(target.TryGetComponent<Health>(out var healthComponent))
    72.             healthComponent.HealOverTime(Value);
    73.     }
    74. }
    75.  
    76. public class ExplosionEffect : IEffect
    77. {
    78.     [field: SerializeField]
    79.     public GameObject ExplosionVFXEffectPrefab { get; private set; }
    80.  
    81.     [field: SerializeField]
    82.     public float Value {get; private set; }
    83.  
    84.     public void Apply(ITargetable target)
    85.     {
    86.         if(target.TryGetComponent<Health>(out var healthComponent))
    87.             healthComponent.Damage(Value);
    88.      
    89.         if(ExplosionVFXEffectPrefab != null)
    90.             ExplosionVFXEffectPrefab.InstantiateAt(target);
    91.     }
    92. }
     
  14. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    And if they are, go towards the fear and work with it, do tutorials, iterate until you actually understand what they mean, what they do, what their common purposes and uses are, etc.

    These things are all just tools of programming.

    It would be like a carpenter being scared of using a saw because he heard it can cut fingers off. No, go learn about the saw, learn about how to use it correctly, then begin using it more and more and more.
     
    MaskedMouse likes this.
  15. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    This method should work just fine as long as the subscriber and subscribee are both in the same prefab. If you need two separate objects to be in one prefab, you can simply create an empty game object and make your two objects children of that empty game object.

    Reading everyone's posts on this thread you can see that there are many different options for how to implement this, though.