Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Need some help with D&D style leveling formula/execution.

Discussion in 'Scripting' started by topherbwell, Jul 13, 2015.

  1. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    I am making an experience/ level up system and am trying to replicate Dungeons and Dragons' leveling curve.

    Currently, I am leveling correctly from level 1 to 2 when I hit 1000 experience, but am then continuing to level again at every 1000 exp, instead of following the expected curve.

    If I disable the line that sets exp back to 0 on levelup, then the character continues to simply level up every frame once 1000 exp is reached.

    Here is the level code I am working on; any help is appreciated:
    Code (csharp):
    1.  
    2. usingUnityEngine;
    3. usingSystem.Collections;
    4.  
    5. public class LevelSystem : MonoBehaviour
    6. {
    7.       //reference to the player script.
    8.       player hero;
    9.  
    10.      public int level;
    11.      public int exp;
    12.      //reference to the level up formula that I am using currently.
    13.      int N;
    14.  
    15.      void Awake()
    16.      {
    17.           hero = player.hero;
    18.           /*the formula I am using, which is working correctly for the first level
    19.           *up, but then  leveling every 1000 xp.*/
    20.           N = 500*(level^2)-(500*level);
    21.      }
    22.  
    23.      void Update ()
    24.      {
    25.           LevelUp ();
    26.      }
    27.  
    28.      void LevelUp()
    29.      {
    30.           if (exp >= N)
    31.           {
    32.                Level Effect();
    33.           }
    34.      }
    35.  
    36.      void LevelEffect()
    37.      {
    38.           /* the following two lines set the exp back to 0, and should level
    39.           *the level by 1. In theory, I should not need that first line, but
    40.           *currently without it the player levels every frame once
    41.           *1000 xp is reached*/
    42.  
    43.           exp = 0;
    44.           level = level + 1;
    45.  
    46.           /*ignore the following; these are just tests for the "what happens"
    47.           *when you level up.*/
    48.           hero.maxHealth = hero.maxHealth * 1.08f;
    49.           hero.health = hero.maxHealth;
    50.           hero.damage = hero.damage * 1.1f;
    51.      }
    52. }
    53.  
     
  2. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    492
    You have your experience formula a little wrong.
    XP needed to reach level X is:
    XP = 500 * (X) * (X -1)

    So, to reach say level 14, the formula would look like this:
    XP = 500 * (14) * (13)

    If I where you, I would have a variable that stores the Hero's total xp gained, one that stores the total xp needed to reach the next level (use the formula above to find this), one that stores the amount of xp needed to reach the level the hero is already at (use above formula), and lastly a variable to store the amount of xp needed to level (aka, the difference between current level xp, and next level xp).
     
    Last edited: Jul 13, 2015
    topherbwell likes this.
  3. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    812
    The main problem is that your formula is only executed once - at initialization time. You need to recalculate the value needed for the next level each time the player levels up.

    I think this is a misunderstanding about how code execution works in C#. There are languages out there that behave by accepting "facts" that are considered and reconsidered at each query, these are called logical paradigm languages.

    C#, however, is an imperative paradigm language. The expression:
    Code (csharp):
    1. N = 500*(level^2)-(500*level);
    is evaluated and executed only once - it does not create a formula that is recalculated every time it's constituents are changed.
     
    topherbwell likes this.
  4. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    Well that makes sense. Since Awake/Start are only called once, the formula would never update.

    I placed this in Update() instead to see what would happen but a few things occur.
    1) 1000 exp still causes level to update every frame
    2) the player levels in a strange pattern. Instead of leveling in order of 1,2,3,4, the player is leveling 1, 4, 5, 8, 9,
    3) the player still levels every 1000 exp.
     
  5. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    I just re-read what you wrote and realized i missed part of the point.
    So now i'm just confused.
     
  6. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    812
    I think you are getting the important part:
    The behavior you just described doesn't make much sense to me. I can tell the code in your OP was either: not compiling when you posted it, or re-written instead of pasted in from the IDE. Are you willing to post you code as it is now?
     
    topherbwell likes this.
  7. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    I've made a few changes along the way trying to sort this, but currently here is where I am.

    The current code posted below causes the player to level from level one to 4 on initialization, and then the player levels in a pattern of 1,4,5,8,9,12...

    I also made the Next Level (N) int public so that I can view it in the inspector. The value never updates from 1000. Due to this, if you disable the line that sets exp to 0, the player will again continue to level every frame once 1000 exp is reached.
    Code (csharp):
    1.  
    2. usingUnityEngine;
    3. usingSystem.Collections;
    4.  
    5. publicclassLevelSystem : MonoBehaviour
    6. {
    7.      player hero;
    8.      public intl evel;
    9.      public int exp;
    10.      public int N;
    11.  
    12.      void Awake()
    13.      {
    14.           hero = player.hero;
    15.      }
    16.  
    17.      void Update ()
    18.      {
    19.           LevelUp ();
    20.           N = 500*(level^2)-(500*level);
    21.      }
    22.  
    23.      void LevelUp()
    24.      {
    25.           if (exp >= N)
    26.           {
    27.                LevelEffect();
    28.           }
    29.      }
    30.  
    31.      void LevelEffect()
    32.      {
    33.           exp = 0;
    34.           level = level + 1;
    35.           hero.maxHealth = hero.maxHealth * 1.08f;
    36.           hero.health = hero.maxHealth;
    37.           hero.damage = hero.damage * 1.1f;
    38.      }
    39. }
    40.  
     
  8. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    I have run the numbers, and your formula and mine yield the exact same need exp to reach any given level. I tried what you suggested but in the end am running into the same issues, which are based around formulas not updating as eisenpony pointed out in his first post.
     
  9. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,112
    Why not recalculate N in LevelEffect? (and once in start so i t has an initial value)
     
    topherbwell likes this.
  10. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    This is still returning the strange leveling pattern that I mention above, although having the formula in start does prevent the immediate leveling from 1 - 4 on initialization. The N value never updates from 1000.

    Going from level 1, 4,5,8,9,12...
     
  11. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    492
    Topher, you will want to create a separate method for handling leveling.
    I would suggest that you check for leveling only when your hero gains xp, not every frame.
    So create an AddXP method, and at the end of the method check to see if the character needs to level.

    Another idea I have, is to create a small Level Script that holds the data that all levels have in common.
    This is useful, because then you could create a List of Levels on startup, each containing things such as the xp needed to reach that level, and the level value of the level.

    Then all you would need to do is create a for loop that adds the correct number of levels to your List of levels, and calculates to XP needed to reach the level.
     
    topherbwell likes this.
  12. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    Here is what I don't understand.

    If I use the following, then "level" updates and the amount of exp needed to update increases appropriately.
    Code (csharp):
    1.  
    2.      void Level Up()
    3.      {
    4.           if (exp >= level* 1000)
    5.           {
    6.                LevelEffect();
    7.           }
    8.      }
    9.  
    ...But if I use the following, I still get the weird level progression (1, 4, 5, 8, 9, 12...) and the amount needed to level remains 1000. Although what TonanBora is suggesting I am confident could work, I really think there has to be a simpler solution.
    Code (csharp):
    1.  
    2.      void LevelUp()
    3.      {
    4.           if (exp >= 500*(level^2)-(500*level))
    5.           {
    6.                LevelEffect();
    7.           }
    8.      }
    9.  
     
  13. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    492
    In Update, wrap the call to the LevelUp method in the following if statement:
    Code (CSharp):
    1. If(Input.GetMouseButton(1)){
    2.     Levelup();
    3. }
    This will give you more control over the whole Leveling testing.
    When in play mode, just right click to call the Levelup method.
     
  14. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    812
    Your code should not behave as it is. I can only assume you have another script touching this script's public fields - probably to assist in your testing. This is the logic behind what some others are suggesting - you want to segment your code a little bit to make sure there is not unexpected interaction.

    Try making your "N" field private temporarily. This will allow you to ensure no other script is interfering with the calculation.

    As for the weird level progression, it could be another side effect of interfering scripts, or it could be that you are simply leveling up too fast to see the numbers change.
     
  15. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    Originally the N field was private. I only opened it to public so that I could see the value in the inspector. I did try this again however, and with the same results. I can also confirm that the LevelEffect is running only one time, as the debug.log message displays only once per kill.

    The Debug.Log message I am referring to is in the enemy script, and is part of the function that is called when the enemy is killed. It would seem that if the player were receiving the 1000 xp multiple times, the message would also play multiple times.
    Code (csharp):
    1.  
    2.      void EnemyDeath()
    3.      {
    4.           Debug.Log ("killed enemy");
    5.           Destroy(gameObject, 0.25f);
    6.           playerLevel.exp = playerLevel.exp + 1000;
    7.           this.enabled = false;
    8.      }
    9.  
     
  16. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    Appreciate your help, but as the LevelUp function still relies on another variable(the experience), this doesn't actually do anything. I can use the same concept with the LevelEffect function (and changed to GetMouseButtonUp), but this bypasses the exact mechanic that I am trying to troubleshoot.
     
  17. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    492
    Yes, GetMouseButtonUp is the better way to go.
    As eisen said, you might be leveling to fast to actually see it, which is why you should wrap what ever is causing the leveling in the If statement , so that it only increments when you tell it too.
     
  18. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    I've been banging my head on this for almost a week. I've watched every tutorial on the internet that I can find regarding exp/leveling systems and I have not found a single one that actually implements a leveling curve rather than a simple exponential increase or something like +level per x experience.

    I can't believe that with something as ubiquitous to video games as exp/ level I cannot find a single resource that clearly explains any working version of this mechanic.
     
  19. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    492
    Also, when is EnemyDeath called?
    Just out of curiosity.
     
  20. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    You guys are definitely on the right track with the multiple levels.

    I added a debug.log in LevelEffect() and when I kill an enemy, the "killed enemy" message from the enemy script plays once, but then the "leveled a level" message plays three times after killing an enemy. The "leveled a level" message plays once after killing another enemy, then three times again after the third enemy, and so on.
    Code (csharp):
    1.  
    2.      void LevelEffect()
    3.      {
    4.           exp = 0;
    5.           level = level + 1;
    6. //the following line displays three times after first kill, then once after second kill, etc.
    7.           Debug.Log ("leveled a level");
    8.           hero.maxHealth = hero.maxHealth * 1.08f;
    9.           hero.health = hero.maxHealth;
    10.           hero.damage = hero.damage * 1.1f;
    11.      }
    12.  

    Here is the relevent code from the enemy script regarding death:
    Code (csharp):
    1.  
    2.      voidUpdate()
    3.      {
    4.           //disregard the following line; just for the gui
    5.           healthbar.fillAmount = (float)health / (float)maxHealth;
    6.  
    7.           if (health > 0)
    8.           {
    9.                Attack ();
    10.                FollowPlayer ();
    11.           }
    12. //in update, if enemy health is less than or equal to 0, call EnemyDeath()
    13.           else
    14.           {
    15.                EnemyDeath();
    16.           }
    17.      }
    18.  
    19.      void EnemyDeath()
    20.      {
    21. //the following line displays once when an enemy is killed.
    22.           Debug.Log ("killed enemy");
    23.           Destroy(gameObject, 0.25f);
    24.           playerLevel.exp = playerLevel.exp + 1000;
    25.           this.enabled = false;
    26.      }
    27.  
     
  21. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    492
    Ah, that is the problem.
    Do not check the health in Update, as it could possibly execute multiple times (as is currently the case).
    Only check the health when the enemy takes damage.

    My suggestion is to create a public TakeDamage method that takes in an Integer (representing the amount of damage taken).
    In this method, reduce the enemie's health, and check for Death.

    You can then call the enemie's TakeDamage method when the player attacks it, instead of directly reducing its health.
     
  22. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    Well, tried this. I already have a function for this in the LifeForm Script, and the damage dealt to the enemy is given in the player scripts attack function. I attempted to do what you suggested but am running into the same weird thing. Leveling three times, then once, then three times again, then once...

    I give up. Going to scrap weeks of work and go back to zero. Which is the second time i've done this in two months.

    If anyone has any resources or video tutorials that actually implement a system that works and is not using a bunch of depricated code please link.

    thank all who tried to help.
     
  23. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    492
    Well, feel free to use the following script for your leveling, it should speed some stuff if your starting from scratch all over again.
    I suggest you study it carefully, and if you have any question about it, feel free to ask!

    To use, create a C# script named "Leveling" and replace everything in it with this code. Then attach the script to a gameobject in your scene.

    Once you set the number of levels you want in the inspector, you can test it by setting the AmountOfXPTo Add to a number you want, and, while in play mode, right click to add that amount of XP to the character.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic; // Required to access the List veriable type.
    4.  
    5. /*
    6. * When using a list, like I do for levels here, keep in mind that the index of elements
    7. * within a list range from 0, to the max value - 1.
    8. * For example, if you had 100 levels, level 1 would be at index 0, and level 100 would
    9. * be at index 99.
    10. * If you wanted to access data at a particular level, you would access the element at the index
    11. * equal to the level -1.
    12. * For example, if I had 50 levels, and wanted to access level 34, I would use the index 33 to
    13. * access that level's information.
    14. */
    15.  
    16. [System.Serializable] // This is required in order to properly display the List of Levels in the inspector.
    17. public class Level {
    18.     [SerializeField]
    19.     private int _value;
    20.     [SerializeField]
    21.     private int _xpToReach;
    22.  
    23.     public Level(int value, int xpToReach) {
    24.         _value = value;
    25.         _xpToReach = xpToReach;
    26.     }
    27.  
    28.     public int Value{
    29.         get{return _value;}
    30.         set{_value = value;}
    31.     }
    32.  
    33.     public int XpToReach{
    34.         get{return _xpToReach;}
    35.         set{_xpToReach = value;}
    36.     }
    37. }
    38.  
    39.  
    40. public class Leveling : MonoBehaviour {
    41.     public int numberOfLevels;
    42.     public int amountOfXpToAdd;
    43.  
    44.     [SerializeField] // I use this to show private veriables in the Inspector
    45.     private List<Level> _levels = new List<Level>();
    46.     [SerializeField]
    47.     private int _currentLevel;
    48.     [SerializeField]
    49.     private int _currentXP;
    50.     [SerializeField]
    51.     private int _xpToLevelUp;
    52.  
    53.     void Start() {
    54.         SetupLevels();
    55.         }
    56.  
    57.     void Update() {
    58.         if (Input.GetMouseButtonUp(1)) {
    59.             AddXP(amountOfXpToAdd);
    60.         }
    61.     }
    62.  
    63.     private void SetupLevels() {
    64.         int calculatedXP;
    65.         for (int cnt = 1; cnt <= numberOfLevels; cnt++)
    66.         {
    67.             calculatedXP = 500 * (cnt) * (cnt -1);
    68.             _levels.Add(new Level(cnt, calculatedXP));
    69.         }
    70.         _currentLevel = _levels[0].Value;
    71.         _currentXP = 0;
    72.         _xpToLevelUp = _levels[1].XpToReach;
    73.     }
    74.  
    75.     private void LevelUp() {
    76.         _currentLevel += 1;
    77.         _xpToLevelUp = _levels[_currentLevel].XpToReach - _levels[_currentLevel - 1].XpToReach;
    78.  
    79.     }
    80.  
    81.  
    82.     public void AddXP(int amount)
    83.     {
    84.         int XpPool = amount;
    85.         while (XpPool > _xpToLevelUp) {
    86.             XpPool -= _xpToLevelUp;
    87.             LevelUp();
    88.         }
    89.         _currentXP += amount;
    90.  
    91.         _xpToLevelUp -= amount;
    92.  
    93.         if(_xpToLevelUp <= 0){
    94.             LevelUp();
    95.         }
    96.     }
    97. }
     
    Last edited: Jul 13, 2015
    topherbwell likes this.
  24. topherbwell

    topherbwell

    Joined:
    Jun 8, 2015
    Posts:
    180
    Thanks buddy. I think I probably just need to step away from the computer for a bit. Starting to get C# rage. I've only been coding for about two months or so and this stuff can get pretty frustrating when I get stuck on something like this, especially after putting in hundreds of hours of work trying to learn and make this work.
     
  25. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    492
    Yah, I know how that feels, I still get that every now and then! :p