PlayerPref.GetInt having an outofbound error [duplicate] - c#

This question already has answers here:
What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?
(5 answers)
Closed last month.
So I'm trying to create a level system somewhat like in angry birds where after you complete one level next one is unlocked and so on.
So to get which all levels are unlocked I used playerPref.GetInt and a for loop to make these buttons interactable. But im getting an Index out of bound error for my **LevelsUnlocked **Loop I dont know why
Totally unsure why it's happening, any ideas? (Probably something stupid because I'm a bit of a noob).
public class LevelManager : MonoBehaviour
{
int LevelUnlocked;
public Button[] Buttons;
void Start()
{
LevelUnlocked = PlayerPrefs.GetInt("LevelUnlocked", 1);
for(int i = 0; i < Buttons.Length; i++)
{
Buttons[i].interactable = false;
}
for (int i = 0; i < LevelUnlocked; i++)
{
Buttons[i].interactable = true;
}
}
}

I cannot be 100% sure without seeing your exact error message but Index out of bounds means that you tried to access an array with a integer that is either too small or too large.
You should make sure that public Button[] Buttons; has more elements than LevelUnlocked
This code should fix your issue:
public class LevelManager : MonoBehaviour
{
int LevelUnlocked;
public Button[] Buttons;
void Start()
{
LevelUnlocked = PlayerPrefs.GetInt("LevelUnlocked", 1);
for(int i = 0; i < Buttons.Length; i++)
{
Buttons[i].interactable = i<LevelUnlocked;
}
}
}
Also, be aware that arrays start at 0. So in, Button[] Buttons = new Button[3]; the first button is Buttons[0], the second is at Buttons[1] and so on. Calling Buttons[3] would provide an Index out of bounds error.

Related

How to get the index of an element (an array of buttons)

I have an array of 9 strings.
I also created 9 UI buttons.
Task:
when pressing the button [0] the line [0] appears.
when button [1] is pressed, line [1] appears
and so on.
using Assembly_CSharp;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;
public class WorldMapScr : MonoBehaviour
{
public GameObject RoomMap;
public TMP_Text txtHeader;
public TMP_Text txtDescription;
public TMP_Text txtNameRoom_1;
public TMP_Text txtNameRoom_2;
public TMP_Text txtNameRoom_3;
public TMP_Text txtNameRoom_4;
public Button[] buttons;
allTxtRoomMap txtRoom = new();
private void Update()
{
for (int i = 0; i < buttons.Length; i++)
{
buttons[i].onClick.AddListener(OpenWindow);
txtHeader.text = txtRoom.headerAndDestcriptionlvl[i];
txtDescription.text = txtRoom.headerAndDestcriptionlvl[i];
txtNameRoom_1.text = txtRoom.roomLvlName[i];
txtNameRoom_2.text = txtRoom.roomLvlName[i];
txtNameRoom_3.text = txtRoom.roomLvlName[i];
txtNameRoom_4.text = txtRoom.roomLvlName[i];
break;
}
}
void OpenWindow()
{
RoomMap.SetActive(true);
}
}
I understand that the operations in the for loop don't matter because there is a "break". I sent this code only for an example, so that you understand what I want to achieve. I also want to clarify. The easiest way would be to just create a few separate methods for each button, but that's completely unprofessional in my opinion. Please tell me how this can be done with an array of buttons. Thanks for any replies.
Added:
Thank you very much for the explanation and code example. Of course, with your help, I managed to run the code, but as you rightly pointed out, because of the for loop, listening and reacting occurs many times. This significantly affected the speed. In the end I have this:
private void Update()
{
for (int i = 0; i < buttons.Length; i++)
{
int index = i;
buttons[i].onClick.AddListener(() => OpenWindow(index));
}
}
void OpenWindow(int i)
{
RoomMap.SetActive(true);
Debug.Log(i);
txtHeader.text = txtRoom.headerAndDestcriptionlvl[0, i];
txtDescription.text = txtRoom.headerAndDestcriptionlvl[1, i];
txtNameRoom_1.text = txtRoom.roomLvlName[i, 0];
txtNameRoom_2.text = txtRoom.roomLvlName[i, 1];
txtNameRoom_3.text = txtRoom.roomLvlName[i, 2];
txtNameRoom_4.text = txtRoom.roomLvlName[i, 3];
}
To be honest, I don't have any idea how I can implement the same without using "for". If you have any ideas let me know. Thank you again. I just put the listener in the Start method and it worked. But I'm still confused: did I do the right thing?
P.S:Delegation is a topic I haven't gotten to yet, but will soon!
You don't have to create separated methods for each button. Create one method with an integer parameter, pass this method to every Button and pass it's corresponding number as parameter. Then the appear the line with the m_textList[number] value where m_textList contains your text.
You can add a function with parameters to the button's listener by using a delegation. Here's a small example that shows how it works with an int, but this can be applied to any type.
void Start()
{
for (int i = 0; i < buttons.Length; i++)
{
int tempvalue = i;
buttons[i].onClick.AddListener(() => Examplefunction(tempvalue));
}
}
void Examplefunction(int i)
{
Debug.Log(i);
}
Note that i is saved in tempvalue, which is passed to the function. This is done so that it doesn't print the Length of the button array, but rather the correct index that was set at the time.

