Unity can't identify which button is beeing pressed - c#

I've been trying to make the options in my game being selected by a keyboard input. I can highlight them, but I don't know how to make Unity identify which of the buttons is beeing pressed in order to do a certain action, it's giving me the NullReferenceException in line 28 of my code. The cript in question is the BattleSystem script, it's attached to the event system, the battleFirstButton is the fight button and the enterKey is "Z".
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class BattleSystem : MonoBehaviour
{
public GameObject battleFirstButton;
public KeyCode enterKey;
Button selectedButton;
// Start is called before the first frame update
void Start()
{
EventSystem.current.SetSelectedGameObject(null);
EventSystem.current.SetSelectedGameObject(battleFirstButton);
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(enterKey))
{
selectedButton.onClick.Invoke();
}
}
public void SetSelectedButton()
{
selectedButton = GetComponent<Button>();
}
public void Fight()
{
print("Fight option submitted");
}
public void Act()
{
print("Act option submitted");
}
public void Item()
{
print("Item option submitted");
}
public void Mercy()
{
print("Get dunked o-, I mean, Mercy option selected");
}
}

selectedButton is a private variable potentially never set to anything, so it's null. Make sure it's set to something before you access it.
Probably the simplest fix to the way you have it set up is:
void Update()
{
if (Input.GetKeyDown(enterKey))
{
// Gets the focused button
selectedButton = EventSystem.current.currentSelectedGameObject.GetComponent<Button>();
if (selectedButton != null)
{
selectedButton.onClick.Invoke();
}
}

Related

Hide all tagged GameObjects upon button click

Trying to combine these two:
https://answers.unity.com/questions/1171111/hideunhide-game-object.html
Show/ hide Unity 3D elements
I got this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Modify_Menu_Button_Handler : MonoBehaviour
{
void Start()
{
GameObject[] buttons_in_create_menu;
buttons_in_create_menu = GameObject.FindGameObjectsWithTag("CreateMenu");
}
public void ChangeMenu()
{
foreach (GameObject button in buttons_in_create_menu)
{
button.SetActive(false);
}
}
}
But, the foreach line is red-underlined, saying the buttons_in_create_menu doesn't exist in the current context.
Coming from Javascript, I'm not sure how scope works in C# and especially within Unity's game loop. Thought the Start() function would be called upon the scene loading, based on the Unity docs: link
You must define the variable globally in the class.
Like this:
public class Modify_Menu_Button_Handler : MonoBehaviour
{
public GameObject[] buttons_in_create_menu;
void Start()
{
buttons_in_create_menu = GameObject.FindGameObjectsWithTag("CreateMenu");
}
public void ChangeMenu()
{
foreach (GameObject button in buttons_in_create_menu)
{
button.SetActive(false);
}
}
}

Error MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it

I have been making a 2D game in Unity. I'm trying to make a game over screen appear every time a ball touches the player. I also added advertisements to release it. I added Restart and Continue buttons. When you press continue, an ad shows and the game continues. When Restart is pressed, it resets the game and your score. Whenever I restart the game and then press continue, the ad is played, but the game does not continue.
Here is the collision script:
using UnityEngine;
public class hitDetect : MonoBehaviour
{
public GameObject menuContainer;
private void OnTriggerEnter2D(Collider2D other)
{
Debug.Log("HIT");
menuContainer.SetActive(true);
Time.timeScale = 0;
}
}
This is the script that restarts the game:
using UnityEngine;
using UnityEngine.SceneManagement;
public class RestartGame : MonoBehaviour
{
public void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
Time.timeScale = 1;
}
}
Here is the advertisement and continue script:
using UnityEngine;
using UnityEngine.Advertisements;
public class continueGame : MonoBehaviour, IUnityAdsListener
{
string placement = "rewardedVideo";
public GameObject menuContain;
private void Start()
{
Advertisement.AddListener(this);
Advertisement.Initialize("4006857", true);
}
public void Continue(string p)
{
Advertisement.Show(p);
}
public void OnUnityAdsReady(string placementId)
{
}
public void OnUnityAdsDidError(string message)
{
}
public void OnUnityAdsDidStart(string placementId)
{
}
public void OnUnityAdsDidFinish(string placementId, ShowResult showResult)
{
if(showResult == ShowResult.Finished)
{
menuContain.SetActive(false);
Time.timeScale = 1;
}
}
}
Once you call
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex)
a "new" scene is loaded and therefore anything currently existing is destroyed.
In continueGame you do
private void Start()
{
Advertisement.AddListener(this);
Advertisement.Initialize("4006857", true);
}
But you never remove that listener!
Thus the next time the Advertisement fires its event it is still trying to execute them of the now already destroyed continueGame instance.
Therefore you always should remove any listeners as soon as you don't need them anymore:
private void OnDestroy ()
{
Advertisement.RemoveListener(this);
}

