Checkbox not working as intended in Mixed reality toolkit V2 - c#

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.

Related

Simple 2d program working with colors, buttons, and if statements

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");
//}
}
}

Unity - Accessing Events Throwing Null Exception

I have a script accessing an instantiated game object of Vuforia from Hierarchy and adding a component to it:
IEnumerable<TrackableBehaviour> tbs = TrackerManager.Instance.GetStateManager().GetTrackableBehaviours();
foreach (TrackableBehaviour tb in tbs)
{
if (tb.TrackableName == "Fashion")
{
if (tb.name == "New Game Object")
{
// change generic name to include trackable name
tb.gameObject.name = ++counter + "DynamicImageTarget-" + tb.TrackableName;
// add additional script components for trackable
tb.gameObject.AddComponent<DefaultTrackableEventHandler>();
tb.gameObject.AddComponent<TurnOffBehaviour>();
Up to here it is working but at this point/line when i try to add
tb.gameObject.GetComponent<DefaultTrackableEventHandler>().OnTargetFound.AddListener(DressFound);
is throwing null exception in runtime
When I try
tb.gameObject.GetComponent<DefaultTrackableEventHandler>().OnTargetFound.RemoveAllListeners();
throwing null exception in runtime as well
However when i try
tb.gameObject.GetComponent<DefaultTrackableEventHandler>().enable = false;
it is working and disabling script component
void DressFound()
{
augmentation.SetActive(true);
}
Why i can't reach events on the script?
/*==============================================================================
Copyright (c) 2019 PTC Inc. All Rights Reserved.
Copyright (c) 2010-2014 Qualcomm Connected Experiences, Inc.
All Rights Reserved.
Confidential and Proprietary - Protected under copyright and other laws.
==============================================================================*/
using UnityEngine;
using UnityEngine.Events;
using Vuforia;
/// <summary>
/// A custom handler that implements the ITrackableEventHandler interface.
///
/// Changes made to this file could be overwritten when upgrading the Vuforia version.
/// When implementing custom event handler behavior, consider inheriting from this class instead.
/// </summary>
public class DefaultTrackableEventHandler : MonoBehaviour
{
public enum TrackingStatusFilter
{
Tracked,
Tracked_ExtendedTracked,
Tracked_ExtendedTracked_Limited
}
/// <summary>
/// A filter that can be set to either:
/// - Only consider a target if it's in view (TRACKED)
/// - Also consider the target if's outside of the view, but the environment is tracked (EXTENDED_TRACKED)
/// - Even consider the target if tracking is in LIMITED mode, e.g. the environment is just 3dof tracked.
/// </summary>
public TrackingStatusFilter StatusFilter = TrackingStatusFilter.Tracked_ExtendedTracked_Limited;
public UnityEvent OnTargetFound;
public UnityEvent OnTargetLost;
protected TrackableBehaviour mTrackableBehaviour;
protected TrackableBehaviour.Status m_PreviousStatus;
protected TrackableBehaviour.Status m_NewStatus;
protected bool m_CallbackReceivedOnce = false;
protected virtual void Start()
{
mTrackableBehaviour = GetComponent<TrackableBehaviour>();
if (mTrackableBehaviour)
{
mTrackableBehaviour.RegisterOnTrackableStatusChanged(OnTrackableStatusChanged);
}
}
protected virtual void OnDestroy()
{
if (mTrackableBehaviour)
{
mTrackableBehaviour.UnregisterOnTrackableStatusChanged(OnTrackableStatusChanged);
}
}
void OnTrackableStatusChanged(TrackableBehaviour.StatusChangeResult statusChangeResult)
{
m_PreviousStatus = statusChangeResult.PreviousStatus;
m_NewStatus = statusChangeResult.NewStatus;
Debug.LogFormat("Trackable {0} {1} -- {2}",
mTrackableBehaviour.TrackableName,
mTrackableBehaviour.CurrentStatus,
mTrackableBehaviour.CurrentStatusInfo);
HandleTrackableStatusChanged();
}
protected virtual void HandleTrackableStatusChanged()
{
if (!ShouldBeRendered(m_PreviousStatus) &&
ShouldBeRendered(m_NewStatus))
{
OnTrackingFound();
}
else if (ShouldBeRendered(m_PreviousStatus) &&
!ShouldBeRendered(m_NewStatus))
{
OnTrackingLost();
}
else
{
if (!m_CallbackReceivedOnce && !ShouldBeRendered(m_NewStatus))
{
// This is the first time we are receiving this callback, and the target is not visible yet.
// --> Hide the augmentation.
OnTrackingLost();
}
}
m_CallbackReceivedOnce = true;
}
protected bool ShouldBeRendered(TrackableBehaviour.Status status)
{
if (status == TrackableBehaviour.Status.DETECTED ||
status == TrackableBehaviour.Status.TRACKED)
{
// always render the augmentation when status is DETECTED or TRACKED, regardless of filter
return true;
}
if (StatusFilter == TrackingStatusFilter.Tracked_ExtendedTracked)
{
if (status == TrackableBehaviour.Status.EXTENDED_TRACKED)
{
// also return true if the target is extended tracked
return true;
}
}
if (StatusFilter == TrackingStatusFilter.Tracked_ExtendedTracked_Limited)
{
if (status == TrackableBehaviour.Status.EXTENDED_TRACKED ||
status == TrackableBehaviour.Status.LIMITED)
{
// in this mode, render the augmentation even if the target's tracking status is LIMITED.
// this is mainly recommended for Anchors.
return true;
}
}
return false;
}
protected virtual void OnTrackingFound()
{
if (mTrackableBehaviour)
{
var rendererComponents = mTrackableBehaviour.GetComponentsInChildren<Renderer>(true);
var colliderComponents = mTrackableBehaviour.GetComponentsInChildren<Collider>(true);
var canvasComponents = mTrackableBehaviour.GetComponentsInChildren<Canvas>(true);
// Enable rendering:
foreach (var component in rendererComponents)
component.enabled = true;
// Enable colliders:
foreach (var component in colliderComponents)
component.enabled = true;
// Enable canvas':
foreach (var component in canvasComponents)
component.enabled = true;
}
if (OnTargetFound != null)
OnTargetFound.Invoke();
}
protected virtual void OnTrackingLost()
{
if (mTrackableBehaviour)
{
var rendererComponents = mTrackableBehaviour.GetComponentsInChildren<Renderer>(true);
var colliderComponents = mTrackableBehaviour.GetComponentsInChildren<Collider>(true);
var canvasComponents = mTrackableBehaviour.GetComponentsInChildren<Canvas>(true);
// Disable rendering:
foreach (var component in rendererComponents)
component.enabled = false;
// Disable colliders:
foreach (var component in colliderComponents)
component.enabled = false;
// Disable canvas':
foreach (var component in canvasComponents)
component.enabled = false;
}
if (OnTargetLost != null)
OnTargetLost.Invoke();
}
}
The issue seems to be that both UnityEvents are not assigned in the case the component is attached on runtime via AddComponent.
Usually these are assigned to automatically by the Unity Inspector since they are serialized.
To be sure you could simply initialize them always using
public UnityEvent OnTargetFound = new UnityEvent();
public UnityEvent OnTargetLost = new UnityEvent();

Is there a way to track vuforia targets in a specific order?

I am trying to track vuforia image targets in a specific order so that the next target will not be trackable until the first one has been scanned.
you should extend vuforia image target recognition and add like order variable. In your other script you can have static variable or currentTrackedOrder
public class VuforiaCustomBehaviour: MonoBehaviour, ITrackableEventHandler
{
public TrackableBehaviour mTrackableBehaviour;
//order of image target
public int order = 0;
void Start()
{
mTrackableBehaviour = GetComponent<TrackableBehaviour>();
if (mTrackableBehaviour)
{
mTrackableBehaviour.RegisterTrackableEventHandler(this);
}
}
public void OnTrackableStateChanged(TrackableBehaviour.Status previousStatus,
TrackableBehaviour.Status newStatus)
{
if (newStatus == TrackableBehaviour.Status.DETECTED ||
newStatus == TrackableBehaviour.Status.TRACKED ||
newStatus == TrackableBehaviour.Status.EXTENDED_TRACKED)
{
if (order == MainScript.currentTrackedOrder)
{
//Activate your object here
DoSmth();
MainScript.currentTrackedOrder++;
}
}
else
{
OnTrackingLost();
}
}
}
So you can add this script to all image targets and put in order variable needed digits. Also you should disable default trackable events handler.

How can I add a global bool flag to control all the doors lock states?

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!

Unity Component is referencing the same gameObject instead of itself

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.

Categories

Resources