gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > Detecting key presses and releases in 8 directions.

#19359 - mr_schmoe - Sat Apr 17, 2004 10:45 pm

Ok, here's the skinny. I want to detect when the player moves the d-pad in one of eight directions. Also, it needs to tell whether it is a new direction or the same one. In others words, if the player first presses the down button it issues a command to start the player moving down (that is, load the proper animation and set the action variable to moving in the down direction,) but then it shouldn't issue that same command next cycle around. I save the key register to compare it with the currect key register next cycle to detect that. Four directions isn't the issue, it's when I work with eight directions it get complicated. here's the code I'm using, please pardon the sloppyness of it.

Code:

static u16 lastkeys;
u16 currkeys = 0;
u16 command = 0;

/* each of the four directions
bit0: 0 - pressed, 1 - released
bit1: 0 - same as last cycle, 1 - just barely
i.e. 00 = 0 = the key was pressed in the previous cycle and in this cycle, contiously pressed
11 = 3 = the key was barely released */
u8 arrowkeys[4];

u16 action;

currkeys = KEYS; // get the current keys from the register

if ((~currkeys & KEY_UP) && (!(~lastkeys & KEY_UP))) arrowkeys[0] = 2;
if ((!(~currkeys & KEY_UP)) && (~lastkeys & KEY_UP)) arrowkeys[0] = 3;
if ((!(~currkeys & KEY_UP)) && (!(~lastkeys & KEY_UP))) arrowkeys[0] = 1;
if ((~currkeys & KEY_UP) && (~lastkeys & KEY_UP)) arrowkeys[0] = 0;

if ((~currkeys & KEY_DOWN) && (!(~lastkeys & KEY_DOWN))) arrowkeys[1] = 2;
if ((!(~currkeys & KEY_DOWN)) && (~lastkeys & KEY_DOWN)) arrowkeys[1] = 3;
if ((!(~currkeys & KEY_DOWN)) && (!(~lastkeys & KEY_DOWN))) arrowkeys[1] = 1;
if ((~currkeys & KEY_DOWN) && (~lastkeys & KEY_DOWN)) arrowkeys[1] = 0;

if ((~currkeys & KEY_LEFT) && (!(~lastkeys & KEY_LEFT))) arrowkeys[2] = 2;
if ((!(~currkeys & KEY_LEFT)) && (~lastkeys & KEY_LEFT)) arrowkeys[2] = 3;
if ((!(~currkeys & KEY_LEFT)) && (!(~lastkeys & KEY_LEFT))) arrowkeys[2] = 1;
if ((~currkeys & KEY_LEFT) && (~lastkeys & KEY_LEFT)) arrowkeys[2] = 0;

if ((~currkeys & KEY_RIGHT) && (!(~lastkeys & KEY_RIGHT))) arrowkeys[3] = 2;
if ((!(~currkeys & KEY_RIGHT)) && (~lastkeys & KEY_RIGHT)) arrowkeys[3] = 3;
if ((!(~currkeys & KEY_RIGHT)) && (!(~lastkeys & KEY_RIGHT))) arrowkeys[3] = 1;
if ((~currkeys & KEY_RIGHT) && (~lastkeys & KEY_RIGHT)) arrowkeys[3] = 0;

if ((arrowkeys[0] == 2) && (arrowkeys[1] & 1) && (arrowkeys[2] & 1) && (arrowkeys[3] & 1))
   command = WALK_BACK;

if ((arrowkeys[1] == 2) && (arrowkeys[0] & 1) && (arrowkeys[2] & 1) && (arrowkeys[3] & 1))
   command = WALK_FORWARD;

if ((arrowkeys[2] == 2) && (arrowkeys[0] & 1) && (arrowkeys[1] & 1) && (arrowkeys[3] & 1))
   command = WALK_LEFT;

if ((arrowkeys[3] == 2) && (arrowkeys[0] & 1) && (arrowkeys[1] & 1) && (arrowkeys[2] & 1))
   command = WALK_RIGHT;

if ((~arrowkeys[2] & 1) && (~arrowkeys[1] & 1) && (arrowkeys[0] & 1) && (arrowkeys[3] & 1))
   command = WALK_FORWARD_LEFT;

