Search Unity

OnGUI() confusion. Need help clarifying for me.

Discussion in 'Scripting' started by asperatology, Aug 1, 2015.

  1. asperatology

    asperatology

    Joined:
    Mar 10, 2015
    Posts:
    981
    I have two objects in the scene, and both of them are from the same prefab. The prefab has a script component attached with the following code:

    Code (CSharp):
    1.     public void OnGUI() {
    2.         if (Input.GetKeyDown(KeyCode.S)) {
    3.             //... Some action
    4.             Debug.Log("Hello world.");
    5.         }
    6.     }
    If I press the S key on my keyboard, the Unity Console tab will show up 2 log messages.

    Now, I have only one object in the scene. Same prefab and same script component. But this time, I Instantiate() the prefab into the scene at the start of the game, therefore the end result is I have 2 of the objects in the scene. I pressed the S key, and the Unity Console tab shows up 1 log message.

    Does this mean Instantiating a prefab at runtime will only allow the player to trigger the log message once for each prefabs placed in the scene? That also means I need to use Update() instead for all instantiated game objects in the scene if I were to have the script component be triggered for all the instantiated game objects of the same prefab?
     
  2. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I have not tested, but instantiating should have no effect on how your script works. If it worked before, it should work with instantiating as well. Are you sure you have two of the same gameobjects in the scene, both with the same component? Maybe you forgot to hit apply to the prefab after you made your component so the instantiated one isnt actually the same as the non instantiated.
     
  3. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Never use "Input" in OnGUI()- there's all sorts of complicated reasons for this involving OnGUI firing multiple times per frame, etc... but the rule of thumb is just to not do it. If you want a GUI to respond to user input in this manner (not just Button/etc behaviour), you need to use some form of boolean trigger (or the Event class).

    Also don't use OnGUI() unless you're making editor scripts- it's been deprecated for this use, and you should look into the new UI (canvas) system instead.

    Instantiated objects from prefabs will have identical scripts attached to them- there's no rule stating that a script (component) will only work when attached to one object or anything like that. If the GameObject or script are disabled, their functions will not run, but beyond that there's nothing that should be keeping an instantiated object from responding to user input.
     
  4. asperatology

    asperatology

    Joined:
    Mar 10, 2015
    Posts:
    981
    OnGUI() only works for one of the two clones of the same prefab I placed in the scene. It doesn't work for the second clone, depending on whichever one was selected in the game.

    I'm not using the OnGUI() for UI related stuffs, but rather game related input event handling. Does that mean I've been using it the wrong way the whole time?

    It actually depends on whichever one was selected to trigger the Input.GetKeyDown() event. I feel I made a very very wrong mistake since the start of the whole project....

    I also rewrote the input triggering code so that it loops through a list of all selected clones of the prefab in the scene and individually trigger the actions I wanted to do. It works normally, especially on compiled binary builds, but I feel something is amiss here...
     
  5. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Yes. You should handle input in Update() or with events rather than constant checks. Forget that OnGUI exists completely until you want to start getting into editor scripts (extending Unity functionality) at some point in the future. I have the feeling that I'm missing something about how you're doing things, so please show the whole script of where and how you're instantiating this prefab.
     
    Last edited: Aug 2, 2015
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    OnGUI can be used to handle input. It gives some additional flexibility. But in 99% of cases its better to use Update.

    OnGUI gets called multiple times per frame, in totally predictable ways. Once per frame and once per input event, plus once for a layout event prior to each of the calls mentioned above. However using it means a lot of event checking to figure out which call you are in. And in most cases the ton of extra calls is not worth the results.
     
  7. asperatology

    asperatology

    Joined:
    Mar 10, 2015
    Posts:
    981
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class Divisible : MonoBehaviour {
    6.     private Selectable ownerSelectable;
    7.     private Selectable spawnedSelectable;
    8.     private Attackable ownerAttackable;
    9.     private NetworkView playerNetworkView;
    10.     private bool isReady;
    11.     private bool canDivide;
    12.    
    13.     public int numberOfUnitsPerSpawn = 1;
    14.     public float cooldownTimer = 15f;
    15.     public GameObject spawnUnitPrefab;
    16.     public SplitManager splitManager;
    17.  
    18.     public void Start() {
    19.         this.ownerSelectable = this.GetComponent<Selectable>();
    20.         this.SetDivisibleReady();
    21.         this.playerNetworkView = this.GetComponent<NetworkView>();
    22.         this.ownerAttackable = this.GetComponent<Attackable>();
    23.         this.canDivide = true;
    24.  
    25.         if (this.spawnUnitPrefab == null) {
    26.             Debug.LogError(new System.NullReferenceException("Spawn Unit Prefab in Divisible is currently null. Please check  the owning unit's Divisble component."));
    27.         }
    28.  
    29.         this.splitManager = GameObject.Find("Split Manager").GetComponent<SplitManager>();
    30.         if (this.splitManager == null) {
    31.             Debug.LogError("Split Manager is wrong here.");
    32.         }
    33.     }
    34.  
    35.     public void OnGUI() {
    36.         if (this.playerNetworkView.isMine) {
    37.             if (Input.GetKeyDown(KeyCode.S) && this.ownerSelectable.isSelected && this.isReady && (!this.ownerAttackable.isReadyToAttack || !this.ownerAttackable.isAttacking) && this.canDivide) {
    38.  
    39.                 for (int j = 0; j < Selectable.selectedObjects.Count; j++) {
    40.                     Selectable ownerSelectable = Selectable.selectedObjects[j];
    41.                     if (ownerSelectable == null) {
    42.                         continue;
    43.                     }
    44.                     ownerSelectable.Deselect();
    45.                     Divisible ownerDivisible = ownerSelectable.gameObject.GetComponent<Divisible>();
    46.                     ownerDivisible.SetDivisibleNotReady();
    47.  
    48.                     for (int i = 0; i < this.numberOfUnitsPerSpawn; i++) {
    49.                         Vector3 spawnedLocation = ownerSelectable.gameObject.transform.position;
    50.                         GameObject unit = (GameObject) Network.Instantiate(Resources.Load("Prefabs/Player"), spawnedLocation, Quaternion.identity, 0);
    51.                         //Clones will not have parentheses around the remote node label (client, or server).
    52.                         unit.name = (Network.isClient ? "client " : "server ") + System.Guid.NewGuid();
    53.  
    54.                         HealthBar foo = unit.GetComponent<HealthBar>();
    55.                         HealthBar bar = this.gameObject.GetComponent<HealthBar>();
    56.                         if (foo != null && bar != null) {
    57.                             foo.currentHealth = bar.currentHealth;
    58.                             foo.maxHealth = bar.maxHealth;
    59.                             foo.healthPercentage = bar.healthPercentage;
    60.                         }
    61.  
    62.                         Selectable spawnedSelectable = unit.GetComponentInChildren<Selectable>();
    63.                         spawnedSelectable.Deselect();
    64.                         ownerSelectable.Deselect();
    65.  
    66.                         if (!Debugging.debugEnabled) {
    67.                             NetworkView view = unit.GetComponent<NetworkView>();
    68.                             if (this.playerNetworkView != null && view != null) {
    69.                                 float randomValue = Random.Range(-180f, 180f);
    70.                                 this.playerNetworkView.RPC("RPC_Add", RPCMode.AllBuffered, ownerSelectable.GetComponent<NetworkView>().viewID, view.viewID, randomValue);
    71.                             }
    72.                         }
    73.                         else {
    74.                             Debug.LogWarning("Debug flag is enabled.");
    75.                         }
    76.                     }
    77.                 }
    78.             }
    79.         }
    80.     }
    81.  
    82.     [RPC]
    83.     private void RPC_Add(NetworkViewID first, NetworkViewID second, float randomValue) {
    84.         NetworkView firstView = NetworkView.Find(first);
    85.         NetworkView secondView = NetworkView.Find(second);
    86.  
    87.         Divisible div = firstView.gameObject.GetComponent<Divisible>();
    88.         div.SetDivisibleNotReady();
    89.  
    90.         Divisible div2 = secondView.gameObject.GetComponent<Divisible>();
    91.         div.SetDivisibleNotReady();
    92.  
    93.         HealthBar foo = firstView.gameObject.GetComponent<HealthBar>();
    94.         HealthBar bar = secondView.gameObject.GetComponent<HealthBar>();
    95.         foo.Copy(bar);
    96.  
    97.         //Debug.Log((Network.isClient ? "(client)" : "(server)") + " is now making SpawnUnit struct object.");
    98.         this.splitManager.spawnGroups.Add(new SpawnGroup(firstView.gameObject, secondView.gameObject, this.cooldownTimer, randomValue));
    99.     }
    100.  
    101.     public void SetDivisible(bool flag) {
    102.         this.canDivide = flag;
    103.     }
    104.  
    105.     public bool IsDivisible() {
    106.         return this.canDivide;
    107.     }
    108.  
    109.     public bool IsDivisibleStateReady() {
    110.         return this.isReady;
    111.     }
    112.  
    113.     public void SetDivisibleReady() {
    114.         this.isReady = true;
    115.     }
    116.  
    117.     public void SetDivisibleNotReady() {
    118.         this.isReady = false;
    119.     }
    120. }
    121.  
    That's how I have been using it. From the docs, OnGUI() is used for input handling, and that's how I learned about using Inputs. I don't know now at this point, because the game works as intended, or it "seems" to work.

    :(
     
  8. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Wich documentation would that be? I put my Input code into OnGUI when I first started out with Unity and wondered why my character would do two steps instead of one for each button press. I was quickly corrected and I carry taht advice over to you, and echo what others have said already: Don't use Input in OnGUI. Use it in Update().
     
    Kiwasi likes this.
  9. asperatology

    asperatology

    Joined:
    Mar 10, 2015
    Posts:
    981
    Okay, I'll heed it.