My game started to have crashes shortly after trying to implement an XP system. A popup for the crash pops up behind the editor, so I can do nothing with it- and I have to force Unity to shut down.
I know, “don’t use PlayerPrefs”, however, this is a very simple single player game- it shouldn’t require anything else.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class XPScript : MonoBehaviour
{
public SimpleHealthBar XPBar;
// Start is called before the first frame update
void Start()
{
EnemyScript.onDeath += OnXP;
PlayerPrefs.GetFloat("XP", 0);
XPBar.UpdateBar(PlayerPrefs.GetFloat("XP"), PlayerPrefs.GetInt("neededXP", 100));
}
void OnXPGain()
{
while (PlayerPrefs.GetFloat("XP") >= PlayerPrefs.GetFloat("XP"))
{
PlayerPrefs.SetInt("level", PlayerPrefs.GetInt("level") + 1);
PlayerPrefs.SetInt("skillPoints", PlayerPrefs.GetInt("skillPoints") + 1);
PlayerPrefs.SetInt("XP", 0);
PlayerPrefs.SetInt("neededXP", PlayerPrefs.GetInt("neededXP") * 2);
}
}
public void OnXP(float xp)
{
PlayerPrefs.SetFloat("xp", PlayerPrefs.GetFloat("XP") + xp);
OnXPGain();
}
}
The Enemy script sends the player the necessary signal upon the enemy’s death.
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("PlayerAttack") && hurt == false)
{
health -= PlayerPrefs.GetFloat("Damage");
if (health <= 0)
{
speed = 0;
Healthbar.gameObject.SetActive(false);
Enemy.GetComponent<Animator>().SetBool("dead", true);
StartCoroutine(WhenDead());
onDeath(XP);
}
I am very certain onDeath is causing this issue, as I moved it to another spot during a test and Unity crashed then as well.
(In this code, hurt is a signal for a coroutine which happens if the enemy is not dead, to prevent further attacks during a brief animation.)
This is your problem:
Unity will lock up 100% of the time EVERY millisecond your scripting code is running.
EDIT: why are you comparing two identical things?! The above statement will NEVER return false unless somehow PlayerPrefs.GetFloat() returns different values for “XP”
Nothing will render, no input will be processed, no Debug.Log() will come out, no GameObjects or transforms will appear to update.
Absolutely NOTHING will happen… until your code either:
As long as your code is looping, Unity isn’t going to do even a single frame of change. Nothing.
No exceptions.
“Yield early, yield often, yield like your game depends on it… it does!” - Kurt Dekker
Dang it… thanks. I knew it was something stupid. It was supposed to check if XP >= neededXP.
Fair enough… you might want to clean that awful code block up however. You shouldn’t sprinkle around string literals like so much spilled salt and pepper.
You’re being mean to yourself and it has already hurt you!
Here’s an example of simple persistent loading/saving values using PlayerPrefs:
Useful for a relatively small number of simple values.
1 Like
Yup, first time with an XP system, so it would be a bit messy… I’ll clean it up.
Another little issue I noticed was the while loop doesn’t stop. I replaced it with an if statement for a very temporary fix.
I’m going to say just DELETE all the above code related to that loop.
My original observation of it being a mess has just been upgraded to “it’s a disaster.”
Case in point:
-
You set “XP” back to ZERO in the loop, yet expect more than one iteration.
-
You use GetFloat(“XP”) and GetInt(“XP”) interchangeably. Same with the “neededXP” PlayerPref. That’s almost certainly going to cause problems. Floats are not ints and they never will be.
Wrap those persistent values up in something that lets you use them just like variables, as shown in that link:
Got it- finally. XP, by the way, is the size of the XP bar’s colored sprite, hence why it drops to zero after the XP is gained. As such, I just killed the loop so the player would have to start fresh on each level up.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class XPScript : MonoBehaviour
{
public SimpleHealthBar XPBar;
// Start is called before the first frame update
void Start()
{
EnemyScript.onDeath += OnXP;
XPBar.UpdateBar(PlayerPrefs.GetFloat("XP", 0), PlayerPrefs.GetFloat("neededXP", 100));
}
void OnXPGain()
{
if (PlayerPrefs.GetFloat("XP") >= PlayerPrefs.GetFloat("neededXP"))
{
PlayerPrefs.SetInt("level", PlayerPrefs.GetInt("level") + 1);
PlayerPrefs.SetInt("skillPoints", PlayerPrefs.GetInt("skillPoints") + 1);
PlayerPrefs.SetFloat("XP", 0);
PlayerPrefs.SetFloat("neededXP", PlayerPrefs.GetFloat("neededXP", 100) * 2);
}
XPBar.UpdateBar(PlayerPrefs.GetFloat("XP"), PlayerPrefs.GetFloat("neededXP", 100));
}
public void OnXP(float xp)
{
PlayerPrefs.SetFloat("XP", PlayerPrefs.GetFloat("XP", 0) + xp);
OnXPGain();
}
}
Careful, people get mad when you take away something they earned, in this case XP beyond what was needed!
The correct level-up logic you seek is basically:
if (neededXP > 0 & XPSpacing >= 0)
{
while( XP >= neededXP)
{
XP -= neededXP;
Level++;
skillPoints++;
Debug.Log( "You reached level " + Level + "!!!");
neededXP += XPSpacing; // each level requires linearly more gap
}
}
All of those identifiers used above should be hidden behind properties as shown in the script above.
NOTE the check to ensure neededXP is positive, otherwise it would be trivial to lock up the above loop with bad data.
1 Like
That is what I was trying to go for exactly. I’ll keep trying…
It finally works as intended.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class XPScript : MonoBehaviour
{
public SimpleHealthBar XPBar;
public TMP_Text LevelText;
// Start is called before the first frame update
void Start()
{
EnemyScript.onDeath += OnXP;
XPBar.UpdateBar(PlayerPrefs.GetFloat("XP", 0), PlayerPrefs.GetFloat("neededXP", 100));
LevelText.text = PlayerPrefs.GetInt("level", 0).ToString();
}
void OnXPGain()
{
while (PlayerPrefs.GetFloat("XP") >= PlayerPrefs.GetFloat("neededXP", 100))
{
PlayerPrefs.SetInt("level", PlayerPrefs.GetInt("level") + 1);
PlayerPrefs.SetInt("skillPoints", PlayerPrefs.GetInt("skillPoints") + 1);
PlayerPrefs.SetFloat("XP", PlayerPrefs.GetFloat("XP") - PlayerPrefs.GetFloat("neededXP"));
PlayerPrefs.SetFloat("neededXP", PlayerPrefs.GetFloat("neededXP", 100) * 5);
}
XPBar.UpdateBar(PlayerPrefs.GetFloat("XP"), PlayerPrefs.GetFloat("neededXP", 100));
LevelText.text = PlayerPrefs.GetInt("level").ToString();
}
public void OnXP(float xp)
{
PlayerPrefs.SetFloat("XP", PlayerPrefs.GetFloat("XP", 0) + xp);
OnXPGain();
}
}
1 Like