Handmade Hero»Forums»Code
Jesse Coyle
37 posts
Input and recording two sources for loopedplayback
Hello, Sailors!

I thought it was pretty interesting how Casey tied the keyboard to mimic controller input, though I was disappointed when I was building a tetris game for my class, I decided to not do SMFL because, well that's too easy I have quite a bit of experience with high level engines... So I did the handmade hero way and followed Casey (though my assignment was 10 days late because of this type of input thing)

Casey, at least at around day 25, the input was passed through UpdateAndRender, it's very handy if you want to, every frame, check if a key is down. But I found it in my assignment when I did it that it is not good of course, if you just want to check if a key is pressed, and not trigger the same event until the key is released, then pressed again.

So a few months later, while going back to rebuilding everything from episode 1 to do a full 3D offshoot, I fixed this problem with how Löve does it's input (in a nutshell). Löve has a function you can call to see if a key is down, useful when you're in the update function and want to check every frame. It then has two separate functions KeyPressed and KeyReleased, that run every time a key is pressed or released (respectively). But instead of a function to see if a key is down, In the gameinput structure I put a 256 bool array, the position in the array being for the ascii code of the key. I then made defines so I don't have to look up every number to a corresponding key like key_f11 or pad_0, stuff like that.

Here's some code for you guys to get an idea of what I got. (Keep in mind this is choppy to cut out the stuff that's not related to the input)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
struct game_input
{
    float dt;
    bool KeyDown[256];
};

#define KEY_PRESSED(name) void name(char Key, int Uni, game_memory *Memory)
typedef KEY_PRESSED(key_pressed);
KEY_PRESSED(KeyPressedStub)
{
}
#define KEY_RELEASED(name) void name(char Key, int Uni, game_memory *Memory)
typedef KEY_RELEASED(key_released);
KEY_RELEASED(KeyReleasedStub)
{
}

/*
Removed a bunch of key defines from here
*/

#define MOUSE_PRESSED(name) void name(char Button, int X, int y, game_memory *Memory)
typedef MOUSE_PRESSED(mouse_pressed);
MOUSE_PRESSED(MousePressedStub)
{
}
#define MOUSE_RELEASED(name) void name(char Button, int X, int y, game_memory *Memory)
typedef MOUSE_RELEASED(mouse_released);
MOUSE_RELEASED(MouseReleasedStub)
{
}

local void
ProcessInputMessages(game_input *Input, game_memory *GameMemory,
                     win32_platform_code *Game, win32_state *Win32State)
{
   MSG Message;
   while(PeekMessage(&Message, 0, 0, 0, PM_REMOVE))
   {
      switch(Message.message)
      {
         case WM_QUIT:
         {
            GLOBAL_RUNNING = false;
         }break;
         
         case WM_SYSKEYUP:
         case WM_KEYUP:
         {
            // NOTE(Jesse): Keyreleased
            uint32 VKCode = (uint32)Message.wParam;
            bool32 AltKeyDown = (Message.lParam & (1 << 29));
            char Key = (char)VKCode;
            
            Game->KeyReleased((char)tolower(Key), VKCode, GameMemory);
            
            Input->KeyDown[VKCode] = false;
         }break;
         
         case WM_SYSKEYDOWN:
         case WM_KEYDOWN:
         {
            bool32 WasDown = ((Message.lParam & (1 << 30)) != 0);
            bool32 AltKeyDown = (Message.lParam & (1 << 29));
            uint32 VKCode = (uint32)Message.wParam;
            if(!WasDown)
            {
               char Key = (char)VKCode;
               
               Game->KeyPressed((char)tolower(Key), VKCode, GameMemory);
               
               if(VKCode == VK_ESCAPE)
               {
                  GLOBAL_RUNNING = !GLOBAL_RUNNING;
               }
               
               Input->KeyDown[VKCode] = true;
            }
            if(VKCode == VK_F11)
            {
               if(Message.hwnd)
               {
                  ToggleFullscreen(Message.hwnd);
               }
            }
            if(VKCode == VK_F1)
            {
               if(Win32State->InputRecordingIndex == 0)
               {
                  Win32BeginRecordingInput(Win32State, 1);
               }
               else
               {
                  Win32EndRecordingInput(Win32State);
                  Win32BeginInputPlayback(Win32State, 1);
               }
            }
         }break;
         
         case WM_LBUTTONUP:
         case WM_MBUTTONUP:
         case WM_RBUTTONUP:
         case WM_XBUTTONUP:
         {
            int X = (int)(WORD)(Message.lParam);
            int Y = (int)(Message.lParam >> 16);
            char Button = 0;
            switch(Message.message)
            {
               case WM_LBUTTONUP:
               {
                  Button = 'l';
               }break;
               case WM_MBUTTONUP:
               {
                  Button = 'm';
               }break;
               case WM_RBUTTONUP:
               {
                  Button = 'r';
               }break;
            }
            
            Game->MouseReleased(Button, X, Y, GameMemory);
         }break;
         
         case WM_LBUTTONDOWN:
         case WM_MBUTTONDOWN:
         case WM_RBUTTONDOWN:
         case WM_XBUTTONDOWN:
         {
            int X = (int)(WORD)(Message.lParam);
            int Y = (int)(Message.lParam >> 16);
            char Button = 0;
            switch(Message.message)
            {
               case WM_LBUTTONDOWN:
               {
                  Button = 'l';
               }break;
               case WM_MBUTTONDOWN:
               {
                  Button = 'm';
               }break;
               case WM_RBUTTONDOWN:
               {
                  Button = 'r';
               }break;
            }
            
            Game->MousePressed(Button, X, Y, GameMemory);
         }break;
         
         default:
         {
            TranslateMessage(&Message);
            DispatchMessageA(&Message);
         }break;
      }
   }
}


