Sunday, April 21, 2013

Harvest Node Action

For this update I will work on a few items:

  • Main Loop
  • Follow Action
  • Harvest Node Action

Main Loop

For the main loop there are two issues. The first is that time accumulated goes on even when the window loses focus. Due to this when the window is back to focus the game updates too quickly. We can solve this by not accumulating time when the window is out of focus. Second, the frame rate is currently not limited to 60 FPS and is not doing vsync.

There's two different changes that can be made to the code for time accumulation for updateTick calls. First we can check, via Ogre's API, whether the window is active and if not, choose not to update anything. Second we can cap the max accumulated time for updateTick. This makes sense if you think about it since no player can handle a massive spew of game updates. It's better for the game to stall than for the player to be out of control while things are happening

void CGameEngine::updateTick(unsigned long lTimeSinceLastCall)
{
 if (m_nWindow->isActive())
 {
  m_lTimeSinceLastUpdate += lTimeSinceLastCall;

  if ( m_lTimeSinceLastUpdate > (m_lTimePerUpdate * 5))
  {
   m_lTimeSinceLastUpdate = m_lTimePerUpdate * 5;
  }

  //TODO: probably set some sort of game speed setting
  if (m_lTimeSinceLastUpdate > m_lTimePerUpdate)
  {
   m_nAppStateManager->updateTick();
   m_lTimeSinceLastUpdate -= m_lTimePerUpdate;
  }
 }
}

I'm going to limit FPS to around 60 (or at least some number that makes it look okay on most LCD monitors which typically refresh at 60 hertz). So it seems that vsync doesn't happen in windowed mode and I'll like have the game run in windowed mode. Later if I create a fullscreen mode, then I will probably just let Ogre perform auto-rendering with vsync and not bother with manually calling renderOneFrame. However, I'm not convinced my code is correct. My problem is that you really need to wait on vsync and then render for it to be correct. In any case, for now, I'll just do 60 FPS.

void CGameEngine::render(unsigned long lTimeSinceLastCall)
{
 if (m_nWindow->isActive())
 {
  m_lTimeSinceLastRender += lTimeSinceLastCall;

  long timePerFrame = (1 / 60) * 1000;
  if (m_lTimeSinceLastRender > timePerFrame)
  {
   m_lTimeSinceLastRender -= timePerFrame;
   m_nRoot->renderOneFrame(timePerFrame);
  }
 }
}

It's probably important to note that since my game is running in windowed mode only, the active flag is not all that useful (it's always active if in windowed mode) and also vsync is ignored (at least on Linux for sure, and I think Windows as well). I'll still limit to 60 FPS so it will at least not nuke my gpu/cpu usage. My game will be built for Windows first and then Linux after (because that's easy). I'd leave Mac for the very last since the platform is... what I'll call "special needs" kind of platform :)

Follow Action

With move action already coded, most of what we need is already there for follow. We already detect if you clicked on another unit with your right-click and we already queue a follow action on the selected units' action stacks. What remains is coding the doAction of a FollowAction. In the updateTick of a unit the followAction->doAction is fairly simple: if squared distance is greater than 2, move closer. Otherwise do nothing. In fact this action is never finished. I could later add optimisation (if it proves to be an issue to constantly check distance because I think it is a tad overkill) that they only recheck distance if the unit moves (meaning that the target unit informs its followers that it is moving and only upon moving do the followers decide to also move).

I'm likely to leave that optimisation out until there is an event system. Units would instead check distance until they are close enough and then go into an idle state that waits on "move events" coming from the target unit. Then once that event is fired they go out of idle state and perform a distance check again.

The only optimisation that occurs right now is a polling frequency check if the unit is following another unit. Every ten cycles (about one second in real life) they will make a distance check. Otherwise they will wait. This means there will be a small delay before a unit moves to follow another unit.

Additionally, the move counter was originally part of the unit action but I've since moved it to the unit. Obviously, this makes the moveCounter persistent across actions and so you can't make a unit go faster with some strange method of enqueuing and dequeuing actions.

Harvest Node Action

Here's the more meaty task. I'm going to split the harvest action into two different actions. One is harvesting simple nodes (bushes, grass and trees) and the other is harvesting things you have to kill (currently just animals). In the future this might include looting corpses (for example, after a battle your army scours the field for goodies). Here I'm only doing Harvest Node.

