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

No comments:

Post a Comment