for some reason, i can only generate up to 3 prefabs with this script. otherwise, I get a MissingReferenceException - unity3d

I tried to make a dynamic scroll view to display selectable options that can be both generated or destroyed during runtime but for some reason it only lets me to generate up to three, otherwise I get a Missing Reference Exception.
When I generate one then delete it, it will also give a Missing Reference Exception.
But when I generate two, then delete one or even two, everything works just fine no matter what I do.
How can I fix this strange Behaviour?
The code:
private List<JL> JLList;
public List<GameObject> JLID;
public GameObject ButtonPrefab;
public GameObject PrefabParent;
public GameObject JLMenuManager;
public GameObject ScrollViewContent;
private void Update()
{
JLList = JLMenuManager.GetComponent<AddJL>().JLList;
CheckForChange();
}
private void GenerateOptions()
{
foreach (Transform child in ScrollViewContent.transform)
{
Destroy(child.gameObject);
}
for (int i = 0; i < JLList.Count; i++)
{
GameObject newJLOption = Instantiate(ButtonPrefab, PrefabParent.transform);
JLID.Add(newJLOption);
newJLOption = JLID[i];
int JLIndex = i;
newJLOption.GetComponent<Button>().onClick.AddListener(() => LoadJLOptions(JLIndex));
}
}
private void LoadJLOptions(int JLIndex)
{
SendMessage("SetJL", JLIndex);
Debug.Log(JLIndex);
}
private void CheckForChange()
{
int allJLOptions = ScrollViewContent.transform.childCount;
if (allJLOptions != JLList.Count)
{
GenerateOptions();
}
}

It seems like it is only spawning in how many JLList.Count is set to, open your script JLList and there should be a variable called count . Check how many that is set to. If it is 3, then that is your issue. If not, then follow what #BugFinder said and try remove them from the list

Related

Unity EditorWindow OnGUI doesn't save data for List<string[]> types

It appears that anything I add to a List<string[]> will get added, but when I save any scripts and Unity does compiles everything, the items in the list disappears.
Here is a simple class I wrote that just displays a window and adds labels according to how many items are in the list:
public class TestEditorWindow : EditorWindow
{
string windowLabel = "Test Window";
[SerializeField] List<string[]> myList = new List<string[]>();
[MenuItem("Tools/My Window")]
static void Init()
{
TestEditorWindow myWindow = (TestEditorWindow)GetWindow(typeof(TestEditorWindow));
myWindow.Show();
}
private void OnGUI()
{
GUILayout.Label(windowLabel, EditorStyles.boldLabel);
EditorGUILayout.Separator();
GUILayout.BeginVertical("box", GUILayout.ExpandWidth(true));
for(int i = 0; i < myList.Count; i++)
{
EditorGUILayout.LabelField("Stupid");
}
if(GUILayout.Button("+", GUILayout.MaxWidth(30)))
{
//myList.Add(new string[2]); //<-- Also tried it this way
myList.Add(new string[] { "" });
}
GUILayout.EndVertical();
}
}
The window shows and every time I hit the button a new label is added to the window, but as soon as Unity compiles anything, the values go away.
If I change the list to List<string> it behaves as intended
I've also tried setting up the list like so and got the same results:
[SerializeField] static List<string[]> myList;
[MenuItem("Tools/My Window")]
static void Init()
{
myList = new List<string[]>();
TestEditorWindow myWindow = (TestEditorWindow)GetWindow(typeof(TestEditorWindow));
myWindow.Show();
}
Am I doing something wrong with how I'm loading the list?
Unity cannot serialize multidimensional collections.
There is a work around though.
Create a new class that contains the string array, and create a list using that type.
[System.Serializable]
public class StringArray
{
public string[] array;
}
and in your window use:
public List<StringArray> myList = new List<StringArray>();

Change the same bool in a class with player input from multiple different keys

