« Dungeon Generation | Main | General »
Tuesday, September 29, 2009
Input and User Commands
I just finished implementing the system for handling user input and commands, and I must say that I'm liking how it's turned out. Some of it was planned, some of it I had no choice, and some of it felt like a hack but turned out nice.
The first thing I had to deal with was user input on Windows. Since I'm using the Win32 API, I decided to get my input from WndProc and that the best thing to do would be to queue up all input as it came in, then have the game poll the input queue whenever it needed user input. So currently, my game loop infinitely run, and each time around, the game logic allocates energy to objects and gives turns to objects with enough energy. If the player has enough energy for a turn, the game logic polls the input queue. If there is no input waiting, the game logic exits out, and the loop continue until the user HAS pressed a key.
The game logic then takes action based on the key pressed, and code for all sorts of commands gets called. Sometimes the command doesn't actually use the player's turn, so the game logic continues to poll for more input, until the user actually does something that uses up their turn. Energy is taken from the player, and the whole process starts over.
I really like how Crawl takes multiple turns to do certain things, like puting on and taking off armor, eating, etc. To make this work under my system I hacked in a sort of internal command queue to the game logic. If a user command takes multiple turns, fake user input along with helper parameters are placed in an internal queue that gets polled just before the real user input. The internal queue gets consumed first, and things happen automatically without the need for the user to explicitly tell the game to do them.
For example: If you try to put on a helm when you already have one on, the game simply queues up an order to take off the current helm then put on the new one. If you try to put on heavy armor, the game queues up the command internally along with a timer parameter that keeps track of how many game turns are left until the 'puting on' is complete. I love the realism this provides without causing the user pain.
Eventually I'll need to add in an alerting system that aborts internal commands if certain bad things happen, like a monster comes around. It shouldn't be too difficult, as all I'll have to do is clear the internal queue and only accept user input. A cool side effect of this whole system is that I'll be able to record and replay entire games simply by feeding the keystrokes the original user made into the input queue! I never call getc() or anything of the sort - it all comes from the queue. I'm just now starting to think about the possibilities. The game could easily be advanced to a certain game turn. This could be any arbitrary turn, or one chosen from a recorded list of 'notable moments of the game', like the first encounter with a unique, or the player's HP dropped to 1, or the player arrived at such 'n such town. The user could step through the turns, or let it run at a given speed and watch it like a move. Players could learn tactics from observing others or share battle stories as an actual short clip of the game.
I really am making some good progress with things, and there are a lot of fun game elements that I'm going to be able to start to put into the game.
|
Timing, Turns, and Speed
My first thoughts on turns and game timing were directed towards a priority queue of events. A creature takes a turn, then adds itself back on the queue for a later time. I thought about leveraging the queue for other things, like when hunger starts to take a toll, poisons that wear off over time, etc. But the more I go into coding it, I started to realize that efficiently managing that queue was going to be hard. So I decided to save myself the days and days of coding and testing and opted for a simpler solution.
Like many others I'm using an energy system, where the game has an internal loop, each loop being a sort of 'game turn'. Any object that takes actual turns will have an amount of energy added to it, and when a certain amount of energy is reached, that object takes an actual turn and loses some energy. What's nice is you can adjust how fast a creature gets to take a turn by adjusting the amount of energy they gain each 'game turn'.
My system uses a linear approach to speed. Currently, objects get 30 energy multiplied or divided by a speed factor each game turn, and they get a turn once they have at least 600 energy. The speed factor is 1.0 if the objects speed is 0. For speeds greater than zero, the 30 is multiply by 1.0 + 0.1 * speed. So +10 speed means you get 2X turns, +20 speed means you get 3X turns. For speeds less than zero, the factor is calculated the same, but the 30 is divided by it instead. So -10 speed means you get 1/2X turns and -20 means you get 1/3X turns.
|
Thursday, September 17, 2009
The new look of RogueRunner
So here's a look at what I've been working on for the past few weeks. As mentioned before, OpenGL is out, ASCII is in. I'm loving it. I'm hard at work adding more and more to the game now that the display changes have been made, and I'm planning on lots of areas of the game to start to really develop.
Here's the new title screen. I'm sure it will go through a few tweaks, but I quite like it.
A sample shot of the current layout of the main display. I'm planning on moving most of the status stuff to other areas of the screen, but you get the idea. The numbers are all random, and my character is apparently in pretty bad shape.
Edited on: Thursday, September 17, 2009 4:47 PM
Categories: Game Mechanics
Friday, June 19, 2009
Mingos' FOV Algorithm
Tuesday, September 02, 2008
Moving Between Areas
The goal of many of the features of RogueRunner is to meet the needs of both randomly generated and hand designed worlds. Moving between areas is one of those key features. In Angband, for example, there is 1 town at the top, and X number of floors in the dungeon. You can't move off the edge of the map, and stairs are the only way to move up or down ( besides scrolls ) - pretty simple. In RogueRunner, all travel between levels will be handled with generic 'warps'. A warp is simply a cell that can take the player from one area to another, or from one level of an area to another.
The are 2 types of warps - directional and point:
Directional warps will take the player to neighboring areas in the 4 cardinal directions and above and below. If the area is a multilevel dungeon, the above and below warps will be used like stairs to connect each level, and only the top and bottom levels will take the player into a new area.
Point warps take the player to a specific cell in a specific area. This can be in the same level or in an entirely different area - it doesn't matter. Since these are so specific, they probably won't be used much in randomly generated parts of the world, but they can be very useful for things hand designed. One area where they will come into use is on the overworld map. When the player walks off the edge of a town map, for example, the player will be warped to the specific location of the town on the overworld.
Both types of warps can be used in 2 ways - walking onto them and activating them.
Not surprisingly, walking warps are activated by simply walking on them. Areas that have neighbooring areas in the cardinal directions will automatically have these types of warps created for them on the edges of the map. There's lots of situations where a walk-on warp makes sense, like a portal that warps you to another realm, or a trap door that 'warps' you to a pit level you have to fight your way out of.
Activation warps will need to be activated in some way. These are warps that you can choose if and when you actually use them. The most common will be using the '<' and '>' keys to warp up and down in a dungeon. Remember that point warps can also be activation triggered - so say the player beats the final boss and must now destroy the forbidden tome he dropped to erase the memory of the dark spells that he used to reign in terror. When the player smashes the book on the nearby altar, there is a great explosion, all goes black, and they wake up in their bed at home. Behind the scenes, the altar was a activation point warp.
|Friday, August 22, 2008
Door Mechanics
I don't know how other Roguelikes have implemented their doors. In RogueRunner, Cells have a pointer to a door object, which is NULL if there is no door. Here's a rundown of what the door object holds:
State - The state of the door i.e. closed, open, broken
Locked Rating - If this is greater than 0, the door is locked. The higher it is, the more difficult it is to pick the lock.
Hidden Rating - If this is greater than 0, the door is hidden. The higher it is, the more difficult it is to find the door by searching.
Key ID - If this holds a value, the door is magically locked. Magically locked doors can only be unlocked with the key with matching ID. This could be difficult to make work on completely random levels, but can easily be used when making hand designed levels through the level editor.
Door Set Index - There is a global array that defined the tiles to use for closed, open, and broken doors. A door just needs to know which set of tiles to use.
When a door is attempted to be opened or searched ( when hidden ), the door is passed a pointer to the creature trying to do it, and the door decides what happens. The ratings for locked and hidden doors determines how long it will take to succeed at the action. The exact numbers have yet to be finalized because I don't even have character stats finalized, but the idea is for the rating of doors to decrease on each attempt based on how good the character is at picking locks or searching. In the case of magically locked doors, the door simply checks the inventory of the creature opening the door and sees if they have the right key.
|Tuesday, August 19, 2008
Line of Site Using Shadowcasting
Now that I have some dungeons that I can actually move around in, line of site needs to be handled. There is an article at RogueBasin by Bjorn Bergstrom that explains in great detail a method called shadowcasting. If done correctly, this method actually avoids altogether cells that lie in shadowed areas. So even though everything is handled recursively, it's still very fast - especially when the player is in tighter locations, like a dungeon.
Even in open areas it can handle very large vision radii. I ran a test on an open 256 X 256 map with the character standing in the middle at 128, 128. With a vision radius of 128 it took 5 milliseconds to calculate. That's radius, so it basically checked the whole map. This took 1 millisecond to calculate:
The one thing I will add to his description is how I handled the different octants. The optimal solution is to write a different method for each octant. This is bulky and time consuming, and lots of code is duplicated. Instead, I wrote one method that uses a MapToOctant method whenever it actually looks up cells. The solution is simple and elegant.
|