For harvest node we now need structures (to store goods we have collected) and the concept of a carry limit for a unit (to determine when they are full and must return to a drop-off point). We'll ignore private ownership for now. In the stone age, you'll be living in communal society in which all goods are public (later iterations of the game will include more complex stone age societies that have private ownership).

As an infrastructure task, I've made units and structures derive from the same base class. Later when I add the combatStats class (which records hp, armour, resistances and damage types) I won't have to double my effort. Since the game development is going rather iteratively, I'm doing this refactoring after the fact rather than as part of a pre-planned design. Oh wells.

Here are the structures:

  • Cave
  • Resource Pile

A structure needs to know several pieces of information:

  • Combat Stats (leave for later
  • Food Capacity
  • Non-food Capacity
  • Food Stored
  • Non-food stored
  • Living Capacity (for later)
  • Furniture (for later)
  • In-building locations(like, where is the bed to sleep in?, leave for later)

As a side note, a cave structure implies three new graphics: a cave structure, a cave floor and a cave wall. We'll have all of these only take up 1x1 area. I'll have to think a bit about structures larger than 1x1 and how to figure out pathing and so on (like for instance, a person walks into a 2x3 bedroom and then has to figure out where the bed is inside it)

There's a small problem with adding terrain artifacts like cave walls:

Pooooop! The issue is that the tiles are rotated along the x-axis by 60 degrees. So everything is flatter in isometric view. I went to take a look at gnomoria and they seem to just draw it so that it fits into the flatter squares. So I'll just follow their lead.

As a side note, the cave walls are drawn like trees to get the same blocking happening.

Food capacity is important for calculating spoilage later. Non-food capacity is relevant for storage space inside vaults. How I might do graphics is another question for later. I'd prefer to be able to show a place is full and by what. Resource piles in particular are a zero-cost structure that also has storage space but does not protect against spoilage.

The harvest action is a state-machine action. A unit moves up to a node, harvests a node, makes a drop-off and then goes back to the first step. This repeats until it goes to the node and it is empty and the unit has nothing to drop-off (so there's a final trip where the unit returns to an empty node and then stands there doing nothing). I think this mimics expected RTS behaviour. I may want to additional have the unit continue to harvest if there is nearby resources of the same type, which mimics more advanced RTS behaviour.

While taking ownership of caves is not yet implemented, the testbed will simply give a cave to the player faction. When a unit cannot find a place to do a resource drop-off, the unit will create a resource pile at the closest storage location.

Here is a unit harvesting:

I'll make a more interesting screenshot next time when I add some visual cues. I figure some numbers fading in and out should be the right way to go. There's a few tidy-up debugging I need to do to make sure everything is absolutely okay. I'll leave that for later as I've blown much of the weekend at this point on this :P

Saturday, April 13, 2013

Move Action

For this update I'm doing purely the move action by itself and nothing else. I'll modify the architecture as I go along to implement each action.

A move command requires a few changes to the current design of Cultura. Some of these are common between all actions, some are specific to the move action.

  • Some way for units to know what action they are to do and to do them
  • Updating the position of a unit in all references
  • Figuring how to move

Okay so what does this mean? Well we're going to add an action stack to a unit. There will then be action objects. These will be subclassed for the different type of actions. I'll refactor after for any common members between different actions, but for now the only common component is a pointer to the unit to which the action is attached to (to make it easier to manipulate the unit from the action itself rather than passing states information around or firing events). Then updateTick will be modified to carry out the action. Then we'll need the different components to actually be able to carry out the action with pathfinding.

First let's look at updateTick:

We have an outer loop based on a set timer to call the highest level updateTick which then calls it on lower and lower level components until reaching actual actions which then carry out what needs to be done and modifies the relevant variables as needed (the Unit or GameState interface allows the action to do so).

For a move action we need a pathfinder object to figure out where the unit should move. This requires information about the world; which terrain is passable and where structures are. Then it needs a way to modify the position of a unit. It makes a "moveUnit" call on GameState and the GameState updates several variables (delegating the call to Faction which updates the Faction's hashMap of units by location and then calls move on the unit itself to modify the position). The units by location hash map on a Faction is used for determining which units are selected quickly when making a selection box.

Okay, so we've already done unit selection and now UIInputManager takes in a right-click, figures out if it is an empty square and then if so, issues a move command to a unit. The unit clears its action stack and then queues the move action. Now it'll move during updateTick based on its move speed.

Oh poop balls what happened? Actually the mesh used for the humans didn't do the alpha check when rendering. Let's add that now.

Huzzah! Units are moving!

So now to polish it up, there is some jittery movement which I think is related to the main game loop (might not be designed correctly and thus not the same amount of time passes between each updateTick) and I want an x to show up where you told the unit to move.

Saturday, April 6, 2013

Actions and Future Ideas

I'm at the point where I can start issuing commands to units. Part of the piping work also involved a few infrastructure changes to the code but most of it was changing things into pointers to make memory management more clear (more obvious when things can be deleted and where they exist in memory). That's more of a c++ specific issue what with my awesome well-thought-out tight coupling of systems requiring me to create and pass objects between components. Code quality is dropping fast but I'll try to keep a lid on technical debt with respect to bad code.

Anyway, the general design of how to give units orders needs to be planned out. On that note, I have a general design for this.

  • GameState->updateTick() calls updateTick on all factions
  • Faction->updateTick() calls updateTick on all units
  • Unit->updateTick() checks the top of the action stack and then performs that action, this may update their world position, may have to call react on a target and may have to broadcast location to other units nearby to check if they need to react to its presence

There are several types of actions:

  • Move
  • Harvest
  • Attack
  • Follow

And some actions are reactions:

  • Flee
  • AttackLimited

It might be possible to roll the reaction actions into the original set of reactions with a few member variables attached. For instance, flee might actually just be a move action. AttackLimited might just be an Attack action with information about how far a unit should pursue an enemy before giving up. Each action has certain conditions to be met before being completed and others are somewhat more complex. For instance, a harvest action will attempt to gather as much material as a unit can carry and then push a "return goods" action to the top of the stack.

Later when there are industries and other such activities, there will be more complex actions that can rest as a foundation for a unit. A blacksmith may have a "blacksmith" action and when it is reached it simply pushes several actions on top, such as a move action to get the unit to a workplace and then a build item action and so on. And then the blacksmith action is never completed, every time it is reached in the action stack, it simply pushes new actions onto the stack. This way a unit continues to be a blacksmith until it is cleared.

These actions and reactions will require a little bit of infrastructure. A unit will now have an action stack where the actions are pushed onto and popped when completed. Some actions are replaceable, it remains to be seen how complicated this may become due to the nature of the game (a Settlers style industry implies a lot of automated action scripts that may occur). For reactions, the biggest problem is reacting to the presence of an enemy. Either you flee or you attack but in both cases it requires a way to perform a distance check between all units at any given time where this condition can be met.

There are several optimisations I have thought about for this problem. The first is to have a world hash map that contains all the units bucketed by "super squares". That is, I reduced the entire map to a smaller number of squares and only perform distance checks within groupings of squares. It's sort of like an oct tree but not quite. Simpler for the purpose I have in mind. The other is to only perform a distance check between a unit that has moved (in its updateTick function) and other units "near" it as determined by those super squares. And finally, only certain units even care. That is, the world hash map only contains military units. All other unit types are simply oblivious to approaching enemies until they are attacked.

For pathfinding, I'd prefer to use A* pathfinding but beyond a certain distance threshold it may be best to use a blind greedy algorithm. Just move in a square that goes toward the target location. In later iterations of Cultura where I want infrastructure (roads are especially important), I may have to do something more fancy. As a rule of thumb in those cases, roads/waterways are preferred over self-made paths not only because they may be faster but they may also be safer. An easier way to handle it is to make the roads speed up movement enough that they are almost always preferred without any special consideration on top of that and also to use a limited A* search (only do a partial pathfind and then move on the partial path).

There's been a bit of code clean up as well. Most of it is switching things into pointers to make it more obvious when memory is/should be cleared. Later on when the first iteration of the game (the alpha version) is done I will probably then proceed to implement an event based system (or I may even wait until I make a first release of the game before making large infrastructure code improvements). The event system is the biggest improvement that could happen. I picture it to be perhaps a singleton accessible from anywhere to allow all the different components to fire an event and register to listen to events.

Finally, I've had some thoughts on what I'd want the alpha to be. Essentially I'm going to split the game into "stages". Each stage is reached by reaching the population count necessary for it. For every stage there are new problems that can happen. Those problems are all centered around three variables in your society: health, happiness and administration. Stage 0 is where you start and you have no considerations. You simply go around collect resources, craft items and grow as you see fit. Stage 1 introduces health, where you have to maintain health or people can die from disease. And then finally Stage 2 is where happiness is introduced and you could face emigration should happiness fall too low. There are more possible consequences for failing to maintain health/happiness at later stages.

The Alpha version of the game will just be Stage 0. I'll complete all the necessary components (structures, population growth, UI, actions etc) and all the polish work (loading screens, responsive UI, good user experience) and then I'll move onto working on Stage 1 and Stage 2 where I have a "complete" game and I'm simply adding features. The release candidates would be with the first three stages (0, 1, 2). Then I'd work on 3,4,5 that focus on a wider range of consequences. For instance, low happiness could result in a small revolt or low health could result in a plague outbreak whereas in the earlier stages only one person might die from illness or a small group of people would just leave your society peacefully.

The polish work will likely require the help of some friends to point out UI experience issues that I may overlook because I focus too much on implementing various features. It's important for the user to always be aware of what's important in the game, to waste as little time on trying to find buttons or information and for everything to be "obvious" but to maintain the game complexity. It should be easy to understand the complexity and be fun to play through it.

What is my current wishlist? For an alpha I'd like to have the following:

  • Actions - user input and reactionary ones
  • Resource piles - placing resources anywhere on the ground
  • Natural Structures - caves mostly and also a resource pile with a limit inside, eventually would like to have dwarven mountains, elven tree tops, goblin bogs
  • Fighting - different damage types, weapons, hp, armour, dying, dropping of equipment onto the ground, looting equipment
  • Population Growth - some way of having population growth
  • Loading Screen - loading screen for starting a new game or loading a game
  • New Game set up - a screen for setting up a new game
  • Loading and saving - serialising game objects and deserialising them for saving/loading
  • Map Generation - based on some settings be able to generate a new random map that is good to play on (fair, balanced, varied landscape, sensible positioning of animals and plants)
  • Update Tick - this is a more general concept, just making sure that game speed is respected, units are redrawn in the correct spot, UI elements such as selection circles appear and disappear properly
  • Large Maps - get as large of a map as possible, possibly use a texture atlas and a shader that renders a texture, memory management of all the game entities see how far the limit can go
  • Diplomacy - ability to speak with other groups, maintaining the faction list, ability to make treaties based on diplomatic technologies known
  • AI Opponents - ability to have AI manage units, collect resources, craft items, and make diplomatic approaches with other entities, have personality, act based on relations and personality
  • Wildlife - have wildlife flee upon being attacked, migrate aimlessly, seasonal migration, just look vibrant
  • Vegetation Growth - be able to have regrowth of natural resources such as trees and grass, be able to record planted/dug up vegetation so that regrowth reflects environmental changes made by player
  • Land Regions - have arbitrarily defined land regions for game purposes
  • Maturation - have plants, animals and people have a baby to adult process, possibly a death by age process as well

These are some post-alpha ideas:

  • Serialisation versioning - able to have older save games be translated into newer save games without null point exceptions and so on
  • Labour skills - people can get better at particular tasks and produce higher quality goods or product them faster
  • Injuries - damage sustained that requires medical technologies to heal
  • Seasons - resources in random piles spoil but not in structures, health concerns if there is lack of warmth, regrowth of vegetation and wildlife in spring
  • Families - death by age and children, as well as family-based resource management
  • Artificial structures - structures the players builds, ability for people to understand how to use them and/or have assignments or possession of it
  • Trade - permanent trade routes, festivals and trade festivals
  • Health/Happiness - maintaining a calculation of health/happiness, have consequences for having low health/happiness
  • Families - death by age and children, as well as family-based resource management

Sunday, March 31, 2013

Fixing Selection Box

The original selection box code had some problems and the issue dealt with the fact that your box is drawn as if the grid were a normal cartesian plane but the reality is that it is rotated 45 degrees clockwise. This creates oddity in the coordinates. For instance, which point is more to the "left"? You have to calculate based on "translated" coordinates to figure that out because just looking at only the x or only the y would give you inaccurate results. If you wanted the height of a box that is facing the user in the non-rotated view then you have to translate.

Here's a figure to show what I mean:

And now here is the updated code that draws the selection box properly to the correct size:

                //get the bottom left corner and the top right corner based on the two locations
  std::pair<float,float> firstCorner = std::pair<float,float>(m_firstBoxCorner.first + m_firstBoxCorner.second, m_firstBoxCorner.second - m_firstBoxCorner.first);
  std::pair<float,float> secondCorner = std::pair<float,float>(m_mousePos.first + m_mousePos.second, m_mousePos.second - m_mousePos.first);
  
  float width = 0;
  float height = 0;

  if(firstCorner.first < secondCorner.first)
  {
   width = Ogre::Math::Abs((firstCorner.first - secondCorner.first) * Ogre::Math::Cos(Ogre::Degree(45)));
  }
  else
  {
   width = Ogre::Math::Abs(firstCorner.first - secondCorner.first) / 2;
  }

  if(firstCorner.second < secondCorner.second)
  {
   height = Ogre::Math::Abs((firstCorner.second - secondCorner.second) * Ogre::Math::Cos(Ogre::Degree(45)));
  }
  else
  {
   height = Ogre::Math::Abs(firstCorner.second - secondCorner.second) / 2;
  }


  float leftX = m_firstBoxCorner.first;
  float bottomY = m_firstBoxCorner.second;
  float rightX = m_mousePos.first;
  float topY = m_mousePos.second;

  //secondCorner is left of firstCorner
  if (secondCorner.first < firstCorner.first)
  {
   //move right
   rightX += width;
   topY += width;

   //move left
   leftX -= width;
   bottomY -= width;
  }

  //secondCorner is below firstCorner
  if (secondCorner.second < firstCorner.second)
  {
   //move up
   rightX -= height;
   topY += height;

   //move down
   leftX += height;
   bottomY -= height;
  }

  std::pair<float,float> lowerLeftCorner = std::pair<float,float>(leftX / UNIT_DISTANCE, bottomY / UNIT_DISTANCE);
  std::pair<float,float> topRightCorner = std::pair<float,float>(rightX / UNIT_DISTANCE, topY / UNIT_DISTANCE);

We can use the same logic to figure out the two corners and feed that into our unit selection code. But for unit selection itself there's an additional step which I will call "square picking". Based on the two corners (bottom left and top right) it needs to "pick" the squares that were selected. The issue arises when we get to really small selections, the square picking is much more apparent. Let's take a look at this example:

Maybe in the future I'll implement "fuzziness" to the edges of the selection. Players might not expect that the instant the box touches a square that it selects it. There may be some other "one off" box selection problems but for now I'll move onto context-sensitive right-click. Let's move some units around!

Tuesday, March 5, 2013

Selection Box

In order to create a selection box, I want a very simple green box. It appears when you press the left mouse button, expands to where your pointer is and knows how to create the box no matter whether your second corner your mouse pointer makes is above/below or to the left or right of your starting point.

To begin, I made a quad. Well actually, I made a set of four lines that made a square and made it unit size. Simple enough.

The input manager handling the UI takes in the mouse input. Wherever the mouse clicks, if it is a left mouse button press then the position is recorded. As long as the button is held down, the box is displayed and the position of the mouse represents the second corner of the box. When the mouse button is released, the box disappears (if it was a left mouse button release).

Okay, so we've the box and it appears where we click. But if we drag we could go and make any of the four other possible corners of a rectangle. The second corner could be the top left, top right, bottom left or bottom right. But that's important. We want to draw our box at the bottom left and then scale it outward to the top right corner so that it is the right size.

Then my next problem is calculating the bottom left corner. Well, if we simply took the lowest x and lowest y, it would be flat wrong. Unfortunately this is isometric coordinates. But, if instead we translated our two corners into the form of

(x,y) => ((x+y) , (y-x))

Now you have the isometric style x and y coordinates that matches what the player sees. As you move right, x+y increases. As you move up, y-x would increase. It is correct... but only enough to determine which is more "left" and which is more "down". You'll notice some weird division going on, it has to do with the fact that you've got a square rotated on the side and I'm trying to determine the length and thus scale the unit sized quad I had before correctly. Also don't be a foolbag like I was and use Ogre::Entity->scale instead of properly using Ogre::Entity->setScale. Yeah, results are not good if you use the wrong method. Hurr.

std::pair<float,float> firstCorner = std::pair<float,float>(m_firstBoxCorner.first + m_firstBoxCorner.second, m_firstBoxCorner.second - m_firstBoxCorner.first);
std::pair<float,float> secondCorner = std::pair<float,float>(m_mousePos.first + m_mousePos.second, m_mousePos.second - m_mousePos.first);
  
float x = m_firstBoxCorner.first;
float y = m_firstBoxCorner.second;
  
float width = 0;
float height = 0;
    
if(firstCorner.first < secondCorner.first)
{
 width = Ogre::Math::Abs((firstCorner.first - secondCorner.first) / Ogre::Math::Sqrt(2));
}
else
{
 width = Ogre::Math::Abs(firstCorner.first - secondCorner.first) / 2;
}

if(firstCorner.second < secondCorner.second)
{
 height = Ogre::Math::Abs((firstCorner.second - secondCorner.second) / Ogre::Math::Sqrt(2));
}
else
{
 height = Ogre::Math::Abs(firstCorner.second - secondCorner.second) / 2;
}

if (secondCorner.first < firstCorner.first)
{
 //move left
 x -= width;
 y -= width;
}

if (secondCorner.second < firstCorner.second)
{
 //move down
 x += height;
 y -= height;
}

Ogre::SceneNode* pSelectionBoxSceneNode = m_selectionBoxEntity->getParentSceneNode();
pSelectionBoxSceneNode->setPosition(Ogre::Vector3(x, y, 0)); 
pSelectionBoxSceneNode->setScale(Ogre::Vector3(width, height, 1));
pSelectionBoxSceneNode->setVisible(true); //hope this call isn't expensive 

Notice that when I get the x,y coordinate it is a bit inaccurate (perhaps off by 1). I might also be able to take the x,y from the original points based on who was more to the left and bottom but I'll have to convince myself of that later.

What do we do? We start our box at the bottom left. Then we scale it out according to the width and height. We use the translated points for that.

Ogre::SceneNode* pSelectionBoxSceneNode = m_selectionBoxEntity->getParentSceneNode();
pSelectionBoxSceneNode->setPosition(Ogre::Vector3(x, y, 0)); 
pSelectionBoxSceneNode->setScale(Ogre::Vector3(width, height, 1));
pSelectionBoxSceneNode->setVisible(true); //hope this call isn't expensive

Then we can use these values to scale our box.

pSelectionBoxSceneNode->setScale(Ogre::Vector3(width, height, 1));

And remember that we needed to rotate our square 45 degrees along the z axis otherwise it'll be an expanding diamond instead of a square!

Ogre::Quaternion boxRotationZ = Ogre::Quaternion(Ogre::Degree(45), Ogre::Vector3::UNIT_Z);
pSceneNode->rotate(boxRotationZ);

Voila

Given what you see here, you might have asked... gee does your selection code from before work? Yeah probably not. Some testing of it last night revealed lots of issues. I believe the above code works much better so I'm going to do the same thing when picking which squares to look at. I'll work on that more later, for now I've made it "work", but the square picking at the edges are terribly inaccurate.

Sunday, March 3, 2013

RTS Input

Just wanted to point out a little strange thing I ran into, which is what I consider "syntactic" (although I suppose it is technically not). I wanted to make a std::hash_map using an std::pair. In C++ that most people are using, it seems that if you want to use std::pair then you'll run into a type cast error because it tries to hash a pair object but it can't! You'll have to define your own hash function and use it with the std::hash_map. Poop!

Here is the code if you are interested. This is as simplified as I could make and thus you should replace it with what you need yourself. (Such as a more complicated hash function)

#include <hash_map>

struct hash_int_pair : public std::hash_compare< std::pair<int,int>>
 {
  const size_t operator() ( const std::pair<int,int> &p ) const
  {
    return p.first * 10000 + p.second;
  }

 bool operator() ( const std::pair<int,int> &a, const std::pair<int,int> &b) const
 {
  if (a.first == b.first)
  {
    return a.second < b.second;
  }
  else
  {
    return a.first < b.first;
  }
 }
};

typedef std::hash_map<std::pair<int,int>,std::vector<CUnit>, hash_int_pair> UnitMap;

Anyhow, we move into the world of the basic RTS UI and forget about isometric engine performance right now. We can go back to that when it becomes too slow again. Right now we want to be able to move the mouse around, click, capture the click and translate it into a unit selection box. Nothing fancy. Anything inside the box gets selected. But, there is the standard RTS selection algorithm.
  • Select your own units first
  • Select enemy units if any, but only the first
  • Select animals if any, but only the first
  • Select a piece of terrain if it was just a click
Drawing a box around the units is a bit confuzzling due to the rotated nature of the cartesian xy plane. Here is a little excerpt of the mash up code I wrote to just do it. I convert the coordinates into something "rotated" and then convert back and forth as necessary to get the right numbers. This is a bit inaccurate but close enough for now.

Quickly explained, the code basically selects a box along a zig-zagging top, iterate downward to reach the bottom zig-zagging row and then move to the next column toward the right.  I'm quite sure there's lots of problems with this right now but I'm just trying to get mouse picking working initially.

void CFaction::getUnitsByArea(std::pair<int,int> firstCorner, std::pair<int,int> secondCorner, std::vector<CUnit*> &unitsInArea, bool getFirstUnitOnly)
{
//convert coordinates into (x+y),(y-x)
std::pair<int,int> convertedFirstCorner = std::pair<int,int>(firstCorner.first + firstCorner.second, firstCorner.second - firstCorner.first);
std::pair<int,int> convertedSecondCorner = std::pair<int,int>(secondCorner.first + secondCorner.second, secondCorner.second - secondCorner.first);

int leftX = convertedFirstCorner.first < convertedSecondCorner.first ? firstCorner.first : secondCorner.first;
int rightX = convertedFirstCorner.first < convertedSecondCorner.first ? secondCorner.first : firstCorner.first;
int topY = convertedFirstCorner.second < convertedSecondCorner.second ? secondCorner.second : firstCorner.second;
int bottomY = convertedFirstCorner.second < convertedSecondCorner.second ? firstCorner.second : secondCorner.second;

//this is actually too fine grain
int currColumnX = leftX;
int currColumnY = topY;
bool moveDownRight = true;
for (int i = 0; i < (Ogre::Math::Abs(convertedFirstCorner.first - convertedSecondCorner.first) + 1); i++)
{
int jMax = Ogre::Math::Abs(float(convertedFirstCorner.second) - float(convertedSecondCorner.second)) / 2.0f;
for (int j = 0; j <= jMax; j++)
{
//shift down
int x = currColumnX + j;
int y = currColumnY - j;

std::pair<int,int> currLocation = std::pair<int,int>(x,y);

for (int i = 0; i < m_unitsByLocation[currLocation].size(); i++)
{
unitsInArea.push_back(&m_unitsByLocation[currLocation][i]);

if (getFirstUnitOnly)
{
return;
}
}
}

if (moveDownRight)
{
currColumnX++;
moveDownRight = false;
}
else
{
currColumnY++;
moveDownRight = true;
}
}
}

What do in Ogre3d land?  We create a ray based on the mouse position.  Ogre provides an API to do so.  We then calculate it's intersection with the XY plane.  Ogre also has code to do this.  Then we translate the x, y coordinates to a game coordinate (I have a constant called UNIT_DISTANCE in my code).


if (btn == OIS::MB_Left)
{
Ogre::Ray mouseRay = m_nCamera->getCameraToViewportRay(evt.state.X.abs/float(evt.state.width), evt.state.Y.abs/float(evt.state.height));
std::pair<bool, Ogre::Real> intersectionValues = mouseRay.intersects(Ogre::Plane(Ogre::Vector3::UNIT_Z, 0));

Ogre::Vector3 intersectionPoint;
if (intersectionValues.first)
{
intersectionPoint = mouseRay.getPoint(intersectionValues.second);
//translate the intersection point into gameworld coordinates
m_firstCorner = std::pair<int,int>(intersectionPoint.x / UNIT_DISTANCE, intersectionPoint.y / UNIT_DISTANCE);
}
}


And now let's see it!

Okay, so a static image doesn't really help show it. :P

In any case, it will have to be improved with several features.  First, I need to draw the selection box.  Second, make sure the box works no matter how you define the two corners.  Third, add in the ability to use shift and alt clicking which is a pretty standard UI interface.  The selection is somewhat inaccurate due to the weird isometric layout, but I'll just leave it alone for now (also because I box based on actual grid squares instead of something more fine).

Saturday, March 2, 2013

Easier IsometricGood

If you read the last post, certainly it seems that was a lot of work just to get blocking to look right. You weren't satisfied? Neither was I! This is my second solution which also works. I wanted to do an alpha check and then a depth check and then decide to overwrite the pixel in a particular square, all the while using batched geometry (and the issues that causes when it draws super quads which overlaps properly between super squares but not between smaller squares).

Ultimately, I've changed the rendering order such that objects that cannot possibly block each other can be written in large batches. I merely have to keep the ordering between layers but not between squares. So I batch up the grass, the bushes, rendering it in order of: terrain, grass, bushes. Then comes the new trickery. I am drawing in 3d but showing a 2d game, so what do? Well, I draw the trees, the only objects that are taller than a square in the game so far, with a little bit of "height".

Try to picture the above across the 2d plane. The squares in the bottom right corner of the map is already closest to the orthographic camera plane because the entire world is tilted 30 degrees downward. Why not use this z-level information to do proper depth checking and z-buffering? The issue was that the default settings is to write every pixel information to the screen no matter the alpha value.

But then there's this in Ogre3d:

pTreeFirstPass->setAlphaRejectSettings(Ogre::CMPF_GREATER, 0, false);

Anything with an alpha channel value of zero is not drawn at all, thus the z buffer doesn't get polluted with magical pixels you can't see. In order to understand why it is a problem let's go back to how it decides the colour value of each pixel.

  • Original value is some colour, an alpha value and a z-value of 0. (Remember that nothing writes to the z-buffer, so the z-value is 0)
  • NewValue = OldValue * (1 - AlphaValue) + IncomingValue * (AlphaValue)

If the alpha value is always 1 or 0 (which is the case in such 2d graphics), then basically the colour of the pixel is completely overwritten by any new pixel. That is, NewValue = OldValue * 0 + IncomingValue * 1. Therefore, NewValue = IncomingValue. However, what if the incoming value had an alpha channel value of 0? Then the colour remains the same as before. But, if I want my tree quads to record z-value information, then the z-value is overwritten with some new non-zero value. So what do you see on the screen? When it draws the humans it'll see this greater than 0 z-value and thus decide that it "blocks" the human. Yet to you, you see what is "behind" the human. It looks confusing but it's only because when the tree quad is drawn it overwrites the z-value but nothing else and thus blocks anything else from drawing there, yet all you see is grass, not tree.

Now it decides not to draw anything if the alpha channel value is 0, then the z-value is not overwritten. When the human is drawn, then it happily gets drawn overtop the grass, which had a z-value of 0 (that was not overwritten by the tree quad in the square "downwards" from it) but gets blocked when trying to draw overtop a tree that is in front. That is absolutely correct. And thus I avoided having to write some kind of complicated shader to do anything.

In the future I intend to have massive gigantic 5000x5000 maps, which will likely require a simple shader (one that looks at the RGB value of the texture and then draws things specified in a texture atlas... anyway I'll get to that when I have big maps. For now I have small maps so it doesn't matter. EVERYTHING IN MEMORY LIKE I'M A JAVA DEV!)

I may update this post with what I'll do later, but at this point, I'm going to leave the iso-engine alone and optimise it again when I need to (for larger maps or more units). For now, here is a screenshot! Yummy red gazelles and a brown dragon turtle hiding in the forest.