I am currently working on a small space game, and I wanted to activate the same thruster with multiple keys. This is so I can activate multiple thrusters with one key input.
This was the solution I came up with. However, when I assign the same instance of the thruster script more than one key code, it stops functioning. I am not receiving any errors, and it is detecting the input.
I know I am missing something, but I just can't seem to find the answer since I don't even know where the problem lies. Thank you all in advance for your help.
public List<ThrusterConfig> ThrusterConfigs = new List<ThrusterConfig>();
[Serializable]
public class ThrusterConfig
{
public Thruster thruster;
public KeyCode[] keyCodes;
}
private void Update()
{
foreach(ThrusterConfig config in ThrusterConfigs)
{
for (int i = 0; i < config.keyCodes.Length; i++)
{
SetThrustersActive(config.keyCodes[i], config);
}
}
}
public void SetThrustersActive(KeyCode keyCode, ThrusterConfig config)
{
foreach(KeyCode key in config.keyCodes)
{
config.thruster.IsActive = Input.GetKey(key);
}
}
Something like this may work for you. The change in the logic is to not assign the result from every key to the thruster but instead to figure out if any key is down and then assign that.
Perhaps you have 3 keys set in a config (a, b, c) and a is pressed. In your current logic a is checked and the thruster is set to active, then b is checked and it is turned off, then c is checked and it is turned off.
This logic assumes the thruster will be turned off by default but if any key is down it will turn it on instead and skip checking the rest of the keys since they don't matter.
public List<ThrusterConfig> ThrusterConfigs = new List<ThrusterConfig>();
[Serializable]
public class ThrusterConfig
{
public Thruster thruster;
public KeyCode[] keyCodes;
}
private void Update()
{
foreach (ThrusterConfig config in ThrusterConfigs)
{
UpdateThruster(config);
}
}
public void UpdateThruster(ThrusterConfig config)
{
bool anyKeyDown = false;
foreach (KeyCode key in config.keyCodes)
{
if (Input.GetKey(key))
{
anyKeyDown = true;
break;
}
}
config.thruster.IsActive = anyKeyDown;
}

Access variable from a different scene in Unity

