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:
parent
d878cca8ab
commit
5bb7fa8c13
113
src/Asteroid.c
113
src/Asteroid.c
@ -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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ typedef struct GameState {
|
||||
|
||||
AudioManager audioManager;
|
||||
AssetManager assetManager;
|
||||
KeyInput input;
|
||||
InputBuffer input;
|
||||
Renderer renderer;
|
||||
World world;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user