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

[SOLVED] Best way to script Elemental damage (fire > earth > water).

Discussion in 'Scripting' started by Deleted User, Aug 16, 2014.

  1. Deleted User

    Deleted User

    Guest

    I'd like fire attacks to do more damage vs earth and less vs water.
    Same for earth and water.
    Is storing the element as a string or integer and using if/then/else the only way or is there a better way?
     
  2. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    Code (csharp):
    1. public enum Element { Fire, Earth, Water }
    2.  
    3. public float CalculateDamage(Element source, Element target)
    4. {
    5.      if (source == Element.Fire)
    6.      {
    7.           if (target == Element.Fire)
    8.                return 0.2f;
    9.           else if (target == Element.Earth)
    10.                return 0.3f;
    11.           else if (target == Element.Water)
    12.                return 0.1f;
    13.      }
    14.      else if (source == Element.Earth)
    15.      {
    16.           // same
    17.      }
    18.      else if (source == Element.Water)
    19.      {
    20.           // same
    21.      }
    22. }
     
    Deleted User likes this.
  3. Deleted User

    Deleted User

    Guest

    Thanks a lot!
     
  4. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    You could also make a 2D array such as float[SrcElement, TargetElement] and put in the corresponding values. I'm pretty sure it would be a little faster if you're using it extensively, and at the very least you get more elegant code... (but i guess this is subjective :p)
     
  5. Deleted User

    Deleted User

    Guest

    Yea I'll be using it a lot. But what would I do with my 2D array?
     
  6. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    You could do something like this :

    Code (csharp):
    1.  
    2. float[,] elementToElementDamageRatios = new float[3,3];
    3.  
    4. void Start()
    5. {
    6.      //Init Fire to X
    7.      elementToElementDamageRatios[(int)Element.Fire, (int)Element.Fire] = 0.2f;
    8.      elementToElementDamageRatios[(int)Element.Fire, (int)Element.Earth] = 0.3f;
    9.      elementToElementDamageRatios[(int)Element.Fire, (int)Element.Water] = 0.1f;
    10.  
    11.      //Init Earth to X
    12.      elementToElementDamageRatios[(int)Element.Earth, (int)Element.Fire] = __;
    13.      elementToElementDamageRatios[(int)Element.Earth, (int)Element.Earth] = __;
    14.      elementToElementDamageRatios[(int)Element.Earth, (int)Element.Water] = __;
    15.  
    16.      //Init Water to X
    17.      elementToElementDamageRatios[(int)Element.Water, (int)Element.Fire] = __;
    18.      elementToElementDamageRatios[(int)Element.Water, (int)Element.Earth] = __;
    19.      elementToElementDamageRatios[(int)Element.Water, (int)Element.Water] = __;
    20. }
    21.  
    22. public float CalculateDamage(Element source, Element target)
    23. {
    24.      return (elementToElementDamageRatios[(int)source, (int)target]);
    25. }
    26.  
    You could make the variable public and have a nice editor script to show the names of the elements if you're intending to work a lot on thoose values but that's not mandatory. At least the lookup of the value is as fast as it gets and you can have millions of units happily firing at each other without lag (at least coming from this :p)
     
    er1227 and (deleted member) like this.
  7. Deleted User

    Deleted User

    Guest

    That's really smart. Thanks!
     
  8. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    681
    Hate to "zombie" a thread, but I really think this will help with an issue that I'm currently having. I just have to ask one more question @_Daniel_ and @_met44 . Will this still work if the "target" has multiple elements, and those elements have different weaknesses/resistances/immunity to the "source" element?

    Here's an excerpt from my thread on this:
    So to put a face on this. For example:
    -Creature attacks player with Toxic.
    -Toxic's elements type = POISON.
    -Player's element types = GROUND & ROCK.
    -Both GROUND and ROCK take half damage from POISON element.
    -This would result in the player taking 25% of the total damage.

    Then Type1 = 0.5 (POISON vs GROUND) ...and... Type2 = 0.5 (POISON vs ROCK) which makes the attacks total effectiveness (which is what I use the chart to solve in the first place) 0.25 (because 0.5 * 0.5 = 0.25).


    To solve: Modifier = STAB * (Type1 * Type2) * Critcal * other * Random.Range(0.85, 1);


    So far I've been trying to use an Excel spreadsheet, but if I can use @_met44 's method it would be awesome.
     
  9. Deleted User

    Deleted User

    Guest

    @DRRosen3, you could make the enum an Array and then use a for each element loop and calculate the value that way.
    I'm not home right now so I can't post any code but I think it'd work.
     
  10. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    681
    Okay. I think I know what you're saying. It'd pretty much look exactly the same as the example @_met44 gave right? Except the return would be different because my players/creatures have two types.

    1. public float CalculateDamage(Element source, Element target1, Element target2)
    2. {
    3. return (elementToElementDamageRatios[(int)source, (int)target1, (int)target2]);
    4. }
    Is that right?

    EDIT - ACTUALLY...for me I think it'd be more like this:
    I have to solve: Modifier = STAB * (Type1 * Type2) * Critcal * other * Random.Range(0.85, 1);

    1. public float CalculateType1(Element source, Element target1)
    2. {
    3. return (elementToElementDamageRatios[(int)source, (int)target1]);
    4. }
    5. public float CalculateType2(Element source, Element target2);
    6. {
    7. return (elementToElementDamageRations[(int)source, (int)target2]);
    8. }
    And then set the Type1 variable to be equal to CalculateType1 and the Type2 variable to be equal to CalculateType2.
     
    Last edited: Oct 20, 2014
  11. Deleted User

    Deleted User

    Guest

    Not exactly.

    First off, I created a public enum Element to let the user select the object's element.
    For you, you could make it like this:
    public enum Element = {
    Fire,
    Ice,
    Earth
    }
    public Element[] element;
    private float damageFactor;

    Then you'd have a loop

    foreach (Element e in Element) {
    damageFactor += (elementToElementDamageRatios[(int)source, (int)target]);
    }
     
  12. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    681
    Hmmm. Keep in I have two elements on each object.
     
  13. Deleted User

    Deleted User

    Guest

    Well that's why you use an array and a loop.
    when you do "public Element[] element;" you can specify how many and what elements the object will have. 1, 2, 3 or 10.
    Then your loop will loop through every element, whether it's once or ten times, if the object has 10 different elements.
     
  14. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    That's almost it except he need to multiply instead of adding.

    But if you have only 2 elements forever, go simple with just using the same method twice and multiply the result, no need to create other methods, you just pass in the right parameters :).

    so you simply end up with:
    Code (CSharp):
    1. float type1 = CalculateDamage(elem, target1);
    2. float type2 = CalculateDamage(elem, target2);
    3. Modifier = STAB * (type1 * type2 ) * Critcal * other * Random.Range(0.85, 1);
     
  15. cmcpasserby

    cmcpasserby

    Joined:
    Jul 18, 2014
    Posts:
    315
    more complex but more flexiable way is to just make your damage and armour there own data types with classes, which contain all the information about why they are strong and weak against, with some built in methods for comparing armor types to damage types
     
  16. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    681
    That's exactly what I have done. I'm just working on, right now, how to access the arguments for the method. The CalculateDamage method is written inside my CalculateTotalDamage script, but the 2D array is in its own script.

    EDIT - Sorry, I just realized what I said. The CalculateDamage(); method is written in the same script as the 2D array. I then call the method in my CalculateTotalDamage script, but, the information needed for the arguements that CalculateDamage(); needs is in a TOTALLY DIFFERENT script.

    Well, first, they're element types and not armor, but I get what you mean. However, I think it's a bit extreme, as I have 19 different element types, so that's a LOT of scrips. Granted when compiled the scripts are very tiny, I still think that's a bit excessive.
     
    Last edited: Oct 21, 2014
  17. Senshi

    Senshi

    Joined:
    Oct 3, 2010
    Posts:
    556
    Just to add another alternative to this discussion for future reference, I would personally likely opt for the following code. It's a bit more readable imho, though at a small runtime cost in relation to a 2D array.

    Code (csharp):
    1. enum Element {Fire, Earth, Water, Air};
    2.  
    3. Dictionary<Element, Element[]> doubleDamage = new Dictionary<Element, Element[]>();
    4. Dictionary<Element, Element[]> halfDamage = new Dictionary<Element, Element[]>();
    5.  
    6. doubleDamage.Add(Element.Fire, new Element[]{Element.Earth});
    7. doubleDamage.Add(Element.Air, new Element[]{Element.Fire, Element.Earth});
    8.  
    9. halfDamage.Add(Element.Water, new Element[]{Element.Earth, Element.Air});
    10.  
    11. void CalculateDamage(Element sourceElement, Element[] targetElements){
    12.     float damageMultiplier = 1f;
    13.  
    14.     foreach(Element elem in targetElements){
    15.         if(doubleDamage[sourceElement].Contains(elem)){
    16.             damageMultiplier *= 2;
    17.         }
    18.         else if(halfDamage[sourceElement].Contains(elem)){
    19.             damageMultiplier *= 0.5f;
    20.         }
    21.     }
    22.  
    23.     return damageMultiplier; //+ STAB + ....
    24. }
    This also means you don't need to store every possible combination; just the ones you actually need/ use.

    Just my $0.02. =)
     
    er1227 and Der_Absender like this.
  18. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    Easy, use a reference to that component, either by making it a public parameter or using GetComponent on the object that holds it.

    You could also use the singleton pattern if the component holding the data is doing it for your whole game.