I am having this problem where when I instantiate multiple objects at once, the gameObject references the same instance on all objects whereas they should instead reference the gameObject that component is attached to.
So, I have this MonoBehaviour, which is the main part of my code. When the awake function runs it should create an instance of each BootstrapMacro so I don't overwrite the data in the .asset file (Which happens without this part).
Then in the new instance, I set a reference to the current Bootstrap component.
public class Bootstrap : MonoBehaviour {
[SerializeField] List<BootstrapMacro> macros;
public List<BootstrapMacro> runtimeMacros = new List<BootstrapMacro>();
void Awake() {
// Create a runtime version of the macro so to not overwrite the original asset file
macros.ForEach(macro => {
if (macro != null) {
var mac = Instantiate(macro);
mac.events.ForEach(evt => {
evt.bootstrap = this;
evt.actions.ForEach(act => { act.bootstrap = this; });
});
runtimeMacros.Add(mac);
}
});
RunMacro(e => e.OnObjectAwake());
}
void Start() { RunMacro(e => e.OnObjectStart()); }
void RunMacro(System.Action<BootstrapEvent> action) {
runtimeMacros.ForEach(macro => {
if (macro == null) return;
macro.events.ForEach(evt => {
if (!evt.enabled) return;
action.Invoke(evt);
});
});
}
}
My BootstrapMacro file is really basic:
[CreateAssetMenu(fileName = "Bootstrap Macro.asset", menuName = "Boostrap/Macro")]
public class BootstrapMacro : ScriptableObject {
public List<Bootstrap.BootstrapEvent> events = new List<Bootstrap.BootstrapEvent>();
}
Then the event looks like this:
[BootstrapEvent(0)]
public class OnCreate : BootstrapEvent {
public override void OnObjectStart() {
Debug.Log(bootstrap.gameObject.name);
Trigger();
}
}
Which extends this:
public class BootstrapEvent : ScriptableObject {
Bootstrap _bootstrap;
public Bootstrap bootstrap {
get { return _bootstrap; }
set { if (_bootstrap == null) _bootstrap = value; }
}
public virtual void OnObjectStart() { }
}
I am instantiating the objects like this:
var o = Instantiate(_gameObject, _position, Quaternion.identity);
o.name = Random.Range(0, 1000).ToString();
So it is creating the object and giving it a random number. So when it is created I log the name as seen above (code block 3). However, they are all referencing the first object created....
So, what is causing the items to reference the same gameObject?
From the following picture what is happening is the Debug.Log is printing, the object name. As seen there are multiple object names getting printed. A new object name is written to the console every time the previous one is destroyed.
Edit
So, It looks like the issue is with my property. If I remove if(_bootstrap == null), then the code works as expected. If I leave it, it is using the first created item as the reference until it is destroyed then the next create item becomes the reference. Why?
public Bootstrap bootstrap {
get { return _bootstrap; }
set { _bootstrap = value; }
}
var o = Instantiate(_gameObject, _position, Quaternion.identity);
o.name = Random.Range(0, 1000).ToString();
"So, what is causing the items to reference the same gameObject?"
_gameObject is your object reference, all of your var o objects are clones of this. this is the desired behavior with instantiation.
Note that if _gameObject gets destroyed, var o will no longer have a reference, and you will crash. that's why we use prefabs. they cant be removed from the scene so your reference wont go null.
Related
There are 3 buttons present. Clicking a button will change its color. The task here is to match every color of the buttons to a shade of green. Now, I have the buttons programmed to cycle through 4 colors, the same palette. It works, of course.
What I want to implement now is, to make a text appear once all the buttons match colors. I'm working on Unity and the c# script is on Visual Studio.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ChangeColor : MonoBehaviour
{
[SerializeField] int count = 0;
[SerializeField] Color[] colorArray;
public void button ()
{
if(count < colorArray.Length)
{
gameObject.GetComponent<Image>().color = new Color(colorArray[count].r, colorArray[count].g, colorArray[count].b);
if(count == colorArray.Length - 1)
{
count = -1;
}
count += 1;
}
}
}
First of all your button method can be simplified a lot.
And then I would use a callback that checks your colors everytime one of the buttons was pressed:
public class ChangeColor : MonoBehaviour
{
[SerializeField] int count = 0;
[SerializeField] Color[] colorArray;
[SerializeField] private Image _image;
public Color CurrentColor => _image.color;
public event Action OnChangedColor;
private void Awake ()
{
if(!_image) _image = GetComponent<Image>();
}
public void button ()
{
var count = (count + 1) % colorArray.Length;
var nextColor = colorArray[Count];
_image.color = nextColor;
OnChangedColor?.Invoke();
}
}
And now in a central controller script you have references to all your buttons ChangeColor components and their according target Color like e.g.
[Serializable]
public class ButtonColorPair
{
public ChangeColor changeColor;
public Color targetColor;
// Returns true if a ChangeColor is referenced and the colors are matching
public bool IsMatching => changeColor && changeColor.CurrentColor == tatgetColor;
}
public class ColorChecker : MonoBehaviour
{
public ButtonColorPair[] buttonColorPairs;
private void Awake ()
{
// Register a callback which is called every time one of the buttons
// has changed the color
foreach(var pair in buttonColorPairs)
{
// It is save to remove a callback even if it wasn't added before
// but it makes sure there is only one single callback
pair.changeColor.OnChangedColor -= HandleChangedColor;
pair.changeColor.OnChangedColor += HandleChangedColor;
// Here you would also set the targetColor if you want to do it via code e.g. like
pair.targetColor = Color.green;
// or to be save e.g. pick a random value from the color array etc
// Maybe this class would even be responsible for telling the buttons which colors are
// available at all
}
}
private void OnDestroy()
{
// As a good practice always remove callbacks as soon as you don't need them anymore
foreach(var pair in buttonColorPairs)
{
pair.changeColor.OnChangedColor -= HandleChangedColor;
}
}
// Finally check if the color matches for all buttons everytime one of them was changed
private void HandleChangedColor ()
{
// This is Linq and require "using System.Linq;" on top of your file
if(buttonColorPairs.All(p => p.IsMatching)
{
Debug.Log("All buttons are matching their target color");
}
else
{
Debug.Log("Nope not all matching yet");
}
// it basically equals doing something like
//bool allMatch = true;
//foreach(var p in buttonColorPairs)
//{
// if(!p.IsMatching)
// {
// allMatch = false;
// break;
// }
//}
//if(allMatch)
//{
// Debug.Log("All buttons are matching their target color");
//}
//else
//{
// Debug.Log("Nope not all matching yet");
//}
}
}
I am trying to add value from another class to the list created in another class. I have set up a very simple experiment but it always throw me the error.
the first class is simply gameObject script which attached to the object
public class gameObject : Agent
{
public Area area;
public override void Initialize()
{
var testData = new test
{
name = "test value",
};
area.testList.Add(testData);
}
}
and I try to add that value to the list in another class as shown belows
public class test
{
public string name;
}
public class Area : MonoBehaviour
{
public List<test> testList = new List<test>()
}
why the value always return null ? it work properly if I add the data with in the area class
You initialize testList list in VoxelArea class but you are using Area.
replace Area with VoxelArea or initialize area.testList and area before adding.
public class gameObject : Agent
{
public Area area; //initialize Area
public override void Initialize()
{
var testData = new test
{
name = "test value",
};
//area = new Area(); //initialize Area
area.testList = new List<test>();//<--NOTE THIS
area.testList.Add(testData);
}
}
G'day, I seem to be having a problem with a class variable in my Windows Form Application.
It is a string variable in my Player class. When I create a Player object, I cannot seem to assign a string value to the playerName variable.
I've manually created my Get and Set methods and I can't see anything wrong with it.
I've gotten a message a few times that informs me that the string playerName isn't assigned and will remain its default value of null. I'm not sure why this is the case.
class Player
{
private string playerName = ""; //Variable used for giving each player a unique identifier, eg. 'Player One', etc
//public string PlayerName { get => playerName; set => playerName = value; }
public void setPlayerName(string name) //Sets the player's name for the game (UNSC or Covenant)
{
name = this.playerName;
}
public string getPlayerName() //Returns the player's name
{
return playerName;
}
}
Creating a Player class and trying to show the playerName to a textbox doesn't work, the playerName value remains null
public partial class FrmBoardGameApp : Form
{
public FrmBoardGameApp()
{
InitializeComponent();
}
ArrayList Players = new ArrayList();
public void creationOfPlayers() //BUG!!! Values stay null
{
Player playerOne = new Player(); //Player 1
Player playerTwo = new Player(); //Player 2
playerOne.setPlayerName("Player One");
playerTwo.setPlayerName("Player Two");
Players.Add(playerOne); //Player name value is still null at this point
Players.Add(playerTwo);
}
//Here's the textbox assignment code
public bool playerTurn = true; //A bool variable used to keep track of whose turn it is
public bool setupCheck = false; // Ensures that the endturn button cannot be hit unless all setup is done
public int setupCheckValue = 0; //Ensures that the setup button can only be hit once
public void testingPlayerTurn() //A method used to test whose turn it is
{
if (setupCheck != true)
{
MessageBox.Show("Game is not setup, please setup the game");
}
else
{
//Textbox is empty, as playerName value remains null
if (playerTurn)
{
Players.ToArray();
Player firstPlayer = (Player)Players[0];
txtAns.Text = firstPlayer.getPlayerName();
/*
* This method of accessing an element and controlling/manipulating its values works
*/
}
else if (!playerTurn)
{
//playerTwo.setPlayerName(factionTwo);
//txtAns.Text = playerTwo.getPlayerName();
}
}
}
private void btnEndTurn_Click(object sender, EventArgs e) //Ends the turn of the current player
{
changePlayer();
//testingPlayerTurn();
testingPlayerNames();
}
}
I've added into the code example some methods that handle player assignment, just in case it helps in any way. But the issue starts at the creationOfPlayers method and in the Player class.
this:
public void setPlayerName(string name) //Sets the player's name for the game (UNSC or Covenant)
{
name = this.playerName;
}
should be the other way around:
public void setPlayerName(string name) //Sets the player's name for the game (UNSC or Covenant)
{
this.playerName = name;
}
Problem: you actually assigned the value of your field to the local parameter of the method.
Explanation: on the left side of the = should be the variable that receives the value, on the right side should be the variable that gives the value (or reference in other cases).
In your player class, inside setPlayerName method, you should be asisgning name parameter value to this.playerName. I see you have done reverse
public void setPlayerName(string name) //Sets the player's name for the game (UNSC or Covenant)
{
//name = this.playerName;
this.playerName = name;
}
Checkboxes are currently unusable in the new toolkit, as you cannot bind functions to states.
There is a development commit on GitHub, but it's not usable yet, so I need a script to work around, without changing the toolkit.
Should be able to easily set starting state, and call functions on state change - which you currently can't.
Found a solution that works without changing the toolkit, and should work on newer versions.
using UnityEngine;
using Microsoft.MixedReality.Toolkit.UI;
using UnityEngine.Events;
[RequireComponent(typeof(Interactable))]
public class CheckBoxInteractableSwitch : MonoBehaviour
{
public bool startChecked = true;
public UnityEvent OnCheck;
public UnityEvent OnUncheck;
private Interactable interactable;
private int state = 1;
void Start()
{
interactable = GetComponent<Interactable>();
if (OnCheck == null)
OnCheck = new UnityEvent();
if (OnUncheck == null)
OnUncheck = new UnityEvent();
OnCheck.AddListener(Checked);
OnUncheck.AddListener(UnChecked);
//works with 2 dimensions only
if (startChecked)
{
if (interactable.GetDimensionIndex() == 0) interactable.IncreaseDimension();
}
else
{
if (interactable.GetDimensionIndex() == 1) interactable.IncreaseDimension();
}
}
void Update()
{
if (interactable == null) return;
//state check
if (state != interactable.GetDimensionIndex())
{
state = interactable.GetDimensionIndex();
if (state == 0) OnUncheck.Invoke();
if(state == 1) OnCheck.Invoke();
}
}
private void Checked()
{
}
private void UnChecked()
{
}
}
Works with checkboxes only (2 dimension), you can set the default state for the checkbox, and you can subscribe to states on state changes.
Now it's working fine in editor and in runtime for each door.
But I want to add a global public flag that will control all the doors at once in editor and in runtime. If I change the global flag to true all the doors will be locked and same if set to false.
The DoorsLockManager script:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
[ExecuteInEditMode]
public class DoorsLockManager : MonoBehaviour
{
[HideInInspector]
public List<HoriDoorManager> Doors = new List<HoriDoorManager>();
private void Awake()
{
var doors = GameObject.FindGameObjectsWithTag("Door");
Doors = new HoriDoorManager[doors.Length].ToList();
for (int i = 0; i < doors.Length; i++)
{
Doors[i] = doors[i].GetComponent<HoriDoorManager>();
}
}
}
And the editor script:
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(DoorsLockManager))]
public class DoorsLockManagerEditor : Editor
{
private SerializedProperty _doors;
private void OnEnable()
{
_doors = serializedObject.FindProperty("Doors");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
for (int i = 0; i < _doors.arraySize; i++)
{
var door = _doors.GetArrayElementAtIndex(i);
// if door == null the script itself has an error since it can't even find the SerializedProperty
if (door == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get door property", target);
return;
}
if (door.objectReferenceValue == null) continue;
// FindPropertyRelative seems not to only work for MonoBehaviour classes
// so we have to use this hack around
var serializedDoor = new SerializedObject(door.objectReferenceValue);
// If it's public no worry anyway
// If it's private still works since we made it a SerializeField
var lockState = serializedDoor.FindProperty("doorLockState");
// Fetch current values into the serialized "copy"
serializedDoor.Update();
if (lockState == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get lockState property", target);
return;
}
// for the PropertyField there is
// no return value since it automatically uses
// the correct drawer for the according property
// and directly changes it's value
EditorGUILayout.PropertyField(lockState, new GUIContent("Door " + i + " Lockstate"));
// or alternatively
//lockState.boolValue = EditorGUILayout.Toggle("Door " + i + " Lockstate", lockState.boolValue);
// Write back changes, mark as dirty if changed
// and add a Undo history entry
serializedDoor.ApplyModifiedProperties();
}
}
}
Well, you could simply add one to the DoorsLockManager
[ExecuteInEditMode]
public class DoorsLockManager : MonoBehaviour
{
[HideInInspector]
public List<HoriDoorManager> Doors = new List<HoriDoorManager>();
// The global state
[SerializeField] private bool _globalLockState;
// During runtime use a property instead
public bool GlobalLockState
{
get { return _globalLockState; }
set
{
_globalLocakState = value;
// apply it to all doors
foreach(var door in Doors)
{
// now you would need it public again
// or use the public property you had there
Door.doorLockState = _globalLocakState;
}
}
}
private void Awake()
{
var doors = GameObject.FindGameObjectsWithTag("Door");
Doors = new HoriDoorManager[doors.Length].ToList();
for (int i = 0; i < doors.Length; i++)
{
Doors[i] = doors[i].GetComponent<HoriDoorManager>();
}
}
}
and in the editor make it "overwrite" all the other flags if changed:
[CustomEditor(typeof(DoorsLockManager))]
public class DoorsLockManagerEditor : Editor
{
private SerializedProperty _doors;
private SerializedProperty _globalLockState;
private bool shouldOverwrite;
private void OnEnable()
{
_doors = serializedObject.FindProperty("Doors");
_globalLockState = serializedObject.FindProperty("_globalLockState");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
shouldOverwrite = false;
// Begin a change check here
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_globalLockState);
if(EditorGUI.EndChangeCheck())
{
// overwrite only once if changed
shouldOverwrite = true;
}
for (int i = 0; i < _doors.arraySize; i++)
{
var door = _doors.GetArrayElementAtIndex(i);
// if door == null the script itself has an error since it can't even find the SerializedProperty
if (door == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get door property", target);
return;
}
if (door.objectReferenceValue == null) continue;
var serializedDoor = new SerializedObject(door.objectReferenceValue);
var lockState = serializedDoor.FindProperty("doorLockState");
serializedDoor.Update();
if (lockState == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get lockState property", target);
return;
}
// HERE OVERWRITE
if(shouldOverwrite)
{
lockState.boolValue = _globalLockState.boolValue;
}
else
{
EditorGUILayout.PropertyField(lockState, new GUIContent("Door " + i + " Lockstate"));
}
serializedDoor.ApplyModifiedProperties();
}
serializedObject.ApplyModifiedProperties();
}
}
You could create a static class with the flag in it, and then have the door's "activated" state tied to it. Example:
public static class GlobalVariables()
{
public static bool DoorsLocked = true; //Doors would be locked dependent on logic
}
Then you just set the doors' state to the global variable in code. When you change the global bool, the doors change.
GlobalVariables.DoorsLocked = false; //unlocks all doors reading the global bool
Hope this helps!