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.
Related
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.
so i'm trying to do a little game on unity . And i did a script , when you press left click on an object , it open a canvas and another one and then it add +1 to i , like that when i found every object . (so when i=3) it shows the end screen. But when i go in game mode , when i press left click it doesnt add the number , like every object have his same i , but i dont understand they have the same function no ?
And sometimes 2 of 3 works, i dont know if thats clear to understand.
using UnityEngine;
using UnityEngine.UI;
public class ActionObjet : MonoBehaviour
{
public Image image1;
public Image image2;
public Image image3;
public int i = 0;
void Start()
{
image1.enabled = false;
image2.enabled = false;
image3.enabled = false;
}
void Update()
{
if (Input.GetKeyDown("space"))
{
if (image1.enabled == true)
{
i = i + 1;
print(i);
image2.enabled = !image2.enabled;
image1.enabled = !image1.enabled;
FindObjectOfType<AudioManager>().Play("bruit_recuperer_objet");
}
}
}
void OnMouseDown()
{
if (image1.enabled == false)
{
image1.enabled = !image1.enabled;
}
}
}
https://i.stack.imgur.com/sOSJH.png
So I found the solution , I'm a bit sad that I wasted 5 hours just for that but yea..
I just changed the
public int i=0
into
static int i=0;
This is because each gameObject with this script has its own instance of all the variables. A static variable's value is universal to all instances of the same class so any one of them can update it for all of them when marked static.
I made a script that is supposed to manage audio files, so multiple can play at once.
Here is the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MultipleSoundManager : MonoBehaviour
{
#region AudioSource Variables
public AudioSource AS1;
public AudioSource AS2;
public AudioSource AS3;
public AudioSource AS4;
public AudioSource AS5;
#endregion
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void AudioSourceManager(AudioClip clippy)
{
for (int i = 1; i > 1; i++)
{
AudioSource[] audioManagerArray = {AS1, AS2, AS3, AS4, AS5};
audioManagerArray[i].clip = clippy;
audioManagerArray[i].Play();
if(i == 5)
{
i = 1;
}
}
}
}
And the AudioSourceManager function is called from other scripts like so:
public MultipleSoundManager soundManagerScript;
soundManagerScript.AudioSourceManager(AKMShot);
When this function is called, nothing happens whatsoever. I am a bit of a noob to Unity, and I really don't know what the issue is.
I think your problem is
for (int i = 1; i > 1; i++)
How many times do you think this cycle will run?
If you provide more information, it will help a lot.
Here is some suggestion:
1.Try to find out if the [AKMShot] is null, may be is this cause the problume.
2.Try to find out if the [AS1/2/3/4/5] is null, may be is this cause the problume.
2.you can use "foreach" to replace the "for" in your [AudioSourceManager], try to google "C# foreach" and learn, but it cant solve this question, it just can make your code look pretty.
3.The "start" and update part is useless. You can just delete them.(also just to make your code pretty)
The code will never run into the for-loop:
for (int i = 1; i > 1; i++)
{
//...
}
I have an object, which contains a list of GameObjects. I wish to destroy all of these GameObjects in its destructor.
However, when I attempt to call GameObject.Destroy() from inside the destructor, it seems to halt execution (the line after GameObject.Destroy() never executes, but the line before it does)
If i copy and paste exactly the same code into a function called not_a_destructor() and call that instead, it works perfectly. What gives? I've got it working, but I would really like to understand what's going on.
Destructor and not_a_destructor() code:
// Destructor DOES NOT work
~MoveAction(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("wasd");
GameObject.Destroy(arrows[i]);
Debug.Log("asdf");
}
}
// Identical code, calling not_a_destructor() works perfectly
public void not_a_destructor(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("PRETEND DESTRUCTOR!");
GameObject.Destroy(arrows[i]);
Debug.Log("GameObject destroyed successfully");
}
}
As requested in comments, a full copy of the class:
public class Action
{
public int type;
public string debug_string;
public GameObject ui_pill; // Only present for Actions created on the client
}
public class MoveAction : Action
{
public int type = ActionType.MOVE;
public MapHex origin;
public List<MapHex> route; // Intermediate hexes travelled through during the move (includes target_hex)
public Fleet fleet;
private List<GameObject> arrows = new List<GameObject>(); // Arrows for the graphical representation of the pending move on the tactical map
public MapHex target_hex {
get {
return route[route.Count - 1];
}
}
public string debug_string {
get {
return "MOVE ACTION WITH FLEET: " + fleet.name;
}
}
public MoveAction(Fleet _fleet, List<MapHex> _route){
fleet = _fleet;
route = _route;
origin = fleet.planned_position;
update_arrows_from_route();
}
public void update_arrows_from_route(){
Material default_material = new Material(Shader.Find("Sprites/Default"));
// Create one arrow for every hex we will pass through.
MapHex last = fleet.planned_position;
foreach (MapHex hex in route){
// Create arrow from last to hex
GameObject arrow_gameobj = new GameObject();
arrow_gameobj.name = "move_order_arrow";
LineRenderer line_renderer = arrow_gameobj.AddComponent<LineRenderer>();
line_renderer.material = default_material;
line_renderer.SetColors(fleet.owner.color, fleet.owner.color);
line_renderer.positionCount = 2;
arrow_gameobj.layer = layers.tactical_map;
Vector3[] line_points = new Vector3[]{last.position, hex.position};
line_renderer.SetPositions(line_points);
line_renderer.startWidth = 0.1f;
line_renderer.endWidth = 0.1f;
arrows.Add(arrow_gameobj);
last = hex;
}
}
public void not_a_destructor(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("PRETEND DESTRUCTOR!");
GameObject.Destroy(arrows[i]);
Debug.Log("GameObject destroyed successfully");
}
}
~MoveAction(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("wasd");
GameObject.Destroy(arrows[i]);
Debug.Log("asdf");
}
}
Its probable best to use more of Unity and less of C#, there is a good callback called OnDestroy() which would be a fine place to destroy all the arrows. If execution of your unity code depends on running a finalizer on something, this is a very strong code smell.
Unless you are using IO in a way that REQUIRES an action to happen in a finalizer (possibly things like releasing an IO resource), its best to leave them empty, and put Unity code inside Unity callbacks
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.