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. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

Integrating NGUI and Rewired

Discussion in 'Assets and Asset Store' started by aer0ace, Jul 19, 2018.

  1. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    I've been having a hell of a time trying to figure out the best way to integrate the two packages.

    The most helpful recommendations I've seen are to override the NGUI UICamera input delegates with Rewired behavior, but that's a severe over simplification of the actual problem.

    NGUI's existing delegates and entire event system in UICamera have so much going under the hood, that swapping in Rewired seems to be a task that defeats the purpose of using both assets together for convenience.

    I would like to hear from other developers that have integrated or plan/planned to integrate these two assets.

    Have you built your game on NGUI and have difficulty integrating Rewired as I am?
    Have you built your game with Rewired first and now have difficulty using NGUI?

    Have you felt that Rewired was more valuable and chosen a different UI asset/Unity GUI, or had to scrap NGUI and integrate something else?

    Have you felt that NGUI has been invaluable with its performance and relative ease of development, and felt that it had good-enough controller support for your needs and felt that integrating Rewired was not worth the time and effort to integrate?

    Thoughts and advice would be greatly appreciated.
     
  2. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,218
  3. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    Thanks. I appreciate your response, but this is one of the sources I've found that led me to the statement:
    I'm looking more from an architectural standpoint, and I'm curious about what some projects did to design their Rewired Input system to work with NGUI. There are numerous systems that are at play here, like hit detection, dragging, and mouse relative vs. absolute coordinates, so, I guess my questions are more geared towards mouse input, but overall, UICamera is so loaded with functionality, that I'm not sure if swapping every instance of Input.<blah> with ReInput.... is enough to do a full-fledged implementation.

    Like, if you have a Rewired input manager, and forward those events to UICamera, surely there will be other systems in the game that consume the Rewired input. How do you prevent the input state from getting out of sync? Typically, that would be to have a single source of input, which every system draws from. That means I'll have to uncouple my input from the UICamera events, and create an intermediate event manager for Rewired specifically, and hope that forwarding these events to NGUI is enough to not break things. Ugh. Just talking out loud at this point.
     
  4. Steve-Tack

    Steve-Tack

    Joined:
    Mar 12, 2013
    Posts:
    1,240
    I have a game I've been poking at for like four years that uses Rewired and NGUI. My approach so far has been to not do any input integration at all, and just use NGUI (and TextMesh Pro) to output sprites and text.

    I had initially focused heavily on gamepad support, as I saw the game as mostly a PS4 game, so I wrote my own screen manager to handle gamepad-friendly navigation of my UI's.

    Now I'm thinking that a PC version would make sense, so at some point I'll have to add real mouse support. At the moment, the only mouse support I have is for indirectly manipulation of the UI - there's no using the mouse to press on-screen buttons, hover events, scroll bar dragging, or anything like that.

    So I'm curious at hear whatever you come up with.
     
  5. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    Thanks for your input (pun not originally intended).

    Well, I suppose I'm going to use this thread as a platform for me to talk about my trials during this process, at least until I formulate a solution that's suitable for a blog entry.

    So, I'm using NGUI's UICamera event handlers to capture mouse movement, buttons, and keyboard input. It's a turn-based board game, so I am opting to use the system hardware cursor rather than relying on a software cursor. I chose Rewired because I wanted to support gamepads and keyboard-only play. When I started to integrate Rewired, I noticed that there are only bindings for delta movement on GetAxis(), which is understandable, since this is what controllers can generically generate. It's the damn mouse that's the oddball, and I have to manage my absolute screen position separately from all other input. I have the discussion in the Rewired thread here.

    So, having accepted that, I am turning my attention to all other controllers that are NOT the mouse. The way I am approaching it for this first pass is, I'm going to implement a RewiredGameInputManager for capturing Rewired input Action events, and then use my event system to fire off events to all my other systems. For example, I want the Escape key, or a gamepad's 'Start' button to invoke the in-game Pause Menu. The RewiredGameInputManager would poll for that Rewired "PauseMenu" action, send off a PauseMenuMsg to the event system, and the UI would receive that event to invoke the actual popup menu, and the simulation would receive that same event to pause the simulation.

    Using my event system is what would take place of using UICamera's event handlers. I essentially will have to implement that layer myself, to also handle drag events and other events that I relied on UICamera to provide me. On top of this, I am not yet sure how to hook this up to NGUI. The only lead I have is the various sources that are similar to the one that @MostHated provided above. It's a reasonable solution, to basically override NGUI's input functions to capture Rewired input instead of Unity GUI input.

    Another interesting lead I had was that Rewired provides an example of overriding Unity Input with Rewired input, with the UnityInputOverride script. Since Unity GUI is somewhat based off of the NGUI implementation, I was hoping I can follow this model to completely re-vamp UICamera for Rewired. I'm going to try this as a last resort though, as I don't think I will need to go this route. However, I'm leaving the option open if all else fails.
     
    Last edited: Jul 20, 2018
  6. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,218
    It may take a while to get it, but have you thought about approaching either of the devs of rewired or ngui to see if they would mind making an integration? Tons of big assets make integrations for each other's products. It might be worth a shot?
     
  7. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    It might be. If anything, @guavaman (Rewired) might be willing to do it, but @ArenMook (NGUI) hasn't really shown much sign of support for NGUI lately, outside of Discord (though, I'll give him credit for frequent bug-fix updates even to this day). I have a feeling that since Unity GUI has taken greater adoption rates, that ArenMook hasn't been as proactive with support, considering it's essentially "first-party NGUI". But those of us who use NGUI know where it's superior to Unity GUI, and are not going to change over just yet.

    I created this thread partially to rally support for this effort, and/or to ask the community to see how much interest there really is.
     
  8. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    There is a lot of vague information in your various posts. It's really not clear to me ultimately what you're trying to achieve. It would be very helpful to have your goals clearly defined and enumerated. From reading your messages, it sounds to me like you're actually looking to add features to NGUI that don't exist rather than looking for a Rewired integration.

    In my view, an integration would do only one thing -- change the input source of NGUI from UnityEngine.Input to Rewired. That could be achieved in any number of ways, the simplest being using Unity Input Override. The second simplest is doing a find/replace of all Input.Get___ calls with ReInput.players.GetPlayer(0).Get___ calls and then replacing the GetKey calls with GetButton calls to equivalent Actions. Both of these methods will achieve a fully-functional integration, causing all input to be taken from Rewired instead of UnityEngine.Input. The 3rd method, and what I would do if I were to make an integration, would be to override the delegates in UICamera like others have suggested. That is the method the author of NGUI designed for you to be able to change input sources, so it's pretty safe to say that's the right way to do it.

    As far as an integration goes, I don't understand why there are any relative/absolute mouse axis issues at all. (Actually, this isn't an issue of relative and absolute, it's an issue of absolute screen pixel position vs an absolute axis value.) If NGUI wants an absolute X, Y mouse cursor position, just leave it as is. Rewired's not going to give you any different value than UnityEngine.Input. You can swap out Input.mousePosition for ReInput.controllers.Mouse.screenPosition, but it's not going to be any different. If NGUI wants an axis for joystick control, it will be using UnityEngine.GetAxis which also returns absolute axis values (-1 to +1). This is fully interchangeable with Rewired's Player.GetAxis. The only scenario I can see where the mouse screen pos/axis value would be any sort of an issue is the scenario you posted in Rewired's thread -- namely, trying to use a software sprite cursor to interact with NGUI. If NGUI wasn't written to allow for this, then adding this ability to NGUI is far beyond an integration. In my view, this is a feature request for NGUI and not something that should be added through a 3rd party tool integration. Because using Rewired to move a software mouse cursor is ultimately no different at all from using UnityEngine.Input to move a software cursor.
     
    Last edited: Jul 21, 2018
  9. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    I managed to find the source code to UICamera.cs from NGUI. (Careless public github upload.) I see nothing in the source code of UICamera that leads me to believe that NGUI supports a software cursor controlled by joystick axis values. Everything in the code related to the mouse is based on Input.mousePosition. So this doesn't look like a feature NGUI offers.

    To make it use a software cursor instead of the OS cursor, you would have to replace It is not difficult to derive an absolute screen position from an axis value.

    Code (csharp):
    1. Vector2 screenPosition = new Vector2(
    2.     Mathf.Clamp(screenPositionPrev.x + movementVector.x * Time.deltaTime * cursorSpeed, 0f, Screen.width),
    3.     Mathf.Clamp(screenPositionPrev.y + movementVector.y * Time.deltaTime * cursorSpeed, 0f, Screen.height)
    4. );
    You would replace any calls to Input.mousePosition with your screenPosition value and it should work. This would have to be done anywhere the mouse position is used such as when raycasting.

    I would just make a new static SpriteCursor class that gets its position from both player.GetAxis and the mouse screen position. It would just expose a final screenPosition property. If the mouse is being used, pass the actual screen position. If something else is being used, pass the computed position.

    This is not something a Rewired integration would do because this is modifying core functionality of NGUI and requires code changes to achieve. I would not be maintaining a copy of UICamera that I constantly keep updated with the latest changes as I do with Unity's UI system because UICamera is not open-source and cannot be copied or redistributed.

    If UICamera is the only place Input.screenPosition is used by NGUI, it would make sense to ask the author to simply add another delegate at the top of the script. The same for the calls to Input.GetMouseButton so you could reroute those also for the software cursor clicks.
     
    Last edited: Jul 21, 2018
    Recon03 and MostHated like this.
  10. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    @guavaman Your responsiveness is uncanny, support is comprehensive, and you go above and beyond what any asset author can ever ask for. Thank you so much for the feedback. You've laid out several actionable options, and I appreciate that. I'll try to raise the recommendations to the NGUI author. Failing that, I'll make your suggested changes to UICamera myself. Thanks again!
     
    MostHated likes this.
  11. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    @aer0ace Here is a working example of a class creates a "virtual mouse" which creates a screen position out of both an absolute screen position and absolute axis values. Open the example scene and you will be able to move the cube around with the mouse, gamepad, or keyboard, and click around to make spheres. The left and right buttons as well as the wheel are supported (wheel is right stick Y on gamepads). You can use this to replace the input source for the mouse screen position and button clicks in UICamera.cs.

    [Edit: File removed. Newer version here.]
     
    Last edited: Jul 24, 2018
    aer0ace and recon0303 like this.
  12. recon0303

    recon0303

    Joined:
    Apr 20, 2014
    Posts:
    1,634

    oops I missed this. Yes I agree with you 100% , I did something like this years back with Rewired and NGUI. I asked before for this from Aren and it just never happen, as I don't think many ask for it, but if more do, maybe he will.
     
  13. recon0303

    recon0303

    Joined:
    Apr 20, 2014
    Posts:
    1,634
    aer0ace likes this.
  14. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    Thanks, everyone. I appreciate the help, and will get back to this once I get a chance to make my changes. I'm in the middle of refactoring my existing codebase in preparation for this.
     
    recon0303 likes this.
  15. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    Yeah. This is the way the author meant for the input source to be overridden. I would change a couple of things in that code like not using the System Player for joystick input but detecting which Rewired Player should be used based on the joystick id which you can get from the keycode passed. (Actually, that would depend on if NGUI was written to handle UI input from multiple users. If not, just map everything to a Player id/ids you can set in the inspector.) I would also cache integer ids for the Rewired Actions and use those instead of strings. If you don't want the other non-joystick keycodes to be taken from the keyboard but rather taken from Player Actions, you'd need to map those also. Basically I would make a NGUI -> Rewired mapping inspector like I've done for other integrations. But other than that, it doesn't look difficult at all to do. However, this system would not help with @aer0ace's goal of adding software cursor support. That requires more delegates that aren't included, but would take only few minutes to do modifying the code. All in all, an NGUI integration looks pretty simple and much the same as the Unity UI integration.
     
    Last edited: Jul 22, 2018
  16. recon0303

    recon0303

    Joined:
    Apr 20, 2014
    Posts:
    1,634
    ya, as far as adding a delegate himself it wasn't that big of a deal for any programmer. I doubt Aren will add it , or anything. he seems to be done with NGUI I was just told this. I been using NGUI about 5 years, I think. .. he mainly will only fix things that break from Unity. So I wouldn't expect it. So my suggestion, is to what what was shown, and what you had stated. I got both rewired, and NGUI working years ago, it took a bit of work. but also wasn't horrible either.


    @aer0ace' adding the delegates he needs is fairly easy for any programmer should take a short time . I do this for other things NGUI doesn't have. access to for other assets I needed to work with.
     
  17. Steve-Tack

    Steve-Tack

    Joined:
    Mar 12, 2013
    Posts:
    1,240
    One challenge I would have to deal with is that the UI the player would interact with is dumped onto a render texture that's mapped to an in-game mesh. So I don't know if it would make sense to somehow translate a screen space mouse cursor to the position on the UI or what. So I suppose my use case is a bit different.
     
  18. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    So, here's an example of one of the many confusions that I'm running into while integrating:

    Code (CSharp):
    1.  
    2.         static bool GetKeyDown (KeyCode key)
    3.         {
    4.                 if (key >= KeyCode.JoystickButton0)
    5.                 {
    6.                         Rewired.Player rewiredSystemPlayer = Rewired.ReInput.players.GetSystemPlayer(); // Done every time this is called???
    7.  
    8.                         switch (key)
    9.                         {
    10.                         case KeyCode.JoystickButton0: return rewiredSystemPlayer.GetButtonDown(_actionBottomRow1);        // Action bottom row 1 (Xbox "A" button, for instance)
    11.                         case KeyCode.JoystickButton1: return rewiredSystemPlayer.GetButtonDown(_actionBottomRow2);        // Action bottom row 2 (Xbox "B" button, for instance)
    12.                         }
    13.                 }
    14.                 return false;
    15.         }
    16.  
    That's the example input function, taken from the NGUI forums, that overrides NGUI's default input function with custom code, which basically maps, say, JoystickButton0 to _actionBottomRow1.
    That just seems wrong to me. It's like it destroys the entire purpose of using Rewired. You've just hard-coded the binding between hardware input to action, for Rewired to use the same hardware input that's already been resolved on Rewired's side. Please correct me if I'm wrong, but this solution says to me that you can no longer have the user apply custom bindings. Is there possibly another solution that basically looks up a reverse map from-KeyCode-to-action from the Rewired Input Manager (@guavaman, is this what you meant by "mapping inspector"?), such that you're looking at the current Rewired binding rather than assuming it's going to be JoystickButton0?


    @guavaman, you asked for a more specific description of what I'm doing. Hopefully this helps:
    Imagine Civilization-like TBS 3D game with overlaying 2D UI elements, like Civ 4 onward. Primarily played by mouse and keyboard, but I want to optionally support keyboard-only and gamepad/controller play. I would prefer to have all bindings user-mappable, but I would definitely have some input reserved (i.e. permanent bindings), like for the Escape key to open a pause menu, or general Cancel action.

    As far as pointing is concerned, if a mouse is present (hasMouse and useMouse), then use the hardware cursor. A "software" cursor can still be rendered underneath the hw cursor. In this sense, my sw cursor would be an outline to highlight the currently-selected tile. This sw cursor can be used by all controller types. I will draw some inspiration from your VirtualMouse example.

    And if you imagine an "End Turn" button, mouse will typically handle this, but sans-mouse input options would simply bind to a key on the kb or gamepad.

    Also, NGUI has this concept of MouseOrTouch which is rather confusing.
    There's a lot of code in UICamera.ProcessMouse() that looks pretty important, including Input.GetMouseButton() which doesn't have a Rewired equivalent. I assume it would just be any action binding originally intended for those buttons, or something. I don't know...
    And if I eventually need to support touch, I will cross that bridge when I get there.

    It's like, I feel like I'm at the mercy of NGUI's UICamera internal workings, when I think what I should be doing is minimizing my reliance on UICamera, and trying to work around it, building up from Rewired as my abstract input layer, and then build my secondary input systems (mouse position, dragging, etc) myself, and then I remind myself that I still need input to go through NGUI for text fields and drag/drop of UI elements. And that's how it's been.

    @Steve-Tack UICamera.currentRay has worked well for me, to differentiate between 2D UI elements and world space 3D GameObjects. I've abstracted this out as CursorRay() such that I can just swap in screen Position from somewhere, and cast a ray. It doesn't sound like this is similar to your RenderTexture system.

    I appreciate all the feedback that I've received in this thread. It's been incredibly helpful in designing/refactoring my input/UI system with Rewired.
     
  19. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    I will answer this post piece at a time since it has so much information.

    You're not binding a hardware input to an Action. You're binding a KeyCode to an Action. If NGUI wants to get values from a specific KeyCode, this just reroutes that to a specific Rewired Action. I guess NGUI is written for controller-centric input. That is, get a value for a specific element on a controller. As far as NGUI is concerned, it's getting values from Joystick Button 0 which might be the X button on a 360 controller or not, but it uses whatever button is at index 0 for the "Submit" command. You're mapping the source of that "virtual button" over to "UISubmit."

    Code (csharp):
    1. private static string _actionBottomRow1 = "UISubmit";
    2. // ...
    3. case KeyCode.JoystickButton0: return rewiredSystemPlayer.GetButtonDown(_actionBottomRow1);
    The person who wrote it called this variable _actionBottomRow1, but that's not what it is. It is UISubmit and you are free to bind that to whatever you feel like.

    All that's happening here is a mapping between Unity's joystick button KeyCodes and Rewired's Actions. It's another level of binding. Anything in NGUI that looks for JoystickButton0 will get the value from Rewired's UISubmit on the System Player regardless of the source of that input. You're free to rebind UISubmit to whatever you want and NGUI will still get the correct value.

    There are a few things I see with this code:

    1. Don't use the System Player. It doesn't get joysticks auto-assigned.

    2. There is nothing wrong with getting the Player every frame.

    3. I don't know what keycodes NGUI calls, but using the keycodes JoystickButton# applies to ALL joysticks, not just one particular joystick. If that's how NGUI works (and it appears it does), then there's no way around it, but this wouldn't be able to support multiple players. If it's just a one-player game, that's fine, but I would set the joystick auto-assignment to 10+ controllers per player so all joysticks get assigned to Player 0.

    All I was referring to was allowing you to set up your bindings for each NGUI input in the inspector instead of having to hard-code them. The simplest implementation is exposing one string variable for each NGUI axis name or keycode. Then you build a dictionary of the mappings on start and use that to get the right Rewired Action id when you receive a KeyCode or axis name request through these delegates. A fancier implementation would allow you to pick your Actions from drop-down lists, but that requires that the component be on the Rewired Input Manager so I can get the source for the Action list.
     
    Last edited: Jul 24, 2018
  20. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    Thanks for providing more details as to what you're doing.

    You should be able to just use the Virtual Mouse script as-is as long as you modify UICamera to get its mouse screen position from a Func<Vector2> delegate instead of directly from Input.mousePosition. Then in your script, do this:

    uiCamera.mousePositionDelegate = () => virtualMouse.screenPosition;

    Mouse or touch would just be a way of abstracting the two input sources. As long as you can make sure all the mouse input is coming from delegates and there's no hard-coded calls to Input.GetMouseButton, Input.mousePosition, Input.mouseWheelDelta, etc., it will just work without any other changes.

    >> Input.GetMouseButton() which doesn't have a Rewired equivalent

    It does. ReInput.controllers.Mouse.GetButton().

    But that's not really relevant.

    Replace the call to Input.GetMouseButton with a Func<int, bool> delegate, change the call to use that, then swap the delegate out with the Virtual Mouse button.

    uiCamera.getMouseButton = index => virtualMouse.buttons[index].value;

    (I've since added GetButtonXXX methods to Virtual Mouse that won't throw exceptions when a bad index is passed but haven't updated the script.)

    Every time the delegate is called by UIScript, the Virtual Mouse button value will be taken instead. This button can be mapped to any Action you want in Rewired and will work for joysticks, keyboard, mouse, etc.

    All this code in UICamera is eerily similar to the Unity UI StandaloneInputModule, presumably because the NGUI author had a hand in that.

    You really only have two choices if you're going to use NGUI:
    1. Replace UICamera with your own implementation. This is going to be a LOT of work.
    2. Modify it to suit your needs.
    If it does everything you want it to except it doesn't allow you to set your own mouse screen position, etc., there's no need to replace it. Just dig into the code for the mouse to understand what it's doing and I'm sure you will find it's not that complicated.
     
    Last edited: Jul 24, 2018
  21. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    Here's a new version of Virtual Mouse with more features. Note that the IUIMouseInputSource and IUITouchInputSource interfaces in the VirtualMouse.cs file are just for something I'm playing with using UnityUI. They're not used in this example, but the Virtual Mouse implements IUIMouseInputSource and I didn't want to remove that right now. When I'm done with this, I will probably include it in Rewired in the normal Rewired namespace.
     

    Attached Files:

    Last edited: Jul 24, 2018
  22. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    This dictionary of mappings. Can't this be created automatically? I can't imagine a useful use-case where the KeyCode supplied by the NGUI handler needs to be different from the element mapping to the Rewired action.
    With that thought, can you, for example, on game start (or on Apply user-mapping via Control Mapper), derive it from an ActionElementMap?

    Like, if my KeyboardMap contains key "W" mapped to the "MoveForward" action, couldn't I initialize the map from the ActionElementMap and do something like:

    Code (csharp):
    1.  
    2. KeyCode keyCode = ActionKeyCodeMap["MoveForward"];
    3. if (keyCode == key)
    4. {
    5.    return rewiredPlayer.GetButtonDown("MoveForward");
    6. }
    7.  
    I don't even know if that makes sense.
     
  23. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    No, it doesn't. :confused:

    I think we are not on the same page still.

    Why would you be looking up a KeyCode for "MoveForward"? This is backwards.

    Let me try to explain how I understand things:

    1. NGUI will call GetButton(KeyCode keyCode) for Submit and Cancel.
    2. NGUI will call GetAxis(string someAxisName) for whatever other commands it has. Horizontal, Vertical presumably.

    The Rewired "bridge" simply maps from KeyCode to Rewired Action or NGUI axis string to Rewired Action:

    NGUI KeyCode = "JoystickButton0" -> Rewired Action = "UISubmit"
    NGUI KeyCode = "JoystickButton1" -> Rewired Action = "UICancel"
    NGUI axis name = "Horizontal" -> Rewired Action = "UIHorizontal"
    NGUI axis name = "Vertical" -> Rewired Action = "UIVertical"

    (That's the complete mapping right there as far as I am aware.)

    The communication only has to go one way. There's no need for NGUI to ever know about Rewired or to change anything in NGUI when remapping controls.

    When NGUI asks for "Horizontal", the Rewired bridge gives it the value of player.GetAxis("UIHorizontal").

    You would use the dictionaries to map an NGUI "action" to a Rewired Action with a different string name or for the KeyCodes:

    NGUI "Horizontal" = Rewired "UIHorizontal" (or even better, use the integer id for UIHorizontal to make it even faster).
    NGUI JoystickButton0 = Rewired "UISubmit"

    The only purpose of the inspector would be to allow the user to map NGUI "Horizontal" to a Rewired Action of a different name, for example "Move Horizontal". Also to define the mappings for the 2 KeyCode-based actions, Submit and Cancel.

    There's no reason I can imagine to try to derive these mappings from an ActionElementMap. It's far simpler to just set them up once beforehand and forget about it.

    When you want to change what UIHorizontal is mapped to, just do it through the standard means in Rewired. It will propagate to NGUI with no effort or changes on your part. NGUI will still make the same calls in code and Rewired will give it the correct results.

    There is never any reason I can see to change these NGUI -> Rewired mappings at runtime.

    I don't think this is as complicated as it seems like you think it is.
     
    Last edited: Jul 24, 2018
  24. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    This would be it in its entirety. It could be expanded to use the Virtual Mouse as well, but that requires code changes to UICamera.cs.

    Bear in mind I don't know anything about NGUI. If it has some way to add more axis actions or supports multiple controllers, you'd want to add those here too. If you can add actions to NGUI, then you'd want an array of objects which map from NGUI axis name to Rewired Action name which you could configure in the inspector.

    Code (CSharp):
    1. // Copyright (c) 2018 Augie R. Maddox, Guavaman Enterprises. All rights reserved.
    2.  
    3. namespace Rewired.Integration.NGUI {
    4.  
    5.     using UnityEngine;
    6.     using System;
    7.     using System.Collections.Generic;
    8.  
    9.     public class RewiredNGUIBridge : MonoBehaviour {
    10.  
    11.         [Serializable]
    12.         public class KeyMapping {
    13.             public KeyCode NGUIKey;
    14.             public string rewiredAction;
    15.         }
    16.  
    17.         [Serializable]
    18.         public class AxisMapping {
    19.             public string NGUIAxis;
    20.             public string rewiredAction;
    21.         }
    22.  
    23.         [Tooltip("The Rewired Player Id which will be used to control the UI.")]
    24.         [SerializeField]
    25.         private int _playerId;
    26.  
    27.         [SerializeField]
    28.         private KeyMapping _submitAction = new KeyMapping() { NGUIKey = KeyCode.Return, rewiredAction = "UISubmit" };
    29.         [SerializeField]
    30.         private KeyMapping _cancelAction = new KeyMapping() { NGUIKey = KeyCode.Escape, rewiredAction = "UICancel" };
    31.         [SerializeField]
    32.         private AxisMapping _horizontalAction = new AxisMapping() { NGUIAxis = "Horizontal", rewiredAction = "UIHorizontal" };
    33.         [SerializeField]
    34.         private AxisMapping _verticalAction = new AxisMapping() { NGUIAxis = "Vertical", rewiredAction = "UIVertical" };
    35.         [SerializeField]
    36.         private AxisMapping _scrollAction = new AxisMapping() { NGUIAxis = "Mouse ScrollWheel", rewiredAction = "UIScroll" };
    37.  
    38.         public KeyMapping submitAction { get { return _submitAction; } }
    39.         public KeyMapping cancelAction { get { return _cancelAction; } }
    40.         public AxisMapping horizontalAction { get { return _horizontalAction; } }
    41.         public AxisMapping verticalAction { get { return _verticalAction; } }
    42.         public AxisMapping scrollAction { get { return _scrollAction; } }
    43.  
    44.         private Dictionary<int, int> _keyMappings;
    45.         private Dictionary<string, int> _axisMappings;
    46.  
    47.         private Player player { get { return ReInput.isReady ? ReInput.players.GetPlayer(_playerId) : null; } }
    48.  
    49.         void Awake() {
    50.             _keyMappings = new Dictionary<int, int>();
    51.             _axisMappings = new Dictionary<string, int>();
    52.  
    53.             if(!ReInput.isReady) {
    54.                 Debug.LogError("Rewired has not been initialized. You must have an enabled Rewired Input Manager in the scene to use the NGUI Bridge");
    55.                 return;
    56.             }
    57.  
    58.             InputAction action;
    59.  
    60.             action = GetAction(_submitAction.rewiredAction);
    61.             if(action != null) _keyMappings.Add((int)_submitAction.NGUIKey, action.id);
    62.  
    63.             action = GetAction(_cancelAction.rewiredAction);
    64.             if(action != null) _keyMappings.Add((int)_cancelAction.NGUIKey, action.id);
    65.  
    66.             action = GetAction(_horizontalAction.rewiredAction);
    67.             if(action != null) _axisMappings.Add(_horizontalAction.NGUIAxis, action.id);
    68.  
    69.             action = GetAction(_verticalAction.rewiredAction);
    70.             if(action != null) _axisMappings.Add(_verticalAction.NGUIAxis, action.id);
    71.  
    72.             action = GetAction(_scrollAction.rewiredAction);
    73.             if(action != null) _axisMappings.Add(_scrollAction.NGUIAxis, action.id);
    74.         }
    75.  
    76.         void Start() {
    77.             UICamera.GetKey = GetKey;
    78.             UICamera.GetKeyDown = GetKeyDown;
    79.             UICamera.GetKeyUp = GetKeyUp;
    80.             UICamera.GetAxis = GetAxis;
    81.         }
    82.  
    83.         bool GetKeyDown(KeyCode key) {
    84.             if(!ReInput.isReady) return false;
    85.             int actionId;
    86.             if(!_keyMappings.TryGetValue((int)key, out actionId)) return UnityEngine.Input.GetKeyDown(key);
    87.             return player.GetButtonDown(actionId);
    88.         }
    89.  
    90.         bool GetKey(KeyCode key) {
    91.             if(!ReInput.isReady) return false;
    92.             int actionId;
    93.             if(!_keyMappings.TryGetValue((int)key, out actionId)) return UnityEngine.Input.GetKey(key);
    94.             return player.GetButton(actionId);
    95.         }
    96.  
    97.         bool GetKeyUp(KeyCode key) {
    98.             if(!ReInput.isReady) return false;
    99.             int actionId;
    100.             if(!_keyMappings.TryGetValue((int)key, out actionId)) return UnityEngine.Input.GetKeyUp(key);
    101.             return player.GetButtonUp(actionId);
    102.         }
    103.  
    104.         float GetAxis(string name) {
    105.             if(!ReInput.isReady) return 0f;
    106.             int actionId;
    107.             if(!_axisMappings.TryGetValue(name, out actionId)) return 0f;
    108.             return player.GetAxis(actionId);
    109.         }
    110.  
    111.         InputAction GetAction(string name) {
    112.             InputAction action = ReInput.mapping.GetAction(name);
    113.             if(action == null) Debug.LogWarning("There is no Rewired Action named \"" + name + "\". You must create this Action in the Rewired Input Manager.");
    114.             return action;
    115.         }
    116.     }
    117. }
     
    Last edited: Jul 25, 2018
    shindig82 likes this.
  25. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
  26. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    The part that I don't understand is that in Rewired Input Manager, I will assign mappings in the KeyboardMap. But then, JoystickButton0, JoystickButton1 etc will also be mapping to actions. Does that mean I'm getting input from two different sources? Rewired and NGUI? I was hoping to have all input routed through Rewired, and NGUI then uses this input. That's the way I was thinking.

    I suppose if this is the case, I would have to extend my own custom ControlMapper to not only allow the user to map Rewired input, but also a system to re-map NGUI input (?).
     
  27. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    No. You do nothing with regard to input in NGUI after it is set up. Everything is 100% handled by Rewired and is completely transparent to you and the end user.

    The JoystickButton0 and JoystickButton1 bindings do absolutely nothing but allow NGUI to detect "Submit" and "Cancel" through a joystick. (Mouse button detection is hard coded and needs to be changed.)

    You are not getting input from anything but Rewired. NGUI itself is getting input for its own purposes of UI navigation. The source of that input is ultimately Rewired if using the bridge.

    If a button is highlighted, NGUI is going to be polling GetButtonDown(KeyCode.JoystickButton0) to see if you're trying to submit that button with a joystick. It gets the result from Rewired's UISubmit and that's it.

    1. In Rewired, you create two Actions: "UISubmit", "UICancel".
    2. You create Keyboard and Joystick maps that bind these Actions to something.
    3. When NGUI wants to know if Submit was pressed, it gets the result for UISubmit no matter what it's mapped to. Key, joystick button, etc.

    There's nothing else involved. You don't have to do anything else. There's no working with two input systems, worrying about NGUI bindings, or anything of the sort.
     
  28. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    I don't know if this helps anything or not. You can see that all that's happening here is you're swapping NGUI's source of input from UnityEngine.Input to Rewired. In your previous response you state "I was hoping to have all input routed through Rewired, and NGUI then uses this input." That's exactly what's happening.

    RewiredNGUIBridge.png

    The only thing the bridge does is convert NGUI's requests in the language it chooses to use (KeyCodes and axis names) into the language Rewired uses (Actions) and gives it back the result.
     
  29. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    The specific case of Submit and Cancel makes sense, but I think I'm trying to generalize how the input/UI system works that I'm not fully understanding, and maybe focusing too much on supporting custom bindings. I'm hung up on why it has to be JoystickButton0/1 rather than Enter/Backspace, or any other input. But I guess what you're saying is that NGUI (or, the author of that code snippet) has determined that those are the keycodes for Submit/Cancel. It's like, can the user ever change that?

    At this point, I don't really have any real-world examples, only contrived cases and that doesn't really help the matter.
    Thanks for being patient with my questions, and I hope this helps others out.

    I'll undoubtedly have more questions, once I can find more solid use cases. Thanks!
     
  30. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    I'm assuming it's because those are hard-coded in NGUI somewhere. That may not be the case. Like I said, I don't know NGUI. If they let you rebind this stuff somewhere, you would just settle on a fixed set of KeyCodes and axis names you want to use for those NGUI actions, map those to Rewired Actions in the Rewired Bridge, and you can totally forget about it after that. Rebind to your heart's content in Rewired -- it will just work.

    The only way to know whether you can define those bindings somewhere in NGUI is to look through NGUI or its documentation.

    It doesn't matter one bit what KeyCode NGUI is using to get the value of Submit. Rewired maps that over to the Rewired Action that you choose. From that point on, it's a moot point. NGUI thinks it's JoystickButton0 but it's really F8 on the keyboard because you set that up in Rewired. If you then change it to Right Trigger on a gamepad, NGUI is none the wiser but it still works.
     
    Last edited: Jul 25, 2018
  31. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    Here is another angle of my codebase. I rely heavily on the UICamera event handlers:

    Code (CSharp):
    1.  
    2. UICamera.onPress += new UICamera.BoolDelegate(HandlePress);
    3. UICamera.onKey += new UICamera.KeyCodeDelegate(HandleKey);
    4. UICamera.onMouseMove += new UICamera.MoveDelegate(HandleMove);
    5. UICamera.onDragStart += new UICamera.VoidDelegate(HandleDragStart);
    6. UICamera.onDragEnd += new UICamera.VoidDelegate(HandleDragEnd);
    7. UICamera.onDrag += new UICamera.VectorDelegate(HandleDrag);
    8. UICamera.onScroll += new UICamera.FloatDelegate(HandleScroll);
    9.  
    These handlers pretty much run all user input for my game. That's kind of why I'm so concerned about overriding "Input." with the Rewired altrenatives.

    I feel like decoupling these handlers from the UICamera event system, and re-working the input such that it's all Action based to integrate with Rewired is the way to go. Would you recommend this, or do you think I should leave these handlers untouched and work on refactoring UICamera to use Rewired instead of Unity Input?
     
  32. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    By far the easiest solution would be to use the Rewired Bridge script above. You only need to worry about changing UICamera.cs to get the software pointer working. And that's probably only a handful of lines of code to change even and should take a few minutes to do. (I count 10 places in UICamera.cs that need to be changed to use delegates.)

    In my view, there's a great advantage to "just switching" Rewired out for UnityEngine.Input. You make next to no code changes and most of the features of NGUI should just work with whatever controller you use. If you do the software cursor change too, you should be able to literally use any joystick like a mouse and have all the exact same features of mouse usage. Because you didn't change any of the handlers, dragging, clicking, scrolling, etc. should all automatically just work with joysticks, mouse, or keyboard if you choose to do that. I think it's great when the author of an asset designs it to allow for swappable input. One simple integration like the above Rewired Bridge is all that is needed to get it working. If you choose to switch to some other input system, swapping that component out is simple. I really can't see any disadvantages to this approach.

    I found this in the NGUI docs. It looks like there's only 7 actions in NGUI and you only need to worry about 5 of them (no need for duplicates as Rewired can handle that itself easily). You can simply set them up to whatever string name and keycodes you want, enter those same values in the Rewired Bridge, and you should be good to go. It should take you all of 2 minutes to test out this integration, so I would definitely give this a try before you start making sweeping changes to NGUI and your code.



    If you can get access to these via scripting, you can just bind them automatically on start in the Rewired NGUI Bridge. No need for manual mapping.
     
    Last edited: Jul 25, 2018
  33. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    I guess I'm not clear on something -- do you ever make calls for input through NGUI? For example, is it possible to have a custom "Fire" action in any of your code that would be obtained through NGUI? I would think the answer would be no, but I want to verify.

    The only one of those delegates I see above that I would imagine to pose any sort of possible issue is the onKey delegate depending on what you use it for. If it's for reading keyboard input in a text box for example, that would be fine to leave as is. If it's for some other "action" type use, that would be another story.

    I'm kind of at the end of my ability to help with NGUI since I don't know enough about its structure and limitations to give you educated advice. The method I've shown here is virtually identical to the functionality of UnityUI integration, so I'm pretty sure it would also be sufficient for an NGUI integration. But without knowing more about the system, I can't really do much more.
     
    Last edited: Jul 25, 2018
  34. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
  35. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    Not currently, but I was initially planning on it, and I think that's what is adding to my confusion. For example, the WASD keys pan the camera up/down/left right. As it was, I was checking for Input.GetKeyDown(), and was going to move those checks into the NGUI handler. This is part of the reason why I was thinking I would be receiving input from both NGUI and Rewired. For those WASD keys, I would rather check for the Rewired actions "CameraPanUp/Down/Left/Right", and I think this is the right thing to do. Of course, I think I'd have to add some sort of check for whether NGUI already handled the key, for the case that NGUI is accepting the actual letters in a text field.

    EDIT:
    On that last point, I essentially need to check when UI is modally blocking, but I guess I can use a Rewired Category for this too.
     
  36. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    I didn't know NGUI had a customizable input system that extends beyond the basic UI interaction functions. I can't really provide any useful information in that area since I don't know how it works.

    Using Controller Map Categories for switching between input modes is a perfectly good use case. You could also do this with a simple permission flag/enum in some static class and check permissions before consuming input.
     
  37. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    I just wanted to clarify, that NGUI doesn't have a "customizable input system that extends beyond basic UI interaction functions". I must have misunderstood. NGUI's author noted in UICamera's documentation that UICamera is a misnomer and it's just an event system (just like how Unity UI adds an event system object when you place a UI element in the world). So I've been utilizing UICamera as a regular event system. All input is routed through UICamera via Unity's Input, which you've already taken a look at. What drew me to use UICamera's event handlers was that it tracks objects under the cursor, but since I've started to do that myself, its utility has become less necessary.
     
  38. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,495
    Ahh, I see. That makes perfect sense and follows the same structure as Unity UI. I would say then that using this event system for some input would be perfectly acceptable as long as it doesn't return KeyCodes in the event callbacks. It would be perfectly fine for you to trigger an NGUI event from a Rewired input and have that event delivered by NGUI's event system. There's no real advantage to doing that over using Rewired events or input polling except perhaps to decouple your code from Rewired if that's an issue. But I also don't see any real problem with doing it either. You could do the same with Unity's event system.