Hey everyone i am trying to make an inventory list in C# for unity but i run into an array error when i pickup my item, I cant figure out why i wondered if someone could help. I have searched everywhere for some tips but not come across any the error is :-
ArgumentException: Destination array was not long enough. Check destIndex and length, and the array's lower bounds
As an edit iv attached the full code
code attached below :-
using UnityEngine;
using System.Collections;
[AddComponentMenu ("Inventory/Inventory")]
public class Inventory : MonoBehaviour {
//This is the central piece of the Inventory System.
public Transform[] Contents; //The content of the Inventory
public int MaxContent = 12; //The maximum number of items the Player can carry.
bool DebugMode = true; //If this is turned on the Inventory script will output the base of what it's doing to the Console window.
private InventoryDisplay playersInvDisplay; //Keep track of the InventoryDisplay script.
public Transform itemHolderObject; //The object the unactive items are going to be parented to. In most cases this is going to be the Inventory object itself.
//Handle components and assign the itemHolderObject.
void Awake (){
itemHolderObject = gameObject.transform;
playersInvDisplay = GetComponent<InventoryDisplay>();
if (playersInvDisplay == null)
{
Debug.LogError("No Inventory Display script was found on " + transform.name + " but an Inventory script was.");
Debug.LogError("Unless a Inventory Display script is added the Inventory won't show. Add it to the same gameobject as the Inventory for maximum performance");
}
}
//Add an item to the inventory.
public void AddItem ( Transform Item ){
ArrayList newContents = new ArrayList();
//FIXME_VAR_TYPE newContents= new Array(Contents);
newContents.Add(Item);
//Contents=newContents.ToBuiltin(Transform); //Array to unity builtin array
newContents.CopyTo(Contents); //Array to unity builtin array
System.Array.Resize(ref Contents, 1);
if (DebugMode)
{
Debug.Log(Item.name+" has been added to inventroy");
}
//Tell the InventoryDisplay to update the list.
if (playersInvDisplay != null)
{
playersInvDisplay.UpdateInventoryList();
}
}
//Removed an item from the inventory (IT DOESN'T DROP IT).
public void RemoveItem ( Transform Item ){
ArrayList newContents = new ArrayList();
//FIXME_VAR_TYPE newContents=new Array(Contents); //!!!!//
int index = 0;
bool shouldend = false;
foreach(Transform i in newContents) //Loop through the Items in the Inventory:
{
if(i == Item) //When a match is found, remove the Item.
{
newContents.RemoveAt(index);
shouldend=true;
//No need to continue running through the loop since we found our item.
}
index++;
if(shouldend) //Exit the loop
{
//Contents=newContents.ToBuiltin(Transform); //!!!!//
Contents=newContents.ToArray(typeof (Transform)) as Transform[];
if (DebugMode)
{
Debug.Log(Item.name+" has been removed from inventroy");
}
if (playersInvDisplay != null)
{
playersInvDisplay.UpdateInventoryList();
}
return;
}
}
}
//Dropping an Item from the Inventory
public void DropItem (Item item){
gameObject.SendMessage ("PlayDropItemSound", SendMessageOptions.DontRequireReceiver); //Play sound
bool makeDuplicate = false;
if (item.stack == 1) //Drop item
{
RemoveItem(item.transform);
}
else //Drop from stack
{
item.stack -= 1;
makeDuplicate = true;
}
item.DropMeFromThePlayer(makeDuplicate); //Calling the drop function + telling it if the object is stacked or not.
if (DebugMode)
{
Debug.Log(item.name + " has been dropped");
}
}
//This will tell you everything that is in the inventory.
void DebugInfo (){
Debug.Log("Inventory Debug - Contents");
int items=0;
foreach(Transform i in Contents){
items++;
Debug.Log(i.name);
}
Debug.Log("Inventory contains "+items+" Item(s)");
}
//Drawing an 'S' in the scene view on top of the object the Inventory is attached to stay organized.
void OnDrawGizmos (){
Gizmos.DrawIcon (new Vector3(transform.position.x, transform.position.y + 2.3f, transform.position.z), "InventoryGizmo.png", true);
}
}
Hope someone could help thank you in advance :)
Arrays have a fixed size when they are allocated only complex types that mimic array interaction have auto grow functionality.
Check the capacity of the target array you are copying your original array contents too.
I am not sure what ToBuiltIn method does but it must be creating a new array as a copy of the origin (your data might already be in the array by the way, check it by debugging it) with the capacity set to the total number of items in the array which could be 1 or 0.
Check your variables and debug the code to see the lengths and capacities of you arrays.
UPDATE (From the updated code)
Your Contents array is not initialized when calling the Add Item method.
To make it clear I have rewritten the code you pasted I dont have all the classes you are using so I have not tested or checks it it builds however it will give you an idea of what you can do. Your array management can be simplified if you use generic list.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[AddComponentMenu ("Inventory/Inventory")]
public class Inventory : MonoBehaviour {
//This is the central piece of the Inventory System.
public List<Transform> Contents; //The content of the Inventory
public int MaxContent = 12; //The maximum number of items the Player can carry.
bool DebugMode = true; //If this is turned on the Inventory script will output the base of what it's doing to the Console window.
private InventoryDisplay playersInvDisplay; //Keep track of the InventoryDisplay script.
public Transform itemHolderObject; //The object the unactive items are going to be parented to. In most cases this is going to be the Inventory object itself.
public Inventory()
{
this.Contents = new List<Transform> (MaxContent);
}
//Handle components and assign the itemHolderObject.
void Awake (){
itemHolderObject = gameObject.transform;
playersInvDisplay = GetComponent<InventoryDisplay>();
if (playersInvDisplay == null)
{
Debug.LogError("No Inventory Display script was found on " + transform.name + " but an Inventory script was.");
Debug.LogError("Unless a Inventory Display script is added the Inventory won't show. Add it to the same gameobject as the Inventory for maximum performance");
}
}
//Add an item to the inventory.
public void AddItem ( Transform Item ){
if (this.Contents.Count < this.MaxContent) {
Contents.Add (Item);
if (DebugMode) {
Debug.Log (Item.name + " has been added to inventroy");
}
//Tell the InventoryDisplay to update the list.
if (playersInvDisplay != null) {
playersInvDisplay.UpdateInventoryList ();
}
} else {
// show message that inventory is full
}
}
//Removed an item from the inventory (IT DOESN'T DROP IT).
public void RemoveItem ( Transform Item ){
if (this.Contents.Remove (Item)) {
if (DebugMode) {
Debug.Log (Item.name + " has been removed from inventroy");
}
if (playersInvDisplay != null) {
playersInvDisplay.UpdateInventoryList ();
}
return;
} else {
// Item is not in inventory
}
}
//Dropping an Item from the Inventory
public void DropItem (Item item){
gameObject.SendMessage ("PlayDropItemSound", SendMessageOptions.DontRequireReceiver); //Play sound
bool makeDuplicate = false;
if (item.stack == 1) //Drop item
{
RemoveItem(item.transform);
}
else //Drop from stack
{
item.stack -= 1;
makeDuplicate = true;
}
item.DropMeFromThePlayer(makeDuplicate); //Calling the drop function + telling it if the object is stacked or not.
if (DebugMode)
{
Debug.Log(item.name + " has been dropped");
}
}
//This will tell you everything that is in the inventory.
void DebugInfo (){
Debug.Log("Inventory Debug - Contents");
int items=0;
foreach(Transform i in Contents){
items++;
Debug.Log(i.name);
}
Debug.Log("Inventory contains "+items+" Item(s)");
}
//Drawing an 'S' in the scene view on top of the object the Inventory is attached to stay organized.
void OnDrawGizmos (){
Gizmos.DrawIcon (new Vector3(transform.position.x, transform.position.y + 2.3f, transform.position.z), "InventoryGizmo.png", true);
}
}
Related
I was wrote a script that combines whale characters to create a new character. However, it is difficult to save and load data from a list of names, levels and locations of the newly created characters.
Whale data does not seem to be stored and loaded when the game is played.
I'd really thanks it if you could tell me where's wrong.
using system;
using System.Collections;
using System.Collections.General;
using system.IO;
using LitJson;
using UnityEngine;
using Random = UnityEngine.Random;
[System.Serializable]
Public class WhaleData
{
Public in dataLevel{get; set;// whale level
public Vector3 dataMousePosition{get; set;//the whale location
public string dataName{get; set;} //whale name
}
Public class Whale: MonoBehaviour
{
public int level;
Private pool isDrag;
Public GameObject NextPrepab;
Private Vector3 mousePosition;
Public WhaleData whaleData;
Private List[WhaleData] WhaleDatas = new List[WhaleData]();
private pool IsSave; //bool value to check for stored data
void start()
{
IsSave = PlayerPrefs.HasKey ("0_name"); //saved_level Check if a key value named //saved_level exists
//initialize all data values if no save data exists
If (!IsSave)
{
Debug.Log ("No data stored).");
PlayerPrefs.DeleteAll();
}
//recall value if save data exists
else
{
Debug.Log ("Stored Data").");
LoadData();
}
}
Private void Update ()
{
SaveData();
LoadData();
}
private void onMouseDown()
{
isDrag = true;
}
private void OnMouseDrag()
{
if (isDrag)
{
mousePosition.x = Input.mousePosition.x;
mousePosition.y = input.mousePositionY;
mousePosition.z = 10;
//change the mouse coordinates to the screen to world and set the position of this object
gameObject.transform.position = Camera.main.ScreenToWorldPoint(mousePosition);
// store the location of the whale in the whalData.dataMousePosition= gameObject.transform.position; //
}
}
private void onMouseUp()
{
//OverlapSphere : Return from the specified location to the collider array in contact within range
var colliders = Physics.OverlapSphere (transform.position, 0.1f);
foreach (var col in colliders) // arrangement of surrounding contacts
{
If (name!= col. gameObject.name) //if the name is different, go
{
If (level==col.gameObject.GetComponent[Whale>().level) //If the level of contact with the level stored in me is the same,
{
whatData.dataLevel = col.gameObject.GetComponent[Whale>().level; // store the level of the whale in the whalData class
var newWhale = Instantiate(NextPrepab, transform.position, quaternion.identity);
newWhale.name = Random.Range (0f, 200f).ToString(); //name is random.
dateData.dataName = name;// store the name of the whale in the whalData class
SaveData();
print(col.gameObject.name);
print(gameObject.name);
whatDatas.Add(whaleData);
Destroy (col.gameObject);
Destroy (gameObject);
// delete if there is any whale information with the same name as Whale's name that will disappear from the dateDatas list (use a simple calculation expression of the unknown method)
if (whaleDatas.Find(whaleData=>whaleData.dataName=colgameObject.GetComponent[Whale>(.name).dataName==colgameObject.GetComponent[Whale>(.name)
{
whaleDatasRemove (col.gameObject.GetComponent[Whale>(whaleData));
Destroy (col.gameObject);
Destroy (gameObject);
}
else
{
break;
}
break;
}
}
}
isDrag = false;
}
Public static void setVector3 (string key, Vector3 value)
{
PlayerPrefs.SetFloat (key + "X", value.x);
PlayerPrefs.SetFloat (key + "Y", value.y);
PlayerPrefs.SetFloat (key + "Z", value.z);
}
Public static Vector3 GetVector3 (string key)
{
Vector3 v3 = Vector3.zero;
v3.x = PlayerPrefs.GetFloat (key + "X");
v3.y = PlayerPrefs.GetFloat (key + "Y");
v3.z = PlayerPrefs.GetFloat (key + "Z");
return v3;
}
private void saveData()
{
for (int i=0; i <whaleDatas.Count; i++)
{
PlayerPrefs.SetString(i+"_name",whaleDatas[i‐dataName));
PlayerPrefs.SetInt(i+"_level",whaleDatas[i‐dataLevel));
Vector3 value = whatDatas[i].dataMousePosition;
string key = i + "_position";
SetVector3 (key,value);
PlayerPrefs.Save();
Debug.Log ("Saved");
}
}
private void LoadData()
{
for(int i=0; i <whaleDatas.Count; i++)
{
whaleDatas[i].dataName = PlayerPrefs.GetString(i+"_name");
whaleDatas[i].dataLevel = PlayerPrefs.GetInt(i+"_level");
string key = i + "_position";
whaleDatas[i].dataMousePosition = GetVector3(key);
}
}
}
Your code is actually broken in term of design.
First PlayerPref doesnt support boolean save by default.
IsSave = PlayerPrefs.HasKey //this will always return false.
thats why everytime you start the game all your saved data is deleted.
You can try this work around to save and load a boolean type How to save bool to PlayerPrefs Unity
Now, How on earth you decided to save and load data at update function?! , this is terrible and has huge performance cost if your data is huge. you just cant do it this way.
Instead, try to make events that store the data and load it instead, like once player enter a trigger box , the data is saved, and ofcourse you dont need to reload it if its saved , bec why you would have to load the data after its being saved?!!
Once again, dont read and write at Update Function like this all the time, this is bad design .
My problem is inside playmode I get expected array sequence: showing all gameobjects, RedCube, Interior, BlueCube, and then GreenCube. But, after I built it and test in my android. I get a different array sequence: showing all gameobjects, GreenCube, BlueCube, RedCube and then Interior. This screenshot shows the sequence of my array.image0.
There are 4 gameobjects under gameobject - Levels Image1. Added a script to Levels.... Image2complete scene screenshot.
public GameObject[] levels;
public Button levelBtn;
int i = 0;
private void Awake()
{
levels = GameObject.FindGameObjectsWithTag("levels");
Button btn = levelBtn.GetComponent<Button>();
}
// Start is called before the first frame update
void Start()
{
levelBtn.onClick.AddListener(onLevelclick);
}
public void onLevelclick()
{
if (i < levels.Length - 1)
{
i++;
}
else if (i >= levels.Length - 1)
{
i = 0;
}
Debug.Log(i);
if (i == 0)
{
levels[0].SetActive(true);
levels[1].SetActive(true);
levels[2].SetActive(true);
levels[3].SetActive(true);
}
else if (i == 1)
{
levels[0].SetActive(true);
levels[1].SetActive(false);
levels[2].SetActive(false);
levels[3].SetActive(false);
}
else if (i == 2)
{
levels[0].SetActive(true);
levels[1].SetActive(true);
levels[2].SetActive(false);
levels[3].SetActive(false);
}
else if (i == 3)
{
levels[0].SetActive(true);
levels[1].SetActive(true);
levels[2].SetActive(true);
levels[3].SetActive(false);
}
}
Inside this script I have a gameobject array. When I click the UIbutton, it will loop within the array.length.... But the array sequence is different from playmode and built. I couldn't figure out why?
If you assign gameobjects to levels in the unity inspector like image0, you don't need to this line.
levels = GameObject.FindGameObjectsWithTag("levels");
I am trying to check if the value in string word from one class matches any element in the array stringAnswers in another and if it does i want the score to increment by 1. For some reason the code im using below increments the score by 1,2 and 3 depending on the word that is displayed any help would be awesome cheers.
public class Player : MonoBehaviour
{
public int Score = 0;
private string[] StringAns = {"steve", "grace", "lilly"};
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
if (Input.GetKeyDown(KeyCode.Space))
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit))
{
if (hit.transform.GetComponent<ButtonNo>() != null)
{
foreach (string stringAnsers in StringAns)
{
if (stringAnsers.Equals(FindObjectOfType<GameController>().word))
{
Debug.Log(" Button has been looked at");
FindObjectOfType<GameController>().RandText();
}
else
{
Debug.Log(" Button has been looked at");
FindObjectOfType<GameController>().RandText();
Score++;
}
}
}
}
if (Physics.Raycast(transform.position, transform.forward, out hit))
{
if (hit.transform.GetComponent<ButtonYes>() != null)
{
foreach (string stringAnsers in StringAns)
{
if (stringAnsers.Equals( FindObjectOfType<GameController>().word) )
{
Debug.Log(" Button has been looked at");
FindObjectOfType<GameController>().RandText();
Score++;
}
else
{
FindObjectOfType<GameController>().RandText();
}
}
}
}
}
}
}
GameController
public class GameController : MonoBehaviour
{
public TextMesh InfoText;
public TextMesh WallText;
public string word;
public Player player;
public string[] Strings = { "stev", "lilly", "grace" };
// Use this for initialization
void Start()
{
RandText();
}
// Update is called once per frame
void Update()
{
InfoText.text = "Is this Spelling Correct ?\n Score: " + player.Score;
}
public void RandText()
{
word = Strings[Random.Range(0, Strings.Length)];
WallText = GameObject.Find("WallText").GetComponent<TextMesh>();
WallText.text = word;
}
}
In general you should call FindObjectOfType<GameController>() only once e.g in Start
private GameController _gameController;
private void Start()
{
_gameController= FindObjectOfType<GameController>();
}
and than reuse that _gameController reference everywhere instead of FindObjectOfType<GameController>().
The same for WallText in GameController:
public TextMesh WallText;
private void Start()
{
WallText = GameObject.Find("WallText").GetComponent<TextMesh>();
RandText();
}
public void RandText()
{
word = Strings[Random.Range(0, Strings.Length)];
WallText.text = word;
}
better would even be you already reference those in the Inspector in Unity via drag&drop than you don't have to use Find at all
Than to your problem: You are setting a new value for word in every iteration for the loop
foreach (string stringAnsers in StringAns)
{
if (stringAnsers.Equals(FindObjectOfType<GameController>().word))
{
Debug.Log("Button has been looked at");
FindObjectOfType<GameController>().RandText();
}
else
{
Debug.Log("Button has been looked at");
FindObjectOfType<GameController>().RandText();
Score++;
}
}
So it might happen that the word doesn't match so you call Score++; but at the same time you do FindObjectOfType<GameController>().RandText(); everytime so a new random word is generated/selected for each iteration.
So in the next iteration of the foreach loop you check against a new random word which might or might not match the next answer string in the list.
Instead you should only generate a new random word after the loop is done e.g. like
foreach (string stringAnsers in StringAns)
{
if (stringAnsers.Equals(FindObjectOfType<GameController>().word))
{
Debug.Log("Button has been looked at-> matched");
Score++;
}
else
{
Debug.Log("Button has been looked at -> didn't match");
}
}
FindObjectOfType<GameController>().RandText();
Note this would still add 1-3 points depending how many of the given stringAnswer match the word. So you should add a break; after increasing Score once if you want only 1 added.
Using Linq you could also do this in only one line instead a loop:
if(StringAns.Any(answer => string.Equals(answer, _gameController.word)))) Score++;
_gameController.RandText();
or for the No button
if(!StringAns.Any(answer => string.Equals(answer, _gameController.word)))) Score++;
I guess the problem is that you are generating a new random word inside the foreach loop that check the word itself. Also, i suggest you not to use a foreach loop, because items inside the array could be more that 3 items, so it could slow the method. I suggest you to use contains instead, like so:
if (StringAns.Contains(FindObjectOfType<GameController>().word))
{
Debug.Log(" Button has been looked at");
Score++;
}
else
Debug.Log(" Button has *not* been looked at");
FindObjectOfType<GameController>().RandText();
I have a scene where I should be able to select a 3D model from a list of items, which will then be loaded into the next scene. I want to generate the list of items programmatically, based on what is already stored in the local database. But I have problems getting the list of items to be displayed in the scene ...
So I have a class for the items:
[System.Serializable] public class Item {
public string ObjectGuid;
public string Name;
public string Description;
public Sprite Icon;
public Button.ButtonClickedEvent SelectObject;
}
A script for my scene, that contains a list of items and a method that I call to load the objects from the database:
public class ObjectSelectionScript : MonoBehaviour {
public List<Item> itemList;
public Transform contentPanel;
void LoadObjects() {
StreamReader str = new StreamReader (Application.persistentDataPath + "/" + DataManager.LOCATION_XML_PATH);
string result = str.ReadToEnd ();
str.Close();
var jo = SimpleJSON.JSON.Parse (result);
if (jo ["Objects"] != null) {
for (int i = 0; i < jo["Objects"].Count; i++) {
if (!string.IsNullOrEmpty(jo["Objects"][i]["FileGuid"])) {
Item newObject = new Item();
newObject.ObjectGuid = jo["Objects"][i]["ObjectGuid"];
newObject.Name = jo["Objects"][i]["Name"];
newObject.Description = jo["Objects"][i]["Description"];
//newObject.SelectObject = new Button.ButtonClickedEvent();
itemList.Add(newObject);
}
}
}
}
A ModelObject class:
public class ModelObject : MonoBehaviour {
public Button button;
public Text Name;
public Text Description;
public Image Icon;
}
And a prefab ModelObjectPrefab that contains Name, Description, a Thumbnail, added to my assests folder.
The PopulateList() method is my problem. I can't get to instatiate the prefab and then create ModelObjects that I can throw in the contentPanel in the interface. This is my code:
void PopulateList()
{
try {
foreach (var item in itemList)
{
GameObject newModel = Instantiate(ModelObject) as GameObject;
ModelObject myModelObject = newModel.GetComponent<ModelObject>();
myModelObject.Name.text = item.Name;
myModelObject.Description.text = item.IDescription;
myModelObject.icon.sprite = item.Icon;
myModelObject.button.onClick = item.selectObject;
newModel.transform.SetParent(contentPanel);
}
} catch (System.Exception ex) {
}
}
I currently get null pointer on instantiate. I have tried Resources.Load, and variations of the Instatiate method, but I did not find a solution.
Can anyone give me a hint what is the problem? How should PopulateList() look like to get the items to the interface?
Note that the Instantiate() method should not be given a type (eg. ModelObject) as an argument. Rather, you should be passing it an object instance (in this case, your prefab GameObject "ModelObjectPrefab"). The documentation specifically indicates that it requires:
An existing object that you want to make a copy of
Otherwise, you're probably going to get unexpected behaviour like this. In your code, we need to pass a reference to "ModelObjectPrefab" as an argument of the Instantiate() call. I would suggest modifying your class containing PopulateList() to look like:
// New variable to hold prefab object
public GameObject modelObjectPrefab;
// ...
void PopulateList()
{
try {
foreach (var item in itemList)
{
// Reference prefab variable instead of class type
GameObject newModel = Instantiate(modelObjectPrefab) as GameObject;
ModelObject myModelObject = newModel.GetComponent<ModelObject>();
myModelObject.Name.text = item.Name;
myModelObject.Description.text = item.IDescription;
myModelObject.icon.sprite = item.Icon;
myModelObject.button.onClick = item.selectObject;
newModel.transform.SetParent(contentPanel);
}
} catch (System.Exception ex) {
}
}
Then, in the Editor window, drag your prefab "ModelObjectPrefab" from the assets panel onto the new Model Object Prefab field in the script properties, to form the connection between the script and the prefab.
Hope this helps! Let me know if you have any questions.
Note for readers. "GUI" is no longer available in Unity. Simply use the new UI system. Example http://www.folio3.com/blog/creating-dynamic-scrollable-lists-with-new-unity-canvas-ui/
The only thing that did work was to create the whole GUI programmatically.
I created a GUI.matrix and added everything inside it. It did not look very good, so I spent a lot of time trying to make it look better and to be responsive on mobile devices, but in the end this was the only solution that worked.
Just as a quick reference for who ever might have the same issue, the solution has this structure:
public void OnGUI ()
{
var savedMatrix = GUI.matrix;
scrollPosition = GUI.BeginScrollView (new Rect (10f, 10f, Screen.width - 10f, Screen.height - 10f), scrollPosition, new Rect (10f, 10f, Screen.width - 10f, (_files.Count + 2) * 200f));
if (Input.touchCount == 1) {
Vector2 touchDelta2 = Input.GetTouch (0).deltaPosition;
scrollPosition.y += touchDelta2.y;
}
var selected = GUILayout.SelectionGrid (gridInt, _files.ToArray (), 1, MyStyle);
if (selected >= 0) {
if (selected == 0) {
if (selectedItem != null )
if (selectedItem.Parent != null)
selectedItem = selectedItem.Parent;
else
selectedItem = null;
} else {
SelectFolder (selected);
}
RefreshViewModel ();
}
GUI.EndScrollView ();
GUI.matrix = savedMatrix;
}
I'm making a menu for an Android 2d app, I have a bunch of UI panels in multiple menu's and when I press the next right or previous left button the script should set the next panel active and deactive the previous panel, I've tried doing this with a public class with gameobjects but that didn't work and thought a list should work.
In the Unity editor I've set the size to 4 and dragged 4 panels into the script
and I'm getting this error:
ArgumentOutOfRangeException: Argument is out of range.
Parameter name: index
Here is the relevant part of the script:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MenuControl : MonoBehaviour
{
//Buttons
public GameObject ShipUpgradeRight;
public GameObject ShipUpgradeLeft;
//Ships
private int shipUpgradeSelected = 0;
List<GameObject> ShipList = new List<GameObject>();
public void Keyword (string keyword)
{
if (keyword == "ShipUpgradeLeft") {
shipUpgradeSelected--;
ShipList[shipUpgradeSelected].SetActive (true);
ShipList[shipUpgradeSelected+1].SetActive (false);
}
if (keyword == "ShipUpgradeRight") {
shipUpgradeSelected--;
CheckShip ();
ShipList[shipUpgradeSelected].SetActive (true);
ShipList[shipUpgradeSelected-1].SetActive (false);
}
}
}
Based on your comments and the actual question I see one possible problem.
The value of shipUpgradeSelected never gets increased. Moreover, shipUpgradeSelected has zero as initial value.
public void Keyword (string keyword)
{
if (keyword == "ShipUpgradeLeft") {
shipUpgradeSelected--;
ShipList[shipUpgradeSelected].SetActive (true);
ShipList[shipUpgradeSelected+1].SetActive (false);
}
if (keyword == "ShipUpgradeRight") {
shipUpgradeSelected--;
CheckShip ();
ShipList[shipUpgradeSelected].SetActive (true);
ShipList[shipUpgradeSelected-1].SetActive (false);
}
}
When keyword equals to ShipUpgradeRight or ShipUpgradeLeft the value of shipUpgradeSelected is decreased (so it's less than zero). And then you try to access the item of list at index that is less than zero.
But this is first problem.
Also you don't clamp (or don't cycle) value of shipUpgradeSelected. So for example you have
if (keyword == "ShipUpgradeRight") {
shipUpgradeSelected++;
CheckShip ();
ShipList[shipUpgradeSelected].SetActive (true);
ShipList[shipUpgradeSelected-1].SetActive (false);
}
If call Keyword("ShipUpgradeRight"); five times (for example), the value of shipUpgradeSelected is 5. And again it's out of range. So you need to decide how to clamp value of this variable.
This code works perfect:
public void Keyword(string keyword) {
if (keyword == "ShipUpgradeLeft") {
shipUpgradeSelected--;
if (shipUpgradeSelected < 0) {
shipUpgradeSelected = 0;
return;
}
ShipList[shipUpgradeSelected].SetActive(true);
if (shipUpgradeSelected + 1 < ShipList.Count) ShipList[shipUpgradeSelected + 1].SetActive(false);
else ShipList[0].SetActive(false);
}
if (keyword == "ShipUpgradeRight") {
shipUpgradeSelected++;
if (shipUpgradeSelected >= ShipList.Count) {
shipUpgradeSelected = ShipList.Count - 1;
return;
}
ShipList[shipUpgradeSelected].SetActive(true);
if (shipUpgradeSelected > 0) ShipList[shipUpgradeSelected - 1].SetActive(false);
else ShipList[ShipList.Count-1].SetActive(false);
}
}
but if i would start over i'd do it like this:
private void Start()
{
ShipUpgradeLeft.GetComponent<Button>().onClick.AddListener(() => { Previous(); });
ShipUpgradeRight.GetComponent<Button>().onClick.AddListener(() => { Next(); });
}
public void Next()
{
ShipList[shipUpgradeSelected].SetActive(false);
shipUpgradeSelected = Mathf.Clamp(shipUpgradeSelected++, 0, ShipList.Count - 1);
ShipList[shipUpgradeSelected].SetActive(true);
}
public void Previous()
{
ShipList[shipUpgradeSelected].SetActive(false);
shipUpgradeSelected = Mathf.Clamp(shipUpgradeSelected--, 0, ShipList.Count - 1);
ShipList[shipUpgradeSelected].SetActive(true);
}
Thanks to walther and d12frosted for helping me solve the issue