How to know which prefab button I am clicking?

I made a couple of prefab buttons. When I click each one of them they go to the same scene, but I that scene to have different information according to which button I clicked. How can know which was the button I clicked? The function goArtistDetail is the one being called by every button.
I have this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ButtonScriptTwo : MonoBehaviour
{
// Use this for initialization
void Start ()
{
Debug.Log("ButtonScriptTwo");
Debug.Log("counter: " + GlobalState.counter);
}
public void goArtistDetail(Button button)
{
GlobalState.counter++;
Application.LoadLevel("ArtistDetail");
}
}
Prefab of the Button
is there any reason why you are trying to use the same method for both calls other than that the same scene is being loaded? If not why not just have a different method for each buttons onClick and set a PlayerPref.
public void ButtonClick_1()
{
GoArtistDetail(1);
}
public void ButtonClick_2()
{
GoArtistDetail(2);
}
// C# Methods start with a Captial
private void GoArtistDetail(int refererBtn)
{
GlobalState.counter++;
PlayerPrefs.SetInt("Referer Button", refererBtn)
Application.LoadLevel("ArtistDetail");
}
An then in the ArtistDetail scene
private void Start()
{
int refererBtn = 1; //Default value
if(PlayerPrefs.HasKey("Referer Button"))
{
refererBtn = PlayerPrefs.GetInt("Referer Button");
PlayerPrefs.DeleteKey("Referer Button");// Remove it if it is use once
}
//Evaluate refererBtn to show what info is relevant
}

