key_state

by IainPeregrine
A small library for handling player key presses.
ID:107809
 
Keywords: key_state
I have just released a new library for managing player input via the keyboard, called Key_State.

Key_State is a small and simple library which provides a way to answer to basic questions that are very important in video games:
1: Did the player press a certain key?
2: Is the player holding down a certain key?

DM uses an event based environment where code is tied directly to player commands, called
"verbs". In games with different environments, especially those with "game loops", the default event based player command system is a major obstacle. Using key states, a developer can passively log player commands, so that the game can actively respond on its own terms and at its own time. An example of this kind of system can be found in my action game Casual Quest (CQ). Go try the game, then come back; that way you'll be better prepared for the next paragraph.

In CQ, a central game object maintains a list of players, enemies, projectiles, and other game objects and information. Every server tick the game loops through this list and gives each object a chance to execute its own custom behavior: enemies move around and attack player, projectiles move forward and impact things, and player characters respond to client input.

This works via key_state. Every time a player character is given its turn by the central game object, the player character queries the client for key-state information, using this exact same library. If the client is holding the up arrow key (client.key_state(NORTH)) then the player character will move north. If the client has pressed the space-bar (client.key_press(PRIMARY)) then the player character will attack, and then clear the key-press data (client.clear_press(~PRIMARY)).

Using key-state info and a game loop, BYOND developers can create smooth movement and fun action games.

I'm keeping the library in Beta for now because I want more feedback before I finalize it. Don't let that stop you from using it, though; It is a fully functioning and bug-free library which I use in all my projects. If you have ideas to make the library better, or you do find a bug, please post on the forum.



If you found this link from the blog front page, you may be interested in my other blog post from 5 minutes ago, New Library: Dmm Suite
SuperAntx wrote:
Brackets in DM.

Included free of charge.
You know, I never write in any other style, and I don't see other people's code, so I've basically forgotten that DM can be written without them.
This seems pretty solid, but it wouldn't fix the issues I'm having with keyjamming. There aught to be a way to periodically check if a key is still being held down after the key+down macro has been triggered, but that's outside of the scope of this lib.

If memory serves right there was actually another lib also called KeyState. I think it used JavaScript to detect which keys a person was pressing then relayed that information to DS. It had the added bonus of detecting screen resolution as well.
SuperAntx wrote:
This seems pretty solid, but it wouldn't fix the issues I'm having with keyjamming.

What's keyjamming?


There aught to be a way to periodically check if a key is still being held down after the key+down macro has been triggered, but that's outside of the scope of this lib.

This was confusing for me at first, so it'll probably be confusing for anyone reading it. I'm guessing that you mean some sort of periodic re-trigger of an event tied to a macro, so that you could have continuous movement but still use BYOND's default event based input system instead of a game loop.

That would require a loop on macro that would start when the key is pressed, and end as soon as it is released.

Hm... thinking about how this would work in the code, I think I now understand what you meant by "key jammking". Is the problem that players who jam the keys very quickly spawn multiple movement loops? You may be able to get around this by using a recursive function instead of a loop. Provide the function with an argument representing the tick on which the function was spawned and then -- I'm going to cut myself off here before I mentally program something I don't need to. :/

If you think that most users would benefit from a built in retriggering feature, I can certainly look into that. The problem is that such a thing is so far removed from how I normally program, I'd like more input before I make something that would end up being useful to nobody.


If memory serves right there was actually another lib also called KeyState. I think it used JavaScript to detect which keys a person was pressing then relayed that information to DS. It had the added bonus of detecting screen resolution as well.

I am aware of it. Thankfully there's a way to do it natively now, instead of through a JavaScript hack.
Keyjamming as in keyboard hardware failing to call the right macros on account of mashing many keys at once. In some instances key+down will register but key+up wont. For all the game knows the player is still holding down the key, there's no periodic check to see if it's still actually being held.

Normally this isn't a problem, but Decadence in particular has key combinations where you're running, strafing, crouching, reloading, all at once. Every now and then a key+up command will slip through the cracks and the game won't know it was released until they go to press that button down again.

There also seems to be another problem related to key+up commands where they fail to register if you switch focus while it's still being held.
SuperAntx wrote:
Keyjamming as in keyboard hardware failing to call the right macros on account of mashing many keys at once. In some instances key+down will register but key+up wont. For all the game knows the player is still holding down the key, there's no periodic check to see if it's still actually being held.

I remember that this was a major problem when the +UP macro modifier was first introduced, and Dantom eventually put out an update to deal with it, after which I had no problems. Now that you mention it, though, that would explain a problem some new(b) CQ players have complained about where they "can't move" even though they've just started the game. If they were up against the edge of the map, and a key was jammed going in that direction, they'd might get stuck. Thanks for the info, I'll have to do some tests for this.
I pushed this to listed, as the documentation explained the use case pretty well I thought, and the code was reasonably considered.