I am fairly new to Unity and C# and am having some trouble. I am designing a 2d game, which has multiple levels. Each level contains a LevelManager which stores whether the level has been completed or not. They also have the DontDestroyOnLoad command in them. I want to access all the LevelManager gameObjects in my game and then store them in a level select scene. I want to use the win/lose bool to determine if the level has been completed so I can unlock the next level. To be clear, I want a way to access all the LevelManagers in my ENTIRE game and then store them as an array in a GameManager script. How do I do that?
Below is the LevelManager script which declares whether the level has been won or not.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneController : MonoBehaviour
{
private GameObject[] StartHouseCount;
private GameObject[] StartDragonCount;
private GameObject[] LiveDragonCount;
private GameObject[] FinishedHouseCount;
public int NumOfHouses;
public int NumOfFinishedHouse;
public int NumOfDragons;
public int LiveNumOfDragons;
public GameObject[] Players;
public GameObject CurrentPlayer;
[Header("Player")]
public float RefuelRate;
public float RepairRate;
public GameObject canvas;
public bool GameIsPaused = false;
private GameObject MainPlayer;
public bool Win = false;
public int Level;
// Start is called before the first frame update
void Start()
{
CurrentPlayer = Players[0];
StartHouseCount = GameObject.FindGameObjectsWithTag("House");
StartDragonCount = GameObject.FindGameObjectsWithTag("Dragon");
NumOfHouses = StartHouseCount.Length;
NumOfDragons = StartDragonCount.Length;
MainPlayer = Players[0];
}
// Update is called once per frame
void Update()
{
GameIsPaused = canvas.GetComponent<PauseMenu>().GameIsPaused;
LiveDragonCount = GameObject.FindGameObjectsWithTag("Dragon");
LiveNumOfDragons = LiveDragonCount.Length;
FinishedHouseCount = GameObject.FindGameObjectsWithTag("ThankYou");
NumOfFinishedHouse = FinishedHouseCount.Length;
if (Input.GetKeyDown(KeyCode.Alpha1))
{
CurrentPlayer = Players[0];
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
CurrentPlayer = Players[1];
}
if (NumOfFinishedHouse == NumOfHouses)
{
SceneManager.LoadScene("WinScene");
}
if (MainPlayer == null)
{
SceneManager.LoadScene("LoseScene");
}
if (MainPlayer.GetComponent<BasicHelicopterController>().CurrentFuel <= 0 || MainPlayer.GetComponent<BasicHelicopterController>().CurrentHealth <= 0)
{
SceneManager.LoadScene("LoseScene");
}
}
}
You can create a ScoreManager script to store scores of each level using PlayerPrefs and attach this script to a GameObject in the first scene.
public class ScoreManager: MonoBehavior {
public static ScoreManager Instance;
const string LEVEL_KEY = "SCORE_";
const string MAX_LEVEL_KEY = "MAX_LEVEL";
public static int MAX_LEVEL {
get { return PlayerPrefs.GetInt(MAX_LEVEL_KEY, 1); }
set {
if(value > MAX_LEVEL) {
PlayerPrefs.SetInt(MAX_LEVEL_KEY, value);
}
}
}
void Awake() {
if(Instance != null) {
Destroy(this.gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(this);
}
public void SaveScore(int level, float score) {
PlayerPrefs.SetFloat(LEVEL_KEY + level, score);
MAX_LEVEL = level;
}
public float GetScore(int level) {
return PlayerPrefs.GetFloat(LEVEL_KEY + level, 0);
}
public bool IsLevelUnlocked(int level) {
// You can write your own level-unlock logic here.
return level <= MAX_LEVEL;
}
...
}
Then you can just call its functions and properties from other scripts.
...
public void GameOver() {
ScoreManager.Instance.SaveScore(level, score);
}
...
Having all LevelManger scripts with DontDestroyOnLoad will cause a memory leak and sometimes it will affect the game performance.
First, DontDestroyOnLoad is not supposed to do this - main usage for this is to implement things like unity-singleton (easy-to-use template). It means, you`ll have one instance of levelManager, not one per level.
To do it in your way, you need to load all the scenes additively, and get all the LevelManager instances. Or you can load all scenes one-by-one, getting level name and it`s isPassed value from current LevelManager. It can take a while, and its a wrong way.
The right way is to store inter-scene data in ScriptableObject models. It`s like a MonoBehaviour, but not related to scene - it just lays somewhere in project.
So, in your case, you can create ScriptableObject called LevelProgress. It will store the list of all passed levels. It will have public method LevelPassed(string levelName), as a parameter you can use anything - levels name, levels index in build, scene itself, whatever. Every time, when player passes any level, your LevelManager (remove DontDestoryOnLoad from it), which has a reference to LevelProgress scriptable object, will call LevelProgress.LevelPasses(currentLevel). And then, if you need to know, was some level passed, or not, you can just check, is LevelProgress.PassedLevels list contains this level.
Additionally, you need to implement between-sessions persistance for this scriptable object. IMO, the best way for this is to use JSONUtility to convert ScriptableObject into JSON string, and then write it to PlayerPrefs.
I seemed to have figured it out. I created a gameObject containing a GameManagement script, that had the DontDestroyOnLoad line. I had also added a specific tag. I then searched for that object in each level and updated my values to that. The GameManagement script had an array of bools for levels completed and levels unlocked. Each levelmanager decided whether the level was won and updated that. Using that I determined what level was unlocked. I did though need to use Fire King's Awake Command. It ensures that there are no other copies of the script in the game. Solves my problems.

Unity Extension Method GameObject

I'm trying to add an extension method to my gameobject, that's works, but my problem is the GameObject share the same result. My goal is to have a different result for each GameObject.
// AddExtension.cs
public static class GameObjectExtensions
{
private static int life;
public static int Life(this GameObject gameObject)
{
return life;
}
public static void ChangeLife(this GameObject gameObject, int numberToAdd)
{
life += numberToAdd;
}
}
And in my main code, I would like to manage GameObject like :
void Start()
{
GameObject.Find("Perso0").ChangeLife(2);
GameObject.Find("Perso1").ChangeLife(4);
GameObject[] rootGOs = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (GameObject g in rootGOs)
{
if(g.name == "Perso0")
{
Debug.Log("Perso0 : " + g.Life());
}
if(g.name == "Perso1")
{
Debug.Log("Perso1 : " + g.Life());
}
}
}
But both GameObject have 6 in "Life" ( 2 + 4 )
I whould like to get only 2 for "Perso0" with Life and 4 with "Perso1" with Life
Do you have some clue to helping me ?
Thank you and best Regards
Because your life variable is static, it's going to be the same value you're editing every time you call ChangeLife on a GameObject.
Since extension methods need to belong to static classes, and a static class can only have static members, you cannot achieve the goal you want with extension methods.
Even if you could, it's not the right way to go with the Unity paradigm. With this setup, you're essentially saying "Every GameObject in my scene has a life value," which I don't think you want to do.
Instead, you can create your own components, as below.
public class Enemy : MonoBehaviour
{
[SerializeField] private int _initialHealth = 100;
private int _health = -1;
public int health { get { return _health; } }
private void Awake()
{
_health = _initialHealth;
}
public void IncrementHealth(int health)
{
_health += health;
}
}
This is just an example, but you can make something to suit your needs.
static applied to a member means all instances share a single copy. That is, there's only one life variable, which is modified when you call ChangeLife() on any GameOject.
Since extension methods have to be in a static class, I don't think you can accomplish what you want this way.
However, you should be able to add custom properties to your players and other objects in Unity. I don't remember if they're called "custom properties" exactly, but I know some of the basic tutorials like Roll-a-Ball cover this (or at least used to).

Unity Adding points to a user only once after collision

So I'm getting ready to create my first game, I just finished classes on the C# language so I apologize if I'm using stuff such as interfaces wrong and all that. However, for my question; I'm trying different things and seeing what works. I've created a coin, and a player. The coin works as it should, however sometimes when I collect it, it will give me twice the points it should. The coins value is 15, sometimes when I collect a coin it'll add 15 points, other times it will add 30. How do I prevent this from happening.
Here's my code:
Coin Controller Class:
public class CoinController : MonoBehaviour, IEconomy {
private int MoneyValue;
void Start () {
MoneyValue = 15;
}
void Update () {
}
void OnTriggerEnter(Collider col) {
if (col.CompareTag("Player")) {
Destroy(transform.gameObject);
Value();
}
}
public int Value() {
return EconomyController.Money += MoneyValue;
}
}
Economy Controller:
public class EconomyController : MonoBehaviour{
public static int Money;
void Start() {
Money = 0;
}
}
Economy Interface:
public interface IEconomy {
int Value();
}
I would like to point some things about your code:
A good practice when declaring variables is using lowerCamelCase:
thisIsLowerCamelCase
ThisIsNot
This is a variable name convention that is largely used in programming to differentiate Methods and Classes from variables.
Another thing I've noticed is that your "Money" variable is static and it is still being updated on your CoinController. I'd set this variable to be private int variable and use a setter to update it. With that in mind... Have you tried to use Debug.Log to check if the OnTriggerEnter is triggering twice before the object is destroyed?
Simply write:
Debug.Log ("This should only happen once!");
And play the game. If your console shows this message two times, this trigger is being called twice. Another thing that you might notice is that you are calling the Value () method after you called the Destroy (transform.gameObject).
I would do something like:
public class CoinController : MonoBehaviour{
private int moneyValue = 15;
private EconomyController economyController;
void Start (){
economyController = FindObjectOfType (typeof (EconomyController)) as EconomyController;
}
void OnTriggerEnter (Collider col) {
if (col.CompareTag("Player")) {
AddValue();
}
}
public int AddValue() {
EconomyController.money += moneyValue; //Option one.
EconomyController.AddMoney (moneyValue) ; //Option two.
DestroyGameObject ();
}
private void DestroyGameObject (){
Destroy(transform.gameObject);
}
}
Using the clean code principles, option 2 is using a public void function created inside the EconomyController class changing a private variable.
My intuition tells me that you are probably collecting two coins at the time. I'm not sure how you are setting out the coins but I've had a similar problem before.
Imagine a game of snake. Lets say you've programmed it so once you eat a square you create a new one to a random location. There is a probability that the new square would appear inside the snake so it would instantly be eaten. This could be why it happens only some of the time.
Try disabling the collider before you destroy it.
Destroying a gameobject isn't instant and it's (annoyingly) quite easy to set off triggers multiple times.
void OnTriggerEnter(Collider col) {
if (col.CompareTag("Player")) {
// Pseudo Code: GetComponent<TheColliderItIs>().Enabled = false;
Value();
Destroy(transform.gameObject);
}
}