I'm new to events and delegates and I'm having trouble making the functional connection between things, specifically the events manager and how events get connected to specific buttons in my program. So far I've created a few delegates and then I have a class that should subscribe to those delegates but I don't get how this system actually gets connected to buttons on different scenes. I just need someone to help me make the connection so that I can see how everything functions. Thank you.
Here is my event manager class
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Event_Manager : MonoBehaviour
{
public static Event_Manager evt = null; // create singleton
public delegate void GoToStartingSceneDelegate();
public static GoToStartingSceneDelegate onGoToStartingSceneDelegate;
public delegate void GoToSelectionSceneDelegate();
public static GoToSelectionSceneDelegate onGoToSelectionSceneDelegate;
public delegate void GoToColoringSceneDelegate();
public static GoToColoringSceneDelegate onGoToColoringSceneDelegate;
public delegate void GoToCaveSceneDelegate();
public static GoToCaveSceneDelegate onGoToCaveSceneDelegate;
private void Awake()
{
if (evt == null)
evt = this;
else if (evt != null)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
}
public static void OnStartGameButtonClick()
{
Debug.Log("Start Game");
if (onGoToStartingSceneDelegate != null)
onGoToStartingSceneDelegate();
}
public static void OnStartOverButtonClick()
{
Debug.Log("Start Over");
if (onGoToSelectionSceneDelegate != null)
onGoToSelectionSceneDelegate();
}
public static void OnSendAnimalButtonClick()
{
Debug.Log("Send Animal");
if (onGoToCaveSceneDelegate != null)
onGoToCaveSceneDelegate();
}
public static void OnPlayAgainButtonClick()
{
Debug.Log("Play Again");
if (onGoToStartingSceneDelegate != null)
onGoToStartingSceneDelegate();
}
}
and this is the class that I'm wanting to subscribe to those events
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections;
using UnityEngine.UI.ProceduralImage;
public class LoadSceneButtonController : MonoBehaviour
{
[SerializeField] public Object startingScene;
[SerializeField] public Object selectionScene;
[SerializeField] public Object coloringScene;
[SerializeField] public Object caveScene;
GameObject transitionManager;
public GameObject touchToPlayButton;
public GameObject sendAnimalToForestButton;
public GameObject startOverButton;
private void OnEnable()
{
Event_Manager.onGoToStartingSceneDelegate += GoToStartingScene;
Event_Manager.onGoToSelectionSceneDelegate += GoToSelectionScene;
Event_Manager.onGoToColoringSceneDelegate += GoToColoringScene;
Event_Manager.onGoToCaveSceneDelegate += GoToCaveScene;
}
private void OnDisable()
{
Event_Manager.onGoToStartingSceneDelegate -= GoToStartingScene;
Event_Manager.onGoToSelectionSceneDelegate -= GoToSelectionScene;
Event_Manager.onGoToColoringSceneDelegate -= GoToColoringScene;
Event_Manager.onGoToCaveSceneDelegate -= GoToCaveScene;
}
void GoToStartingScene()
{
SceneManager.LoadScene(startingScene.name.ToString());
}
void GoToSelectionScene()
{
SceneManager.LoadScene(selectionScene.name.ToString());
}
void GoToColoringScene()
{
SceneManager.LoadScene(coloringScene.name.ToString());
}
void GoToCaveScene()
{
SceneManager.LoadScene(caveScene.name.ToString());
}
Your code is not working but the problem has nothing to do with your events or delegates.
The problem is how you declared your scene variables. Do not use UnityEngine.Object for that. If you do, you can only cast it to SceneAsset which is from the UnityEditor namespace. This means that your code will only work in the Editor with the AssetDatabase class.
Use UnityEngine.SceneManagement instead of UnityEngine.Object. You are currently passing Object.name to the SceneManager.LoadScene function and Object.name refers to the name of the Object not a scene name.
Replace all:
[SerializeField] public Object startingScene;
[SerializeField] public Object selectionScene;
[SerializeField] public Object coloringScene;
[SerializeField] public Object caveScene;
with
public Scene startingScene;
public Scene selectionScene;
public Scene coloringScene;
public Scene caveScene;
Here is a simple Unity tutorial for events and delegates. I don't think you need that since that part of your code looks fine.
As for connecting it to your buttons, it is very easy. Just subscribe to the onClick.AddListener button event and notify the Event_Manager event when there is a click.
public Button startingButton;
public Button selectionButton;
public Button coloringButton;
public Button CaveButton;
void OnEnable()
{
//Register Button Events
startingButton.onClick.AddListener(() => buttonCallBack(startingButton));
selectionButton.onClick.AddListener(() => buttonCallBack(selectionButton));
coloringButton.onClick.AddListener(() => buttonCallBack(coloringButton));
CaveButton.onClick.AddListener(() => buttonCallBack(CaveButton));
}
private void buttonCallBack(Button buttonPressed)
{
if (buttonPressed == startingButton)
{
Debug.Log("Clicked: " + startingButton.name);
Event_Manager.OnStartGameButtonClick();
}
if (buttonPressed == selectionButton)
{
Debug.Log("Clicked: " + selectionButton.name);
Event_Manager.OnStartOverButtonClick();
}
if (buttonPressed == coloringButton)
{
Debug.Log("Clicked: " + coloringButton.name);
Event_Manager.OnSendAnimalButtonClick();
}
if (buttonPressed == CaveButton)
{
Debug.Log("Clicked: " + CaveButton.name);
Event_Manager.OnSendAnimalButtonClick();
}
}
Related
I have a gameObject that needs to call lots of other functions. I was hoping to do something like this
Where I can select the gameobject and script for it to run. I can't figure out how to do this.
I'm calling a function on button click for a gui.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class AvatarSystem : MonoBehaviour
{
public Button general;
public Button face;
public Button hair;
public Button eyebrows;
public Button clothing;
// Start is called before the first frame update
void Start()
{
var root = GetComponent<UIDocument>().rootVisualElement;
general = root.Q<Button>("general");
face = root.Q<Button>("face");
hair = root.Q<Button>("hair");
eyebrows = root.Q<Button>("eyebrows");
clothing = root.Q<Button>("clothing");
general.clicked += generalClicked;
face.clicked += faceClicked;
hair.clicked += hairClicked;
eyebrows.clicked += eyebrowsClicked;
clothing.clicked += clothingClicked;
}
void generalClicked()
{
}
void faceClicked()
{
}
void hairClicked()
{
}
void eyebrowsClicked()
{
}
void clothingClicked()
{
}
}
The 5 bottom functions should do something like the top image. Thanks
You can this script which creates buttons in the inspector.
This should be outside of your AvatarSystem class.
using Unity.Editor;
[CustomEditor(typeof(AvatarSystem))]
public class AvatarSystemGUI : Editor
{
public override void OnInspectorGUI()
{
AvatarSystem t = AvatarSystem(target);
if (GUILayout.Button("Test"))
{
t.ExampleMethod();
}
}
}
Let me know of any problems! :)
You want to use a Unity Event
You can use UnityEvent<T> to pass a dynamic type in your function.
Then you can use Unity IPointerClickHandler to capture a click on the object.
You can use AddListener of UnityEvent;
you can write the code like this;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class AvatarSystem : MonoBehaviour
{
public Button general;
public Button face;
public Button hair;
public Button eyebrows;
public Button clothing;
// Start is called before the first frame update
void Start()
{
var root = GetComponent<UIDocument>().rootVisualElement;
general = root.Q<Button>("general");
face = root.Q<Button>("face");
hair = root.Q<Button>("hair");
eyebrows = root.Q<Button>("eyebrows");
clothing = root.Q<Button>("clothing");
general.onClick.AddListener(() => {
this.generalClicked();
this.faceClicked();
this.hairClicked();
this.eyebrowsClicked();
this.clothingClicked();
}) ;
}
void generalClicked()
{
}
void faceClicked()
{
}
void hairClicked()
{
}
void eyebrowsClicked()
{
}
void clothingClicked()
{
}
}
In short. I have a tunnel building game where every tunnel piece has a Vector2 ChunkedPostion that generally describes where they are located in world space.
The problem is I need to Recalculate some things when one of these tunnels is destroyed and it would take longer than necessary to reload the whole map. Therefore, I'm hoping someone could tell me how to pass along this Chunkedpostion in an event that's called on the destruction of a tunnel so each tunnel could quickly check if the event is relevant to them before making the calculations.
simplified code:
public class transmitter : MonoBehaviour
{
public delegate void SendSignel();
public static event SendSignel OnSendSignel;
Vector2 ChunkedPostion;
private void Start()
{
ChunkedPostion = new Vector2(transform.position.x, transform.position.z);
}
void Update()
{
if (Input.GetKeyDown("s"))
{
OnSendSignel();//<= i would like to pass along ChunkedPostion in the Event
}
}
}
public class receiver : MonoBehaviour
{
//and then have someway to "Debug.Log" the recieved coordinates
private void OnEnable() { transmitter.OnSendSignel += PrintSignel; }
private void OnDisable() { transmitter.OnSendSignel -= PrintSignel; }
void PrintSignel()
{
Debug.Log("received");
}
}
Working Code:
public class transmitter : MonoBehaviour
{
//public delegate void SendSignel();
public delegate void SendSignel(Vector2 SenderPostion);
public static event SendSignel OnSendSignel;
Vector2 ChunkedPostion;
private void Start()
{
ChunkedPostion = new Vector2(transform.position.x, transform.position.z);
}
void Update()
{
if (Input.GetKeyDown("s"))
{
OnSendSignel(ChunkedPostion);//<= i would like to pass along ChunkedPostion in the Event
}
}
}
public class receiver : MonoBehaviour
{
//and then have someway to "Debug.Log" the recieved coordinates
private void OnEnable() { transmitter.OnSendSignel += PrintSignel; }
private void OnDisable() { transmitter.OnSendSignel -= PrintSignel; }
//void PrintSignel()
//{
// Debug.Log("received");
//}
void PrintSignel(Vector2 SenderPostion)
{
Debug.Log("received");
Debug.Log("at: " + SenderPostion);
}
}
I'm pretty sure you're using the wrong syntax, at least in the question you are.
public static event SendSignel OnSendSignel;
//should be
public static event EventHandler<Vector2> OnSendSignal
using this you'll be able to provide OnSendSignal a vector2 when you invoke it
I'm confused about finding reference GameObject but different Scene and set the onclick when difference scene, so I have GameManager who manage all but available only on the Main menu. So I decide to make Dontdestroyonload, the issue start at this, so when I play to the MainGame Scene, the field of GameManager at inspector will find, but I can't drag n drop different scene, right? That confuses me.
And if the GameManager at the MainMenu scene, the question is how to drag n drop at the onClick event, like I want pause button active or something else in the game.
]3
I tried with onLloadscene(scene s, Mode mode), but nothing happens, and here the scrip for the GameManager. :
public static GameManager gameManager;
[Header("Main Menu panels")]
public GameObject startPanel;
public GameObject settingPanel;
public GameObject levelPanel;
[Header("InGame Panels")]
#region Panel
public GameObject pausePanel;
public GameObject ObjectivePanel;
public GameObject shopPanel;
private int click = 0;
[Header("Int Tweaks")]
public int indexLevel;
public int onlevel;
public bool isPaused;
_levelSelect LevelSelect;
public static GameManager Instance { set; get; }
public int levelindexPlayerPrefs;
private void Awake()
{
if (gameManager != null)
{
Instance = this;
Destroy(gameObject);
}
else
{
DontDestroyOnLoad(gameObject);
}
}
void Start()
{
LevelSelect = FindObjectOfType<_levelSelect>();
OnStart();
onlevel = int.Parse(LevelSelect.levelIndex) + 1;
indexLevel = int.Parse(LevelSelect.levelIndex);
getPlayerData();
}
// Update is called once per frame
void Update()
{
ExitApp();
}
public void OnStart()
{
startPanel.SetActive(true);
settingPanel.SetActive(false);
levelPanel.SetActive(false);
}
#region Buttons
public void startbutton()
{
levelPanel.SetActive(true);
startPanel.SetActive(false);
settingPanel.SetActive(false);
}
public void backButtonMainMenu()
{
levelPanel.SetActive(false);
startPanel.SetActive(true);
settingPanel.SetActive(false);
}
public void settingbutton()
{
levelPanel.SetActive(false);
startPanel.SetActive(false);
settingPanel.SetActive(true);
}
public void PauseButton()
{
Time.timeScale = 0f;
pausePanel.SetActive(true);
ObjectivePanel.SetActive(false);
}
public void Resume()
{
Time.timeScale = 1f;
}
#endregion
public void ExitApp()
{
if (Input.GetKey(KeyCode.Escape))
{
click++;
StartCoroutine(ClickTime());
if (click>1)
{
print("Exit Game");
Application.Quit();
}
}
}
IEnumerator ClickTime()
{
yield return new WaitForSeconds(0.5f);
click = 0;
}
public void getPlayerData()
{
levelindexPlayerPrefs = PlayerPrefs.GetInt("LevelIndex", 0);
}
public void updateLevel(int Index)
{
if (levelindexPlayerPrefs < Index)
{
PlayerPrefs.SetInt("LevelIndex", Index);
levelindexPlayerPrefs = PlayerPrefs.GetInt("LevelIndex");
}
}
#region onloadedScenePickRefferences
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
pausePanel = GameObject.FindGameObjectWithTag("PausePanel");
ObjectivePanel = GameObject.FindGameObjectWithTag("ObjectivePanel");
}
#endregion
//public IEnumerator EndChapter()
//{
// updateLevel(indexLevel + 1);
// getPlayerData();
//}
Here is what I would probably do:
Have a static class for storing and sharing all your references. It doesn't have to be in any scene but simply "lives" in the assets:
public static class GlobalReferences
{
// as example just for one reference but you can implement the rest equally yourself
// here this class actually stores the reference
private static GameObject startPanel;
// A public property in order to add some logic
// other classes will always access and set the value through this property
public static GameObject StartPanel
{
get
{
// if the reference exists return it right away
if(startPanel) return startPanel;
// as a fallback try to find it
startPanel = GameObject.FindGameObjectWithTag("StartPanel");
// ofcourse it might still fail when you simply try to access it
// in a moment it doesn't exist yet
return startPanel;
}
set
{
startPanel = value;
// invoke an event to tell all listeners that the startPanel
// was just assigned
OnStartPanelReady?.Invoke();
}
}
// An event you will invoke after assigning a value making sure that
// other scripts only access this value after it has been set
// you can even directly pass the reference in
public static event Action<GameObject> OnStartPanelReady;
}
So now in your component(s) that is(are) in the new loaded scene you assign the value as early as possible (Awake). Here you can already store it via the Inspector since it is a scene reference:
public class ExampleSetter : MonoBehaviour
{
// already reference it via the Inspector
[SerializeField] private GameObject startPanel;
private void Awake()
{
// as a fallback
if(!startPanel) startPanel = GameObject.FindObjectWithTag("startPanel");
// assign it to the global class
GlobalReferences.StartPanel = startPanel;
}
}
And in other scenes that where already loaded before you add a listener so they do their stuff as soon as the other scene is ready:
public class ExampleConsumer : MonoBehaviour
{
[Header("Debug")]
[SerializeField] private GameObject startPanel;
private void Awake()
{
// Try to get the reference
startPanel = GlobalReferences.StartPanel;
// if this failed then wait until it is ready
if(!startPanel)
{
// it is save to remove callbacks even if not added yet
// makes sure a listener is always only added once
GlobalReferences.OnStartPanelReady -= OnStartPanelReady;
GlobalReferences.OnStartPanelReady += OnStartPanelReady;
}
// otherwise already do what you want
else
{
OnStartPanelReady(startPanel);
}
}
private void OnDestroy()
{
// always make sure to clean up callbacks when not needed anymore!
GlobalReferences.OnStartPanelReady -= OnStartPanelReady;
}
private void OnStartPanelReady(GameObject newStartPanel)
{
startPanel = newStartPanel;
// always make sure to clean up callbacks when not needed anymore!
GlobalReferences.OnStartPanelReady -= OnStartPanelReady;
// NOTE: It is possible that at this point it is null anyway if another
// class has set this actively to null ;)
if(startPanel)
{
// Now do something with the startPanel
}
}
}
The other way round when you need a reference in the new loaded Scene form the main scene ... it should already be set since the mainscene was loaded first and has already assigned its according references.
Now you can either go for this static class or simply implement the same logic for each reference that needs to be shared directly in an according component where you reference them via drag&drop .. it makes no difference since anyway you will use static fields and events that are not bound to any instance but the type(s) itself.
I'm making an event that does the failLevel stuff when it fires off. For that I have made a delegate
public delegate Coroutine FailGame(IEnumerator function);
public static event FailGame gameFailedEvent;
like so and I subscribed the appropriate function to it
void Start ()
{
gameFailedEvent += StartCoroutine;
}
It works when it is called from the same script like so:
gameFailedEvent(WaitThenFailLevel());
when this WaitThenFailLevel() looks like this:
IEnumerator WaitThenFailLevel()
{
CharacterController2D.playerDied = true;
if (CharacterController2D.animState != CharacterController2D.CharAnimStates.fall)
{
CharacterController2D.currentImageIndex = 0;
CharacterController2D.animState = CharacterController2D.CharAnimStates.fall;
}
CharacterController2D.movementDisabled = true;
yield return new WaitForSeconds(0.2f);
StartCoroutine(ScaleTime(1.0f, 0.0f, 1.2f));
}
It works fine here. Now, I have another object that can kill the player (dangerous times I know) and I don't want to copy paste everything again, I just want it to fire off the static event made in the script above.
I DID try making the WaitThenFailGame function
public static
and make static all my other ienumerators but I got an error named "An object reference is required for non-static field..."
Hence I tried the event stuff.
All well and fine, but I can't call the event from the other script because I can't pass it the function from the script mentioned.
What to do now?
Here is the example code:
EventContainor.cs
public class EventContainer : MonoBehaviour
{
public static event Action<string> OnGameFailedEvent;
void Update()
{
if (Input.GetKey(KeyCode.R))
{
// fire the game failed event when user press R.
if(OnGameFailedEvent = null)
OnGameFailedEvent("some value");
}
}
}
Listener.cs
public class Listener : MonoBehaviour
{
void Awake()
{
EventContainer.OnGameFailedEvent += EventContainer_OnGameFailedEvent;
}
void EventContainer_OnGameFailedEvent (string value)
{
StartCoroutine(MyCoroutine(value));
}
IEnumerator MyCoroutine(string someParam)
{
yield return new WaitForSeconds(5);
Debug.Log(someParam);
}
}
using UnityEngine;
public class ScriptA : MonoBehaviour
{
public ScriptB anyName;
void Update()
{
anyName.DoSomething();
}
}
using UnityEngine;
public class ScriptB : MonoBehaviour
{
public void DoSomething()
{
Debug.Log("Hi there");
}
}
This is linking functions between scripts , Copied from Here, maybe coroutines are the same, Then you need to start the coroutine in void Start() {} , You may find this useful as well.
I'm a Chinese developers, English is not very good, please predecessors read it for editing, thanks in advance.
In Unity, i write one script to listener an Event and in delegate function, i add another script the same listener in previous Event, but why the same listener be called immediately?
The following is add another script the same listener script:
using System.Collections.Generic;
using UnityEngine;
public class SceneManager : MonoBehaviour
{
private static SceneManager m_Instance;
public static SceneManager Instance
{
get
{
if (m_Instance == null)
{
m_Instance = (SceneManager)FindObjectOfType(typeof(SceneManager));
}
if (!m_Instance)
{
Debug.LogError("SceneManager could not find himself!");
}
return m_Instance;
}
}
[HideInInspector]
public List<EventDelegate> EventDelegetDo = new List<EventDelegate>();
public void RegisterBackBtnDo()
{
EventDelegate.Add(EventDelegetDo, Do);
}
public void UnRegisterBackBtnDo()
{
EventDelegate.Remove(EventDelegetDo, Do);
}
private void Start()
{
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.T))
{
EventDelegate.Execute(EventDelegetDo);
}
}
private void Do()
{
Debug.Log("===============ZJDebug=============== From SceneManager");
}
}
The following is initial register listener script:
using UnityEngine;
public class TDelegate : MonoBehaviour
{
public void RegisterBackBtnDo()
{
EventDelegate.Add(SceneManager.Instance.EventDelegetDo, Do);
}
public void UnRegisterBackBtnDo()
{
EventDelegate.Remove(SceneManager.Instance.EventDelegetDo, Do);
}
private void Start()
{
RegisterBackBtnDo();
}
private void Update()
{
}
private void Do()
{
Debug.Log("===============ZJDebug=============== From TDelegate");
SceneManager.Instance.RegisterBackBtnDo();
//Invoke("WaitForTest", float.NegativeInfinity);
}
private void WaitForTest()
{
SceneManager.Instance.RegisterBackBtnDo();
}
}
I have tried multiple variations of this, if invoke float.NegativeInfinity second register it not be called Immediately, can someone tell me why? in addition to delay call There is no better way called by after? English is too bad please forgive me thanks!