Some more verbose documentation of the verbs themselves would be quite neat in the future. You can essentially make an API.dm file that has just the stubs of verbs with documentation I believe, probably done in people's more used indentation based syntax. That way you're free to code as you please on your end (and people don't see code they shouldn't need to see), and people get a more familiar structure on the API. I know that works for procs anyway, haven't checked verbs.
Maybe I'm just accustomed to the way my libraries handle keyboard input but it's taking a lot of work to make sense of your library and demo. I don't like the use of bit flags because of the limited number of keys you can support. I also don't like how you define 16 to correspond to attacking, it's arbitrary and relies on the macros you've defined. Now that I've found ways around it, I am not too fond of forcing users to define specific macros.

I recently updated by Handy Stuff library to include support for defining macros at runtime. I might be a little biased, but I happen to like my method better:

mob/Login()
client.add_macro("space")

// k is the key, m is the modifiers (CTRL, ALT, RELEASE, REPEAT, etc.)
mob/macro(k, m)
if(k == "space")
src << "Attack!"


The code explains what happens, you don't need to check the macros in the .dmf file to see (or change) what's happening.

My Sidescroller library keeps an associative list to remember which keys are pressed, so you can check mob.keys["a"] to see if the A key is being held. I think it's a lot more elegant to be able to use key names rather than numbers because there's no number-to-key mapping to remember (or, more importantly, to forget).
You could use #define to add your own little tags to the bitflags.
Forum_account wrote:
I don't like the use of bit flags because of the limited number of keys you can support.

One of the reasons I haven't released these libraries in the past is because I have my own style, and my solution isn't a global solution that will work for everyone.

What I've learned to love is a simple solution that does one thing well, and not to worry about limitations that don't matter to me. My system doesn't support over 16 keys, but that isn't a problem to me. Having a game with more than 16 keys would be a problem to me, as my game development style tends to emulate console games with controllers.

I also strongly advise other users to adopt a simplified control scheme, as I believe this will drastically increase a game's level of polish and its fun factor.

This isn't to say that a system should be adopted because of limitations. I use bit flags in my library because it allows me to handle movement keys in a special way. Notice the calls to Turn() in the library. I won't go into the exacts here of how it works, but by using bit flags I'm able to make the arrow keys work in a much more natural way, at least in my experience.

This solution may not work for you. That's fine. Having multiple libraries to choose from is a good thing. I swear by Deadron.XML, but for the recent 8k contest, Audeuro.Ini_Reader was a much better match.


I recently updated by Handy Stuff library to include support for defining macros at runtime.

Yeah, I saw your blog post a little while back. I define my macros at runtime, too, so that I can offer players the ability to customize their controls from within the game. Log into CQ or Regressia some time, and check out the options menu.

The reason I didn't do that here is because the actual macros and interface are outside the current scope of the library. That may change in the future, based on user input. That's why it's still a beta. Maybe I'll include the in-game macro editor in a future version of the library; if that's what developers need, that's the direction this library will take.


I think it's a lot more elegant to be able to use key names rather than numbers because there's no number-to-key mapping to remember (or, more importantly, to forget).

I agree that there is a benefit to associating a word with a function. That's why, as SuperAntx suggests, I define my controls as the very first couple lines in my project, just as I do in the demo. I've never had a problem remember what direction NORTH means in DM, and I've never had a problem remembering what ATTACK (or, as it appears in my code, PRIMARY) means. The only time the number would be arbitrary is when defining a macro in a skin file (where preprocessor macros can't be used), and we both agree that these should be made at runtime anyway. Besides, a string would be just as arbitrary here. Is it "north", "North", "NORTH", "Nortb", "Up", or what? The code doesn't understand that word at all, it just knows it's a value between quotes. The programmer still has to map it to a command. Here bit-flags have the advantage, at least for movement, in that the game does understand exactly what step(mob, NORTH) means.

You and I focus on many of the same areas, so it doesn't surprise me that you've developed your own way of doing things, and having multiple libraries that do the same thing, in different ways, is good for the platform.
Looking at it more closely, the clear_press proc seems strange. I wouldn't expect it to be necessary and from the name I'd expect it to take a key to clear. clear_press(ATTACK) looks like you're clearing the ATTACK key when in fact it would clear every other key.

Besides, a string would be just as arbitrary here. Is it "north", "North", "NORTH", "Nortb", or what? The code doesn't understand that word at all, it just knows it's a value between quotes.

You're not defining a string that gets mapped to a key by a macro (like space bar -> 16), the string *is* the macro (which does have meaning to BYOND) and it's also the value passed to the event handler.

