I am trying to access a public method from the ARCoreBackgroundRenderer class which is a MonoBehavior and is located in GoogleARCore namespace, in another class which is also MonoBehavior and named UIController and is located in another namespace called CompanyController. Is this possible? How can I do it?
the method I am trying to call in my second class is:
public void DisableARBackgroundRendering()
{
if (m_CommandBuffer == null || m_Camera == null)
{
return;
}
m_Camera.clearFlags = m_CameraClearFlags;
.......
}
I want to call this method in my second Class in a simple method.
In general it helps to refer to the according API
ARCoreBackgroundRenderer (API Reference) as you said yourself is a class in the namespace GoogleARCore .. so import it via
using GoogleARCore;
or use
GoogleARCore.ARCoreBackgroundRenderer
as type in your code in UIController.
using UnityEngine;
using GoogleARCore;
namespace CompanyController
{
public class UIController : MonoBehaviour
{
// Reference this via the Inspector by drag and drop
// [SerializeField] simply allows to serialize also private fields in Unity
[SerializeField] private ARCoreBackgroundRenderer arRenderer;
// Alternatively you could as said use the type like
//[SerializeField] private GoogleARCore.ARCoreBackgroundRenderer arRenderer;
private void Awake ()
{
// As a fallback find it on the scene
if(!arRenderer) arRenderer = FindObjectOfType<ARCoreBackgroundRenderer>();
}
public void DisableARBackgroundRendering()
{
// Now use any public method of the arRenderer
arRenderer.SomePublicMethod();
}
}
}
However, you can also see that the method DisableARBackgroundRendering is private and you won't be able to use it. Also m_Camera and m_CommandBuffer are both private so you won't be able to access them.
What you can and - if you look closer into the implementation of ARBackgroundRenderer - want to do here is simply enabling and disabling the according component:
public void EnableARBackgroundRendering(bool enable)
{
arRenderer.enabled = enable;
}
since internally it will take care of the rest itself and you don't need any further access to its methods, fields and properties:
private void OnEnable()
{
if (BackgroundMaterial == null)
{
Debug.LogError("ArCameraBackground:: No material assigned.");
return;
}
LifecycleManager.Instance.OnSessionSetEnabled += _OnSessionSetEnabled;
m_Camera = GetComponent<Camera>();
m_TransitionImageTexture = Resources.Load<Texture2D>("ViewInARIcon");
BackgroundMaterial.SetTexture("_TransitionIconTex", m_TransitionImageTexture);
EnableARBackgroundRendering(); // AS YOU SEE IT ALREADY CALLS THIS ANYWAY
}
private void OnDisable()
{
LifecycleManager.Instance.OnSessionSetEnabled -= _OnSessionSetEnabled;
m_TransitionState = BackgroundTransitionState.BlackScreen;
m_CurrentStateElapsed = 0.0f;
m_Camera.ResetProjectionMatrix();
DisableARBackgroundRendering(); // AS YOU SEE IT ALREADY CALLS THIS ANYWAY
}
Related
Hi I'm a completely new to coding and am trying to create a card game. I've watched some tutorials and tried to take things into my own hands but cant seem to figure out something. I currently have a BattleState set up;
public enum BattleState { START, PLAYERMAINPHASE, PLAYERBATTLEPHASE, PLAYERENCORESTEP, ENEMYTURN, WON, LOST }
and would like it so when i change the BattleState with a script, it changes it for every other script that references this BattleState. Sorry for the bad wording. Coding is rough :/
You can use interfaces, create an interface such as IBattleStateChanger and have a method on it
interface IBattleStateChanger{
void ChangeBattleState(YourClass.BattleState state);
}
Then on every script you want the value to change implement this interface as
ClassExample : IBattleStateChanger {}
This will then force you to create a method in the script to change the state
After that, whenever you want to change the value globally on the scripts where you implemented this interface, you can do a foreach loop finding each type of this interface such as
BattleState newState = BattleState.START;
foreach (var obj in FindObjectsOfType<IBattleStateChanger>){
obj.SetBattleState(newState);
}
You could use a static event and attach listeners/callbacks to it like e.g.
public enum BattleState
{
START, PLAYERMAINPHASE, PLAYERBATTLEPHASE, PLAYERENCORESTEP, ENEMYTURN, WON, LOST
}
public static class BattleStateMgr
{
private static BattleState _state;
public static BattleState State => _state;
public static event System.Action<BattleState> OnStateChange;
public static ChangeState(BattleState s)
{
_state = s;
OnStateChange?.Invoke(_state);
}
}
public class OtherScript : MonoBehaviour
{
private void Awake()
{
BattleStateMgr.OnStateChagne += OnBattleStateChange;
}
private void OnDestroy()
{
BattleStateMgr.OnStateChagne -= OnBattleStateChange;
}
private void OnBattleStateChange(BatlleState newState)
{
Debug.Log($"Changed Battle State to{newState}", this);
}
}
I believe you are confused about the scope of your variable. Each script you place an instance of the enum Battlestate, is a local version of that enum. If you want the reference to be global, you will need to have a central point where all scripts can grab this reference.
public class BattleManager : MonoBehaviour
{
private BattleState battleState;
// setter / getters
public BattleState GetBattleState(){return battleState; }
public void SetBattleState(BattleState state){ battleState = state; }
}
You are going to want to make a single script that holds the only reference to your enum Battlestate, then have your other scripts reference the variable.
public class OtherScript : MonoBehaviour
{
// assign this reference in the inspector
[SerializeField] private BattleManager bm = null;
private void YourFunction()
{
if(bm.GetBattleState() == BattleState.randomStateHere)
{
// run logic here
}
}
}
There are a number of ways to go about doing this, but the easiest would most likely be by declaring the variable static.
public class BattleManager : MonoBehaviour
{
private static BattleState battleState;
// setter / getters
public static BattleState GetBattleState(){return battleState; }
public static void SetBattleState(BattleState state){ battleState = state; }
}
public class OtherScript : MonoBehaviour
{
private void YourFunction()
{
if(BattleManager.GetBattleState() == BattleState.randomStateHere)
{
// run logic here
}
}
}
I do not know how many scripts you need to access this variable, but if it is only a handful, I would instead assign references to the script that holds the enum to each of the scripts that need it. I would avoid simply using static as it is the easy approach but creates what is called a code smell. The reason for this is OOP (object-oriented programming) by design should generally not have mutable global variables.
If you have a single instance of an object that manages all of your battle activity and a lot of scripts need to access it, you can look into the Singleton pattern. As you are new to programming, I would not implement this pattern until you understand the time and place to properly use it. You can also completely avoid using it by properly assigning the references you need in the inspector or by using a Object.FindObjectOfType in either Start or Awake.
During the implementation of my game using Unity I was faced with the following setup:
I have a ScriptableObject (as an asset) that has a c# event delegate.
I have a MonoBehaviour that has a serialized reference to the ScriptableObject that has the delegate.
I want to "subscribe" the MonoBehaviour to that ScriptableObject's event, properly handling the event to avoid memory leaks. Initially I supposed that subscribing to the event on the OnEnablecallback and unsubscribing it on the OnDisablewas enough. However, a memory leak occurs when a developer, by using the Unity Inspector, swaps the value of the serialized reference to the ScriptableObject during play.
Is there a canonical way to safely subscribe and unsubscribe to c# events in a serialized reference to a ScriptableObject, given that I want the developers of the game to be able to swap value in the inspector during play?
To illustrate that, I have written a simple code for that scenario:
SubjectSO.cs (The ScriptableObject with the event)
using UnityEngine;
using System;
[CreateAssetMenu]
public class SubjectSO : ScriptableObject
{
public event Action<string> OnTrigger;
public void Invoke()
{
this.OnTrigger?.Invoke(this.name);
}
}
ObserverMB .cs (The MonoBehaviour that wants to subscribe to the event in the ScriptableObject)
using UnityEngine;
public class ObserverMB : MonoBehaviour
{
public SubjectSO subjectSO;
public void OnEnable()
{
if(this.subjectSO != null)
{
this.subjectSO.OnTrigger += this.OnTriggerCallback;
}
}
public void OnDisable()
{
if(this.subjectSO != null)
{
this.subjectSO.OnTrigger -= this.OnTriggerCallback;
}
}
public void OnTriggerCallback(string value)
{
Debug.Log("Callback Received! Value = " + value);
}
}
InvokesSubjectSOEveryUpdate .cs (Auxiliary MonoBehaviour, for testing)
using UnityEngine;
public class InvokesSubjectSOEveryUpdate : MonoBehaviour
{
public SubjectSO subjectSO;
public void Update()
{
this.subjectSO?.Invoke();
}
}
For testing, I have created two assets of the type SubjectSO, named:
SubjectA
SubjectB
Then, I have created a GameObject in scene, and attached the following components:
ObserverMB, referencing SubjectA
InvokesSubjectSOEveryUpdate, referencing SubjectA
InvokesSubjectSOEveryUpdate, referencing SubjectB
When hitting play, the message Callback Received! Value = SubjectA is printed in the Console every Update, which is expected.
Then, when I use the inspector to change the reference in ObserverMB from SubjectA to SubjectB, while the game is still playing, the message Callback Received! Value = SubjectA still keeps being printed.
If I disable and enable ObserverMB in the inspector, both messages Callback Received! Value = SubjectA and Callback Received! Value = SubjectB start being printed every Update.
The initial callback subscription is still in effect, but, as a subscriber, ObserverMB has lost the reference to that event.
How can I avoid that situation?
I really believe that this seems to be a common use scenario for the use of c# event delegates and ScriptableObjects and it seems strange for me that OnEnable and OnDisable do not properly handle the serialization case of a developer tweaking the inspector.
Well you would have to check whether the subjectSO is being changed and unsubscribe in this case.
After you switch the value via the Inspector your class cannot unsubscribe from the previous value anymore. So whatever was subscribed to at the beginning will stay subscribed.
For checking on runtime
I would e.g. do it using a property like
// Make it private so no other script can directly change this
[SerializedField] private SubjectSO _currentSubjectSO;
// The value can only be changed using this property
// automatically calling HandleSubjectChange
public SubjectSO subjectSO
{
get { return _currentSubjectSO; }
set
{
HandleSubjectChange(this._currentSubjectSO, value);
}
}
private void HandleSubjectChange(SubjectSO oldSubject, SubjectSO newSubject)
{
if (!this.isActiveAndEnabled) return;
// If not null unsubscribe from the current subject
if(oldSubject) oldSubject.OnTrigger -= this.OnTriggerCallback;
// If not null subscribe to the new subject
if(newSubject)
{
newSubject.OnTrigger -= this.OnTriggerCallback;
newSubject.OnTrigger += this.OnTriggerCallback;
}
// make the change
_currentSubjectSO = newSubject;
}
so every time some other script changes the value using
observerMBReference.subject = XY;
it automatically first unsubscribes from the current subject and then subscribes to the new one.
For checking a change via the Inspector
There are two options:
Either you go via the Update method and yet another backing field like
#if UNITY_EDITOR
private SubjectSO _previousSubjectSO;
private void Update()
{
if(_previousSubjectSO != _currentSubjectSO)
{
HandleSubjectChange(_previousSubjectSO, _currentSubjectSO);
_previousSubjectSO = _currentSubjectSO;
}
}
#endif
Or do (thanks zambari) the same thing in OnValidate
#if UNITY_EDITOR
private SubjectSO _previousSubjectSO;
// called when the component is created or changed via the Inspector
private void OnValidate()
{
if(!Apllication.isPlaying) return;
if(_previousSubjectSO != _currentSubjectSO)
{
HandleSubjectChange(_previousSubjectSO, _currentSubjectSO);
_previousSubjectSO = _currentSubjectSO;
}
}
#endif
Or - since this will happen only in the case the field is changed via the Inspector - you could implement a Cutsom Editor which does it only in case the field is changed. This is a bit more complex to setup but would be more efficient since later in a build you wouldn't need the Update method anyway.
Usually you put editor scripts in a separate folder called Editor but personally I find it is good practice to implement it into the according class itself.
The advantage is that this way you have access to private methods as well. And this way you automatically know there is some additional behavior for the Inspector.
#if UNITY_EDITOR
using UnityEditor;
#endif
...
public class ObserverMB : MonoBehaviour
{
[SerializeField] private SubjectSO _currentSubjectSO;
public SubjectSO subjectSO
{
get { return _currentSubjectSO; }
set
{
HandleSubjectChange(_currentSubjectSO, value);
}
}
private void HandleSubjectChange(Subject oldSubject, SubjectSO newSubject)
{
// If not null unsubscribe from the current subject
if(oldSubject) oldSubject.OnTrigger -= this.OnTriggerCallback;
// If not null subscribe to the new subject
if(newSubject) newSubject.OnTrigger += this.OnTriggerCallback;
// make the change
_currentSubjectSO = newSubject;
}
public void OnEnable()
{
if(subjectSO)
{
// I recommend to always use -= before using +=
// This is allowed even if the callback wasn't added before
// but makes sure it is added only exactly once!
subjectSO.OnTrigger -= this.OnTriggerCallback;
subjectSO.OnTrigger += this.OnTriggerCallback;
}
}
public void OnDisable()
{
if(this.subjectSO != null)
{
this.subjectSO.OnTrigger -= this.OnTriggerCallback;
}
}
public void OnTriggerCallback(string value)
{
Debug.Log("Callback Received! Value = " + value);
}
#if UNITY_EDITOR
[CustomEditor(typeof(ObserverMB))]
private class ObserverMBEditor : Editor
{
private ObserverMB observerMB;
private SerializedProperty subject;
private Object currentValue;
private void OnEnable()
{
observerMB = (ObserverMB)target;
subject = serializedObject.FindProperty("_currentSubjectSO");
}
// This is kind of the update method for Inspector scripts
public override void OnInspectorGUI()
{
// fetches the values from the real target class into the serialized one
serializedObject.Update();
EditorGUI.BeginChangeCheck();
{
EditorGUILayout.PropertyField(subject);
}
if(EditorGUI.EndChangeCheck() && EditorApplication.isPlaying)
{
// compare and eventually call the handle method
if(subject.objectReferenceValue != currentValue) observerMB.HandleSubjectChange(currentValue, (SubjectSO)subject.objectReferenceValue);
}
}
}
#endif
}
I'm not really sure how to describe it exactly so let me show you what is going on.
I have a PlayerControls script which looks like this (note: I stripped everything except for the necessities).
namespace Player.Controls {
internal class PlayerControls: MonoBehaviour {
public bool IsClimbing { get; private set; } = false;
public bool IsGrounded { get; private set; } = false;
}
}
These variables are set in this class depending if the player is climbing/touching the ground. This script resides on the "Player" GameObject in the scene.
I have another script called PlayerControllerwhich looks like this
using Player.Controls;
public class PlayerController: Singleton<PlayerController> {
internal PlayerStats stats = new PlayerStats();
//PlayerStats nested class (see below)
}
The Singleton class only checks if the generic type is null, if it is, it will use FindObjectOfType to get an instance. This script also resides on the "Player" GameObject.
Inside the PlayerController class, I have a nested class called PlayerStats. It looks like this
internal class PlayerStats : PlayerControls {
public new bool IsClimbing { get { return base.IsClimbing; } }
public new bool IsGrounded { get { return base.IsGrounded; } }
}
Notice this nested class in inheriting from PlayerControls.
The idea is that the PlayerControls class in inaccessible to all other classes except for PlayerController, and any information I want to obtain regarding the player can be obtained by getting the player's instance (via the singleton) and accessing the PlayerStats variable.
For example, assuming the variable inside Singleton which holds the instance is called Instance, one could do PlayerController.Instance.stats.IsClimbing; Everything works as expected, except for one thing.
In the Awake method of the PlayerController class, I do this
private void Awake() {
Debug.LogFormat("In PlayerController Awake(). Is PlayerController.stats null? {0}",
(stats.Equals(null) ? "Yes" : "No"));
Debug.LogFormat("IsClimbing : {0}", stats.IsClimbing);
}
In the output window, it prints
In PlayerController Awake(). Is PlayerController.stats null? Yes
IsClimbing : False
If I also put the same IsClimbing debug in the Update() method, the value is correct for when I start climbing.
So, finally, my question, how can I access the variables of the PlayerStats class with the stats variable if stats is null? I thought it may have been somehow calling straight to the PlayerControls properties, so I changed their names, removed the new inside of PlayerStats and even put a debug statement inside one of the properties inside PlayerStats, and it definitely gets called. For example,public bool IsClimbing { get { Debug.Log("Called IsClimbing inside PlayerStats."); return base.Climbing; } }
If it is getting called and working properly, how can it be null? I asked my professor and he doesn't seem to know why either. What is really going on here?
Edit:
As requested, the Singleton class:
public abstract class Singleton<T>: MonoBehaviour where T : MonoBehaviour {
private static T instance;
public static T Instance {
get {
if(instance == null) {
instance = FindObjectOfType<T>();
}
return instance;
}
}
}
Here is an image of the console output.
Digging around on the Unity forums it appears that the Equals method has been overridden (on Object which MonoBehaviour eventually derives from) which is why comparing a MonoBehaviour to null is not giving you what you might expect. The answer I link to suggests code like this is more appropriate:
stats == null || stats.Equals(null)
I am trying to get all my abilities to follow instructions from a superclass to decrease the amount of repetitive code. I tried doing this by passing in Monobehavior as a parameter in constructor. This would work perfectly, except I get a warning saying I simply can't do this. Here is my super class.
public class Ability : MonoBehaviour {
private SpriteRenderer renderer;
private MonoBehaviour ability;
public Ability(MonoBehaviour b) {
ability = b;
renderer = ability.GetComponent<SpriteRenderer>();
}
public void Start () {
}
void Update () {
}
public void checkAvailability()
{
if (ability.GetComponentInParent<SpeedBall>().getAvail())
{
renderer.enabled = true;
}
else
renderer.enabled = false;
}
public void updateRenderer()
{
renderer.enabled = true;
renderer.transform.position = ability.GetComponentInParent<BoxCollider>().transform.position;
renderer.transform.localScale = new Vector3(.2f, .2f, 0);
}
and here is one of the child classes, which would work perfectly.
public class Sprite : MonoBehaviour {
private Ability ability;
void Start () {
ability = new Ability(this);
}
// Update is called once per frame
void Update () {
ability.updateRenderer();
ability.checkAvailability();
}
}
This is untraditional, but it should work. Is there anyway to accomplish this same thing without passing in Monobehavior. I can't extend multiple classes, and I need it to extend MonoBehavior. Thanks for any help!
You can access base class inherit members into derived class. For clarification suppose you have ExtendMonobhevior class (Your own version of Monobehaviour with additional functionalities).
class MonoBehaviourExtended : MonoBehaviour {
//your extended featuer of MonoBehaviour goes here
}
Now you can drive your normal classes(which you want to attach with gameobjects) from MonoBehaviourExtended(your custom extended version of MonoBehaviour ) it also contains MonoBehaviour
//inherit with extended monobehviour also contains extended features
public class Player : MonoBehaviourExtended {
//your normal class functinality
}
//inherit with extended monobehviour also contains extended features
public class Enemy : MonoBehaviourExtended
{
//your normal class functinality
}
And you get full access to the MonoBehaviour also.
In order to get variable(s), function(s) in another class, I have known 2 ways of doing this. First, is to use Get Component to the Script that we want to get the variable(s), function(s) into. Second, is to use Instance of the Script itself.
So I have made the following code:
First case: Get Component to the Script itself
public class Manager : MonoBehaviour
{
private AnotherManager _anotherManager;
private void Awake()
{
_anotherManager = GameObject.Find("Managers").GetComponent<AnotherManager>();
}
private void Start()
{
_anotherManager.myIntVariable = 10;
_anotherManager.MyFunction();
}
}
public class AnotherManager : MonoBehaviour
{
public int myIntVariable;
public void MyFunction()
{
}
}
Second case: Use Instance of the Script itself
public class Manager : MonoBehaviour
{
private void Start()
{
AnotherManager.instance.myIntVariable = 10;
AnotherManager.instance.MyFunction();
}
}
public class AnotherManager : MonoBehaviour
{
public static AnotherManager instance;
public int myIntVariable;
private void Awake()
{
instance = this;
}
public void MyFunction()
{
}
}
My question is: Is there any difference between those cases? In terms of good practice of coding for programmer or performance or it is just a matter of programmer's perspective or whatever else?
Thanks
The second example is the what is known as the Singleton Pattern and should be used very sparingly.
I try to never use the first approach either where you find the gameobject and hope it exists.
You can expose a field for the Unity Inspector so that you can wire it up the same as you can expose any other variable
public AnotherManager AnotherManager;
Alternatively, if you hate using public all over the place like that, like me, you can also indicate to Unity that you wish to expose this variable in the inspector with the SerializeField attribute
[SerializeField]
private AnotherManager anotherManager;
With both of these methods, you can then drag an an object that has the AnotherManager component attached into the field in the inspector.
If instantiated objects need access to this, you will need to wire it up when it is instantiated.
If you need help attaching it in unity I can attach some screenshots.