Event to detect ActiveSelf - c#

Is there a listener event to detect if an object is set to inactive from active state or to active from inactive state? I do not want to add it in Update as there will be multiple calls and it would affect my game's performance. So is there an alternative for this?
public GameObject Go_1;
public GameObject Go_2;
void Update () {
if (Go_1.activeSelf) {
} else if (Go_2.activeSelf) {
}
}

You could implement something using the Update method like e.g.
public class ActiveSelfWatcher : MonoBehaviour
{
private Dictionary<ActiveSelfProvider, bool> _lastActiveSelfState = new Dictionary<ActiveSelfProvider, bool>();
private void OnObjectBecameActive(GameObject obj)
{
Debug.Log($"{obj.name} became active!", this);
}
private void OnObjectBecameInactive(GameObject obj)
{
Debug.Log($"{obj.name} became inactive!", this);
}
private void Update()
{
// Iterate through all registered instances of ActiveSelfProvider
foreach(var provider in ActiveSelfProvider.Instances)
{
// pre-cache the GameObject reference
var obj = provider.gameObject;
// pre-cache the current activeSelf state
var currentActive = obj.activeSelf;
if(!_lastActiveSelfState.TryGetValue(provider))
{
// we don't know this provider until now
// TODO here you have to decide whether you want to call the events now once for this provider or not
// TODO otherwise it is only called for providers you already know and changed their state
}
if(currentActive != _lastActiveSelfState[provider])
{
// the state is not the one we stored for this instance
// => it changed its states since the last frame
// Call the according "event"
if(currentActive)
{
OnObjectBecameActive(obj);
}
else
{
OnObjectBecameInactive(obj);
}
}
// store the current value
_lastActiveSelfState[provider] = currentActive;
}
}
}
This is your watcher class you currently already have anyway.
Then on all the objects you want to be able to watch you use
public class ActiveSelfProvider : MonoBehaviour
{
private static readonly HashSet<ActiveSelfProvider> instances = new HashSet<ActiveSelfProvider>();
public static HashSet<ActiveSelfProvider> Instances => new HashSet<ActiveSelfProvider>(instances);
private void Awake()
{
// register your self to the existing instances
instances.Add(this);
}
private void OnDestroy()
{
// remove yourself from the existing instances
instances.Remove(this);
}
}
If this is "efficient enough" for you use case you would have to test ;)
If you want to go super fancy, someone once made a Transform Interceptor .. a quite nasty hack which on compile time overrules parts of the Unity built-in Transform property setters to hook in callbacks.
One probably could create something like this also for SetActive ;)
Note: Typed on smartphone but I hope the idea gets clear

Yes, try with OnEnable and OnDisable.
// Implement OnDisable and OnEnable script functions.
// These functions will be called when the attached GameObject
// is toggled.
// This example also supports the Editor. The Update function
// will be called, for example, when the position of the
// GameObject is changed.
using UnityEngine;
[ExecuteInEditMode]
public class PrintOnOff : MonoBehaviour
{
void OnDisable()
{
Debug.Log("PrintOnDisable: script was disabled");
}
void OnEnable()
{
Debug.Log("PrintOnEnable: script was enabled");
}
void Update()
{
#if UNITY_EDITOR
Debug.Log("Editor causes this Update");
#endif
}
}

Related

PUN2: Specify an instanced player gameObject through RPC call