if ((~arrowkeys[3] & 1) && (~arrowkeys[1] & 1) && (arrowkeys[0] & 1) && (arrowkeys[2] & 1))
   command = WALK_FORWARD_RIGHT;

if ((~arrowkeys[2] & 1) && (~arrowkeys[0] & 1) && (arrowkeys[1] & 1) && (arrowkeys[3] & 1))
   command = WALK_BACK_LEFT;

if ((~arrowkeys[3] & 1) && (~arrowkeys[0] & 1) && (arrowkeys[1] & 1) && (arrowkeys[2] & 1))
   command = WALK_BACK_RIGHT;

if (command == WALK_BACK) action = 8;
if (command == WALK_FORWARD) action = 9;
if (command == WALK_LEFT) action = 10;
if (command == WALK_RIGHT) action = 11;
if (command == WALK_BACK_LEFT) action = 12;
if (command == WALK_BACK_RIGHT) action = 13;
if (command == WALK_FORWARD_LEFT) action = 14;
if (command == WALK_FORWARD_RIGHT) action = 15;

if (action == 8) // do some appropreate action stuff
...



Any suggestions? I could figure this out on my own, it might take a while, but currently I have to start getting ready for work. And besides it get others a chance to work those brain mussels. Thanks alot.

#19365 - sajiimori - Sun Apr 18, 2004 12:38 am

Too complicated! You need to abstract over the hardware, then forget about it.
Code:

int pressed(int key)
{
  return !(*KEYS & key);
}

Enumerate the 9 keypad states (nothing pressed or one of 8 directions), and write a function that returns the current state.
Code:

enum
{
  DIR_NONE,
  DIR_U,
  DIR_UR,
  DIR_R,
  DIR_DR,
  DIR_D,
  DIR_DL,
  DIR_L,
  DIR_UL,
  DIR_NUMDIRS
};

int get_dirpad_state()
{
  if(pressed(KEY_UP))
    if(pressed(KEY_LEFT))
      return DIR_UL;
    else if(pressed(KEY_RIGHT))
      return DIR_UR;
    else
      return DIR_U;
  else if(pressed(KEY_DOWN))
    if(pressed(KEY_LEFT))
      return DIR_DL;
    else if(pressed(KEY_RIGHT))
      return DIR_DR;
    else
      return DIR_D;
  else if(pressed(KEY_LEFT))
    return DIR_L;
  else if(pressed(KEY_RIGHT))
    return DIR_R;
  return DIR_NONE;
}

Because you need more functionality than that, add another abstraction layer. Don't add more values to the DIR_ enum or change get_dirpad_state() to add features -- keep your abstractions minimal and to the point.

You could implement another function that uses get_dirpad_state(), but also maintains other information, such as the state on the previous frame.
Code:

struct DirPadState
{
  int current;
  int previous;

  // other info you need
  ...
};

void load_dirpad_state(DirPadState* s)
{
  static int previous;
  s->current = get_dirpad_state();
  s->previous = previous;
  previous = s->current;

  // load other info into struct
  ...
}

Make the struct hold all the information you'll need to decide what to do in the game. After that, you just write one last function that does the appropriate thing to the game's state based on the directional pad's state.
Code:

void apply_dirpad_state(DirPadState* s, GameState* g)
{
  switch(s->current)
  {
    ...
  }
}

The direction the player is moving is part of the game's state, not part of input. They should be seperate. This sort of modularization really pays off in the end.

If you think that "command" concept will be useful (for instance, if the player could customize controls, keys could map to different commands), you could do this instead of apply_dirpad_state():
Code:

enum
{
  CMD_NONE,
  CMD_WALKUP,
  ...
}

// Note that the command issued is dependent on the
// game's state.  Also, the full key state might be needed
// rather than just the dir pad, so another struct would
// wrap it.
int determine_command(DirPadState* s, GameState* g)
{
  ...
}

void apply_command(int cmd, GameState* g)
{
  ...
}

Notice how the additional concept of a "command" added another step to the process. This is normal -- it is said that most problems in programming can be solved by adding an additional indirection.

#19380 - mr_schmoe - Sun Apr 18, 2004 6:45 pm

