Author : Yordan Gyurchev
Page : << Previous 2 Next >>
keys[control.key[1]] |
keys[control.key[2]] | keys[control.key[3]];
We have an external pointer to store the state of the control somewhere else (although I don’t have it in my implementation it makes much more sense to store the state directly somewhere else than to iterate each time trough all control ids to retrieve the wanted control state).
So, now that we have a control structure, let’s make some instances and organize them.
You have to choose a data structure to store your control objects. The one I have chosen is the STL vector (yes I’m addicted to it – have some mercy - I’m trying some "lists" just now; since I’m an independent developer code reusability is an important thing – it saves time :) because it is faster for iterative access and we do not need any fast insert or delete operations on the collection (if you're not familiar with STL, just think of a vector as a dynamically sized array).
Here we are: we have an empty collection of controls. The first thing we should do is to populate it with the system controls and limit the change/customize access to them.
Next we have to put some real controls in there. For example, to create a "forward" control we need an AddControl() method implemented by the class. What parameters should we use? Not much, just the ID and the flags. So we have: AddControl(id,flags). "But where are the keys associated with this control?" you might ask. Ok, there is a AddKey(id,key) method although nobody said that you cannot customize control with no keys associated to it. The AddKey method must have some information about the keys sharing and properly handle the exclusive flag. This means that when a key is added it should check whether there is another control associated with this key and if that control has the exclusive keys flag set.
So we end this section with a collection of controls structures and couple of methods to control them. For our convenience we will add another method called FormatControlString(szstr,id) to retrieve control key information into a string sequence, once again keeping an eye on the flags for the Boolean operator used. When we get to alternate keys the string will look like this "Right Alt , Space" but for the AND operator it will change to "Right Alt + Space".
How to customize controls? In fact you could do it with the AddKey method but that will assume you have obtained (in some way) the key the user wants to use for this particular control. This presumes that you have an alternative set of DirectInput objects or some other way of getting the user input. All this is unnecessary. All we need to do is implement the RecordControl(id) method. When we make the call, it waits for the user input and the first pressed key is added to the specified control using the AddKey method. So, in order not to bother the game with the keys the player wants to use, we just call the RecordControl method when user wants to change a control and continually update the information about the controls using FormatStringControl.
The problem here is how to figure out what was the first pressed key after the record method was called. Here the common buffer comes in quite handy. We just scan it for differences each time it is updated and when we detect such a change we stop recording, passing the detected key to the other method. (Remember that the mouse stores its keys in the keyboard buffer so no additional checks are needed)
That was easy, wasn’t it?
Hey, wait a moment what about mouse movement? Oh, yes, don’t forget to implement some method for adjusting the mouse sensitivity. You can do it by implementing a simple value for scaling the mouse input for relational input, but what about absolute input? Remember the direct input properties and the granularity? That’s right, set proper granularity in order to adjust the mouse movement.
Let’s return to the point. The task we want to perform is to get information from input devices, to filter it trough our collection of controls, and transfer it to a collection of control states that we can use. I named the method UpdateState() and it is invoked at least once per game iteration. Well, what does it do?
DirectInput objects are responsible for retrieving the mouse and keyboard states into our buffer. Having that information, we iterate trough our collection of controls and perform the Listing 4 algorithm to calculate the control state.
Listing 5: Updating the control states iterating the collection
vector<control>::iterator it=controls.begin();
for(; it != controls.end(); it++) {
it->oldstate = it->state;
if (it->flags&WJ_CTRLS_OPAND2)
it->state = keys[it->key[0]] & keys[it->key[1]];
else
it->state = keys[it->key[0]] | keys[it->key[1]] |
keys[it->key[2]] | keys[it->key[3]];
}
At the end of UpdateState() we have our collection updated with the appropriate control states. That is fine but we need these states outside the class.
There are two approaches to this problem: first. implement a GetControlStatus(id) method, and second, implement an external state pointer. The first one is much clearer but at the cost of some overhead (each time the program searches the needed control in the collection by id). The second one is fast enough (just an assignment) but it leaves an opening for making logical errors such as releasing memory assigned to the pointer or other memory problems.
And finally the mouse: some GetDeltaX(),GetDeltaY() and GetDeltaZ() methods will be in use and remember to clear the mouse data members in order to accumulate new delta values in.
Controls persistence is an important thing. Somewhere, we have to store the controls configuration until the next game instance runs. Otherwise the player will soon get bored of configuring keys each time he starts the game.
I won’t discuss file operations with collections here - that's up to you - but there are few interesting issues involved. When you save the collection to the file, do not save the system controls - they can be regenerated by the code. This saves space and prevents system controls from be violated accidentally or on purpose by the user. When loading controls just initialize the collection, add system controls and then do the file input.
Speaking of persistence, users have an incredible talent of mixing up all the keys, so be sure to keep a clean configuration "defaults" file.
A common need when working with user input is text entry (for example – player name entry, game name, message to other players, etc.). But our input devices are exclusively acquired and the ordinary way of dealing with text input will not work. We need an additional method to our class, which can handle the translation of the scan codes returned from the GetKeyboardState.
The main points here are:
Retrieve the current keyboard layout for the thread using:
HKL GetKeyboardLayout( DWORD dwThreadId);
Translate the scan code into a virtual key code using:
UINT MapVirtualKeyEx( UINT uCode, UINT uMapType, HKL dwhkl);
Translate the virtual key code into an ascii character or characters:
int ToAsciiEx( UINT uVirtKey, UINT uScanCode, CONST PBYTE lpKeyState, LPWORD lpChar, UINT uFlags, HKL dwhkl);
All these API functions are explained in the MSDN Library so take a look if you are interested.
How does this algorithm fit into our class? It’s up to you. Do you need to cancel the user input during the text entry or do you need to run it concurrently? Two methods come in handy: StartText(mode) and EndText() where mode determines whether other controls should work during the text entry (of course the system controls work all the time). So when the StartText is invoked it starts recording and translating key codes to characters into a buffer and EndText finishes that process and returns the buffer.
The conversion process is pretty simple, and I have seen it in Photon’s article at gamedev.net.
Another common need is alternative mouse input during game play. Imagine that you use relational mouse coordinates to navigate your player in 3D but sometimes you need a mouse cursor and absolute mouse coordinates in order to handle the inventory.
Two possibilities here:
Keep some absolute coordinates calculated from the currently updated relative ones
Switch the DirectInput Device properties and change the device behavior
The first approach is much more flexible because you don’t need to switch any DirectX modes or properties, it simply add relative values to the absolute internal ones (you need to keep data members for absolute mouse coordinates), and of course restricts them to the screen size (one more need – screen coordinates for clipping).
The second one does not need any additional information but will demand setting the device’s properties, requiring that the device first be unacquired.
Well, you may think we have finished, but there are still some more topics to cover.
Imagine that you have added a
Page : << Previous 2 Next >>