I'm aware you can't pass gameObject references through RPC calls since every client uses it's own unique references for every instanciation. Is there a way to refer to a specific gameObject on ALL clients via an RPC call?
Here's a sample of what i'm trying to accomplish
if(Input.GetKeyDown("r")){
PhotonView.RPC("ChangeReady", RPCTarget.AllBuffered, gameObject)
}
[PunRPC]
void ChangeReady(GameObject PLAYER) {
PlayerScript PSCRIPT = PLAYER.GetComponent<PlayerScript>();
if (PSCRIPT.READY)
{
PSCRIPT.GetComponent<PlayerScript>().READY = false;
}
else {
PSCRIPT.READY = true;
}
}
I am trying to make a ready check system for players so they can toggle ready on and off and the information is transmitted to the other clients. Not sure how to refer to the specific gameObject since as previously mentioned it's impossible to do in RPC.
How would I go about doing this?
There are a few ways of doing this, if all you want to do is set a ready state:
a) Use CustomProperties, which allows you to store player specific data in a key-value pairs. and then, you can locally check for these values on your player(s) GameObjects and change their behaviours with your RPC. here is an Example:
bool ready = (bool)PhotonNetwork.player.customProperties["Ready"];
Hashtable hash = new Hashtable();
hash.Add("Ready", true);
PhotonNetwork.player.SetCustomProperties(hash);
Follow this link for info on CustomProperties
b) Use OnPhotonInstantiate by implementing IPunMagicInstantiateCallback On your Monobehaviour
void OnPhotonInstantiate(PhotonMessageInfo info)
{
// e.g. store this gameobject as this player's charater in Player.TagObject
info.sender.TagObject = this.GameObject;
}
the TagObject helps you associate a GameObject with a particular player. So once they are instantiated, This is called automatically, and Your GameObject can be tied to the player and retrieved using Player.TagObject and Execute your ready state logic (Via RPC or any other way)
Follow this link for info about OnPhotonInstantiate
See Photon RPC and RaiseEvent => "You can add multiple parameters provided PUN can serialize them" -> See Photon - Supported Types for Serialization
=> No it is not directly possible.
Solution
You will always need a way to tell other devices which object you are referring to since they don't have the same refernences as you.
If you are referring to objetcs of your Photon Players, though you can instead send over the
PhotonNetwork.LocalPlayer.ActorNumber
and on the other side get its according GameObject via the ActorNumber.
There are multiple ways to do so.
I once did this by having a central class like
public class PhotonUserManager : IInRoomCallbacks, IMatchmakingCallbacks
{
private static PhotonUserManager _instance;
private readonly Dictionary<int, GameObject> _playerObjects = new Dictionary<int, GameObject>();
public static bool TryGetPlayerObject(int actorNumber, out GameObject gameObject)
{
return _instance._playerObjects.TryGetValue(actorNumber, out gameObject);
}
[RuntimeInitializeOnLoadMethod]
private static void Init()
{
_instance = new PhotonUserManager();
PhotonNetwork.AddCallbackTarget(_instance);
}
public void OnCreateRoomFailed(short returnCode, string message){ }
public void OnJoinRoomFailed(short returnCode, string message){ }
public void OnCreatedRoom() { }
public void OnJoinedRoom()
{
Refresh();
}
public void OnLeftRoom()
{
_playerObjects.Clear();
}
public void OnJoinRandomFailed(short returnCode, string message){ }
public void OnFriendListUpdate(List<FriendInfo> friendList) { }
public void OnPlayerEnteredRoom(Player newPlayer)
{
Refresh();
}
public void OnPlayerLeftRoom(Player otherPlayer)
{
if(_playerObjects.ContainsKey(otherPlayer.ActorNumber))
{
_playerObjects.Remove(otherPlayer.ActorNumber);
}
}
public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged){ }
public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps){ }
public void OnMasterClientSwitched(Player newMasterClient){ }
private void Refresh()
{
var photonViews = UnityEngine.Object.FindObjectsOfType<PhotonView>();
foreach (var view in photonViews)
{
var player = view.owner;
//Objects in the scene don't have an owner, its means view.owner will be null => skip
if(player == null) continue;
// we already know this player's object -> nothing to do
if(_playerObjects.ContainsKey(player.ActorNumber)) continue;
var playerObject = view.gameObject;
_playerObjects[player.ActorNumber] = playerObject;
}
}
}
This automatically collects the GameObject owned by the joined players. There is of course one limitation: If the players spawn anything themselves and own it .. it might fail since then there are multiple PhotonViews per player => in that case use the one with the lowest NetworkIdendity. In Photon the viewIDs are composed of first digit = ActorNumber + 3 digits ids ;)
and later do
if(!PhotonUserManager.TryGetPlayerObject(out var obj))
{
Debug.LogError($"Couldn't get object for actor number {receivedActorNumber}");
}
As another option you can use OnPhotonInstantiate
public class SomeComponentOnYourPlayerPrefab : PunBehaviour
{
public override void OnPhotonInstantiate(PhotonMessageInfo info)
{
// only set if not already set in order to not
// overwrite the tag for a player that spawns something later
if(info.sender.TagObject == null)
{
info.sender.TagObject = gameObject;
}
}
}
and then later do
var obj = PhotonNetwork.LocalPlayer.Get(receivedActorNumber).TagObject;

Unity 3d pass bool variable between two objects