How can I create a custom inspector guilayout.toggle?

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
Control control = (Control)target;
if (GUILayout.Toggle(control.isControl, "Control"))
{
control.ToControl();
}
}
}
And
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
public Rigidbody rigidbody;
public bool isControl = false;
// Start is called before the first frame update
void Start()
{
}
public void ToControl()
{
if(isControl == false)
{
}
else
{
Destroy(rigidbody);
}
}
}
What I want to do is a guilayout.toggle or a button and to be able to destroy and to add a Rigidbody to the gameobject the Control script will be on.
How do I create back add the Rigidbody to the gameobject ?
And how do I use the isControl flag ? The idea is to use the guilayout.toggle in the editor script.
I want to destroy or add a new rigidbody while the game is running ! But using a guilayout.toggle or button in the inspector.
Actually you wouldn't need an inspector script for that at all. Simply add a repeadetly check for the bool like e.g. in LateUpdate and make the component [ExecuteInEditoMode] like
using UnityEngine;
[ExecuteInEditoMode]
public class Control : MonoBehaviour
{
public Rigidbody rigidbody;
public bool isControl;
// repeatedly check the bool
private void LateUpdate()
{
ToControl();
}
public void ToControl()
{
if (!isControl && rigidbody)
{
// in editmode use DestroyImmediate
if (Application.isEditor && !Application.isPlaying)
{
DestroyImmediate(rigidbody);
}
else
{
Destroy(rigidbody);
}
rigidbody = null;
}
else if(isControl && !rigidbody)
{
rigidbody = gameObject.AddComponent<Rigidbody>();
// adjust settings of rigidbody
}
}
}
This way LateUpdate is called both, in playmode and in editmode, and will simply react to the isControl value.
Ofcourse there is an overhead for calling this LateUpdate all the time so if you want to avoid it you can call it only from the editor. However, since you are using base.OnInspectorGUI(); you don't really need an additional Toggle since you already have the one of the default inspector.
So could simply do
using UnityEngine;
public class Control : MonoBehaviour
{
public Rigidbody rigidbody;
public bool isControl;
public void ToControl()
{
if (!isControl && rigidbody)
{
if (Application.isEditor && !Application.isPlaying)
{
DestroyImmediate(rigidbody);
}
else
{
Destroy(rigidbody);
}
rigidbody = null;
}
else if(isControl && !rigidbody)
{
rigidbody = gameObject.AddComponent<Rigidbody>();
}
}
}
and in the editor script simply do
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
private Control control;
// calle when the object gains focus
private void OnEnable()
{
control = (Control)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (!control.isControl && control.rigidbody)
{
control.ToControl();
Repaint();
}
else if (control.isControl && !control.rigidbody)
{
control.ToControl();
Repaint();
}
}
}
BUT you will already notice that this might affect how Undo/Redo works - in this case it would e.g. reset the isControl value but not remove along the RigidBody component leading to errors (see more below)
Or since you asked it you can add the ToggleField (currently you will have it twice since one also ships with base.OnInspectorGUI();)
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
private Control control;
// calle when the object gains focus
private void OnEnable()
{
control = (Control)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
control.isControl = EditorGUILayout.Toggle("additional isControl", control.isControl);
if (!control.isControl && control.rigidbody)
{
control.ToControl();
Repaint();
}
else if (control.isControl && !control.rigidbody)
{
control.ToControl();
Repaint();
}
}
}
BUT you will notice that this solution changing the value using the additional isControl lacks the possibility of using Undo/Redo completely and it will NOT mark your scene as "dirty" so Unity might not save those changes!
So if you really want to have your custom toggle field in an inspector script I would actually recommend strongly to use proper SerializedPropertys instead of directly making changes to the target (sometimes it can't be avoided like with the adding of the component though):
[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
private SerializedProperty _isControl;
private SerializedProperty rigidbody;
private Control control;
// calle when the object gains focus
private void OnEnable()
{
control = (Control)target;
// link serialized property
_isControl = serializedObject.FindProperty("isControl");
rigidbody = serializedObject.FindProperty("rigidbody");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
// load current values into the serialized copy
serializedObject.Update();
if (!_isControl.boolValue && rigidbody.objectReferenceValue)
{
DestroyImmediate(rigidbody.objectReferenceValue);
rigidbody.objectReferenceValue = null;
}
else if (_isControl.boolValue && !rigidbody.objectReferenceValue)
{
var rb = control.gameObject.AddComponent<Rigidbody>();
rigidbody.objectReferenceValue = rb;
}
// write back changed serialized values to the actual values
serializedObject.ApplyModifiedProperties();
}
}
This looks more complicated and actually you have duplicate code but it gives you full Undo/Redo support and marks your objects and scenes dirty so Unity saves the changes.

Use EventSystem for key-pressing events

Context: say you're checking whether "W" is pressed on the keyboard, the most common way to check this is through the following code:
void Update(){
if (Input.GetKeyDown("W"))
DoSomething();
}
Is there a way to do the following, using Unity's EventSystem? In other words, is there an implementation of an interface like IPointerClickHandler, for example, to check whether a button is pressed, without doing so in an Update() function?
Is there a way to do the following, using Unity's EventSystem? In other words, is there an implementation of an interface like IPointerClickHandler,
No. The EventSystem is mostly used for raycasting and dispatching events. This is not used to detect keyboard events. The only component from the EventSystem that can detect keyboard events is the InputField component. That's it and it can't be used for anything else.
Check whether a button is pressed, without doing so in an Update()
function?
Yes, there is a way with Event.KeyboardEvent and this requires the OnGUI function.
void OnGUI()
{
if (Event.current.Equals(Event.KeyboardEvent("W")))
{
print("W pressed!");
}
}
This is worse than using the Input.GetKeyDown function with the Update function. I encourage you to stick with Input.GetKeyDown. There is nothing wrong with it.
If you are looking for event type InputSystem without Input.GetKeyDown then use Unity's new Input API and subscribe to the InputSystem.onEvent event.
If you are looking for feature similar to the IPointerClickHandler interface you can implement it on top of Input.GetKeyDown.
1.First, get all the KeyCode enum with System.Enum.GetValues(typeof(KeyCode)); and store it in an array.
2.Create an interface "IKeyboardEvent" and add functions such as OnKeyDown just like OnPointerClick in the IPointerClickHandler interface.
3.Loop through the KeyCode from #1 and check if each key in the array is pressed, released or held down.
4.Get all the components in the scene and check if they implemented the IKeyboardEvent interface. If they do, invoke the proper function in the interface based on the key status from #3.
Here is a functional example that can still be extended or improved:
Attach to an empty GameObject.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class KeyboardEventSystem : MonoBehaviour
{
Array allKeyCodes;
private static List<Transform> allTransforms = new List<Transform>();
private static List<GameObject> rootGameObjects = new List<GameObject>();
void Awake()
{
allKeyCodes = System.Enum.GetValues(typeof(KeyCode));
}
void Update()
{
//Loop over all the keycodes
foreach (KeyCode tempKey in allKeyCodes)
{
//Send event to key down
if (Input.GetKeyDown(tempKey))
senEvent(tempKey, KeybrdEventType.keyDown);
//Send event to key up
if (Input.GetKeyUp(tempKey))
senEvent(tempKey, KeybrdEventType.KeyUp);
//Send event to while key is held down
if (Input.GetKey(tempKey))
senEvent(tempKey, KeybrdEventType.down);
}
}
void senEvent(KeyCode keycode, KeybrdEventType evType)
{
GetAllRootObject();
GetAllComponents();
//Loop over all the interfaces and callthe appropriate function
for (int i = 0; i < allTransforms.Count; i++)
{
GameObject obj = allTransforms[i].gameObject;
//Invoke the appropriate interface function if not null
IKeyboardEvent itfc = obj.GetComponent<IKeyboardEvent>();
if (itfc != null)
{
if (evType == KeybrdEventType.keyDown)
itfc.OnKeyDown(keycode);
if (evType == KeybrdEventType.KeyUp)
itfc.OnKeyUP(keycode);
if (evType == KeybrdEventType.down)
itfc.OnKey(keycode);
}
}
}
private static void GetAllRootObject()
{
rootGameObjects.Clear();
Scene activeScene = SceneManager.GetActiveScene();
activeScene.GetRootGameObjects(rootGameObjects);
}
private static void GetAllComponents()
{
allTransforms.Clear();
for (int i = 0; i < rootGameObjects.Count; ++i)
{
GameObject obj = rootGameObjects[i];
//Get all child Transforms attached to this GameObject
obj.GetComponentsInChildren<Transform>(true, allTransforms);
}
}
}
public enum KeybrdEventType
{
keyDown,
KeyUp,
down
}
public interface IKeyboardEvent
{
void OnKeyDown(KeyCode keycode);
void OnKeyUP(KeyCode keycode);
void OnKey(KeyCode keycode);
}
Usage:
Implement the IKeyboardEvent interface and the functions from it in your script just like you would with IPointerClickHandler.
public class test : MonoBehaviour, IKeyboardEvent
{
public void OnKey(KeyCode keycode)
{
Debug.Log("Key held down: " + keycode);
}
public void OnKeyDown(KeyCode keycode)
{
Debug.Log("Key pressed: " + keycode);
}
public void OnKeyUP(KeyCode keycode)
{
Debug.Log("Key released: " + keycode);
}
}
I created an simples script to trigger simple event.
I used OnguiGUI instead of Update.
See it bellow!
using UnityEngine;
using UnityEngine.Events;
public class TriggerKey : MonoBehaviour
{
[Header("----Add key to trigger on pressed----")]
public string key;
// Unity event inspector
public UnityEvent OnTriggerKey;
public void OnGUI()
{ // triiger event on trigger key
if (Event.current.Equals(Event.KeyboardEvent(key)))
{
OnTriggerKey.Invoke();
print("test trigger btn");
}
}
}

Categories