Toggle on KeyboardPushed?

Just starting out? Need help? Post your questions and find answers here.
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Toggle on KeyboardPushed?

Post by spacesurfer »

Is there some neater code to toggle the flag var on KeyboardPushed() inside the screen rendering loop? The problem is we have to check if the key was released before it was pushed for the next time (similarly as KeyboardReleased() checks if the key was first pushed). Here's my code to toggle the onscreen info with the F9:

Code: Select all

#screen_width  = 960
#screen_height = 720

#window_0 = $00
#title = "Toggle on KeyboardPushed()"

Global done.b       = $00 ;flag
Global debug_info.b = $ff ;flag

If InitSprite() = 0 Or InitKeyboard() = 0 Or InitSound() = 0
  MessageRequester("Error","InitSprite or InitKeyboard or InitSound",0)
  End
EndIf

If OpenWindow(#window_0, 0, 0, #screen_width, #screen_height, #Title,  #PB_Window_SystemMenu | #PB_Window_TitleBar | #PB_Window_ScreenCentered) = 0
  MessageRequester("Error","OpenWindow",0)
  End
EndIf

If OpenWindowedScreen(WindowID(0),0,0,#screen_width, #screen_height,0,0,0) = 0
  MessageRequester("Error","OpenWindowedScreen",0)
  End
EndIf

Procedure process_input()
  
  Static info_pushed.b = $00
  
  Event = WindowEvent()      
  If Event = #PB_Event_CloseWindow  
    done = 1  ;remember 'close' event
  EndIf
  
  ExamineKeyboard()
  
  If KeyboardPushed(#PB_Key_Escape) 
    done = 1 ;remember ESC key
  EndIf
  
  ;if F9 was pushed, check for 'released'
  If info_pushed And Not KeyboardPushed(#PB_Key_F9)
    info_pushed = $00   ;remember 'F9 released'
  EndIf
  ;if F9 was released, check for 'pushed'
  If Not info_pushed And KeyboardPushed(#PB_Key_F9)
    debug_info ! $ff  ;toggle the flag
    info_pushed = $ff ;remember 'F9 pushed'
  EndIf
  
EndProcedure

SetFrameRate(60)

Repeat
  
  ClearScreen($000000)
  
  process_input()
  
  If debug_info
    If StartDrawing(ScreenOutput())
      DrawText(5, 5, "Some info... (press F9 to toggle)", $0000ff)
      StopDrawing()
    EndIf
  EndIf
  
  ;screen rendering
  
  FlipBuffers()
  
Until done = 1

End
There must be a way to do this part:

Code: Select all

If info_pushed And Not KeyboardPushed(#PB_Key_F9)
  info_pushed = $00
EndIf
If Not info_pushed And KeyboardPushed(#PB_Key_F9)
  debug_info ! $ff
  info_pushed = $ff
EndIf
in a more clever way :-/ It's too much compared to:

Code: Select all

If KeyboardReleased(#PB_Key_F9)
  debug_info ! $ff
EndIf
I know the KeyboardReleased() does the most but anyway...
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Toggle on KeyboardPushed?

Post by kenmo »

Here's one method I use, you could adapt it however you need:
http://www.purebasic.fr/english/viewtop ... 13&t=47312

Yes, it just stores the current and previous key states, so Hit/Pressed/Release can be checked independently. There is a small bit of overhead, but once it's set up, it's simple and clean to extend it to many other keys.

Also, I use a slightly more efficient version now. Instead of storing current and previous states as separate variables and indexing them with ThisFrame/LastFrame... I just assign them to bits in a single variable and discarded the ThisFrame/LastFrame bookkeeping... I could post an example later to show what I mean.
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Re: Toggle on KeyboardPushed?

Post by spacesurfer »

Thank you very much for answering me. Very nice keyboard handling code!! A good idea to be able to detect all three states (hit, pressed, released).
kenmo wrote:Also, I use a slightly more efficient version now. Instead of storing current and previous states as separate variables and indexing them with ThisFrame/LastFrame... I just assign them to bits in a single variable and discarded the ThisFrame/LastFrame bookkeeping... I could post an example later to show what I mean.
I think I can imagine what do you mean, you must have been using something like:

Code: Select all

#ThisFrame_pressed  = $01 << 0   ; %00000001
#ThisFrame_hit      = $01 << 1   ; %00000010
#ThisFrame_released = $01 << 2   ; %00000100
#LastFrame_pressed  = $01 << 3   ; ...
#LastFrame_hit      = $01 << 4
#LastFrame_released = $01 << 5

InitSprite()
InitKeyboard()
OpenWindow(0, 0, 0, 320, 240, "Keyboard Demo: Press Up/Down", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
OpenWindowedScreen(WindowID(0), 0, 0, WindowWidth(0), WindowHeight(0), 0, 0, 0)

#Keys = 2
Dim KeyCode.i(#Keys - 1)
KeyCode(0) = #PB_Key_Up
KeyCode(1) = #PB_Key_Down
Dim keyboard_flags.b(#Keys - 1)   ; flags

Repeat
  Event = WaitWindowEvent(25)
  
  ExamineKeyboard()
  For i.i = 0 To #Keys - 1
    
    keyboard_flags(i) << $03  ; LastFrame = ThisFrame
    
    ; Check if the key is down
    keyboard_flags(i) | (KeyboardPushed(KeyCode(i)) And #ThisFrame_pressed)   ; set (0, keyboard_flags) --> #ThisFrame_pressed = 1
    
    ; Check if the key has just now been hit
    keyboard_flags(i) | ((keyboard_flags(i) & #ThisFrame_pressed) And (~keyboard_flags(i) & #LastFrame_pressed)) << 1
    
    ; Check if the key has just now been released
    keyboard_flags(i) | ((~keyboard_flags(i) & #ThisFrame_pressed) And (keyboard_flags(i) & #LastFrame_pressed)) << 2
    
  Next i
  
  ClearScreen($000000)
  If (StartDrawing(ScreenOutput()))
    
    For i.i = 0 To #Keys - 1
      
      ; Check if the key has just now been hit
      If (keyboard_flags(i) & #ThisFrame_hit)
        Circle(WindowWidth(0)/4, (i+1)*WindowHeight(0)/3, 20, #Green)
      EndIf
      
      ; Check if the key is down
      If (keyboard_flags(i) & #ThisFrame_pressed)
        Circle(WindowWidth(0)/2, (i+1)*WindowHeight(0)/3, 20, #Yellow)
      EndIf
      
      ; Check if the key has just now been released
      If (keyboard_flags(i) & #ThisFrame_released)
        Circle(3*WindowWidth(0)/4, (i+1)*WindowHeight(0)/3, 20, #Red)
      EndIf
      
    Next i
    
    StopDrawing()
    FlipBuffers()
    
  EndIf
  
Until Event = #PB_Event_CloseWindow
End
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Toggle on KeyboardPushed?

Post by kenmo »

Yes, basically. It can be even simpler than that. Here is another modified version that uses only 2 bits per key state (current + previous).

Code: Select all

InitSprite()
InitKeyboard()
OpenWindow(0, 0, 0, 320, 240, "Keyboard Demo: Arrows / WASD", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
OpenWindowedScreen(WindowID(0), 0, 0, WindowWidth(0), WindowHeight(0), 0, 0, 0)

; Define all my key NAMES (not CODES) that we're interested in
Enumeration
  #MyUpKey
  #MyDownKey
  #MyLeftKey
  #MyRightKey
  #TotalKeys
EndEnumeration

; Now define all keyboard CODES we're interested in
Dim KeyCode.i(#TotalKeys - 1)
  KeyCode(#MyUpKey)    = #PB_Key_Up     ; #PB_Key_W ; You can easily change from Arrow keys to WASD keys
  KeyCode(#MyDownKey)  = #PB_Key_Down   ; #PB_Key_S
  KeyCode(#MyLeftKey)  = #PB_Key_Left   ; #PB_Key_A
  KeyCode(#MyRightKey) = #PB_Key_Right  ; #PB_Key_D
Dim KeyState.i(#TotalKeys - 1)



; Macros to tidy up all the later code
Macro KeyHit(ID)
  (((KeyState(i) & %10)>>1) & ((Keystate(i) & 01) ! %01))
EndMacro

Macro KeyPressed(ID)
  ((KeyState(i) & %10) >> 1)
EndMacro

Macro KeyReleased(ID)
  ((((KeyState(i) & %10)>>1) ! %01) & (Keystate(i) & 01))
EndMacro






Repeat
  Event = WaitWindowEvent(25)
 
  ; Update key states
  ExamineKeyboard()
  For i.i = 0 To #TotalKeys - 1
   
    ; Move "current" state bit (#1) down to "previous" state bit (#0)
    KeyState(i) >> 1
    
    ; Set "current" state bit, if pressed
    If (KeyboardPushed(KeyCode(i)))
      KeyState(i) | %10
    EndIf
  Next i
 
  ClearScreen($000000)
  If (StartDrawing(ScreenOutput()))
   
    For i.i = 0 To #TotalKeys - 1
     
      ; Check if the key has just now been hit
      If (KeyHit(i))
        Circle(WindowWidth(0)/4, (i+1)*WindowHeight(0)/(#TotalKeys + 1), 20, #Green)
      EndIf
     
      ; Check if the key is pressed
      If (KeyPressed((i)))
        Circle(WindowWidth(0)/2, (i+1)*WindowHeight(0)/(#TotalKeys + 1), 20, #Yellow)
      EndIf
     
      ; Check if the key has just now been released
      If (KeyReleased((i)))
        Circle(3*WindowWidth(0)/4, (i+1)*WindowHeight(0)/(#TotalKeys + 1), 20, #Red)
      EndIf
     
    Next i
   
    StopDrawing()
    FlipBuffers()
   
  EndIf
 
Until Event = #PB_Event_CloseWindow
End
[/size]

This version shows two more ideas you could play with:

1. Using named constants as the Key ID... For example, if you are making a game, Space might be the default Jump key. Instead of writing #PB_Key_Space throughout your program, just use #MyJumpKey or something similar. Then, if you ever want to change the Jump key, you only have to change one KeyCode definition line. (Also, this allows the user to configure key mapping via your program -- you could save and load custom KeyCodes from a config file. You just need an menu/interface to change the KeyCode(...) values.)

2. Using macros to simplify Hit/Pressed/Released checking. The macros above are specifically designed to return either exactly 0 or 1, not 2 or another other integers. This lets you use them in IF conditions as well as equations... for example:

Code: Select all

xVelocity = #Speed * (KeyPressed(#MyRightKey) - KeyPressed(#MyLeftKey))
Of course if you only want to use the macros in IF conditions (which I usually do) then you can just use:

Code: Select all

Macro KeyHit(ID)
  (KeyState(i) = %10)
EndMacro

Macro KeyPressed(ID)
  (KeyState(i) & %10)
EndMacro

Macro KeyReleased(ID)
  (KeyState(i) = %01)
EndMacro
In this case, KeyPressed() will return either 0 or 2. KeyHit() and KeyReleased() shouldn't be used in equations since PureBasic only officially supports comparison operators (=, >, <, etc) in logical conditions not integer equations.

Those are some ideas, you can mess around and see what suits your program best. Good luck. :)
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Re: Toggle on KeyboardPushed?

Post by spacesurfer »

Another nice peace of code :-) Thank you very much!!
kenmo wrote:This version shows two more ideas you could play with:

1. Using named constants as the Key ID... For example, if you are making a game, Space might be the default Jump key. Instead of writing #PB_Key_Space throughout your program, just use #MyJumpKey or something similar. Then, if you ever want to change the Jump key, you only have to change one KeyCode definition line. (Also, this allows the user to configure key mapping via your program -- you could save and load custom KeyCodes from a config file. You just need an menu/interface to change the KeyCode(...) values.)
You mean we could predefine a few sets of the keyboard commands (arrows, WSAD, ...) and then user could choose between them? How about the 'redefine keys' routine? How to print the key entered by the user? Is there some KeyCode2ASCII function?
kenmo wrote:2. Using macros to simplify Hit/Pressed/Released checking. The macros above are specifically designed to return either exactly 0 or 1, not 2 or another other integers.
Wouldn't be better if you'd change the flag bits so bit #0 (%01) would've been 'current state' and bit #1 (%10) 'previous state' (to avoid shifting in macros)? The KeyState(i) should then be shifted to the left instead to the right.

Regards
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Toggle on KeyboardPushed?

Post by kenmo »

I think there might be some OS-specific ways to convert between keyboard codes and ASCII characters... the KeyboardInkey() function sort of does this.

There are two main ways to let the user configure keys. One is to let them choose between pre-set configurations, which is easy:

Code: Select all

Select (KeyConfiguration)
  Case #WASD
    KeyCode(#Up)    = #PB_Key_W
    KeyCode(#Down)  = #PB_Key_S
    KeyCode(#Left)  = #PB_Key_A
    KeyCode(#Right) = #PB_Key_D
  Case #Arrows
    KeyCode(#Up)    = #PB_Key_Up
    KeyCode(#Down)  = #PB_Key_Down
    KeyCode(#Left)  = #PB_Key_Left
    KeyCode(#Right) = #PB_Key_Right
  ; ...
  ; ...
  ; ...
EndSelect
The other way is to let the user press any key, which will be assigned to some action. (This mostly applies to games -- I assume you're making a game?) I've used this method before too, but it's not as easy. I use a special procedure which monitors the state of ALL keys (0 to 255 seems to be plenty), and when the user hits one, it saves that keycode and moves on to configure the next action.
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Toggle on KeyboardPushed?

Post by kenmo »

spacesurfer wrote:Wouldn't be better if you'd change the flag bits so bit #0 (%01) would've been 'current state' and bit #1 (%10) 'previous state' (to avoid shifting in macros)? The KeyState(i) should then be shifted to the left instead to the right.
You could swap those bits. It doesn't really matter, but I think the macros would barely change, right?

Code: Select all

Macro KeyHit(ID)
  ((KeyState(i) & %01) & ((Keystate(i) >> 1) ! %01))
EndMacro

Macro KeyPressed(ID)
  (KeyState(i) & %01)
EndMacro

Macro KeyReleased(ID)
  (((KeyState(i) & %10)>>1) & ((Keystate(i) & %01) ! %01))
EndMacro

Code: Select all

  ; Update key states
  ExamineKeyboard()
  For i.i = 0 To #TotalKeys - 1
   
    ; Move "current" state bit (#1) down to "previous" state bit (#0)
    KeyState(i) << 1
   
    ; Set "current" state bit, if pressed
    If (KeyboardPushed(KeyCode(i)))
      KeyState(i) | %01
    EndIf
  Next i
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Re: Toggle on KeyboardPushed?

Post by spacesurfer »

kenmo wrote:You could swap those bits. It doesn't really matter, but I think the macros would barely change, right?
I thought something like:

Code: Select all

Macro KeyHit(ID)
  (((KeyState(ID) & %01)) And ((Keystate(ID) & %10) ! %10))
EndMacro

Macro KeyPressed(ID)
  ((KeyState(ID) & %01))
EndMacro

Macro KeyReleased(ID)
  ((((KeyState(ID) & %01)) ! %01) And (Keystate(ID) & %10))
EndMacro
BTW, there should be ID instead of i inside the macros. Here's the full modified code:

Code: Select all

InitSprite()
InitKeyboard()
OpenWindow(0, 0, 0, 320, 240, "Keyboard Demo: Arrows / WASD", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
OpenWindowedScreen(WindowID(0), 0, 0, WindowWidth(0), WindowHeight(0), 0, 0, 0)

; Define all my key NAMES (not CODES) that we're interested in
Enumeration
  #MyUpKey
  #MyDownKey
  #MyLeftKey
  #MyRightKey
  #TotalKeys
EndEnumeration

; Now define all keyboard CODES we're interested in
Dim KeyCode.i(#TotalKeys - 1)
KeyCode(#MyUpKey)    = #PB_Key_Up     ; #PB_Key_W ; You can easily change from Arrow keys to WASD keys
KeyCode(#MyDownKey)  = #PB_Key_Down   ; #PB_Key_S
KeyCode(#MyLeftKey)  = #PB_Key_Left   ; #PB_Key_A
KeyCode(#MyRightKey) = #PB_Key_Right  ; #PB_Key_D
Dim KeyState.i(#TotalKeys - 1)

; Macros to tidy up all the later code
Macro KeyHit(ID)
  (((KeyState(ID) & %01)) And ((Keystate(ID) & %10) ! %10))
EndMacro

Macro KeyPressed(ID)
  ((KeyState(ID) & %01))
EndMacro

Macro KeyReleased(ID)
  ((((KeyState(ID) & %01)) ! %01) And (Keystate(ID) & %10))
EndMacro


Repeat
  Event = WaitWindowEvent(25)
  
  ; Update key states
  ExamineKeyboard()
  For i.i = 0 To #TotalKeys - 1
    
    ; Move "current" state bit (#1) down to "previous" state bit (#0)
    KeyState(i) << 1
    
    ; Set "current" state bit, if pressed
    If (KeyboardPushed(KeyCode(i)))
      KeyState(i) | %01
    EndIf
  Next i
  
  ClearScreen($000000)
  If (StartDrawing(ScreenOutput()))
    
    For i.i = 0 To #TotalKeys - 1
      
      ; Check if the key has just now been hit
      If KeyHit(i)
        Circle(WindowWidth(0)/4, (i+1)*WindowHeight(0)/(#TotalKeys + 1), 20, #Green)
      EndIf
      
      
      ; Check if the key is pressed
      If KeyPressed(i)
        Circle(WindowWidth(0)/2, (i+1)*WindowHeight(0)/(#TotalKeys + 1), 20, #Yellow)
      EndIf
      
      
      ; Check if the key has just now been released
      If KeyReleased(i)
        Circle(3*WindowWidth(0)/4, (i+1)*WindowHeight(0)/(#TotalKeys + 1), 20, #Red)
      EndIf
      
      Debug 10*(KeyPressed(#MyUpKey) - KeyPressed(#MyDownKey)) + 2*(KeyPressed(#MyRightKey) - KeyPressed(#MyLeftKey))
      
    Next i
    
    StopDrawing()
    FlipBuffers()
    
  EndIf
  
Until Event = #PB_Event_CloseWindow
End
[/size]
kenmo wrote:I think there might be some OS-specific ways to convert between keyboard codes and ASCII characters... the KeyboardInkey() function sort of does this.

There are two main ways to let the user configure keys. One is to let them choose between pre-set configurations, which is easy:

The other way is to let the user press any key, which will be assigned to some action.
Yes, but we should print a feedback of every selected button.
kenmo wrote:(This mostly applies to games -- I assume you're making a game?)
I am not really making anything, just examining the PureBasic syntax and the environment, these are in fact my very first programs in PureBasic. I am still examining the Reference Manual and I can already see I'll have a few questions (about double buffering, Sprite vs Sprite3D, measuring speed, var type vs speed etc.).
kenmo wrote:I've used this method before too, but it's not as easy. I use a special procedure which monitors the state of ALL keys (0 to 255 seems to be plenty), and when the user hits one, it saves that keycode and moves on to configure the next action.
Getting the keycode is not to complicated but the problem (as seems to me) is to find out the corresponding keyboard button.
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Toggle on KeyboardPushed?

Post by kenmo »

spacesurfer wrote:BTW, there should be ID instead of i inside the macros.
You are right :shock: I copied that mistake through this entire thread.

And yes, you can use AND in those macros instead of any bitshifting, but when you use logical operators in PureBasic, it only guarantees a zero or non-zero result, so they don't recommend using them where an integer 0 or 1 is expected. But like I said, for conditonal checks like IF statements, that is perfectly fine.

PB is a fun language, I hope you enjoy it :)
User avatar
spacesurfer
User
User
Posts: 15
Joined: Fri May 11, 2012 8:22 am
Location: Hrvatska

Re: Toggle on KeyboardPushed?

Post by spacesurfer »

kenmo wrote:And yes, you can use AND in those macros instead of any bitshifting, but when you use logical operators in PureBasic, it only guarantees a zero or non-zero result, so they don't recommend using them where an integer 0 or 1 is expected. But like I said, for conditonal checks like IF statements, that is perfectly fine.
The only benefit of changing the bits then, is one shift less (in Macro KeyPressed(ID)). Still faster :-) I know it's not worth bothering but I did a lot 8 bit procs&microcontroller assembler programming so I always watch for every byte/bit/cycle :-)
kenmo wrote:PB is a fun language, I hope you enjoy it :)
I do, thanks. It's very fast and allows to do much with not so much code.
Post Reply