Improve input handling using state flags

Fix reading one shot inputs not working due to prematurely clearing out state
data tracking input changes between one frame and another. The transition count
used to track this now stores difference between each frame in the input flags.

This also solves requiring at the end of the frame to update the transition
counts at the end of each update loop.
This commit is contained in:
Doyle Thai 2016-11-25 18:03:25 +11:00
parent d878cca8ab
commit 5bb7fa8c13
8 changed files with 173 additions and 130 deletions

View File

@ -147,68 +147,6 @@ void initRenderer(GameState *state, v2 windowSize)
}
}
enum ReadKeyType
{
readkeytype_oneShot,
readkeytype_delayedRepeat,
readkeytype_repeat,
readkeytype_count,
};
#define KEY_DELAY_NONE 0.0f
INTERNAL b32 getKeyStatus(KeyState *key, enum ReadKeyType readType,
f32 delayInterval, f32 dt)
{
// TODO(doyle): Don't let get key status modify keyinput state
if (!key->endedDown) return FALSE;
switch(readType)
{
case readkeytype_oneShot:
{
if (key->newHalfTransitionCount > key->oldHalfTransitionCount)
return TRUE;
break;
}
case readkeytype_repeat:
case readkeytype_delayedRepeat:
{
if (key->newHalfTransitionCount > key->oldHalfTransitionCount)
{
if (readType == readkeytype_delayedRepeat)
{
// TODO(doyle): Let user set arbitrary delay after initial input
key->delayInterval = 2 * delayInterval;
}
else
{
key->delayInterval = delayInterval;
}
return TRUE;
}
else if (key->delayInterval <= 0.0f)
{
key->delayInterval = delayInterval;
return TRUE;
}
else
{
key->delayInterval -= dt;
}
break;
}
default:
#ifdef DENGINE_DEBUG
DEBUG_LOG("getKeyStatus() error: Invalid ReadKeyType enum");
ASSERT(INVALID_CODE_PATH);
#endif
break;
}
return FALSE;
}
#include <stdlib.h>
#include <time.h>
v2 *createAsteroidVertexList(MemoryArena_ *arena, i32 iterations,
@ -697,33 +635,10 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
for (u32 i = world->asteroidCounter; i < world->numAsteroids; i++)
addAsteroid(world, (rand() % asteroidsize_count));
{
KeyState *keys = state->input.keys;
for (enum KeyCode code = 0; code < keycode_count; code++)
{
KeyState *keyState = &keys[code];
platform_processInputBuffer(&state->input, dt);
u32 halfTransitionCount = keyState->newHalfTransitionCount -
keyState->oldHalfTransitionCount;
if (halfTransitionCount > 0)
{
b32 transitionCountIsOdd = ((halfTransitionCount & 1) == 1);
if (transitionCountIsOdd)
{
if (keyState->endedDown) keyState->endedDown = FALSE;
else keyState->endedDown = TRUE;
}
keyState->oldHalfTransitionCount =
keyState->newHalfTransitionCount;
}
}
}
if (getKeyStatus(&state->input.keys[keycode_left_square_bracket],
readkeytype_repeat, 0.2f, dt))
if (platform_queryKey(&state->input.keys[keycode_left_square_bracket],
readkeytype_repeat, 0.2f))
{
addAsteroid(world, (rand() % asteroidsize_count));
}
@ -739,8 +654,8 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
v2 ddP = {0};
if (entity->type == entitytype_ship)
{
if (getKeyStatus(&state->input.keys[keycode_up], readkeytype_repeat,
0.0f, dt))
if (platform_queryKey(&state->input.keys[keycode_up],
readkeytype_repeat, 0.0f))
{
// TODO(doyle): Renderer creates upfacing triangles by default,
// but we need to offset rotation so that our base "0 degrees"
@ -753,20 +668,20 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
}
Degrees rotationsPerSecond = 180.0f;
if (getKeyStatus(&state->input.keys[keycode_left],
readkeytype_repeat, 0.0f, dt))
if (platform_queryKey(&state->input.keys[keycode_left],
readkeytype_repeat, 0.0f))
{
entity->rotation += (rotationsPerSecond) * dt;
entity->rotation += (rotationsPerSecond)*dt;
}
if (getKeyStatus(&state->input.keys[keycode_right],
readkeytype_repeat, 0.0f, dt))
if (platform_queryKey(&state->input.keys[keycode_right],
readkeytype_repeat, 0.0f))
{
entity->rotation -= (rotationsPerSecond) * dt;
entity->rotation -= (rotationsPerSecond)*dt;
}
if (getKeyStatus(&state->input.keys[keycode_space],
readkeytype_delayedRepeat, 0.05f, dt))
if (platform_queryKey(&state->input.keys[keycode_space],
readkeytype_one_shot, KEY_DELAY_NONE))
{
addBullet(world, entity);
@ -882,11 +797,9 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
continue;
}
f32 dPMultiplier = 10;
Radians rotation = DEGREES_TO_RADIANS((entity->rotation + 90.0f));
ddP = V2(math_cosf(rotation), math_sinf(rotation));
entity->dP = v2_scale(ddP, world->pixelsPerMeter * dPMultiplier);
v2 localDp = V2(math_cosf(rotation), math_sinf(rotation));
entity->dP = v2_scale(localDp, world->pixelsPerMeter * 10);
}
/* Loop entity around world */

View File

@ -94,3 +94,88 @@ i32 platform_readFileToBuffer(MemoryArena_ *arena, const char *const filePath,
return 0;
}
void platform_processInputBuffer(InputBuffer *inputBuffer, f32 dt)
{
KeyState *keyBuffer = inputBuffer->keys;
for (enum KeyCode code = 0; code < keycode_count; code++)
{
KeyState *key = &keyBuffer[code];
u32 halfTransitionCount =
key->newHalfTransitionCount - key->oldHalfTransitionCount;
if (halfTransitionCount > 0)
{
b32 transitionCountIsOdd = ((halfTransitionCount & 1) == 1);
if (transitionCountIsOdd)
{
/* If it was not last ended down, then update interval if
* necessary */
if (!common_isSet(key->flags, keystateflag_ended_down))
{
if (key->delayInterval > 0) key->delayInterval -= dt;
key->flags |= keystateflag_pressed_on_curr_frame;
}
key->flags ^= keystateflag_ended_down;
}
}
else
{
key->flags &= (~keystateflag_pressed_on_curr_frame);
}
key->newHalfTransitionCount = key->oldHalfTransitionCount;
}
}
b32 platform_queryKey(KeyState *key, enum ReadKeyType readType,
f32 delayInterval)
{
if (!common_isSet(key->flags, keystateflag_ended_down)) return FALSE;
switch (readType)
{
case readkeytype_one_shot:
{
if (common_isSet(key->flags, keystateflag_pressed_on_curr_frame))
return TRUE;
}
break;
case readkeytype_repeat:
case readkeytype_delay_repeat:
{
if (common_isSet(key->flags, keystateflag_pressed_on_curr_frame))
{
if (readType == readkeytype_delay_repeat)
{
// TODO(doyle): Let user set arbitrary delay after initial input
key->delayInterval = 2 * delayInterval;
}
else
{
key->delayInterval = delayInterval;
}
return TRUE;
}
else if (key->delayInterval <= 0.0f)
{
key->delayInterval = delayInterval;
return TRUE;
}
}
break;
default:
{
ASSERT(INVALID_CODE_PATH);
}
break;
}
return FALSE;
}

View File

@ -7,7 +7,7 @@
i32 userInterface_button(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
const KeyInput input, const i32 id, const Rect rect,
const InputBuffer input, const i32 id, const Rect rect,
const char *const label)
{
if (math_pointInRect(rect, input.mouseP))
@ -23,7 +23,8 @@ i32 userInterface_button(UiState *const uiState, MemoryArena_ *const arena,
uiState->activeItem == uiState->debugWindow.id ||
uiState->activeItem == 0)
{
if (input.keys[keycode_mouseLeft].endedDown)
if (common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down))
{
uiState->activeItem = id;
}
@ -124,9 +125,9 @@ i32 userInterface_button(UiState *const uiState, MemoryArena_ *const arena,
// If button is hot and active, but mouse button is not
// down, the user must have clicked the button.
if (!input.keys[keycode_mouseLeft].endedDown &&
uiState->hotItem == id &&
uiState->activeItem == id)
if (!common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down) &&
uiState->hotItem == id && uiState->activeItem == id)
{
return id;
}
@ -136,7 +137,7 @@ i32 userInterface_button(UiState *const uiState, MemoryArena_ *const arena,
i32 userInterface_scrollbar(UiState *const uiState,
AssetManager *const assetManager,
Renderer *const renderer, const KeyInput input,
Renderer *const renderer, const InputBuffer input,
const i32 id, const Rect scrollBarRect,
i32 *const value, const i32 maxValue)
{
@ -151,7 +152,8 @@ i32 userInterface_scrollbar(UiState *const uiState,
uiState->activeItem == uiState->debugWindow.id ||
uiState->activeItem == 0)
{
if (input.keys[keycode_mouseLeft].endedDown)
if (common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down))
{
uiState->activeItem = id;
}
@ -253,7 +255,7 @@ i32 userInterface_scrollbar(UiState *const uiState,
i32 userInterface_textField(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
KeyInput input, const i32 id, const Rect rect,
InputBuffer input, const i32 id, const Rect rect,
char *const string)
{
i32 strLen = common_strlen(string);
@ -266,7 +268,8 @@ i32 userInterface_textField(UiState *const uiState, MemoryArena_ *const arena,
uiState->activeItem == uiState->debugWindow.id ||
uiState->activeItem == 0)
{
if (input.keys[keycode_mouseLeft].endedDown)
if (common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down))
{
uiState->activeItem = id;
}
@ -338,9 +341,9 @@ i32 userInterface_textField(UiState *const uiState, MemoryArena_ *const arena,
}
}
if (!input.keys[keycode_mouseLeft].endedDown &&
uiState->hotItem == id &&
uiState->activeItem == id)
if (!common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down) &&
uiState->hotItem == id && uiState->activeItem == id)
{
uiState->kbdItem = id;
}
@ -354,12 +357,14 @@ i32 userInterface_textField(UiState *const uiState, MemoryArena_ *const arena,
i32 userInterface_window(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
const KeyInput input, WindowState *window)
const InputBuffer input, WindowState *window)
{
if (math_pointInRect(window->rect, input.mouseP))
{
uiState->hotItem = window->id;
if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft].endedDown)
if (uiState->activeItem == 0 &&
common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down))
uiState->activeItem = window->id;
}
@ -425,7 +430,8 @@ i32 userInterface_window(UiState *const uiState, MemoryArena_ *const arena,
if (window->windowHeld)
{
if (!input.keys[keycode_mouseLeft].endedDown)
if (!common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down))
{
window->windowHeld = FALSE;
window->prevFrameWindowHeld = FALSE;
@ -458,10 +464,9 @@ i32 userInterface_window(UiState *const uiState, MemoryArena_ *const arena,
window->prevMouseP = input.mouseP;
}
if (!input.keys[keycode_mouseLeft].endedDown &&
uiState->hotItem == window->id &&
uiState->activeItem == window->id)
if (!common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down) &&
uiState->hotItem == window->id && uiState->activeItem == window->id)
{
return window->id;
}

View File

@ -56,7 +56,8 @@ INTERNAL void keyCallback(GLFWwindow *window, int key, int scancode, int action,
i32 offset = 0;
if (key >= 'A' && key <= 'Z')
{
if (!game->input.keys[keycode_leftShift].endedDown)
KeyState *leftShiftKey = &game->input.keys[keycode_leftShift];
if (!common_isSet(leftShiftKey->flags, keystateflag_ended_down))
offset = 'a' - 'A';
}

View File

@ -46,7 +46,7 @@ typedef struct GameState {
AudioManager audioManager;
AssetManager assetManager;
KeyInput input;
InputBuffer input;
Renderer renderer;
World world;

View File

@ -44,6 +44,13 @@ u8 *common_memset(u8 *const ptr, const i32 value, const i32 numBytes);
void common_itoa(i32 value, char *buf, i32 bufSize);
i32 common_atoi(const char *string, const i32 len);
inline b32 common_isSet(u32 bitfield, u32 flags)
{
b32 result = FALSE;
if ((bitfield & flags) == flags) result = TRUE;
return result;
}
//-----------------------------------------------------------------------------
// MurmurHash2, by Austin Appleby

View File

@ -119,19 +119,33 @@ enum KeyCode
keycode_null,
};
/*
NOTE(doyle): EndedDown describes the last state the key was in and is
_NOT_ the same as pressed on current frame. They key may of have been
held down over multiple frames, so endedDown will remain true.
Pressed on current frame captures if it was just pressed on this frame
only.
*/
enum KeyStateFlag
{
keystateflag_ended_down = (1 << 0),
keystateflag_pressed_on_curr_frame = (1 << 1),
};
typedef struct KeyState
{
f32 delayInterval;
u32 oldHalfTransitionCount;
u32 newHalfTransitionCount;
b32 endedDown;
u32 flags;
} KeyState;
typedef struct KeyInput
typedef struct InputBuffer
{
v2 mouseP;
KeyState keys[keycode_count];
} KeyInput;
} InputBuffer;
typedef struct PlatformFileRead
{
@ -155,4 +169,22 @@ void platform_closeFileRead(MemoryArena_ *arena, PlatformFileRead *file);
i32 platform_readFileToBuffer(MemoryArena_ *arena, const char *const filePath,
PlatformFileRead *file);
/*
NOTE(doyle): The keyinput functions are technically not for "communicating to
the platform layer", but I've decided to group it here alongside the input
data definitions. Since we require that the platform layer writes directly
into our input buffer located in the game state.
*/
#define KEY_DELAY_NONE 0.0f
enum ReadKeyType
{
readkeytype_one_shot,
readkeytype_delay_repeat,
readkeytype_repeat,
readkeytype_count,
};
void platform_processInputBuffer(InputBuffer *inputBuffer, f32 dt);
b32 platform_queryKey(KeyState *key, enum ReadKeyType readType,
f32 delayInterval);
#endif

View File

@ -79,23 +79,23 @@ inline i32 userInterface_generateId(UiState *const uiState)
i32 userInterface_button(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
const KeyInput input, const i32 id, const Rect rect,
const InputBuffer input, const i32 id, const Rect rect,
const char *const label);
i32 userInterface_textField(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
KeyInput input, const i32 id, const Rect rect,
InputBuffer input, const i32 id, const Rect rect,
char *const string);
i32 userInterface_scrollbar(UiState *const uiState,
AssetManager *const assetManager,
Renderer *const renderer, const KeyInput input,
Renderer *const renderer, const InputBuffer input,
const i32 id, const Rect scrollBarRect,
i32 *const value, const i32 maxValue);
i32 userInterface_window(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
const KeyInput input, WindowState *window);
const InputBuffer input, WindowState *window);
#endif