Temple of The Roguelike Forums
Development => Programming => Topic started by: mike3 on May 14, 2013, 05:46:06 AM
-
Hi.
I'm wondering: what's the best way to structure the input system in a roguelike game? That is, the part of the program that gets the input from the keyboard (or mouse, or both) and handles it. Namely, what I'm wondering about is, it is possible to somehow centralize the getting of input and then pass it on to the parts of the program that need it? The difficulty arises with stuff like inventory menus and the infamous "-More-" prompt. Suppose we have a "display" object which has a method in it to display messages in the message area. If we tell it to display a message, but the message isn't big enough to fit, then the "-More-" prompt has to be used, which requires waiting for keyboard input. But this would seem to require the display system to be able to fetch input from the keyboard itself. What's the best way to handle this?
-
But this would seem to require the display system to be able to fetch input from the keyboard itself.
Yes, it's called "user interface". You know, for displaying stuff on a computer monitor (form of interface that humans use) and waiting for keyboard commands.
-
There are several factors affecting the way that the input/display/logic loop should be handled in a game:
- What it needs to accomplish
- How often does it need to render? Every time the monitor refreshes (graphical/mouse input) or every time a key is pressed (terminal, keyboard only, no animations)?
- How many different input states are there? (game modes, menus, -more- prompts, etc.)
- How rigid is the relationship between a key being pressed and the game logic advancing a turn? (If every keypess advances the game by one tick, then there's need to separate logic from input)
- How much time you want to spend on it
- Does the game have a deadline like 2 days or 7 days, or is it a long-term project?
- Are you practising game design, good coding patterns, or just trying to get a game out of the door?
- How flexible you want it to be in case of future changes
- Are you writing for a terminal library (redrawing every keypress) and considering adding animations or switching to a graphical library (redrawing at 60FPS)?
- Do you have a simple time system that you might in future replace with a more intricate one?
- Do you have a bug whereby trying to walk into a wall consumes a turn?
- Will the menu structure change or be made scriptable?
A half-sensible loop would be something like this:
- The game window renders the game and/or menu system.
- The game window receives keyboard/mouse input, checks if it needs to respond to them (for instance, resize/close events), and passes them on to the menu system if it doesn't consume them. Otherwise, go back to the render step.
- The menu system checks if it's active, and reacts to input if it is (then goes back to the render step). Otherwise, pass it to the message area.
- The message area checks if it's waiting for input (-more-, yes/no, or text input prompt), and processes this input if it is (then goes back to the render step). Otherwise, pass it to the game logic.
- The game processes input as normal and goes back to the render step.
-
However, what if something in the game logic wants to send a message to the message display, and a "more" is triggered there? In the way that loop is structured, input can't be had until the game logic finishes and the next cycle begins.
-
The point of that loop is to allow rendering with vertical sync (which in some rendering libraries limits the framerate by sleeping before refreshing the screen) to continue as normal while waiting for input, without having to resort to multithreading. So if there's a big delay (like waiting for a keyboard input) then the game logic needs to stop and let the render/input loop run until it has something to process.
If there's a large range of events that can stop the game in the middle of the AI turn (anything that generates a message), making it impractical for the game logic function to be pausable/resumable without multithreading, then there are a few options.
If the user response has no effect on the result of the AI action, then there's no reason not to simply let the game logic complete processing, and record all the animations and message emissions to a list which is then replayed (in a much simpler function which can be paused/resumed without great complication).
It's worth considering whether you really need -more- prompts. They break immersion by dragging my eyes from the map to the message log and making me keep track of whether the game is expecting a command or a spacebar next.
Personally, I much prefer having a message display area that's large enough to contain the average turn's worth of messages (with a command to view the backlog), and relying on animations and peripheral vision of the message log to alert me to any unusual happenings. This aids immersion and makes the game's response to a given keypress more predictable, reducing mistakes.
The other option is to have the logic loop call the render loop. This is similar to using the standard curses getch() function, but wrapping it in a function which does continuous rendering and allows escaping into the menu system.
-
The point of that loop is to allow rendering with vertical sync (which in some rendering libraries limits the framerate by sleeping before refreshing the screen) to continue as normal while waiting for input, without having to resort to multithreading. So if there's a big delay (like waiting for a keyboard input) then the game logic needs to stop and let the render/input loop run until it has something to process.
If there's a large range of events that can stop the game in the middle of the AI turn (anything that generates a message), making it impractical for the game logic function to be pausable/resumable without multithreading, then there are a few options.
If the user response has no effect on the result of the AI action, then there's no reason not to simply let the game logic complete processing, and record all the animations and message emissions to a list which is then replayed (in a much simpler function which can be paused/resumed without great complication).
It's worth considering whether you really need -more- prompts. They break immersion by dragging my eyes from the map to the message log and making me keep track of whether the game is expecting a command or a spacebar next.
Personally, I much prefer having a message display area that's large enough to contain the average turn's worth of messages (with a command to view the backlog), and relying on animations and peripheral vision of the message log to alert me to any unusual happenings. This aids immersion and makes the game's response to a given keypress more predictable, reducing mistakes.
The other option is to have the logic loop call the render loop. This is similar to using the standard curses getch() function, but wrapping it in a function which does continuous rendering and allows escaping into the menu system.
It sounds like what you're mentioning here is a game with continuous rendering (realtime animations?), whereas I'm just doing a simple ASCII system here.
-
See my first post in this thread. The system you should program depends on the game's design objectives. I can't make any specific recommendations without knowing what it needs to accomplish, how much time you want to spend on it, and how flexible you want it to be to future changes.
Even an ASCII game might need continuous rendering. DoomRL and Angband's explosions, highlighting the tile that the mouse points to in a libtcod game, etc.
-
Well, then, here goes:
1. How often does it need to render? I'd think after every keypress would be it, although what do we do then about, say, an explosion or a magic spell or something else like that going off during the process of a turn after a keypress?
2. Input states: there's the main game, menus, and "more" prompt
3. Advancing turn: Well, merely opening a menu shouldn't advance the logic through the turn, but an actual action of the player in the world (pick up an item, attack, move, whatever) should (though trying to move into a wall shouldn't).
4. Time spent: There is no deadline
5. Code practice: Well, I'd like to finish the game, but having a reasonably good code would be nice as well.
6. Terminal or graphics? Right now I'm just interested in a terminal, but in the future (probably well into the future) I'd like to maybe move to a graphical system.
7. The time system right now is based on "events", set to happen at a given time in the future, on a priority queue, sorted by the time remaining. Each entity gets an event upon which it would decide what kind of action to perform, and the time between these events is determined by the entity's speed.
8. Yes, walking on a wall would consume a turn the way it is now. I'd like it not to.
9. The menu system is hardcoded/not based on scripting languages.
-
In the way that loop is structured, input can't be had until the game logic finishes and the next cycle begins.
Are you serious? Of course you can pause (+continue with key press) in any point you want. It doesn't break the "cycle".
-
In the way that loop is structured, input can't be had until the game logic finishes and the next cycle begins.
Are you serious? Of course you can pause (+continue with key press) in any point you want. It doesn't break the "cycle".
But then whatever has the message routine needs to know about the keyboard/input manager/etc. . Is that kind of dependency bad?
-
But then whatever has the message routine needs to know about the keyboard/input manager/etc. . Is that kind of dependency bad?
If you feel really bad about that, you can write a dedicated input routine for the message class.
I'm using C++ and global handles for stuff like GUI and keyboard routines. Sometimes keyboard is a part of GUI class.
-
I also mostly use one class for the interface/terminal, with functions such as get_keypress(), print_message(), players_turn(), and so forth. If I need a more prompt to split up a message or a turn, I basically just ask the class instance to execute get_keypress(), ignoring the result (or waiting for Spacebar to be pressed). Never experienced any problems with doing it that way. Some basic utilities like get_keypress() could probably even be had as global functions.
As always,
Minotauros
-
My loop is
1) Read input - this involves an event manager, which will depend on your particular library to get keyboard/other data. there is a global array that can be accessed like "if (keyHit[key_left]) {do something}"
2) Game logic - for instance, during the player turn I may check for key input and act on it. If no input has been given then there is no logic to perform.
3) Draw logic - this includes animation
4) Render
If you only want to draw the screen and render when something changes, you can just have a trigger for the draw/render section instead of having it be automatic. You can even be really lazy and have a global bool draw_update that you set to false at the beginning of the loop and toggle in the logic as needed. Basically, all you need to do is keep sections separate. You shouldn't have drawing muddled with logic for instance (you certainly can but you'll probably hate yourself later on). Once you have that all your questions should answer themselves. It isn't particularly complicated.
-
My loop is
1) Read input - this involves an event manager, which will depend on your particular library to get keyboard/other data. there is a global array that can be accessed like "if (keyHit[key_left]) {do something}"
2) Game logic - for instance, during the player turn I may check for key input and act on it. If no input has been given then there is no logic to perform.
3) Draw logic - this includes animation
4) Render
If you only want to draw the screen and render when something changes, you can just have a trigger for the draw/render section instead of having it be automatic. You can even be really lazy and have a global bool draw_update that you set to false at the beginning of the loop and toggle in the logic as needed. Basically, all you need to do is keep sections separate. You shouldn't have drawing muddled with logic for instance (you certainly can but you'll probably hate yourself later on). Once you have that all your questions should answer themselves. It isn't particularly complicated.
So what do you do to handle the whole "message" thing (messages depend on what happens in the game logic, but render to the display and may require input (the "more" prompt, for example))?
-
Is there so little screen space that messages have to fit into one line and the game flow needs to be interrupted with a "more" prompt?
-
8. Yes, walking on a wall would consume a turn the way it is now. I'd like it not to.
To fix this should be pretty easy if you are using a queue to order your actors. Just put the player back on the front of the queue if their action doesn't do anything.
-
Is there so little screen space that messages have to fit into one line and the game flow needs to be interrupted with a "more" prompt?
I prefer it this way. If there's a lot happening at once (e.g. several monsters casting spells, attacking, etc), I think it's best if the game shows one thing at a time.
"Monster X casts a spell, you are confused -MORE-"
Alright, what's next?
"Monster Y throws a rock, you are hit! -MORE-"
and so on...
It's a good pace, and allows me to verify each event.
If I end my turn and get 6 messages (events) all at once (no -MORE- prompt) it's much more likely that I miss something. And besides, I think it happens too fast. When I'm in a critical situation I think it's much more suspenseful to just verify one message at a time, each one making you sweat more :)
-
Is there so little screen space that messages have to fit into one line and the game flow needs to be interrupted with a "more" prompt?
I prefer it this way. If there's a lot happening at once (e.g. several monsters casting spells, attacking, etc), I think it's best if the game shows one thing at a time.
"Monster X casts a spell, you are confused -MORE-"
Alright, what's next?
"Monster Y throws a rock, you are hit! -MORE-"
and so on...
It's a good pace, and allows me to verify each event.
If I end my turn and get 6 messages (events) all at once (no -MORE- prompt) it's much more likely that I miss something. And besides, I think it happens too fast. When I'm in a critical situation I think it's much more suspenseful to just verify one message at a time, each one making you sweat more :)
And what happens if I'm in a room with 4 weak monsters throwing stones at me from the opposite side, and decide to walk over and melee them?
"Monster 1 throws a rock, but misses! -MORE-"
"Monster 2 throws a rock, you are hit! -MORE-"
"Monster 3 throws a rock, you are hit! -MORE-"
"Monster 4 throws a rock, but misses! -MORE-"
*moves one step towards monsters*
"Monster 1 throws a rock, you are hit! -MORE-"
"Monster 2 throws a rock, you are hit! -MORE-"
"Monster 3 throws a rock, but misses! -MORE-"
"Monster 4 throws a rock, you are hit! -MORE-"
A few turns, a dozen -MORE- prompts later:
*Kills monster 4*
"Monster 1 throws a rock, you are hit! -MORE-"
"Monster 2 throws a rock, but misses! -MORE-"
"Monster 3 throws a rock, but misses! -MORE-"
This is bad because I knew the risks of crossing the room to kill the weak stone-throwing monsters when I made the decision - I don't need to be reminded four times a turn (+ 6 while killing them) of what they're doing. If the game removes some of those prompts through some arbitrary decision about which ones are "important", then I get the even-worse situation where I don't know how many prompts I need to clear to get through to the next turn. Especially if a key assigned to an action can clear -MORE- prompts, this can cause more player error than it prevents.
I prefer Cataclysm's way of doing this - if an enemy is sighted, it prevents all time-consuming actions until you activate Danger Mode (or whatever it's called) which is taken to mean that you're aware there are things nearby trying to kill you, and that you're paying attention to the message log.
-
Is there so little screen space that messages have to fit into one line and the game flow needs to be interrupted with a "more" prompt?
I prefer it this way. If there's a lot happening at once (e.g. several monsters casting spells, attacking, etc), I think it's best if the game shows one thing at a time.
"Monster X casts a spell, you are confused -MORE-"
Alright, what's next?
"Monster Y throws a rock, you are hit! -MORE-"
and so on...
It's a good pace, and allows me to verify each event.
If I end my turn and get 6 messages (events) all at once (no -MORE- prompt) it's much more likely that I miss something. And besides, I think it happens too fast. When I'm in a critical situation I think it's much more suspenseful to just verify one message at a time, each one making you sweat more :)
And what happens if I'm in a room with 4 weak monsters throwing stones at me from the opposite side, and decide to walk over and melee them?
[...]
Similar lines should be grouped, and just show the number of times it was repeated. So 5 goblins swinging a dagger at you would only print:
"The goblin misses x5" (no -MORE- prompt)
Of course that only partly solves the problem you described above. But it tends to keep the prompts within reason for most situations.
Another disadvantage of the one-line message is that it can be easy to miss messages about discovering traps, or sensing a hidden monster etc. Because the message is cleared when the next player turn ends, if you just hold down the walk key, the message will only be displayed on the screen a small fraction of a second. With a multi-line log, the message will still be on the screen the next turn.
So yeah there's advantages and disadvantages of both systems. I'm not saying either way is objectively better, I just wanted to mention what I think is good about the single line message system. I don't think it's only some obsolete old crap (*cough*vi-keys*cough*) which should be removed without question.
---
A question for anyone:
With multi-line message logs, does it usually (traditionally?) use a -MORE- prompt as well, if more than 6 (or whatever the height of the log is) messages are printed between the player's turns?
-
May be this is a stupid question, but still...
Where do you find 80x25 terminal these days?
My terminal is 140x60. There is PLENTY of space for multirow message log.
Even in old good dos there was 80x50 text mode.
I'd say - stop solving nonexistent problem.
-
A lot of platforms make an 80x24 terminal by default. This guy (http://ttygames.wordpress.com/2013/04/12/encircled/) had to put effort in (using some command, not just dragging a window corner) to get an 80x30 window. This is one of the advantages of libtcod - it reliably gives you the (potentially quite large) terminal size you want. 80x50 seems to be standard for it.
-
Is there so little screen space that messages have to fit into one line and the game flow needs to be interrupted with a "more" prompt?
Someone posted a nice idea where you just print all messages in that turn over the screen and then wait for key to clear it. I think it can be better than having press -more- repeatedly, but haven't tried it yet.
-
I think Dredmor does something like that?
-
I think Dredmor does something like that?
When I played dredmor back in the day the log did not function at all. It did not negatively affect the game at all :P
-
I've just discovered a new wrinkle here: the need to put up prompts in the message area like "Do you want to climb this mountain? (Y/N)" (needs keypress) or similar. What's the best way for handling that? Note that since these are in the message area, there's an obvious connection to the message-printing system, and like "-More-" prompts, it requires the keyboard.
-
Those are much easier to handle than "more" prompts because they happen during the player's turn. If you accept multi-keystroke commands already (with some kind of state machine or equivalent) then you can handle such confirmation requests in the same way.
-
Those are much easier to handle than "more" prompts because they happen during the player's turn. If you accept multi-keystroke commands already (with some kind of state machine or equivalent) then you can handle such confirmation requests in the same way.
The program doesn't currently have support for multi-keystroke commands. Also, checking whether there's a mountain or something would be done in the movement code, yet that doesn't have access to the input system.
-
Also, checking whether there's a mountain or something would be done in the movement code, yet that doesn't have access to the input system.
You must be joking? Or are you mentally challenged?
-
Also, checking whether there's a mountain or something would be done in the movement code, yet that doesn't have access to the input system.
You must be joking? Or are you mentally challenged?
The game loop gets input. This input is passed to a command processor, which then turns it into, say, a movement direction for the player (e.g. mapping "arrow key" presses to grid movement). The movement system then uses this movement direction (not the raw keyboard command) to do the moving of the player. The keyboard object is not global, since globals are evil due to the dependency issues they create. So the movement system object does not know of the keyboard object as it does not interact directly with it, but is fed the movement information from the command processor, which is in turn fed keypresses from the keyboard. The movement system also handles movements of monsters (though they're not implemented yet). This couldn't be given by keyboard input anyway, but would have to be decided internally.
-
The keyboard object is not global, since globals are evil due to the dependency issues they create.
If there is only one global instance of a class, let's say a GUI class, it doesn't create any "dependency issues". It's just like using std::vector or printf in C++. "Globals are evil" makes me laugh. Anything can be evil in programming if you don't understand how to use it properly.
-
8. Yes, walking on a wall would consume a turn the way it is now. I'd like it not to.
The program doesn't currently have support for multi-keystroke commands. Also, checking whether there's a mountain or something would be done in the movement code, yet that doesn't have access to the input system.
You need to fix this first, before anything else in the input system. Even if the game doesn't have any commands that require more than one keypress, you need to be able to process a key without consuming a turn. One way to do this is to have the function return true if the keypress completed a valid command, and false otherwise.
-
8. Yes, walking on a wall would consume a turn the way it is now. I'd like it not to.
The program doesn't currently have support for multi-keystroke commands. Also, checking whether there's a mountain or something would be done in the movement code, yet that doesn't have access to the input system.
You need to fix this first, before anything else in the input system. Even if the game doesn't have any commands that require more than one keypress, you need to be able to process a key without consuming a turn. One way to do this is to have the function return true if the keypress completed a valid command, and false otherwise.
Agreed.
-
One option is to use the T-Engine which takes away all this low level stuff if you want it to.
-
One option is to use the T-Engine which takes away all this low level stuff if you want it to.
Yes, of course, but I've already made code and I'd prefer it not to go to waste :)