Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Dispatching user inputs using events. Is this the right way?

Discussion in 'Scripting' started by RalphM, Feb 12, 2015.

  1. RalphM

    RalphM

    Joined:
    Feb 12, 2015
    Posts:
    3
    Hello guys,

    I am quite new to Unity3D and after following all kind of tutorials, I see that too often user inputs (keyboard, touches, ..) are read inside some PlayerController. So, this script handles the input AND updates the player GameObject.

    Now, I am in a situation where I would like to remove the input-related code from the PlayerController to another class, let's say a KeyboardInputController. The reason is mainly, that I would like in a near future be able to easily support touch inputs, controllers and keyboards. (Note: Using keyboard inputs is much faster for debugging, but my goal is to develop for mobile first.)

    So, my initial approach was to use delegates and events.

    I implement a singleton class called KeyboardInputController extending MonoBehaviour, where I read the keyboard specific inputs and publish an event accordingly. So basically, I create jump events in the update method and move events in the FixedUpdate method.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class KeyboardInputController : MonoBehaviour {
    5.  
    6.     // Singleton Pattern
    7.     private static KeyboardInputController instance;
    8.  
    9.     public static KeyboardInputController Instance {
    10.         get {
    11.             if (!instance) {
    12.                 instance = GameObject.FindObjectOfType (typeof(KeyboardInputController)) as KeyboardInputController;
    13.                 if (!instance)
    14.                     Debug.LogError ("No active KeyboardInputController script ony any GameObject.");
    15.             }
    16.             return instance;
    17.         }
    18.     }
    19.    
    20.     public delegate void KeyboardEvent ();
    21.     public delegate void KeyboardMoveEvent (float horizontalValue);
    22.  
    23.     public event KeyboardEvent OnJumpEvent;
    24.     public event KeyboardMoveEvent OnMoveEvent;
    25.    
    26.     void Update () {
    27.         if (Input.GetKeyDown (KeyCode.UpArrow)) {
    28.             if (OnJumpEvent != null) {
    29.                 OnJumpEvent ();
    30.             }
    31.         }
    32.     }
    33.  
    34.     void FixedUpdate() {
    35.         if (OnMoveEvent != null) {
    36.             OnMoveEvent (Input.GetAxis ("Horizontal"));
    37.         }
    38.     }
    39. }
    40.  
    Now, in the PlayerController, things become much easier. I only subscribe to the events and the right method will be called at the right time. (Right?)

    Code (CSharp):
    1. public class PlayerController : MonoBehaviour {
    2.  
    3. //...
    4.  
    5. void Start () {
    6.         KeyboardInputController.Instance.OnJumpEvent += Jump;
    7.         KeyboardInputController.Instance.OnMoveEvent += Move;
    8.     }
    9.  
    10.     void OnDisable() {
    11.         KeyboardInputController.Instance.OnJumpEvent -= Jump;
    12.         KeyboardInputController.Instance.OnMoveEvent -= Move;
    13.     }
    14.  
    15. // ...
    16.  
    17. public void Move(float moveInputX) { /* Apply some force ... */}
    18. public void Jump() { /* Apply some force ... */ }
    19. // Note: Yes, those 2 methods could stay private
    20.  
    21. }
    So, so far it seems to work as before. So, it seems to me that, I can now start making some super InputController class, and make a KeyboardInputController inherit from it and just implements the Update, FixedUpdate methods. Then the same for a TouchInputController.

    However, I haven't found much about this particular approach (handling inputs with events, etc) on the internet (maybe I don't use the right search terms...). So finally, my questions to you are:

    1) Is this the correct way to remove Input-related code from Player behaviour? Knowing that a few other GameObject will need to react to the exact same user inputs but in a different way obviously.

    2) Are there any performance issues with this approach, especially on mobile devices?

    3) How have you guys implemented something similar? Maybe differently?

    Thank you in advance for your help!

    In case, this is indeed the right approach, I hope it is useful :D

    RalphM
     
    Deleted User likes this.
  2. quizcanners

    quizcanners

    Joined:
    Feb 6, 2015
    Posts:
    107
    Your way is too complicated for me to understand, i don't even know delegates. But I recently made a card game for tablet devices and PC. And it is not one game, it is one deck, one table, but few games, and each game is a different script. So here is how I did touch and mouse input to do the same thing and I think this is the simplest way:

    For mouse I use

    Code (CSharp):
    1. void OnMouseOver(){
    2.      if(Input.GetMouseButtonDown(1))
    3. flagCardTouched=true;
    4. }
    5.  
    Code (CSharp):
    1. void OnMouseUp() {
    2.         flagCardDrop = true;
    3.     }
    I put those inside of a card script so OnMouse... is only called when my mouse is on the card.

    For touch I used camera to find what I am touching:

    Code (CSharp):
    1. if (Input.touchCount > 0)
    2.         {
    3.             for(int i = 0; i < Input.touchCount; i++)
    4.                 if (Input.GetTouch (i).phase == TouchPhase.Began)
    5.             {
    6.                 Ray ray = Camera.main.ScreenPointToRay( Input.GetTouch(i).position );
    7.                 RaycastHit hit;
    8.              
    9.                 if ( Physics.Raycast(ray, out hit) && hit.collider.gameObject.tag == "Card")
    10.                 {
    11.                     hit.collider.GetComponent<Card>().flagCardTouched=true;
    12.                 }else
    13.                     Deck.PlayHittableSound();
    14.             }
    15.         }
    And I attach this script to my camera.

    I had no ptoblems with controls while using this method. So for everyone who are making game for tablet device, try to make all input methods change some flags on your game objects and then your code will just work with the flags. And if something not working with tablet you will be sure that it is just not changing flags the right way. You'll know where to fix it.
     
  3. RalphM

    RalphM

    Joined:
    Feb 12, 2015
    Posts:
    3
    Thank you for your reply. Flags definitely look simpler at first. But somehow it lacks a clear structure in my opinion. For example: MouseOver sets a flag to true. Then who sets the flag to false? OnMouseExit should do it, right? Otherwise, it becomes spaghetti code if someone else is allowed to change flags.
    In case that OnMouseExit unsets the flag, could it be possible that other scripts misses a fast changing flag? This last scenario is not possible using events, I think.
     
  4. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Quite frankly - your critique of the approach should have ended there.

    @RalphM It's unfortunate that it's so difficult to abstract Unity's control scheme into something more generic. I'd love something akin to
    Code (csharp):
    1.  
    2. KeyCode[] keys = Input.AllKeysThisFrame();
    3.  
    which would make player-mapping of keys much nicer. Then you could do this
    Code (csharp):
    1.  
    2. Dictionary<KeyCode, Action> mapping;
    3.  
    4. if (mapping.TryGetValue(keys[0], out action)
    5. {
    6.     action();
    7. }
    8.  
     
  5. quizcanners

    quizcanners

    Joined:
    Feb 6, 2015
    Posts:
    107

    The thing about the flags. They are not reset. Card object holds it until code for the game, that player is playing now, resets it. Game's script is checking if card is touched and if according to game rules player can move this card or turn it, script calls Card's function "TurnMe()" or "DragMe()" . Only then it resets the flag. So objects in my game are not directly interactible, they are proxy-interactable trough GameController.

    There is a lot of flag setting and reseting in my game. Let's say you want to highlight a card if it is pointed at. So if you point, card flag changes to pointed and it drows outline. But if you are not pointing, card should be changed to not pointed, you need to detect it somehow. And it is complicated, so here is what I do: during every update if card is pointed I drow outline and change flag pointed to false. Then next frame if card is pointed at flag is changed to true again and so on. It sounds redundant, but it kind of works for many things.

    *I added a timer for flag to stay pointed=true for 10 frame updates because OnMouseOver is not called per frame.
     
  6. quizcanners

    quizcanners

    Joined:
    Feb 6, 2015
    Posts:
    107
    Well, ser, it is not a critique in any way, read it again. There is a part where he asked:

    3) How have you guys implemented something similar? Maybe differently?
     
  7. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    My advice:

    1) You might not need to bother making a "KeyboardController" and "JoystickController" and "PhoneController", etc, since Unity's standard input is designed to avoid that. Avoid ever using Input.GetKey() and instead use Input.GetButton(). If you look under Edit -> Project Preferences -> Input, you will see a list of buttons (conveniently there is already a "Jump" button) and you can assign "Jump" to multiple things, like the keyboard up arrow (default is Space bar) and a joystick button, then you just check for "Jump" and it will work regardless of what type of input your player is using.

    2) Download the newish Unity Sample Assets from here http://unity3d.com/learn/resources/downloads and they have a "CrossPlatformInput" class that will help if you are planning on releasing on both mobile and PC.

    3) Events and delegates are a good way to go about it, but it all depends on the way the rest of the game is architected. One instance where you will find the delegate approach annoying: let's say that your game has multiple states, and normally the Jump button makes you jump, but when you have the menu open, the Jump button should select whatever button is highlighted. If you have lots of different states and you use events, you'll need to remember to manually unsubscribe and resubscribe to all the correct events from each of your different classes. If you were instead just checking the input in Update() in each one, you could easily handle the states by just disabling the components that shouldn't be updating. This won't work with delegates because the events will fire on the component whether or not it's actually enabled. So really, you'll kind of have to try different approaches and see what works best for your particular game. I think there are a lot of different possible approaches, which is why there is not currently a more standard way of wrapping input into events in Unity.
     
    Deleted User likes this.
  8. RalphM

    RalphM

    Joined:
    Feb 12, 2015
    Posts:
    3
    @KelsoMRK Yes, it's unfortunate. This should be more trivial. I like this idea of mapping keys to actions. Maybe Unity 5 might have some new things in the future.

    @makeshiftwings Thank you for this complete answer! I'll check the Unity Sample Assets.
    Good point about the challenge with states.
     
  9. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You mean Input.inputString? I've used this to similar effect.
     
  10. RuinsOfFeyrin

    RuinsOfFeyrin

    Joined:
    Feb 22, 2014
    Posts:
    785
    @RalphM @makeshiftwings

    I actually think the use of events is an excellent way to handle this, and I don't think having different states in the game play is that big of a factor as there as some easy ways to handle this.

    You could check the game state (this should be pretty easily globally accessible) in the function that is getting called before you do anything else and if you are in the wrong state simply return out of the function.

    Or simply set up the game state so that it is a property of a class somewhere and inside the SET for the property you assign and un-assign the needed handlers based on the game state.
     
  11. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    AFAIK that doesn't handle all keys. It's certainly a start, although doing string parsing for user input is sort of a bummer.
     
    Kiwasi likes this.