how i can pass a simple boolean variable between two different object?
I can try this but didn't work...
First script:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public bool active = false;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.tag == "Obstacle")
{
active = true;
}
}
}
Second script (that read the boolean variable "active")
public class EmptyControllerColl : MonoBehaviour
{
public CollisionController controller;
public PlayerMovement movement;
public bool activeLocal = false;
private void Start()
{
GetComponentInChildren<CollisionController>();
}
void Update()
{
activeLocal = controller.active;
if(activeLocal == false)
{
Debug.Log("Nothing...");
}
if(activeLocal == true)
{
Debug.Log("Game over");
}
}
}
When the variable bool "Active" change its status, the variable "activeLocal" don't change status.. How can I resolve this problem?
Collision Controller is "connect" to Cube Object.
EmptyControllerColl is "connect" to emptyGameObject (parent of Cube).
This line
_ = GameObject.Find("cubo Variant").GetComponent<CollisionController>().active;
makes no sense. First of all there is no field or variable declared with the name _ so this shouldn't even compile at all. And secondly what do you need this for? Rather store the according reference once in the controller field and reuse it later.
Then for your usecase there is no need at all to store the value in a local variable ... this makes things only more complicated. Simply where you need it get the value from controller.active.
Also do not use == for tags. Rather check via CompareTag. The problem is that == silently fails if you have any typo or the tag doesn't exist at all. CompareTag rather throws an error that the given tag is not valid.
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
}
void Update()
{
// No need to store this in a local field at all
if(!controller.active)
{
Debug.Log("Nothing...");
}
// use if else since both cases are exclusive and you don't even need to check the value twice
else
{
Debug.Log("Game over");
}
}
}
Event Driven - part A
In general you should avoid poll checks for a bool value in Update and rather come up with a more event driven solution! An example could look like:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
// Here everyone who wants can add listeners that get called as soon as
// we invoke this event. We will do it everytime the 'active' value is changed
public event Action<bool> OnActiveStateChanged;
// Backing field for 'active'
private bool _active;
// Property that reads and writes '_active'
// Everytime it is assigned it also invokes 'OnActiveStateChanged'
private bool active
{
get { return _active; }
set
{
_active = value;
OnActiveStateChanged?.Invoke(_active);
}
}
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
active = true;
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
// register a callback. It is allowed an save to unregister first
// which makes sure this is only registered exactly once
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
controller.OnActiveStateChanged += HandleControlerActiveStateChanged;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void HandleControlerActiveStateChanged(bool value)
{
if(!value)
{
Debug.Log("Nothing...");
}
else
{
Debug.Log("Game over");
}
}
private void OnDestroy()
{
// always clean up listeners
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
}
}
This now is way more efficient since you don't all time run an Update method. Instead the HandleControlerActiveStateChanged is only called when the value of active is actually changed.
Event Driven - part B
And then actually in your case there is need to use a bool at all you could use a simple event Action instead and remove all the bools entirely:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public event Action OnGameOver;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
OnGameOver?.Invoke();
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
private void Awake()
{
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
controller.OnGameOver -= HandleGameOver;
controller.OnGameOver += HandleGameOver;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void OnDestroy()
{
controller.OnGameOver -= HandleGameOver;
}
}
using UnityEngine;
public class CollisionController : MonoBehaviour
{
void Start()
{
// Calls the function ApplyDamage with a value of 5
// Every script attached to the game object
// that has an ApplyDamage function will be called.
gameObject.SendMessage("ApplyDamage", 5.0);
}
}
public class EmptyControllerColl : MonoBehaviour
{
public void ApplyDamage(float damage)
{
print(damage);
}
}
https://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html

gaze always execute even after PointExit