Why does the onClick.AddListener() in Unity doesn't work while trying to loop through items in a list [duplicate]

This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed 5 months ago.
I am currently trying to build a shop for my game. The inventory is a list that will be displayed on screen. As I want every item of that list to be clickable, I add a Button component as well as an onClick.Addlistener(method), but see yourself:
for (int i = 0; i < inventoryT1.Container.Count; i++)
{
GameObject obj = Instantiate(inventoryT1.Container[i].item.DefaultPrefab, Vector3.zero, Quaternion.identity, transform);
obj.GetComponent<RectTransform>().localPosition = GetPosition(i);
obj.GetComponentInChildren<TextMeshProUGUI>().text = inventoryT1.Container[i].level.ToString();
obj.GetComponent<Button>().onClick.AddListener(delegate { LoadPrisonerStore(inventoryT1.Container[i-1].item); });
}
public void LoadPrisonerStore(PrisonerObject _prisObj)
{
//Updating the UI
DescriptionForMenu.text = _prisObj.description;
//Activating/Deactivating the panels
ShopPrisonerContainer.SetActive(false);
PrisonerPreviewContainer.SetActive(true);
}
The inventoryT1 is a list of items and with the AddListener, I want to load the data of the item that is clicked on with the LoadPrisonerStore(PrisonerObject _prisObj) Method. The Problem that I'm having is that every Object that is created will only load data of the very last Object. Why is that and how can i fix this?
I also tried to just link the object with another Method after the AddListener() and this worked perfectly, but unfortunately I can't access the items data from it:
obj.GetComponent<Button>().onClick.AddListener(() => ReturnObject(obj));
Do you have any idea how I can fix this?
You should cache the index value before using it inside the anonymous function
int cachedIndex = i;
obj.GetComponent<Button>().onClick.AddListener(delegate { LoadPrisonerStore(inventoryT1.Container[cachedIndex -1].item); });
You can use while loop insted of for and write it something like this :
int i = 0;
while(i < inventoryT1.Container.Count)
{
int copyOfi = i;
GameObject obj = Instantiate(inventoryT1.Container[i].item.DefaultPrefab, Vector3.zero, Quaternion.identity, transform);
obj.GetComponent<RectTransform>().localPosition = GetPosition(i);
obj.GetComponentInChildren<TextMeshProUGUI>().text = inventoryT1.Container[i].level.ToString();
***obj.GetComponent<Button>().onClick.AddListener(delegate { LoadPrisonerStore(inventoryT1.Container[copyOfi-1].item); });***
i++;
}
Hope this solves the problem.

Unity Changing material color on multiple gameobjects

I made a right click menu and i want to make objects change material color while i have my mouse on a button in that menu.
This is the code:
Color[] startCo;
public void OnPointerEnter(PointerEventData eventData)
{
GameObject[] objects = GameObject.FindGameObjectsWithTag(myMenu.selected.title);
for (int i = 0; i < startCo.Length; i++)
{
startCo[i] = objects[i].gameObject.GetComponent<MeshRenderer>().material.color;
}
foreach (GameObject obj in objects)
{
obj.gameObject.GetComponent<MeshRenderer>().material.color = Color.red;
}
}
public void OnPointerExit(PointerEventData eventData)
{
GameObject[] objects = GameObject.FindGameObjectsWithTag(myMenu.selected.title);
for (int i = 0; i < objects.Length; i++)
{
objects[i].gameObject.GetComponent<MeshRenderer>().material.color = startCo[i];
}
}
With first for loop it does not work at all, but without it, when I put my mouse on the button, it makes material colors red, but it won't change it back to orginal.
My question is, is there any better way to save original colors with using foreach?
Try this version. It basically does exactly the same thing as your version, but makes sure to initialize the color array before using it (which was probably your main issue). It also keeps a copy of the list of objects whose colors were replaced, which is important in case new objects with a matching tag are created, or existing one's deleted. Finally, it adds a few safeguards to make it more robust in case you want to use ReplaceColors() and RestoreColors() in other places, too.
GameObject[] objectsWithReplacedColors;
Color[] originalColors;
public void OnPointerEnter(PointerEventData eventData)
{
ReplaceColors(GameObject.FindGameObjectsWithTag(myMenu.selected.title), Color.red);
}
public void OnPointerExit(PointerEventData eventData)
{
RestoreColors();
}
private void ReplaceColors(GameObject[] forObjects, Color withColor)
{
if (objectsWithReplacedColors != null) // if there are already objects with replaced colors, we have to restore those first, or their original color would be lost
RestoreColors();
objectsWithReplacedColors = forObjects;
originalColors = new Color[objectsWithReplacedColors.Length];
for (int i = 0; i < objectsWithReplacedColors.Length; i++)
{
originalColors[i] = objects[i].GetComponent<MeshRenderer>().material.color;
objectsWithReplacedColors[i].GetComponent<MeshRenderer>().material.color = withColor;
}
}
private void RestoreColors()
{
if (objectsWithReplacedColors == null)
return;
for (int i = 0; i < objectsWithReplacedColors.Length; i++)
{
if (objectsWithReplacedColors[i]) // check if the objects still exists (it may have been deleted since its color was replaced)
objectsWithReplacedColors[i].GetComponent<MeshRenderer>().material.color = originalColors[i];
}
objectsWithReplacedColors = null;
originalColors = null;
}
Well my guess is you are finding objects every time these methods are called using GameObject.FindGameObjectsWithTag and i am pretty sure the order of these objects returned by FindGameObjectsWithTag is not specified so it can change ever time you call this method. This issue ends up giving you different original colors for different objects. My suggestion would be getting objectsin the Startonce and assigning colors to the array. Then use this array in OnPointerExitfunction.
Other than that your code and logic seems fine to me.