You gave some really good insight, sajiimori. But I still don't think I have enough education or experience to do what I'm trying to do. Do you know of a good book that teaches abstract concepts and good programming technices specifically geared towards game programming. I only program as a hobby and I don't really have any intentions of going pro or selling my game. But I still want to learn. And I believe that's what the coding section of this forum is all about, helping beginners and novices learn good coding technices. So, if you don't mind, let me tell you a little more about what I'm trying to do.

I want to write a top view action adventure game/demo kind of like the Legend of Zelda: A Link to the Past. I have one sprite, (or a series of images for animation) for each of the 4 direction and 4 more for using your weapon in 4 direction, 4 more for getting hit, etc. That way, I can load a new animation if it's a new direction or the player hit the weapon button and just update the sprite attributes for the next frame until the action is completed. But the problem is, I need to poll for input just incase in mid step the player changes direction, but I don't need to poll for a new input if the player uses the weapon because you can't change the action have way through swinging his sword, you need to wait till the character's done. You know, now that I think about it, has someone made a game similar to what I'm doing that is willing to release the source code I can look off of. That would work better. But I think I need to stop writing now, I'm confusing myself.

#19402 - poslundc - Mon Apr 19, 2004 4:17 am

mr_schmoe wrote:
And I believe that's what the coding section of this forum is all about, helping beginners and novices learn good coding technices.


Actually, that's what the Beginner's section is for, but I think we can let it slide. :)

Quote:
I want to write a top view action adventure game/demo kind of like the Legend of Zelda: A Link to the Past. I have one sprite, (or a series of images for animation) for each of the 4 direction and 4 more for using your weapon in 4 direction, 4 more for getting hit, etc. That way, I can load a new animation if it's a new direction or the player hit the weapon button and just update the sprite attributes for the next frame until the action is completed. But the problem is, I need to poll for input just incase in mid step the player changes direction, but I don't need to poll for a new input if the player uses the weapon because you can't change the action have way through swinging his sword, you need to wait till the character's done.


This is the second time tonight that I am advising someone to read up on finite state machines.

FSMs give you a simple and straightforward way to organize a game in the way you are describing. The player has a state, whether he's walking, swinging his sword, standing still, etc. and depending on what state he's in the game will respond differently depending on what buttons are pressed. Every frame you check the input, and based on whatever state he is currently in, you switch to a new state if necessary. You do this input check exactly once per frame, every frame, and update to the new state once per frame, every frame.

Dan.

#19406 - yaustar - Mon Apr 19, 2004 6:12 am

I am not sure what you're original intention was.. (it could be due to the early hours of the morning :p) ar you having trouble with the states or are you having trouble detecting a diagonal?
_________________
[Blog] [Portfolio]

#19419 - Miked0801 - Mon Apr 19, 2004 6:56 pm

Yum. Finite State Machines are your friend. We use them in so many places/ways in our games to make life easy. Actors, Menu system, scripting system, etc.

#19420 - mr_schmoe - Mon Apr 19, 2004 7:24 pm

Quote:
I am not sure what you're original intention was.. (it could be due to the early hours of the morning :p) ar you having trouble with the states or are you having trouble detecting a diagonal?


Actually, it started out as me trying to detect 8 direction movement, but when sajiimori expained a whole lot simpler way it turned into a discution on game engines.

I've been reading up on finite state machines, so far it has been very interesting. My original concept uses something similar to what I've been reading. There was an aciton variable that was set based on when the character was doing at any givin time, and the input changes that action in a certain way based on what kind of input it is. I'm quite interested in reading more to see how I can improve on it.

You guys have been really great, you must have a lot of patients to deal with use noobies.

#19425 - sajiimori - Mon Apr 19, 2004 8:19 pm

I didn't even know what a Finite State Machine was until recently. I had assumed it must be some really complicated technique for graduate students at MIT, until I found out I had been doing essentially the same thing for years. Trying to write good C often leads to FSM-like structure, even unintentionally.

One more comment about code structure. Compare these 2 ways of organizing apply_dirpad_state():
Code:

void apply_dirpad_state(DirPadState* s, GameState* g)
{
  switch(s->current)
  {
  case DIR_U:
    switch(g->player.state)
    {
    case PL_SWING:
      ...
      break;
    case PL_WALKUP:
      ...
    }
    break;

  case DIR_UR:
    switch(g->player.state)
      ...
  }
}