I have a problem about my gaze on VR . What I am trying to do is that gaze upon the button I want to select then hide the first gameobject parent then show the second gameobject parent . Now the second gameobject parent will be shown and when I try to gaze upon the back button it will show the first gameobject parent and hide the second gameobject parent . The problem occurs here, when I am trying to nothing and don't gaze on the buttons it automatically show my second gameobject parent and go back to first parent gameobject and hide and show and hide and show always.
public float gazeTime = 2f;
private float timer;
private bool gazedAt;
public Setting setting;
private void Start()
{
}
public void Update()
{
if (gazedAt)
{
timer += Time.deltaTime;
if (timer >= gazeTime)
{
// execute pointerdown handler
ExecuteEvents.Execute(gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerDownHandler);
timer = 0f;
}
else
{
return;
}
}
else
{
return;
}
}
public void PointerEnter()
{
gazedAt = true;
Debug.Log("PointerEnter");
}
public void PointerExit()
{
gazedAt = false;
Debug.Log("PointerExit");
}
//Method for going to setting
public void Setting()
{
setting.ActivateSetting();
}
//Method for going back to main menu
public void GoBack()
{
setting.GoBackToMainMenu();
}
Here's how my Setting code is setup
public GameObject setting;
public GameObject back;
public void ActivateSetting()
{
setting.SetActive(false);
back.SetActive(true);
}
public void GoBackToMainMenu()
{
back.SetActive(false);
setting.SetActive(true);
}
What I want is that it will only show the gameobject parent if I gaze upon it.
After calling the click once you reset the timer but you didn't reset gazedAt
=> the Update method did still run the timer and call the click again.
It seems that your PointerExit is not called at all and therefore the button never reset.
Instead of the EventTrigger I would strongly recommend to use the interfaces IPointerEnterHandler and IPointerExitHandler like
public class YourClass : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
//...
public void OnPointerEnter()
{
}
public void OnPointerExit()
{
}
I would actually not use Update at all but prefer a Coroutine. Also don't use the complex call of ExecuteEvents.Execute(gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerDownHandler); instead use either Getcomponent<Button>().onClick.Invoke(); or call the method directly
private Button _button;
private void Awake()
{
// Get the reference only once to avoid
// having to get it over and over again
_button = GetComponent<Button>();
}
private IEnumerator Gaze()
{
// wait for given time
yield return new WaitForSeconds(gazeTime);
// call the buttons onClick event
_button.onClick.Invoke();
// or as said alternatively directly use the methods e.g.
setting.ActivateSetting();
}
public void OnPointerEnter()
{
Debug.Log("PointerEnter");
// start the coroutine
StartCoroutine(Gaze());
}
public void OnPointerExit()
{
Debug.Log("PointerExit");
// stop/interrupt the coroutine
StopCoroutine(Gaze());
}
As you can see there is no need at all for the timer and gazedAt values so you can't forget to reset them somewhere. It also avoids that the method is called repeatedly.
If you don't want to use a Button at all you could also add your own UnityEvent like
// add callbacks e.g. in the Inspector or via script
public UnityEvent onGazedClick;
// ...
onGazedClick.Invoke();

Delegates and Events triggering on particular instance