It works. I succeeded in creating separate functions from which to only trigger once per key event. And also allows, every frame for the key to be checked if it's down. Though it also creates a problem...

Casey and his beautiful live looped input recording and playback only handle for his conjoined controller/keyboard input method. I however am stumped trying to find how to not only ready the one key/mouse pressed/released system for saving into file write, but also how to write it. The only thing I can think of to relieve the problem is to make 4 arrays of bools, and when a key is pressed it switches the pressed array ascii index to 1, but every frame 0 out all 4 arrays. Which sounds unfavorable.

If anyone has any ideas on how to alleviate this solution, or have a different solution entirely, I'm welcome to suggestions.
Mox
32 posts
Input and recording two sources for loopedplayback
Hi Zilarrezko,

I do believe that the code Casey wrote has a way to check for state change of a key and the current state of the key:
EndedDown should tell you the state of a key at this time and the HalfTransitionCount is used to detect transitions from up to down or the other way.

From your code I do see that your main loop might be different from the way HH does things. HH first gathers up all input into those two variables per key you want to track (which could be increased to all the keys on the keyboard with a larger array). After all the Windows messages have been processed it passed it to the Game code. This is the input that is saved for looped playback.
Your code seems to send each message to your game. Which is not really compatible with the looped code editing.

Maybe you can explain why you have problems with detecting the input you want? Since I do think the way HH does things should be usable in your code too.

Regards,
Mox
Jesse Coyle
37 posts
Input and recording two sources for loopedplayback
Ah! So he checks if EndedDown is true (if the key is down), then HalfTransitionCount is the amount of times it has transitioned between pressed and released. So in UpdateAndRender, to only do a section of code once per key press would be an if statement with (Input->keys[InsertAsciiHere].EndedDown && Input->HalfTransitionCount == 0). So to see if the state of the key hasn't changed we just check if HalfTransitionCount is 0, because if it changed at all it would be > 0. Genius, because Casey explained that system was to just in case someone hits a button really fast (or my CM Inferno that has over 7000 clicks/keypresses per minute capability) then it will be able to process all of those messages (although it could get laggy).

An extra question though... How then would it handle the same input (more than one HalfTransitionCount's) when a key was pressed more than once in a frame. Does it just process it the next frame then decrement the HTC? I don't remember Casey talking about that. Although you could just I guess to a for loop for every HTC.

Hopefully I got all that right; Makes sense to me. So then I just merge the keydown and keyup message switch statements, and if it's in it's was down state, then I bool true the EndedDown (which is keypressed I'm guessing? Or is it literally, a bool that by the end of the HTC if true then it ended on a keydown state or not).
Mox
32 posts
Input and recording two sources for loopedplayback
If I remember correctly you actually need to check for HalfTransitionCount > 0 for key changes. HTC == 0 would indicate no change.

And for processing more ups and downs in between would be checking for even higher HTC. And even when EndedDown is 0, this will indicate multiple changes.

For instance ED == 0 && HTC == 2 means that there was one key press and release this frame.
ED == 0 && HTC == 4 would mean down->up->down->up

And for ED == 1 && HTC > 1, it means that there where a sequence of events that ended in the key down.

So you always need to look at HTC and look back at the up and down actions that it represents. Now it is a question of finding the count you need for each of the cases of EndedDown, I believe something like this:

1
int downCount = (HTC + (EndedDown ? 1 : 0))/2


This way you should only count each down event once I believe. But working the seperate cases for ED being 0 and 1 out on paper could make this more clear.