having multiple libraries that do the same thing, in different ways, is good for the platform.

In theory I would agree. However, BYOND has a lot of libraries that do the same thing. It would be of great benefit if people would work together to develop a library instead of having 10 different flavors of the same library. One good admin library would be better than 10 inferior ones.

Saying "it suited my needs" is a cop out. Alternatives should provide benefits, explain what they are (they do exist).

I'm not trying to say that my library is better, I'm challenging you to improve yours. Another thing BYOND has is plenty of libraries that get released and see very few updates.
Forum_account wrote:
I'm not trying to say that my library is better, I'm challenging you to improve yours. Another thing BYOND has is plenty of libraries that get released and see very few updates.

I don't see that an improvement in this aspect is possible. Bit flags allow me to use the same directional system as defined by DM. It also allows for more natural arrow key handling. It's hard to explain, because it has to do with preferences built into the minds and hands of my play testers, but I'll try:

If a player presses and holds left, and then presses and holds right, my experience is that they expect the character to then move right, even though they're holding both left and right, because right was pressed after left. Then, when right is released, they expect to start moving left again.

This works very well with bit flags and the turn() proc. If using something other than bit flags, the equivalent of what I've written would have to be written individually for every game. Having a bit flag which unambiguously means WEST (left, in my example) as part of the DM standard is very helpful to this library. Explain to me how you would do this with user defined text strings. Perhaps I'm just not understanding your system.

I'll make it a point to take a look at your system when I have time. Perhaps I will pick up a trick or two.

------

As for clear_press(), it has one function, to clear all the keys that a user has pressed. The idea that a user might want to clear just one or two was an afterthought. If it's incredibly confusing to people (I did name the argument "saved_keys" or something) then I may just delete that altogether.
If the library is tailored for 4-directional movement then I'm not sure its a library that's really worth making. It sounds like you could create a separate, generic library to handle keyboard input and have a demo that shows how to use it for 4-directional movement. What you've released doesn't seem to have a clear split between library and application.

I expected that clear_press would not be necessary and that a key press event would automatically be cleared at the end of each tick. Not only does the name suggest it, but it seems like you'd invert the parameter more often than not.

My library's approach is somewhat fundamentally different. It triggers an event handler when the event happens. Your library provides ways to check if an event happened so you can process input in a single place and process input later (instead of immediately when the macro fires). This is the difference that your method can use to derive benefits, but you might have to abandon some current ideas to realize more of the potential.
Forum_account wrote:
If the library is tailored for 4-directional movement then I'm not sure its a library that's really worth making.

This is FUD. I assume that you post with good intentions, and that your statements come from a misunderstanding about the library, but your statements still act as FUD for new users reading this blog post. To prevent any confusion for people who haven't tried the demo with the library, this is not a "4-directional movement" system. It is in no way limited to movement in only the four cardinal directions. Try the demo, and you'll see that diagonals function perfectly.

What it is is a library which uses BYOND's default movement preprocessor macros. I still don't see why that's such an evil and base thing.


I expected that clear_press would not be necessary and that a key press event would automatically be cleared at the end of each tick.

You are correct. In all my games, clear_press() is called at the end of the game loop. In order for my library to automatically do what you suggest, I'd have to put an endless loop in world/New() (or client/New()) to continually clear inputs every server tick. Considering the types of environments this is meant for, such an inclusion would make the library useless. A server "tick" is meaningless to a game with a central game.loop.


My library's approach is somewhat fundamentally different. It triggers an event handler when the event happens.