I have the following class which defines an event and delegate. I then have a method which fires the event like this:
public class HealthManager : MonoBehaviour {
// Create the delegate
public delegate void ObjectDeath(GameObject go);
// Create the event
public static event ObjectDeath OnObjectDeath;
public static void GiveDamage(GameObject dealer, GameObject receiver, float amount){
/*
Subtract health here
... Code Snipped ...
*/
if(objectHealth.health <= objectHealth.deathHealth){
// Trigger the delegate/event
OnObjectDeath(receiver);
}
}
}
I then have this class which listens for the event to get fired like this:
public class DeathListener : MonoBehaviour {
// Add event listeners when object gets enabled
void OnEnable () {
HealthManager.OnObjectDeath += Died;
}
// Remove event listeners when object gets disabled
void OnDisable () {
HealthManager.OnObjectDeath -= Died;
}
void Died(GameObject go){
if(gameObject == go){
Destroy(gameObject);
}
}
}
The question I have is, is it possible to fire the event on just particular objects?
So, instead of doing this:
// Trigger
OnObjectDeath(receiver);
// ---------------------------- //
void Died(GameObject go){
if(gameObject == go){
Destroy(gameObject);
}
}
I can do something like this:
receiver.OnObjectDeath();
// ---------------------------- //
void Died(){
Destroy(gameObject);
}
I would like to have a way where in the event Died I don't have to check which object to apply it on. Currently if I remove the if statement, then it will apply that to all gameObjects not just the instance I am working with.
You seem to misuse the event pattern in your case. The point of event/listener is that the sender sends the info regardless of who is listening.
But in your case, the sender is aiming at one specific instance and you actually have a reference to it already. Then you might as well get the component of that object and call a method on it.
public static void GiveDamage(GameObject dealer, GameObject receiver, float amount){
/*
Subtract health here
... Code Snipped ...
*/
if(objectHealth.health <= objectHealth.deathHealth){
// Trigger the delegate/event
receiver.GetComponent< DeathListener >().Died(); // no need for parameter
}
}
EDIT: Based on your comment, a more appropriate way (in my opinion) would be to have a IDamageable interface with Damage method. Then all items that should be damaged should implement the method so they get damaged properly. Then you can simply calls the Damage method with the amount of damage and it will take care of the death if needed.
Let's consider a bullet system:
public void Bullet:MonoBehaviour{
public int damage = 10;
// it is hitting either from raycast or collision or else
void OnHit(GameObject collidedObject){
IDamageable dam = collidedObject.GetComponent<IDamageable>();
// if you hit a wall or no damageable item, it will be false
if(dam != null) {
dam.Damage(damage);
}
}
}
The bullet is oblivious to what it is hitting and should not care. Only matters how much it should damage.
public class Player : MonoBehaviour , IDamageable{
int health = 20;
public EventHandler <EventArgs> RaiseDeathEvent;
protected void OnDeath(EventArgs arg){
if(RaiseDeathEvent != null) RaiseDeathEvent(this, arg);
}
public void Damage(int damage){
this.health -= damage;
if(this.health < 0){
Died();
OnDeath(null);
}
}
}
There you go. If you need other items, like UI or scene manager to be informed of death so that they can perform an action, then you would get those to find the event and register to it. The player would not even know about them.
The problem is with the static event because it will be shared across the HealthManager class instances so i did this :
public class HealthManager : MonoBehaviour {
// Create the delegate
public delegate void ObjectDeath(GameObject go);
// Create the event
public event ObjectDeath OnObjectDeath;
//this method must not be marked static also
public void GiveDamage(GameObject dealer, GameObject receiver, float amount){
/*
Subtract health here
... Code Snipped ...
*/
if(objectHealth.health <= objectHealth.deathHealth){
// Trigger the delegate/event
OnObjectDeath();
}
}
}
And in the DeathListener class we have :
public class DeathListener : MonoBehaviour {
public HealthManager _hm;
void Awake(){
_hm = GetComponent<HealthManager>();
}
// Add event listeners when object gets enabled
void OnEnable () {
_hm.OnObjectDeath += Died;
}
// Remove event listeners when object gets disabled
void OnDisable () {
_hm.OnObjectDeath -= Died;
}
void Died(){
Destroy(gameObject);
} }
Now only the owner of each component will be destroyed separately not all of them together
Please consider any syntax error in the code because i wrote this from your own partial code ...
Further note : you can even add a [RequireComponent (typeof (HealthManager))] to your DeathListener class so it can't be added without the healthmanager !

Why is FindObjectOfType(MyType) not finding anything?

Why would I get the following log?
Start OpponentMotionReceiver
motion receiver not found
true
public class ScoreAnimation : MonoBehaviour
{
private OpponentMotionReceiver cachedObject;
private void Start()
{
cachedObject = FindObjectOfType<OpponentMotionReceiver>();
}
private void OnDestroy()
{
var motionReceiver = FindObjectOfType<OpponentMotionReceiver>();
if (motionReceiver == null)
{
Debug.Log("motion receiver not found");
}
if(cachedObject != null)
{
//prints true, another proof that the gameObject is active
Debug.Log(cachedObject.gameObject.activeInHierarchy);
}
}
}
public class OpponentMotionReceiver : MonoBehaviour
{
private void Start()
{
Debug.Log("Start OpponentMotionReceiver");
}
private void OnDisable()
{
Debug.Log("OnDisable OpponentMotionReceiver");
}
private void OnDestroy()
{
Debug.Log("OnDestroy OpponentMotionReceiver");
}
}
P.S. This is extremely simplified version of the code so that the rest brings no confusion. If you need more details, I'd be pleased to answer you!
Changing the scene. Only at that time unity behaves like that. No matter if the object we are looking for is active or not, simply FindObjectOfType doesn't work at this point of the game cycle.
From https://gamedev.stackexchange.com/a/112783/59620
Have you tried to call method by using typeof i.e.FindObjectOfType(typeof(OpponentMotionReceiver)) as stated in official document ?
When OnDestroy is called in your component the GameObject is already deactivated. It's likely already torn out of the normal hierarchy.
Also, if the scene is leaving, it looks like all GameObject instances in the scene that are not set to DontDestroyOnLoad are first deactivated.
When OnDestroy is called all the objects you are looking for are already deactivated.

Categories