#42439 - ymalik - Wed May 11, 2005 2:41 pm
Hello,
What is the best button handler interface? One that can be used for each module (start menu, game play screen, etc.)? I just have a process buttons function that goes through each possible key combination.
Thanks,
Yasir
#42451 - sajiimori - Wed May 11, 2005 6:29 pm
Code: |
enum Key
{
KEY_A,
KEY_B,
...
};
void keyUpdate(); // Call once per game loop.
bool keyIsPressed(Key);
bool keyJustPressed(Key);
bool keyJustReleased(Key);
void menuControl()
{
if(keyJustPressed(KEY_DOWN))
moveCursorDown();
else if(keyJustPressed(KEY_UP))
moveCursorUp();
else if(keyJustPressed(KEY_A))
activateCurrentOption();
}
void playerControl()
{
if(keyIsPressed(KEY_LEFT))
movePlayerLeft();
else if(keyIsPressed(KEY_RIGHT))
movePlayerRight();
if(keyJustPressed(KEY_A))
jump();
}
|
#42453 - jma - Wed May 11, 2005 7:25 pm
As usual, Saj's code is great.
However, something for the OP to be aware of is that keyUpdate() being called once per frame could case potential problems. If the player is able to tap a button (press and release it) very quickly in between calls to keyUpdate(), then nothing will happen. This is a problem (mainly) when your framerate is, say, less than 30 or so.
Another method would be to set up key interrupt handlers (or even use the vblank interrupt), which would ensure that the previous and current controller states would remain up-to-date at all times.
Most of the time, Saj's method is just fine. But it is something to keep in mind if you start noticing fast button presses not taking effect.
Jeff M.
_________________
massung@gmail.com
http://www.retrobyte.org
#42457 - poslundc - Wed May 11, 2005 7:46 pm
jma wrote: |
However, something for the OP to be aware of is that keyUpdate() being called once per frame could case potential problems.
...
This is a problem (mainly) when your framerate is, say, less than 30 or so. |
In this case, I'd say the problem most likely lies with the framerate being less than 30 Hz. The vast majority of GBA games run at 60 Hz. And even for the ones that don't, polling the keystate once per frame is usually more than sufficient.
If for some reason you're making an exceptional game, then by all means do exceptional programming. But don't anticipate problems that are highly unlikely, and I'm willing to bet more than 95% of published games aren't afflicted with.
Dan.
#42461 - sajiimori - Wed May 11, 2005 8:08 pm
In this case, the cost is low for changing implementations late in a project because the interface doesn't change significantly. Good abstraction lessens the need for anticipation.
#42462 - ymalik - Wed May 11, 2005 8:13 pm
Ok, that's what I've been doing. It just seemed to unprofessional. Our project architect wanted to have an array of structures where each structure element has a button mask and the corresponding function pointer. And then you would loop through the array each vdraw period. I thought this was unnecessarily complex.
Sajiimori, in your code, what is difference between
Code: |
bool keyIsPressed(Key); |
and
Code: |
bool keyJustPressed(Key); |
And how do you check for a button that is released?
Thanks,
Yasir
#42464 - poslundc - Wed May 11, 2005 8:33 pm
ymalik wrote: |
Our project architect wanted to have an array of structures where each structure element has a button mask and the corresponding function pointer. |
O_o
Did he give a reason?
Dan.
#42465 - sajiimori - Wed May 11, 2005 8:41 pm
Tell your architect that the structure you described turns into a great burden, mostly for two reasons.
First is the lack of lexical closures. Function pointers have no context, and many input events need a great deal of context to produce their desired effect. In C and C++, you have to bend over backwards to provide parameterized behavior.
Second is the amount of state that must be maintained. If a game mode needs to map a set of events to behaviors, it must remember to clear all of those events when exiting, or else the next game mode may behave incorrectly. In C++, you can solve this problem by using event binding objects with destructors that automatically unbind the event. In practice, the amount of code required to use this scheme (and I'm not even talking about implementing it) is significantly greater than direct polling.
keyJustPressed() only returns true if the key was pressed during the last game loop.
I thought it would be obvious that if keyIsPressed() returns false, then the key is not pressed, and therefore it is released...? If you're talking about detecting when a key was just released, I listed a seperate function for that.
#42467 - jma - Wed May 11, 2005 8:46 pm
ymalik wrote: |
And how do you check for a button that is released? |
Just keep the previous button state and the current state. So, your keyUpdate() function might look something like:
Code: |
#define KEY_PRESSED 0
void keyUpdate() {
g_prev_key_state = g_cur_key_state;
g_cur_key_state = REG_KEYP1;
}
bool keyJustReleased(int key) {
if (g_prev_key_state & key == KEY_PRESSED) {
return g_cur_key_state & key != KEY_PRESSED;
}
return false;
} |
It has been a while since I've coded the GBA, so treat the above as pseudo-code :)
poslundc wrote: |
But don't anticipate problems that are highly unlikely... |
I agree. Just giving the OP additional information that may prove beneficial in the future.
Jeff M.
_________________
massung@gmail.com
http://www.retrobyte.org
#42499 - tepples - Thu May 12, 2005 2:34 am
Incomplete example:
Code: |
/* code fragment dedicated to the public domain by author Damian Yerrick
ABSOLUTELY NO WARRANTY */
#define KEY_A 0x0001
#define KEY_B 0x0002
#define KEY_UP 0x0040
/* etc. */
int main(void)
{
unsigned int last_keys;
while(1) {
unsigned int keys = (*(volatile unsigned short *)0x04000130) ^ 0x03FF;
unsigned int pressed_keys = keys & ~last_keys;
unsigned int released_keys = ~keys & last_keys;
last_keys = keys;
if(pressed_keys & KEY_A)
{
beep();
}
/* do other stuff */
vsync();
/* do vblank stuff */
}
}
|
I also have code for keyboard-style autorepeat if you want the player to control a moving cursor or a moving falling block.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#42538 - ymalik - Thu May 12, 2005 7:15 pm
poslundc wrote: |
ymalik wrote: | Our project architect wanted to have an array of structures where each structure element has a button mask and the corresponding function pointer. |
O_o
Did he give a reason?
Dan. |
Well, let me give you an example of what he was taking about. Here's the struct:
Code: |
#define NMODULES 5
typedef void (*keyfunc_t)(u32);
/*
* masks allow you to map multiple inputs, such as all directional pad keys
* to a single function, which will be passed a single argument: the input as
* an unsigned, 32-bit integer.
*/
struct keymap {
u32 input_mask;
keyfunc_t func;
};
struct keymap * module_keymaps[NMODULES];
|
And here's how it's being used:
Code: |
static struct keymap m_keymap[] = {
{ KEY_A, keyboard_action_select },
{ KEY_B, keyboard_action_backspace },
{ KEYPAD_ANY, keyboard_move_cursor },
{ 0, NULL }
};
void keyboard_run ()
{
unsigned int i;
int input;
struct keymap * module_keymap;
module_keymap = module_keymaps[MODULE_KEYBOARD];
for (;;) {
input = *m_buttons;
for (i = 0; (module_keymap[i]).func != NULL; i++) {
if (!((module_keymap[i]).input_mask & input))
((module_keymap[i]).func)(input);
if (m_quit)
return;
if (m_sprite_redraw) {
keyboard_sprite_draw();
m_sprite_redraw = 0;
}
if (m_string_redraw) {
keyboard_string_draw();
m_string_redraw = 0;
}
}
while ((input & 0x0001) == 0)
input = *m_buttons;
WAIT_VBLANK();
WAIT_VBLANK();
WAIT_VBLANK();
WAIT_VBLANK();
WAIT_VBLANK();
WAIT_VBLANK();
}
}
|
That code is my partener's. I didn't follow that interface because I thought it was too complicated. We had to present examples of good and bad code in front of the class, and the architect made me present my code as bad code, and my parteners's code as good code.
#42539 - ymalik - Thu May 12, 2005 7:18 pm
sajiimori wrote: |
keyJustPressed() only returns true if the key was pressed during the last game loop.
I thought it would be obvious that if keyIsPressed() returns false, then the key is not pressed, and therefore it is released...? If you're talking about detecting when a key was just released, I listed a seperate function for that. |
Oh, ok, so when would you use keyJustPressed()? Isn't ketIsPressed() enough (you only want to know whether a key has been pressed or not in a game, right?)
#42540 - sajiimori - Thu May 12, 2005 7:52 pm
Sounds like you let yourself get pushed around.
The usage example I posted shows situations where you'd use keyJustPressed versus keyIsPressed, specifically, jumping versus walking.
#42551 - poslundc - Thu May 12, 2005 10:10 pm
ymalik: The main problem I have with this system (and sajimori said something like this, but now I'm saying it my way) is that it binds the state of the game to the functionality of the buttons, instead of the buttons to the state of the game.
Think about the process you are following here. Every frame (well, every 6 frames in your code for some reason, plus waiting for a certain button to be depressed in between for some other mysterious reason) you are doing the following for each button:
1. Check to see if the button has been pressed
2. Call that button's function
3. Make sprite changes based on that particular button
Does partitioning out the whole course of a frame by virtue of what to do this frame if "A" is pressed, then what to do this frame if "Up" is pressed, etc. seem like a sensible course of action?
A far better strategy would be to treat a frame as your body of interest. Keep track of the state of the game from frame to frame, and each frame update the state based on the effect the controls can have for that state. ie. delegate the problem of dealing with individual buttons to the state, not the problem of dealing with state to the individual buttons.
Dan.
#42566 - sajiimori - Thu May 12, 2005 11:49 pm
The given example is indeed terrible. I don't even know where to begin.
1) Event driven programming can be good in some situations.
2) Event driven programming is a bitch to pull off well in C++, let alone plain C.
3) Event driven programming is virtually never appropriate in games.
4) Your partner produced a crappy implementation of event driven programming for many reasons, not least of which is that the callbacks have zero context, forcing all of your state to be global.
That about sums it up.
#42572 - poslundc - Fri May 13, 2005 1:09 am
I don't necessarily have a problem with event-driven programming in games, so long as the events are tied to entities that have some relevance to the state.
The system described above would be like trying to code an operating system by listing all possible responses when the user presses "q", all possible responses when they press "w", when they press "e", "r", "t", "y", etc.
A button in a menu on the screen can receive and respond to events effectively, because its very existence is tied to the state of the program. The keys are the method of input; they have no direct relevance to the state of the machine, and having them process events is useful only in the most exceptional cases.
Dan.
#42605 - sgeos - Fri May 13, 2005 2:31 pm
sajiimori wrote: |
the callbacks have zero context, forcing all of your state to be global. |
That can easily be fixed by changing the type of function pointers to something like:
Code: |
typedef struct game_state_t
{
/* Details */
} game_state_t;
typedef int fp_return_t;
fp_return_t (*function_pointer)(int, game_state_t *); /* if you really need the int */ |
I'm not convinced that binding buttons to functionality is the wrong solution for all games, but you'd have to register an input table with every state change. The difference is do you want to write a new input table for each state, or a new function. I think it would be just about as easy to accomodate custom controls with either method.
You need to be careful to make sure that your input table only ties keys to abstract functions that change the game state. Somehow it seems to me that to get the button<->function method to work, the functions would have to set flags in the game state and have some other function work out the details. I don't like it, but as I said I'm not sure it's the wrong solution for all games.
If you need to respond to just_pressed(), is_pressed(), and just_released() keypad events, you really don't want to do the binding thing. Now that I look at it, thats where your partners code is really bad. If I hold A, then the A function gets called until I release it. (Why is he calling vblank six times!? Thats a bad sign. Really bad.)
-Brendan
#42615 - Suboptimal - Fri May 13, 2005 3:56 pm
There are many different methods of handling input, the two most common being polling and event-driven. Both are very viable, and both are used in commercial games, sometimes even mixed. For example, Half-Life 2 uses messaging, which is a sort of filtered event-driven method, for UI, but uses polling for character control. Before picking any method, you need to first understand what type of game you're making, and how it is going to work. For instance, in your program design, when is it legal to have input change the state of the game? Do all of those things need to happen before physics, ai, sound, etc are processed? If you have a solid understanding of how your game is going to work, then as long as you understand the trade-offs between polling and event-driven, the choice between the two will probably be obvious.
I suspect your teacher was saying that the event-driven approach was better, more or less because it is NOT polling, which is often people's first approach to solving the problem. Really, what the teacher SHOULD have done, is make you analyze both in the strcture of the of the game and figure out which method will reduce the overall complexity of the code base, etc. Don't worry about it too much, since the skills you need to design really good software aren't going to come from a classroom. Just try to expose yourself to as many different ways of solving problems as you can. This will add more tools to your sort of coding tool-box, and over time you'll find yourself getting better at applying tools to fit the problem.
If you're curious, this write up includes one game's method of handing player control in the game:
http://www.gamasutra.com/features/20050414/rouwe_01.shtml
#42636 - sajiimori - Fri May 13, 2005 6:33 pm
Brendan, having all the functions take the entire game state involves essentially the same cost as making the game state global. All state will be visible to all functions, and the state must contain all the information that could possibly be relevant to every game mode.
#42837 - sgeos - Sun May 15, 2005 3:03 pm
sajiimori wrote: |
Brendan, having all the functions take the entire game state involves essentially the same cost as making the game state global. All state will be visible to all functions, and the state must contain all the information that could possibly be relevant to every game mode. |
You could pass a (game_mode_x *) to every function when in game mode x. The table driven method is seeming less and less pretty.
In theory the table struct could look something like this:
Code: |
typedef int fp_return_t;
typedef fp_return_t (*input_fp_t)(void *);
typedef struct input_action_t {
int input_mask;
input_fp_t action;
void * data;
} input_action_t; |
Where it is used like:
Code: |
input_action_t action_table[] =
{
{D_PAD, move_player, &player},
{START, show_map, &some_other_struct},
{0, NULL, NULL}
}
fp_return_t move_player(void *p_data)
{
/* IMPORTANT!!! Cast to the right type to get feedback from the compiler */
player_struct_t *player = (player_struct_t *)p_data;
/* calculation not shown */
}
void call_loop(input_action_table *p_action)
{
int i;
for (i = 0; NULL != p_action[i]->action; i++)
{
int mask = p_action[i]->input_mask;
input_fp_t action = p_action[i]->action;
void * data = p_action[i]->data;
if (pressed(mask))
action(data);
}
} |
It seems to me that registering the data to be passed the fuctions might really suck. The table would need to be declared in a place where everything is in scope. I don't really like it, but for interest's sake does anyone have any ideas to make the table based method suck less? Registered actions could be dropped onto a linked list, but that seem 100 times more complicated than just polling.
-Brendan
#42851 - sajiimori - Sun May 15, 2005 5:44 pm
Having a fairly complete knowledge of C++ helps a lot. I took the idea to its logical conclusion, and it was dissatisfying. Let me try to remember what the code looked like.
Code: |
class GameMode
{
public:
GameMode() :
mKeyLeft (KEY_EVENT_HOLD, KEY_LEFT, this, &moveLeft),
mKeyRight(KEY_EVENT_HOLD, KEY_RIGHT, this, &moveRight),
mKeyJump (KEY_EVENT_TRIG, KEY_A, this, &jump)
{
}
private:
void moveLeft();
void moveRight();
void jump();
KeyBinding<GameMode> mKeyLeft, mKeyRight, mKeyJump;
};
|
Constructing a KeyBinding object establishes the binding, and it's automaticallly released when the binding object goes out of scope. There was something aesthetically appealing about it.
But in real life, it was a nightmare. Things that used to be simple became ridiculously circular. If you're in the middle of a method and you want to know if a key is being held... oh god, I can't even explain it in words.
Code: |
class IJustWantToKnowIfTheFsckingKeyIsDown
{
public:
IJustWantToKnowIfTheFsckingKeyIsDown() :
mIsPressed(false),
mPress(KEY_EVENT_TRIG, KEY_A, this, &press),
mRelease(KEY_EVENT_FALL, KEY_A, this, &release)
{
}
void update()
{
if(mIsPressed)
ohMyGodThatWasAWasteOfTime();
}
private:
void press()
{
mIsPressed = true;
}
void release()
{
mIsPressed = false;
}
KeyBinding<IJustWantToKnowIfTheFsckingKeyIsDown>
mPress, mRelease;
bool mIsPressed;
};
|
Gee, we should go write a utility class to encapsulate all this complexity. Then we'd have a super-advanced interface that looks something like:
Code: |
bool keyIsPressed(Key); |
#42879 - DekuTree64 - Sun May 15, 2005 10:44 pm
...yeah. Horrible for in-game, and even for menus it's just a roundabout way of doing an if/else or switch.
Whatever system I'm working on, I wrap up the input however I must and make 3 simple macros, KEYTRIG, KEYDOWN and KEYFALL, so I can give them the key I want to check and be done with it.
One thing that can be handy though, but more for the DS where you have the touch screen, is to make a generic button class that checks the input state automatically and does something if it's pushed. Like if you want your buttons to do a little animation, put that into the class so it happens without any special code in every menu.
Still, I would keep the game logic in an if/else/switch, just asking those button objects if they had been pressed.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku
#42948 - sgeos - Tue May 17, 2005 10:49 am
sajiimori wrote: |
Things that used to be simple became ridiculously circular. If you're in the middle of a method and you want to know if a key is being held... oh god, I can't even explain it in words. |
Why would binding keys for 'automatic' actions and polling otherwise be a bad way of doing things?
sajiimori wrote: |
Gee, we should go write a utility class to encapsulate all this complexity. Then we'd have a super-advanced interface that looks something like:
Code: | bool keyIsPressed(Key); |
|
lol
-Brendan
#42969 - poslundc - Tue May 17, 2005 5:31 pm
sgeos wrote: |
Why would binding keys for 'automatic' actions and polling otherwise be a bad way of doing things? |
I think any mixed-model like that tends to lead to a lot of confusion and design problems down the road. Any situation where it's ambiguous how a control mode ought to be implemented will create code that's much harder to debug and maintain.
There are exceptions: for example, binding the A-B-Start-Select combo to a soft-reset by using an interrupt. But that functionality is the only key binding that ever remains at all consistent throughout a game.
Dan.
#42971 - sajiimori - Tue May 17, 2005 5:45 pm
But Dan, programming wouldn't be any fun at all if there were only one way to do things! :) Seriously though, flexibility is good.
My recommendation: Make the most beautiful and full-featured event-based input system you can imagine. Then keep it in your toolbelt, ready to apply it when it will result in a significant improvement.
My prediction: It will never result in a significant improvement.
#42974 - poslundc - Tue May 17, 2005 6:15 pm
sajiimori wrote: |
But Dan, programming wouldn't be any fun at all if there were only one way to do things! :) |
One should always strive to apply the most appropriate tool to solve a problem, and never assume that one tool is the correct solution for every problem.
But I wouldn't use both a hammer and a saw to pound in a nail.
Dan.
#45001 - Shade - Tue Jun 07, 2005 3:12 am
...sorry, I had to say this. Man, this forum is just freakin' great. Gotta love this topic. And people are so civilized, too. Reminds me of the good ol' times in lua-l.
Cheers!
#45111 - Suboptimal - Tue Jun 07, 2005 7:17 pm
I've been thinking about this thread for awhile. The line of thought usually comes down to "The right tool for the right job, and just checking the button state is the right tool for the job of checking input".
However, I think the problem is that people have been thinking about his in terms of registers, or elegant C++, or in just about any framework other than what it is that actually makes a game. Your UI doesn't care about the state of a register, it cares about whether the user wants to scroll up or down in a list. Blanka doesn't care about what button is being mashed, he cares about what move the player has just told him to execute.
In other words, a good input system will translate the current state of the controls into a a set of events or actions based on the state of the game. It can store those events to be polled by other systems, it can feed them into an event queue, or it can send them to game modules to be executed. Most importantly, if your game is at all complicated, it can reduce the underlying complexity of the code. This will have the benefit of increased flexibility as the project matures. Finally, if you ever work on a platform that supports multiple input types, or input rebinding, it will be absolutely nessisary.
So, yes, if you're making pong or asteroids, you can probably just check the button state and get on with your game. If you're shooting for something more complicated like an RPG, then a good event system could definately strengthen the code.
#45127 - sajiimori - Tue Jun 07, 2005 8:38 pm
Quote: |
If you're shooting for something more complicated like an RPG, then a good event system could definately strengthen the code. |
You seem confident. Have you ever tried it?
Let me give you a challenge. Pretend you have the coolest event-based input system imaginable. Forget about the implementation of the system. Just show me a usage sample for a realtime game that's better than polling. Not just cooler or more impressive, but better in some concrete way: code length, flexibility, modularity, anything. I'm not saying there isn't one -- I honestly don't know.
Quote: |
Your UI doesn't care about the state of a register, it cares about whether the user wants to scroll up or down in a list. |
UIs tend to be more event-based because of their non-realtime nature and because of the number of components that can potentially handle the same input. Rather than iterating over every component and checking if any input is relevant for it, it is better to recursively desend through the hierarchy of objects and funnel events through them, stopping when the event is handled. This is not like a normal game.
Quote: |
Blanka doesn't care about what button is being mashed, he cares about what move the player has just told him to execute. |
Blanka shouldn't be handling input. Another module should poll input and send Blanka the appropriate commands, such as "quickPunch". Blanka doesn't even care if the player told him to do it -- it could have been the AI or a recorded demo.
Quote: |
Finally, if you ever work on a platform that supports multiple input types, or input rebinding, it will be absolutely necessary. |
Polled input can be translated by a key binding table.
#45179 - sgeos - Wed Jun 08, 2005 9:37 am
sajiimori wrote: |
Quote: | Blanka doesn't care about what button is being mashed, he cares about what move the player has just told him to execute. | Blanka shouldn't be handling input. Another module should poll input and send Blanka the appropriate commands, such as "quickPunch". Blanka doesn't even care if the player told him to do it -- it could have been the AI or a recorded demo. |
Agree. The input module should sit apart from the blanka module.
-Brendan
#45326 - Suboptimal - Thu Jun 09, 2005 5:35 pm
I'm not certain we're in disagreement here, I think the differences might ones of language. I'm not nessisarily advocating a system like half-baked callback table that the original poster talked about. My core thesis was this:
Quote: |
A good input system will translate the current state of the controls into a set of events or actions based on the state of the game. It can store those events to be polled by other systems, it can feed them into an event queue, or it can send them to game modules to be executed.
|
Which, in my experience, is ultimately better than littering the codebase with " if ( REGISTER & KEY_BIT ) " statements.
Beyond that, the benfits of an input system often depend on the game in question. Many times, a well designed input system can reduce complexity by using context early in the chain of decision making to cut down on the number of things that need to be checked against when sub systems decide whether or not to change their state. For example:
Code: |
if ( bFoo1 )
{
if ( bBar )
Function1();
}
else if ( bFoo2 )
{
if ( bBar )
Function2();
}
else if ( bFoo3 )
{
if ( bBar )
Function3();
}
|
It's easy to see that this can be optimized to :
Code: |
if ( bBar )
{
if ( bFoo1 )
Function1();
else if ( bFoo2 )
Function2();
else if ( bFoo3 )
Function3();
}
|
A good input system can essentially do the same thing by taking a high-level view of the state of the game, leaving individual modules to handle the details.
I'm very new to GBA programming, so I don't have any examples here to show you. But I've worked on several large games for other platforms that relied on one or sometimes several layers of systems between raw input gathering and that input actually driving changes in the game. Some of them were completely awful, and I've never seen one that didn't have it's own problems, but I would say most were better than simply having the individual game systems to directly access the raw input state.
#45333 - sajiimori - Thu Jun 09, 2005 6:49 pm
Cook input data until it doesn't help any more. How much do you need to get from registers to mario->jump()? Well, you only want to jump when the key is triggered, and the registers only say what is currently pressed. So there should be a layer that keeps track of both the previous and current key states. If you want the player to be able to reconfigure their controls, then add another layer that translates a game action to a key. That may sound backwards, but it isn't: Code: |
void checkInput()
{
if(keyTriggered(keyMap[ACTION_JUMP]))
mario->jump();
} |
I disagree that input should be at a higher level than game logic. Game logic should be implemented in terms of the topmost input modules, and the design of the input module should be in service of simplifying game logic.