Yes, your method is fundamentally different. As the library states, this is not a system for an event based environment. This is why I would not use your library, and why mine was necessary for me (well, also because yours didn't exist when I started programing). I'm not going to change the basic environment of my programs (game loop vs event based) just so I can use a library which does not use the default movement preprocessor macros, with the only benefit being an increased number of keys - a benefit which means nothing for me. This is a clear case of picking a method which fits a project's needs, instead of another method which does not.

I still don't understand your hang up here. Maybe your problem is that I use a game loop. That isn't going to change. Maybe you've never programed software with a centralized loop, and have some strange assumptions about it. Try Casual Quest. You'll notice that it isn't made out of pure evil. Your event's aren't logged somewhere, and then executed 5 hours later. You press a button, and the character reacts instantly - well, after 0.04 seconds wait, maximum. That's instant enough for me.

I would appreciate some actionable feedback, but I feel that your posting on this topic is only spreading FUD. We've already gone over this subject, and it should be clear that there is a fundamental difference between our program environments - a different which makes your solution worthless to me, and a separate passive system necessary. You're not going to change that by continuing to post, and I may start to delete your comments if I feel that they are turning users away from my libraries based on bad info.
The word "tailored" does not mean that is the only thing it can do. You admittedly made design decisions in the library that favor applications with 4-directional movement and simple controls. I seem to remember some of the directional bit flag stuff in the library code, but I'm not sure that it would always make sense there. I seem to also remember reading a note about complications with using diagonal movement. Those issues should be in the demo, not the library, because they relate to the usage of the library and not the library itself.

There is merit to an input library designed for games with central loops but you're not trying to make a library that fills that niche. You're trying to find a niche that your already written library fills. I agree that the library could be useful but you seem to be against making changes. For example, you can still have an input library designed for use with central loops that doesn't use bit flags and would support more keys. If you just wanted to release some code without regard for how useful it will be to others, make a demo.
Forum_account wrote:
I seem to also remember reading a note about complications with using diagonal movement.

I just checked that out again, and it is worded poorly; I'll fix that. As the demo shows, diagonal movement works fine. There is a potential problem with diagonal keys (if you define a diagonal key instead of using two cardinal keys to achieve the diagonal), but a single key can easily be made to move the character diagonally without this problem. I'll rewrite the comments in that section, and include an example of a key being used for diagonal movement in the library.


There is merit to an input library designed for games with central loops but you're not trying to make a library that fills that niche. You're trying to find a niche that your already written library fills.

I did not set out to make some key_state library, do a poor job, and try to make up excuses for it. This library was made because I am squarely in that niche, and I use it all the time. Over the next month I intend to release several such libraries intended for work with non-event-based environments (we'll have a lot of fun then), along with some articles on the subject and a challenge. This library may make more sense to you then.


I agree that the library could be useful but you seem to be against making changes. For example, you can still have an input library designed for use with central loops that doesn't use bit flags and would support more keys.

We've already been over this. Bit flags for movement are a standard and familiar part of the BYOND system, and have other benefits, besides, that are unique to bit flags and not available when using text strings. Also, 16 keys is a restriction I don't feel is low. To me, having a player input system using 16 keys is a design failure on the same level as butting up against the list limit. [I know several people who play my games with joysticks, as do I; maybe you're used to rogue-likes where you have to (q)quaff a potion, (u)use a want, (r)read a scroll, and so on, but I don't see why those functions can't be mapped to one key.] I am not against making changes, but I'm not going to trade what I feel is a good feature for gains in areas which I feel are worth very little.
Perhaps I can offer some kind of compound system which supports both numbers and text strings.
There is a potential problem with diagonal keys (if you define a diagonal key instead of using two cardinal keys to achieve the diagonal), but a single key can easily be made to move the character diagonally without this problem

Without devoting half of the possible macros to movement?

Also, 16 keys is a restriction I don't feel is low

Imagine using it for an RTS. You have the arrow keys to scroll the screen, number keys to select groups of units, and arbitrary letters that map to commands (M to issue the Move command to selected units, A for Attack, etc.). You'd run out of macros very quickly. I suppose you'll say "it's not designed for an RTS" or "then don't use it for that", but a library shouldn't have such a restriction.

The only justification for the use of bit flags I can see is this:

if(which < 16){
var/opposite = turn(which, 180)
if(opposite & old_keys){
key_state |= opposite
old_keys &= ~opposite
}
}
}


Which shouldn't really be in the library as it's specific to 4-directional movement.

You could trivially rewrite the library to not use bit flags (ex: return key_state[which] instead of return key_state & which). You couldn't do the previously mentioned "trick" with the turn proc in the key_up/key_down procs, but this feature should be handled in the demo anyway.
Imagine using it for an RTS.

Having a complex game is not an excuse for having a poor control scheme. Console games, even RTSs, have to map their commands to a joystick, and players are better off for it. I can see how having no limit is better than having one, but only when removing that limit does not require the removal of key features of the library. I'll look into adding support for both numbers and strings, as I said previously.


Which shouldn't really be in the library as it's specific to 4-directional movement.

Again, it's not 4 directional any more than any 2 dimensional game is going to have "4 directional" movement. It's the same "8 directional", if you want to say it that way, movement all BYOND games have, but using the arrow keys for a much natural control, like in most PC games and all console games with a D-Pad.


You could trivially rewrite the library to not use bit flags

Sure, if I didn't want to use bit flags. Again and again and again and again, I'm not going to remove a feature which I find very useful.


You couldn't do the previously mentioned "trick" with the turn proc in the key_up/key_down procs, but this feature should be handled in the demo anyway.

This feature is integral to the natural input scheme this library provides. This is a feature of the library, I wouldn't remove it any more than I'd remove map loading from my dmm_suite, but re-rewrite it in the demo or something. If I do change the library to support both numbers and strings, I may make the special treatment of BYOND's movement macros an opt out feature via preprocessor macros, but it will always be provided as a standard part of the library.
Page: 1 2