void apply_dirpad_state(DirPadState* s, GameState* g)
{
  switch(g->player.state)
  {
  case PL_SWING:
    switch(s->current)
    {
    case DIR_U:
      ...
    case DIR_UR:
      ...
    }
    break;

  case PL_WALKUP:
    switch(s->current)
      ...
  }
}

The first switches on the pad state first, and the second switches on the player's state first. Which is better? It largely depends on which set of states is more likely to be altered in the future.

What if you added another player state? What would you have to do to the first version above, versus the second version? In the first, you would have to add another case to each inner switch block. In the second, you only have to add one case to the outer block.

So, since the number of pad states is fixed to 9, and the number of player states might change, switch on the player's state first.

IMO, the best solution of all is a data-driven approach, but unfortunately C often makes data-driven programming awkward.

In the above functions, how many inner cases are there total? There's 1 inner switch block for each outer case, so the total is the number of player states times the number of game states. You could draw a table where the columns are player states and the rows are game states, and each entry in the table could be 1 case.

In the above functions, we are trying to express 2-dimensional data in a 1-dimensional way. The cases would be much more naturally expressed as a 2D array.
Code:

typedef void (*DirPadStateApplier)(DirPadState* s,
                                   GameState* g);

const DirPadStateApplier
  dirpad_state_appliers[DIR_NUMDIRS][PL_NUMSTATES] =
{
  ...
};

void apply_dirpad_state(DirPadState* s, GameState* g)
{
  dirpad_state_appliers[s->current][g->player.state](s, g);
}

Replacing the "..." in C is the awkward part, because there are no anonymous functions. You would have to make a seperate named function for each table entry.

#19436 - yaustar - Tue Apr 20, 2004 2:26 am

Diagonals I can help :) FSM just learning them myself with AI

Code:
if((*KEYS & 0x00F0)==176)                   //if the UP key is pressed
if((*KEYS & 0x00F0)==112)                 //if the DOWN key is pressed
if((*KEYS & 0x00F0)==208)                 //if the LEFT key is pressed
if((*KEYS & 0x00F0) == 224)                //if the RIGHT key is pressed
if((*KEYS & 0x00F0) == 96)                 //if the RIGHT + DOWN keys are pressed
if((*KEYS & 0x00F0) == 80)                 //if the LEFT + DOWN keys are pressed
if((*KEYS & 0x00F0) == 160)                 //if the RIGHT + UP keys are pressed
if((*KEYS & 0x00F0) == 144)                 //if the LEFT + UP keys are pressed

Basically you mask the bits which the d pad affects from the register check it against the value of the keypress
_________________
[Blog] [Portfolio]

#19493 - abilyk - Tue Apr 20, 2004 9:59 pm

A few suggestions... some have been mentioned before in other topics, but are worth repeating. This is how I do it, at least.

1) Read the keys register only once per frame (or whatever duration suits your project) and store it in a variable. If you instead read the register over and over in a sequence of if statements, for example, the register value could change from one test to the other. Since the register is active low (a 0 represents a pressed button), I find it more logical to store and work with the complement (so now a 1 represents a pressed button).

Code:
keys = ~REG_KEYS;



2) Use defines. It makes your code much easier to read. Further, I use hex numbers in my defines as opposed to decimal. In this case and many others, the values correspond to individual bits being 0 or 1 as opposed to logically representing a value. Hex numbers make such patterns more apparent.

Code:
#define KEY_A                   0x0001
#define KEY_B                   0x0002
#define KEY_SELECT              0x0004
#define KEY_START               0x0008
#define KEY_RIGHT               0x0010
#define KEY_LEFT                0x0020
#define KEY_UP                  0x0040
#define KEY_DOWN                0x0080
#define KEY_R                   0x0100
#define KEY_L                   0x0200



3) Don't define (or calculate values for) specific combinations. Instead use bitwise operators to combine your defined values. Going along with the examples shown above, this will test for a diagonal left+up.

Code:
if(keys & (KEY_LEFT | KEY_UP))
    DoWhatever();