Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Trying to avoid giant switch statements

Discussion in 'Scripting' started by Bluelattice5555, May 27, 2023.

  1. Bluelattice5555

    Bluelattice5555

    Joined:
    May 8, 2020
    Posts:
    34
    The context here is a Role Playing Game.

    I am mapping spell IDs (ints) to action bar slots (an array of ints). This makes it easy for when the player drags a different spell into a slot - you just change the int in that index to remember what spell is associated with that action bar slot. Passing around spells as a spell ID, just an integer, is convenient.

    What's not convenient is when I have to translate that spell ID into the actual class it's supposed to be associated with. For instance, before the spell is cast, we have to make sure it's actually ok to cast the spell. The player could be dead, could not have a target selected, could be in a cutscene, etc, but more importantly, there are special checks for each individual spell. Some spells require you to not be moving, require a certain resource type, or perhaps be within a specific range of your target. So, with Fireball.cs and IceBlast.cs and StoneCrush.cs and so on.... we end up with dozens of spell classes, each containing all of this specific data that Casting.cs will need to perform the necessary checks. CastBar.cs has a spell ID of a spell needing to be cast, so it has to translate that into GetComponent<Fireball>().IsOkToCastSpell() somehow, but for any given spell. I accomplish this with a switch ( spell_ID ) {} with a case: for each spell ID. This works, but makes for very long, very verbose switch statements.

    I would be able to deal with that, but I end up needing another switch statement once those pre-checks are done and we finally get around to trying to animate the cast bar. Now, CastBar.cs needs to know whether it's an instant cast spell (no cast bar at all), whether it's a charge-up spell (the cast bar will stop and hold when fully charged), or a regular cast bar (animates to the end, then casts the spell). So, again, we write a switch (spellID) { and a case: for every spell where we do GetComponent<Fireball>().GetCastBarType(); for Fireball, IceBlast, StoneCrush, etc...}

    And then a third time as well once the cast is finished and we are finally ready to actually create the spell object. (the cast bar is done, so cast the spell. Which spell? Spell ID. Which class is that? Well let me check my giant switch(spell_ID) statement to use the right spell class...)

    Obviously, this becomes unruly with such extravagant switch statements everywhere. I've considered reversing the direction, by getting into the spell class right off the bat and having IT call castbar.cs functions to perform its own pre-checks, and if they evaluate true, call upon the cast bar animation and just send it the cast bar type and duration, which does solve my switch() problem, but it just seems like a weird way of processing everything, you know? Having the spell itself call everything? I feel like I'm trying to build this centralized system that is the engine of my game and IT should be calling upon the data within spells, not the spells calling upon the engine, you know?

    Anyway, I hope I have explained this well enough. I am open to ideas and different ways of going about this, if possible. Thank you in advance.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    You should invest skillpoints in
    ScriptableObjects
    !! Roll against your
    INT
    ...

    All dungeonmaster jokes aside, ScriptableObjects are amazing for exactly this sort of thing like making spells or effects or really any parameterized type of authored thing.

    Not only that you can get clever with base types and / or inheritance and REALLY kick up your data definition game.

    In fact, looka these linkies!

    ScriptableObject usage in RPGs:

    https://forum.unity.com/threads/scr...tiple-units-in-your-team.925409/#post-6055289

    https://forum.unity.com/threads/cre...ssigned-in-the-inspector.946240/#post-6174205

    Usage as a shared common data container:

     
    Bunny83 likes this.
  3. Bluelattice5555

    Bluelattice5555

    Joined:
    May 8, 2020
    Posts:
    34
    I appreciate the reply. I think I'm going to need a bit more of a tailored response to my question though. In fact, looking at the second link there, that scriptable object weapon is not so different from my Fireball class, so I think I have that concept down pat. It's when you have multiple weapons, or spells in my case, that you need to somehow map a spell ID to that class (or scriptable object function).
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    The ID no longer is an ID... it IS the actual ScriptableObject

    I'm not sure how to observe otherwise that you didn't grasp the fundamental power of the SO structure in Unity. It will absolutely get you out of "big switch statements" and result in total data locality. I urge you to work through some simple SO tutorials to see what I mean.
     
    arkano22 and Bunny83 like this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    If you really need an ID (maybe for serialization or synchronisation purposes), each scriptable object can simply have an ID field. In order to look up a certain instance, you would use a dictionary that is filled at start from an array where you simply add all your scriptable object instances to. You can use the dictionary whenever you want to "convert" an ID to the actual object instance.
     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,607
    To add onto what Kurt's saying, you just need to... reference the scriptable object. There's no need for an ID and no need for any kind of mapping. The spell just becomes an object that encapsulates specific behaviour.

    And that's something to bear in mind when you find yourself being big switch statements. C# as an OOP language means you have no need for big switch statements as you can always encapsulate behaviour into objects and pass references to them around.

    In Unity with the need to reference things via the inspector, we have scriptable objects for that most of the time. And more recently we can use SerializeReference to serialize plain C# objects with polymorphism.
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    You can just go crazy too... like decompose all your spell effects into different pieces.

    For instance, a lightning bolt spell might have three spell effect pieces:

    - targeted damage to one entity

    - area damage at that entity's location

    - a lingering glow of light from the air ionization glow.

    The damage packets could be custom for each combination of types of lightning bolts: one might be a huge single blast with almost now AOE, another might just do nothing to the targeted enemy but just crackle everybody in a huge radius all around you.

    I mean you can mix and match as well as you might designing a human-driven tabletop RPG. You would only write code for each broad type of piece.

    And those pieces could also include links to "what icon does it look like" and "what effect does it play" and "What sound does it make?"

    And since these ScriptableObjects are first-class citizens in Unity, you get all the natural Unity dependency injection abilities, as Spiney calls it above, serialization / deserialization.