When I first started researching what it took to create games, I, like many other new developers, fell in love with the Unity game engine. It is easy to understand, light, and it does a lot of things extremely well. However, when I started creating controller supported projects and multiplayer experiences, I found that the support that I needed to provide was not available. Chances are if you have created something in Unity then you know what I am talking about but if not, then I will elaborate. When setting up controller input in Unity, one must first find where the input registry is located, find the correct XBOX controller button layout from the Unity support wiki, connect joysticks and buttons up to string event names, and then implement functionality that at its core comes down to hardcoded string comparison. In most cases, this is fine and works as expected. However, when I needed to create support for two players in Dunkaroos in a simple, modular way, I knew there had to be a better way. Enter my InputManagerModule project.
The InputManagerModule is a .dll plug-in that I created to help provide multiplayer support for all gamepads regardless of brand or type. The idea is simple; provide a "subscribable" interface for the player input that is bound to specific function calls. There were a few important things that I wanted this plug-in to accomplish.
- Create "fire and forget" events
- Support any type of USB gamepad
- Increase code reuse
- Avoid hardcoding string comparisons
- Provide custom rumble support
In my research, I came across an XInput wrapper created by Rémi Gillig that created a solid foundation for this project. Rémi's plug-in already bridged the gap between Unity and XInput by exposing gamepad functionality to the user. This functionality included all major buttons and axes as well as rumble support. So, "Avoid hardcoding string comparisons"; check. "Provide (not custom yet) rumble support"; check.
Again, after more research, I found ways to meet each of the other items that I wanted to accomplish. The solution ended up being a delegate based input manager.
What is a delegate you ask? Well, a delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance. (Source)
Short ‘n sweet version; a delegate is a method that you can “bind” a function to and execute the event.
What this allowed me to do was to implement input with just a simple "subscribe/unsubscribe signature" that directly called a defined function.
The implementation for the user is simple. Determine which button you want to listen for, determine if you want to listen for pressed, released, or held (or all three) events, and "subscribe" a function to the correct delegate event.
After a few helper functions to allow activating and deactivating controllers, creating an interface for users to create custom rumble, and creating generic function calls to interface with XInput's generic native USB controller support, I was happy with the project and was able to implement it easily into the assignment it was created for. The solution that I created is in no way as elegant as it could be, but it accomplished everything I wanted it to and was a great learning experience.
Why am I making this post now? Well recently, I was lucky enough to be able to share the InputManagerModule plug-in with a group of Game Design students that I TA for at DigiPen and figured it was about time to make it open source. So, if you are interested in learning more about the plug-in or just want to use it yourself, head over to my GitHub page and enjoy! I hope it will help mitigate some of your controller input problems and let you create more cool things.
- Jeremy McCarty