Game Development Community

Implementing Custom InputDevice

by Matias Kiviniemi · in Torque X 2D · 03/23/2007 (9:58 am) · 9 replies

Hi,

I was pondering about implementing my own InputDevice for the purpose of network gaming. I.e. a derivate of TorqueInputDevice-class that would take care of sending the input events through network. Idea being that it would handle synchronizing the input device state (buttons, sticks, etc.), and would work as player input the same way as keyboard and gamepad.

But looking at the interface, it's not quite obvious how to do it (or even if possible). As usual, I'd like to hear if anyone has tried something like this, comments, alternative suggestions, warnings, anything :)

Matias

#1
03/23/2007 (10:28 am)
It's certainly possible, and in fact the complexity of the input system is due to a double layer of abstraction designed specifically for your purpose! The one thing that you will have to handle on your own is the networking aspect, and most specifically the timing (which is always a pretty serious issue in multiplayer games). Since XNA (and therefore Torque X) does not have networking currently, you're a bit on your own on that side, although I do recommend researching the Open Torque Networking Library for background theory (it cannot be used commercially without a license, and is actually c++, so it's more of here's a way to do that type of thing).

When I first read the post by the way, I missed the networking part, which changed the nature of the answer a lot, but I'm leaving this information here in case anyone finds it useful. If you want more, let me know!


The basic idea of the input system is that you map control device inputs to one of three types of operations:

BindCommand -- a logical on/off event that calls a single method (the Make method) when the device goes active, and another method (the Break method) when that state is reversed to the original state. Think of pressing a key and letting go of a key.

BindAction -- similar to BindCommand, however the value of the input (normally 0 or 1 for a digital input, but a range of 0.0f to 1.0f for an analog input) is sent to the Make and Break methods.

BindMove -- this literally binds inputs to a data structure within a MoveManager that is assigned to a player to update internal values. Every process tick for your control object, These values are collected, placed into a Move struct (via MoveManager.Update() ), and then delivered to the control object for that player.

What these three types of processing do for us is isolate our game play C# from the device itself, through the abstraction of the MoveManager. The mappings that BindMove allow turn an actual input from a device into a "theoretical input" type input, of the following types:


buttons -- logical (digital) on/off inputs
triggers -- ranged values from 0.0f to 1.0f
levers -- single axis ranged inputs from -1.0f to 1.0f
sticks -- two axis ranged inputs, each from -1.0f to 1.0f

For creating/adding a new device, you would want to analyze the types of inputs that are available to the end user, and determine the most appropriate MoveManager "theoretical input" and then determine the mapping type (digitial input to ranged value, analog input to ranged value, analog input to digital value with ramping, etc., etc.), and create the BindMove statements to do that mapping.

From there, nothing changes--which is the power of the double abstraction system :)
#2
03/23/2007 (1:26 pm)
Hi,

Thanks for the help. I think the abstraction is wery nice in this case as it allows me to tinker with just one layer without changing the rest :) My biggest problem (so far) is that I don't really understand the whole chain of events in the Input/Movement-framework.

My original idea was to implement a "NetworkInputDevice" to handle everything, but it seems like wrong/ overly complex aproach. The two basic things I need to achieve are
a) When the state of the physical device changes, send updates to network clients
b) When network players process the tick, generate a Move based on the latest received device state

For a) I can just subscribe for the ticks of the player with the actual device, and see if anything changed from last time. For b) I could for example use the 'MoveManager.SetStick/Lever/Button/X'-functions whenever receiving state updates from network. This is just the technical side of getting the bits moving, but have to get started from somewhere ;)

Two questions:
- What is the flow in a Tick? E.g. does the MoveManager.Update collect the state from TorqueInputDevice? Or is it the MoveManager that receives the InputEvents from device and keeps track of the state?
- What is MoveManager.GenerateMove-property used for? I replaced it with my delegate, but doesn't seem to effect the Move's. It gets called before each tick, but the move my delegate returns is not passed to the player.

Thanks again,
Matias
#3
03/23/2007 (1:39 pm)
Just a quick gotcha I wanted to point out with working on Simian Escape at GDC and then on my laptop on the way home: tick's aren't a set length of time. tick's were MUCH faster on the alienware Athlon FX boxes at GDC than on my centrino laptop. Things I did in tick like auto-decrementing stamina were MUCH slower on the laptop than on the alienware machine.

If you are going across a network, make sure you handle things based on actual time passes versus how many ticks have passed.

www.linkedin.com/img/webpromo/btn_viewmy_160x25.gif

www.mmogamedev.info/images/imgdc_ad1.gif
#4
03/24/2007 (10:00 am)
Hmm, good point, do you know if it's "a bug or a feature"? But I've also noticed that ProcessTick/elapsed seems to always be exactly the tick interval like 50ms, even though you'd expect it to be '47ms, 52ms, 51ms, 48ms' depending on how much time has actually passed.
#5
03/24/2007 (11:41 am)
ProcessTick is exactly that.. a tick. That is the time passed into ProcessTick is constant time. If you want variable time look into UpdateAnimation.

You can change the amount of constant time for ProcessTick by changing TickMs on the ProcessList. If you set this to zero than you can get variable time. Traditionally, in Torque engines, TickMs is 32ms. When using constant tick time you may also want to look into InterpolateTick which will allow you to interpolate time between the last two ticks.

An example of how ProcessTick works: If the time passes since the last loop is 100ms and the tick rate for the ProcessList is 32ms than ProcessTick will be called 3 times. The remaining time is stored to be used for the next calculation.
#6
03/24/2007 (4:46 pm)
That is strange then. If I'm understanding this correctly, processtick should be the same length of time no matter what computer you are on?

The problem I'm having is we put stamina for Simian Escape in the processtick event to constanctly lower it. On one computer (the dev box) it goes down at a rate we wanted. When we loaded it onto two different laptop machines, the rate of decrementing went WAY down to where instead of about 1 stamina point per second, it would take roughly 3 seconds for 1 point to go down.

Thoughts?
#7
03/26/2007 (9:01 am)
Did you remember to set TickMs to something other than zero?
#8
03/26/2007 (5:43 pm)
Well, it doesn't make any sense. I have:

<UseFixedTimeStep>true</UseFixedTimeStep>
and:
public int TickMS = 30;

Which is the default, yet the time is different depending on the computers involved. I'm going to delve into this some more and find the problem. I see the processlist stuff and will hack away at it in a couple days when I have more time.
#9
03/27/2007 (7:35 am)
Hmm. I did some testing and it appears now that the ticks are all happening at the same interval as my settings above would suggest. Since I'm too lazy to peek back in svn to see if the latest build I have modified the above two settings, I'm going to chalk this up to a freak occurance.

My testing should prove this working correctly in the future (and I now base stamina/score/etc on time instead of ticks).

www.linkedin.com/img/webpromo/btn_viewmy_160x25.gif

www.mmogamedev.info/images/imgdc_ad1.gif