Scripts Works on one scene but error in another [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 4 years ago.
So I'm getting an error which says
Object reference not set to an instance of an object Explode.LateUpdate () (at Assets/Scripts/Explode.cs:40)
I'm not sure why I get this error as everything works fine and I have the exact same script in a different scene without the error message. I'd love any help please.
I'm exploding my player with different coloured boxes.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Explode : MonoBehaviour {
[System.Serializable]
public class ExplodeColours
{
public Color32[] Colours;
}
public static bool explode, explodeOnce;
public ParticleSystem Explodes;
private ParticleSystem explosionSystem;
public Transform player;
public List<ExplodeColours> ColoursList;
// Use this for initialization
void Start () {
PlayerPrefs.SetInt("SelectedChar", 0);
PlayerPrefs.Save();
explodeOnce = false;
explode = false;
}
// Update is called once per frame
void Update () {
if (explode == true)
{
explodeOnce = true;
PlayerExplode();
}
}
void LateUpdate()
{
if (explode)
{
ParticleSystem.Particle[] Particles = new ParticleSystem.Particle[explosionSystem.main.maxParticles];
//ParticleSystem.Particle[] Particles = new ParticleSystem.Particle[Explodes.main.maxParticles];
int NumParticlesAlive = explosionSystem.GetParticles(Particles);
for (int i = 0; i < NumParticlesAlive; i++)
{
Particles[i].startColor = (ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours[Random.Range(0, ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours.Length)]);
}
explosionSystem.SetParticles(Particles, NumParticlesAlive);
explode = false;
}
}
void PlayerExplode()
{
explosionSystem = Instantiate(Explodes, player.position, player.rotation);
explosionSystem.Play();
}
}
If this is line 40 (I had to count):
ParticleSystem.Particle[] Particles = new ParticleSystem.Particle[explosionSystem.main.maxParticles];
Then explosionSystem or main or maxParticles is null. I'm going to close this as a duplicate, this error is so common and the way to resolve it is easy.
But a break point on the line of code, when the code control halts, check which object is null and make sure its set.
EDIT:
Use the GetComponent to initialize the explosionSystem variable in the Start function.First, find the GameObject the ParticleSystem is attached to the use GetComponent to get the ParticleSystem.
private ParticleSystem explosionSystem;
void Start()
{
GameObject obj = GameObject.Find("NameOfObjectParticleSystemIsAttachedTo");
explosionSystem = obj.GetComponent<ParticleSystem>();
}
Also, inside the for loop, you tried to use Particles's startColor property without initializing each one. You can do that with Particles[i] = new ParticleSystem.Particle();.
Change:
for (int i = 0; i < NumParticlesAlive; i++)
{
Particles[i].startColor
= (ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours[Random.Range(0, ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours.Length)]);
}
to
for (int i = 0; i < NumParticlesAlive; i++)
{
Particles[i] = new ParticleSystem.Particle();
Particles[i].startColor
= (ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours[Random.Range(0, ColoursList[PlayerPrefs.GetInt("SelectedChar")].Colours.Length)]);
}
To avoid the null reference error, make sure all variables (which are using) has values other wise at least assign default values in constructor level

Get level index by using scene name in Unity

I know that there is a way to get level name by index:
string levelName = Application.GetLevelNameByIndex(2);
But is there a way to identify the level index of a scene using its name?
Looks like that's not very well supported right now, see this answer.
Only in editor - as per this question.
Vote for it here, it's a bizarrely absent feature.
Note that if you want to show text to the user, you probably want a long name, a description, and a screenshot anyway.
EDIT: Found an answer that has an editor script that may be of assistance.
Here is a method you could use, assuming you know how many scenes are in your game.
using UnityEngine;
using System.Collections;
public class SceneDetector : MonoBehaviour {
public int numberOfScenes = 5;
public String[] sceneNames;
void Start() {
sceneNames = new String[numberOfScenes];
for(int i = 0; i < numberOfScenes; i++)
{
sceneNames[i] = Application.GetLevelNameByIndex(i);
}
}
public int GetSceneIndex(String sceneName)
{
for(int i = 0; i < sceneNames.length; i++)
{
if(sceneName == sceneNames[i])
{
return i;
}
}
return -1;
}
}
And of course as you can see if you run GetSceneIndex and it returns -1, then the string you passed in is not a name of a scene.

Categories

Resources