Thursday, February 28, 2013

IsometricGood Damn it

Okay so I've been having trouble with making the isometric view just right. It's been a combination of learning about graphics and about Ogre3d. Let's describe two things first:

  • Z Buffer
  • Alpha Channel Transparency

Z Buffer. In a graphics engine when it goes through its rendering queue it has to decide what the final pixel it draws on your screen is. As it draws, for each pixel it records a "z value". This z value is based on the distance from your camera. It is usually non-linear; it slices your viewable area (your "viewing frustum") into unequal slices. The closer you are to the camera, the thinner the slices to get better accuracy.

As it decides to render the next pixel, it does a "depth check", and sees whether or not the z-value of the newest pixel is higher. If so, then it replaces the old pixel. If not, then it discards it. If it is of equal value, it might blend them or you might get z-fighting/flimmering (where it draws one or the other pixel).

Alpha Channel Transparency. If you look at a png (or other file types that support this) you'll see that it has colour channels and something called an alpha channel. You'll probably have to dig around to find this in your graphics editor. The alpha channel is a measure of opacity. It's useful for transparent objects. When you tell Ogre3d to do "alpha scene blending" of some kind, it blends the colours of the original existing pixel with that of the new pixel (assuming it passed the depth check).

When I was making my isometric view I made the terrain a staticGeometry. That makes it render super fast. The problem is that it renders super fast because Ogre3d combines the objects together and then renders it. The end result is that the terrain does "blocking" in giant squares, but nothing else does, so it'll just look wrong. I've tried it without the staticGeometry and the blocking works perfectly. Problem? Runs super slowly :/

Now the problem is that I have a lot of entities (my particular test run has about 900), so it runs very slowly. All it is making is 900 quads and applying textures. It's not much. Most of the quads don't need to be updated. In fact in any particular game, only units have to be updated and most of the scenery is static. Structures and piles of resources, which I would add later, would also not need to be updated very often. The main issue is that high number of entities that need to be sent to the video card to be rendered. Unfortunately, there's really only complicated ways to resolve this because I need to preserve the render order. Poop!

But! This is a common issue for many games (especially age-old isometric ones) and thus has solutions. I'm going to take some time to research my options and then move ahead on one.

Okay here is my current solution idea...

Problem? If I use staticGeometry to get quick batched geometry being sent to the video card I get great frame rate (which I rate as 60 FPS v-sync in debug). But staticGeometry groups the quads I make into super quads. Exactly what it is supposed to do but totally flubs my render order that I carefully arranged much like a house of cards. In the utter ruin of this I use Ogre3d render queue groups to get what I want. This is a lot of cheating and silliness but let's go through what I've done.

You can see in the picture I have problems because staticGeometry and render groups mess with my correct overlapping of objects that are taller than the square they occupy. Okay, but let's tackle one problem at a time. Let's deal with the staticGeometry but keep the performance. I can render all the base terrain, then all the grass, then all the bushes and then all the trees and then all the units all in different render queue groups. It takes care of the staticGeometry problem. Terrain overlapping itself doesn't matter. Grass and bush don't expand beyond a square so they can sit in the same render queue group. Trees have to be in a separate render queue group or else they don't overlap properly. Units are rendered last because they are each a separate entity and must be done in a costly square per square rendering.

Okay that solves the static geometry. But now, the unit still blocks the tree because of the render queue grouping. The feet of my humans block the top of trees. What if... we rerender those trees?

Oh noesss! It's still not quite correct. But it is less wrong. Anyway to be absolutely correct, I have to rerender EVERYTHING down the line. Well that seems costly. How to cheat more? I make the bushes smaller and fit exactly in the square. That is, I make as much stuff as possible shorter than a square. That means I rerender everything "downwards" (positive x, negative y) until I hit a square that doesn't have anything that breaches a square.

This renders at 60 FPS v-sync in debug. There is of course much horror and terror going on in my code which will have to be fixed and cleaned up but that is a task for Later Man, the King of Not Optimising Until It Is A Problem.

Wednesday, February 27, 2013

Isometric Render Order

I’ve had significant difficulties in the finer minor details of isometric view blowing a lot of my time and I feel that as this problem is repeated half a hundred times with every single game that is isometric I’m going to try to explain it as well as I can.

I already had a post before about isometric camera. It showed a triangular prism. That’s not correct (because otherwise all objects on the same plane would not be equidistant from the camera and thus you’d get “perspective”).

Let's do that diagram again:

A z buffer is a buffer used when rendering to determine what pixel should be rendered there. So while it is rendering, it records the distance of a particular pixel rendered on the screen to the camera and saves it in the z buffer. When some other object wishes to render in the same spot, it's z coordinate value is checked against what is in the buffer and if it is higher then it replaces the previous pixel (if they're the same value, then the behaviour might be funky).

The stuff you can see is inbetween the near clipping distance and the far clipping distance. The ratio of the two determines the accuracy of something called the "z buffer" in the graphics engine. With Ogre3d, your z buffer accuracy is a number of slices in between those two distances.

That viewable area is called the viewing frustum.

Now then, onto what I want to happen there.

In a particular square I’d like to have, in order of first item is the most hidden and the last item is the most visible:

  • Terrain
  • Grass
  • Bush or Tree
  • Unit
  • Structure

Between squares I’d like to have, in order of first item rendered is blocked by any later item,

For x = 0 to x = max
 For y = max to y = 0
  Render x,y
 End Loop
End Loop

Notice that the inner loop for y is reversed. Imagine a square. Now rotate it 45 degrees. Notice that the top point is (0, max). Notice the bottom point is (max, 0). Switching the order of the inner loop will get what we want.

The loop doesn’t exactly render squares in the exact order that you might write down by hand. But it’s key to remember two points. A square blocks images of squares with a lower x value, or a higher y value. The loop preserves this.

Imagine a 3x3 square. Drawing by hand in the correct order you’d likely do the diagonals

  • (0,2)
  • (0,1) (1,2)
  • (0,0) (1,1) (2,2)
  • (1,0) (2,1)
  • (2,0)

The loop does:

  • (0,2) (0,1) (0,0)
  • (1,2) (1,1) (1,0)
  • (2,2) (2,1) (2,0)

But it satisfies the rules, so we’re okay.

Now, for actual implementation issues, the question is what magical class holds all the data such that it can make these render decisions? Well, I have GameState. This contains all information about terrain and units. But it’s not actually arranged in a way for the graphics rendering to work properly (I have graphics updating inside the updateTick of particular units).

I can change that though.

I can add a method to GameState->createScene() and render everything in the correct order. Then units will update their entities (and this hopefully doesn’t change the render order in Ogre).

Okay, now that I’ve described what I want, let’s see how to do it in Ogre3d.

What we want:

//we could even have a z-level loop if we were doing something akin to dwarf fortress or gnomoria
For x = 0 to x = max
 For y = max to y = 0
  Render terrain
  Render grass
  Render bush or tree
  Render unit
  Render structure
 End Loop
End Loop

We have to create the materials and for these materials, you have to define a rendering “pass”. I use these to get what I want for now:

scene_blend = alpha_blend
depth_check = off

I’ll start with what I want.

In a particular square I’d like to have, in order of first item is the most hidden and the last item is the most visible,

  • Terrain
  • Grass
  • Bush or Tree
  • Unit
  • Structure

Between squares I’d like to have, in order of first item rendered is blocked by any later item,

For x = 0 to x = max
 For y = max to y = 0
  Render x,y
 End Loop
End Loop

Notice that the inner loop for y is reversed. Imagine a square. Now rotate it 45 degrees. Notice that the top point is (0, max). Notice the bottom point is (max, 0). Switching the order of the inner loop will get what we want.

The loop doesn’t exactly render squares in the exact order that you might write down by hand. But it’s key to remember two points. A square blocks images of squares with a lower x value, or a higher y value. The loop preserves this.

Imagine a 3x3 square. Drawing by hand in the correct order you’d likely do the diagonals

  • (0,2)
  • (0,1) (1,2)
  • (0,0) (1,1) (2,2)
  • (1,0) (2,1)
  • (2,0)

The loop does:

  • (0,2) (0,1) (0,0)
  • (1,2) (1,1) (1,0)
  • (2,2) (2,1) (2,0)

But it satisfies the rules, so we’re okay.

Now, for actual implementation issues, the question is what magical class holds all the data such that it can make these render decisions? Well, I have GameState. This contains all information about terrain and units. But it’s not actually arranged in a way for the graphics rendering to work properly (I have graphics updating inside the updateTick of particular units).

I can change that though.

I can add a method to GameState->createScene() and render everything in the correct order. Then units will update their entities (and this hopefully doesn’t change the render order in Ogre).

Tuesday, February 26, 2013

More Isometric?

Okay, so hopefully last issue with my isometric view and textures.  The texture files feature stuff that is drawn in a diamond shape... essentially exactly what you want to see on the screen should be what the texture looks like.  This also lets me ask artists to draw something and when they go "what are the restrictions?" then I call them artist-style stuff like "I want 64x64 png with transparent background, and you can't draw outside the diamond inside the box".  That last restriction is for technical reasons but one that artists can handle.

So what's the issue now?  The x-axis rotation that I didn't account for in my previous attempts at spewing stuff onto the screen.  When you angle the camera "downwards" by 30 degrees, everything looks more squished.  That's how you get that nice rhombus appearance.

But then my textures are also squished.  D:

Let's look at the problem sideways.  Literally:

The top two corners of the square are rotated back in the z-plane.  In order to counteract this, I rotate the top corners of the square "upward".  From the diagram you can see that the calculation involves a simple triangle.  If I were a calculus professor, I would say "Obviously".

Obviously, the value of z is determined by this equation (remembering that the length of the side of the square was square root of 2):

cos60 = z / sqrt(2)
z = sqrt(2) / 2

I offset the top corners and then bam it is good.

Not that much has changed but I did get three new textures courtesy of a man named z00t, but these were just something he whipped up in a few minutes for me until he can make real textures.  I also drew people!  I intend the game to have stone age humans, elves and ogres but for now, here are some humans.


Monday, February 25, 2013

IsometricGood

Cultura is an isometric game, for now at least, and so let's make the isometric view properly.

I want to apply textures to quads which we use for the squares that represent each spot in the grid-like world.  But I've a problem:



Drawing a quad then apply the texture will then result in the texture facing the wrong way.  Sad times!  In order for the texture to look right we need to reorient it.  But you need to rotate it 45 degrees counter clockwise to face the right way and we've no way of rotating only the texture.  And I'd like to simply say, "I want to place wild wheat here" and then it is there.  No need to do math beyond the coordinates.

In order to achieve this, I went about a suggestion from a friend and rotated the square counter clockwise 45 degrees.  Now I apply the texture to this rotated quad.  When the camera rotates clockwise 45 degrees, voila, the quad has the texture facing the correct direction.  Then I render it such that it is blocking the terrain below it.


But there's a new problem.  If I use the quad of the original size, it looks wrong.  Why?  Well if you look carefully above at the diagram, after you have rotated the square, the texture it displays isn't spread across the original amount of area.


That's a normal quad.  After you've rotated it, try putting that original square back over top of it.  You'll notice that the texture doesn't touch the corners of the rotated square!  So I need to make the original square larger AND rotated counterclockwise 45 degrees.

In order to get the correct coordinates, imagine the normal square with sides of length 1.  Now draw a diamond around it such that it just touches the corners.  The centre of the two squares are the same and using the fact that the diamond just touches the corners of the square you can get the length of the diamond's sides.

Draw it out and you'll see that it's a simple application of the hypotenuse.  The length of half the side is 0.5^2 + 0.5^2 = x^2.  Therefore x = 0.5.  So every corner of the diamond is 0.5 distance out from the midpoint of a square's sides.  Look below for how the diamond looks and the coordinates.



And then now, you can draw a texture normally onto the enlarge rotated quad and when it gets displayed it'll look correct.  You do have to draw the texture as a diamond though :P

Sunday, February 24, 2013

Isometric View

Now we move onto making the game isometric!

(Note: Post is updated with more accurate description of isometric view and orthographic camera)


In terms of code what this means is that our camera is now set to an orthographic projection.  For Ogre3d it means doing this when you set up your camera:

camera->setProjectionType(Ogre::ProjectionType::PT_ORTHOGRAPHIC);

After this you might notice that some of the view is cut off in your box. This is where a little bit of math comes in. You'll need to set these two lines:

camera->setPosition(Ogre::Vector3(0,0,1200));

camera->setNearClipDistance(800);
camera->setFOVy(Ogre::Degree(90.0f));

And what those mean is how much of the world you can see.  What's a bit unfortunate is that after a few Google searches and a few Google searches specifically in the forum is that it reveals nothing about this.  You'll just have to learn this from graphics programming in general (it'd be nice to just have a crash course tutorial but I suppose nobody bothered to do so, it's understandable since it's not Ogre specific).

(Math here)

An orthographic camera is like your eye were expanded from a single point to a plane.  That is the difference between a projection view and an orthographic view.  In projection your view is like that of a square based pyramid where your "eye" expands outward based on the "field of view".  Within that pyramid the distances for "near clipping distance" and "far clipping distance" determine the volume inside the pyramid that is visible.

Anything closer than the near clipping distance disappears and anything further than the far clipping distance also disappears.  In the orthographic world, rather than your eye being a "point" it is a rectangular plane.  This then extends out in the same manner.

Here is a diagram:





So say you have a camera and you have a field of view and you have a near clipping distance, what might you see?

FOV: 90 degrees
Near Clip Distance: 100 units

Then the length of the base is found to be twice this:

tan(45) = x / 100





By height I mean the "height and width" of the rectangle at the top of the viewable area.  This gives you a sense of what values you might need to set to be able to view your world (various values would cause your world view to be cut off in the window).

Any mistakes in what I've shown, please point out in comments. :)

(Math stops)


Depending on what you set here, it affects how much of the world you can see. Fiddle around with it to get the correct image showing.

Next, if you want an isometric view (ala Gnomoria for instance), I chosen to replicate it via a camera rotation. Essentially you're rotated 45 degrees around the z-axis and 30 degrees downward from the x-axis. In Ogre code that means


Ogre::Quaternion cameraRotationZ = Ogre::Quaternion(Ogre::Degree(45), Ogre::Vector3::UNIT_Z);
Ogre::Quaternion cameraRotationX = Ogre::Quaternion(Ogre::Degree(60), Ogre::Vector3::UNIT_X);
camera->rotate(cameraRotationZ * cameraRotationX);

And you get something that mimics Gnomoria (or any other isometric game for that matter).  You then have to choose a scalar multiplier for your objects.  As all the meshes I create are 1x1 (or 2x2 for trees), I multiple all of them by a scalar before rendering them.  You could also just draw out your meshes to the correct size I suppose.

At this point the rendering order gets messed up a bit.  So, the way Ogre renders is that it does the furthest objects first and then renders in order from distance to camera.  This would be typical for an FPS where obviously you want things closer to block the view of things further.  Once you switch to isometric land, it's a little weird because the distance to your camera (which is now a line above the surface) shouldn't matter.  Why?  Because you care about the position of objects on the flat x,y plane and not their distance to the camera because everything is supposed to be the "same" distance.

What does this mean?  Let's look at Gnomoria:




Also that guy needs a beer. Where's the Dwarf Fort and the beer stash? Oh right, it's a Gnome hill. Whatever. :D

Okay, so Cultura is very similar, assuming I want my graphics to look like Gnomoria. Now, it's a little silly how much effort it takes to draw that artwork you see in Gnomoria. I'm sure most people think lowly of bit-art but it's not easy stuff! Anyway, so I have some hand drawn crapola to try out the same in Cultura.

So, I have no idea how Gnomoria achieves what it achieves. Maybe he has the engine specifically know how to render in the correct order. And what is the correct order? Well you need to render by each diagonal in your 2d grid.

For instance, your grid is a typical cartesian x-y plane.

0,2 1,2 2,2
0,1 1,l 2,1
0,0 1,0 2,0

Okay great!  So we render from the top down right?  No.  :(

We've rotated the camera for the crazy diagonal shaped squished squares that we all know and love.  So you render from the furthest diagonal from 0,0 first.  Then you render the next diagonal, so that it blocks the view of the furthest diagonal.  That's how you get that nice view in Gnomoria (although what neat tricks he uses, I wouldn't know).

What does that mean?

You render:
(2,2)
(1,2) , (2,1)
(0,2) , (1,1) , (2,0)
(0,1) , (1,0)
(0,0)

But I'm using Ogre3d.  So it basically renders whatever entities have the pivot point furthest from the camera and then works its way toward the camera.  Makes total sense in FPS.  Not so much here.  Remember the triangular prism that was our camera?  That totally flubs the distances and you get really strange behaviour.

So my answer was to screw with the z-plane.  Objects I need to block?  I shift them downward (downward in isometric land, so that is positive x direction, negative y direction) and then I push it upward in the z-plane.

I get this:


First thing you'll note is.  Gee that bush seems like it is just floating on top of the wheat!  Yes.  Yes it is.  So, you'll have to wrap your mind that in isometric land, 3d-ness is all illusionary.  Then you have to wrap your mind around that in fact I AM drawing in 3d.  What does that mean?  The bush looks like it is floating on top of the wheat because the art sucks and doesn't make it look nice.

The only thing that makes something look like it is 3d or that it is sitting inside the wheat is the quality of the artwork to produce the illusion of 3d.

But, you'll see that the tree blocks the bush which blocks the wheat which blocks the terrain.  I'm not settled on that order of blocking but additionally there are things I may wish to consider.  For instance, I may have two quads for wheat.  One represents the wheat itself which gets blocked.  The other is the wheat that gets displayed over everything, so you can see little strands of wheat sit in front of the tree and have a cool "3d" effect.

In fact, the trick may be to have a render order of grass -> bush -> tree -> bush -> grass.  The latter stuff is just pieces to slightly block the view of the other stuff and add more graphical niceness.  

Let's see Cultura now:


Yay.  I call this isometricgood.  Perhaps I'll be able to reach Gnomoria graphics level soon enough!

Aku: "You are 3d?!"
Cultura: "No isometricgood."

Saturday, February 23, 2013

Mouse Scrolling and Terrain

I’ve added mouse scrolling.  One thing to be careful of when using OIS is to ensure you initialise the window size to the mouse input object as well as on resizing the window (you can make your class a listener and then implement the SetWindowExtent method).  Generally, I’m not intending this blog to be a tutorial but I’ll just say for Ogre 1.8x and the associated OIS plugin:

//set up mouse state properly
//make sure to set up mouse state for all other listeners as well
unsigned int width, height, depth;
int top, left;
m_hWnd->getMetrics(width, height, depth, left, top);
const OIS::MouseState &ms = m_nMouse->getMouseState();
ms.width = width;
ms.height = height;


Do that in your init function (or constructor, it depends if your coding paradigm is that you try to do less in the constructor out of fear of throwing an exception there because that would totally hose any C++ code).

Anyway, it detects whether you’re at the edge of the screen and then moves the camera if so.  Nothing terribly difficult.

I’m using the absolute position of the mouse in order to determine the distance of the point to any particular edge.  So my code looks something like...

if( evt.state.Y.abs > (m_iWndHeight - edgeDistance) )
{
m_bIsMouseGoingDown = true;
}
else
{
m_bIsMouseGoingDown = false;
}


So from here what I can tell you is that... OIS is a bit confusing to me because it doesn’t seem to use cartesian coordinates the way you should be using it.  A higher Y value is more “down”, so the 0,0 point is in the top left corner and it goes more positive as you move toward the bottom right corner.  Not very intuitive but oh well.


Note: I ran into an interesting issue with OIS on Ogre 1.8 sdk. Basically I would get to the gameplay state and then the slightest hand twitch would cause mouse scrolling to occur and then after a few moments of this, the problem would go away.

The issue? I think that the absolute value coordinates somehow get reset to 0,0 which causes all sorts of havoc for my cameraManager. At the very beginning of execution it believes that it is at 0,0 and thus scrolls.

Solution? I basically "injected" the mouse position when I switched into the gameplay state so that it had a known value.


if(STATE_GAME == gameState)
{
OIS::MouseState &mutableMouseState = const_cast<OIS::MouseState &>(m_nMouse->getMouseState());
mutableMouseState.X.abs = m_nCamera->getViewport()->getActualWidth() / 2;
mutableMouseState.Y.abs = m_nCamera->getViewport()->getActualHeight() / 2;
}

Now it does what I call "scrollgood".

***

Abusing the z-order rendering of Ogre3d.

The Cultura game world consists of water, people, resources, structures and so on.  The question is how to render it to look “nice”.  For instance, if you build a hut in front of a tree but there is another tree in front of it then it should be rendered such that the house blocks the back tree and the front tree blocks a bit of the view of the house.

For now, let’s forgot occlusion culling (where completely blocked objects are not rendered but actually this one is tricky because units should have outlines visible, whereas non-units aren’t important except maybe resources).

Basically, after a little thought and discussion with a friend (he’s working on a Megaman-style game), what is important is the y-coordinate.  The higher value the y-coordinate in the visible area, the lower the z-coordinate to utilise Ogre3d’s automatic rendering according to z-plane ordering.  And similarly, I will have layers per square, where water is the lowest and so on, so that grass is blocked by bushes which is blocked by trees etc.  

The idea is that any object is placed in a square such that the coordinates are (actual numbers used aren't the same):

(x, y, -y * 0.005 + layer * 0.001)

And the layers are:

  • Layer 0: Water or Terrain
  • Layer 1: Grass
  • Layer 2: Bushes
  • Layer 3: Trees
  • Layer 4: Units
  • Layer 5: Structures

This should render all objects in the correct order and uses the z-plane to put stuff in front of each other properly.  Textures will probably be 32-bit png files with transparency so that I can just slap quads on top of each other to draw out a person, their clothing and equipment.


***

Okay so what point is the game at now?

Visually speaking, it is still donkey balls. I created a few new textures by hand in Gimp for testing use. Basically, I have coded out the terrain portion of the game state. ResourceManager loads up all necessary meshes for my test run. Then I start up a new game it creates a new map for me.

There's nothing but grass, trees and bushes there. Oh and a small pool of water. Those are all actual resources, they just look somewhat flat. Now, I took a look at what Gnomoria looks like (and I'm aiming for that level of graphics for the first release whenever that is), and their trees appear to "breach" into 4 squares of terrain. I may do the same to get a similar look (in addition to rotating the land to look nice and isometric).

Here is a picture of the resources in all of its glory. Normally most professional game studios try to refrain from showing these earliest images of game development because it makes the game look bad. Meh.


Just to tell you what you are looking at. The yellow worm things are "wild wheat". The red dotted green blobs are "strawberry bushes". And then the malformed trees are... "trees". Oh and the blue splotch is a pool of water. All of these are resources that you can collect, and they all do have values here. There's just no units yet to collect anything.

Friday, February 22, 2013

Finishing the Camera and designing the Game State

The camera control is to the point where WASD is happening but I suspect some problems with using the timer.  Currently it works as a frame listener to the Ogre3d engine such that it moves the camera every frame.  That may or may not be wise, I have to see, because I’m unsure whether that makes the camera jittery versus having it part of the game loop (which updates every 100 ms).

I will have to add mouse scrolling.  This implies that I detect the mouse position and if it is near the edge then I scroll, otherwise I do not.  I’m not sure if the usual method is to detect mouse position in the window or to set a bool when the mouse moves to the edge and then set it to false when it is away from it.  My concern is that since I’m going to debug in non-full-screen, detecting position of the mouse could be superior to avoid “perma-lock scrolling”.

Finally, for the camera controller, I need for the GameState to inject the map size to the CameraManager after it is finished loading.  The GameState will inform the AppStateManager who then informs the InputManager who then informs the CameraManager.

I will just say that situation is not ideal.  Later I will likely implement an eventHandler or eventManager, shove it into the GameEngine and then pass a pointer of it down to every class who can then register to listen to events and also send out events.  It’ll be cleaner.  That way I instantiate all the different game components and they can talk without knowing each other.  
 
***

At this point I can start making a high level design of the CGameState object which holds information for the current state of the game (the position of all units, resources, animals, health and everything else).  

My initial thoughts are to use lots of memory for very quick access and performance.  I’m willing to sacrifice optimising on the memory end primarily because most of the data will be integers and other such primitives and basically not worth optimising over runtime performance.  So on that end I was thinking that

Terrain
  • Water, Grass, Bushes, Trees will all be a gigantic 2d array of respective classes CWater, CGrass.. etc
    • Each class is an instantiation of water, grass and so on, to keep track of the current condition of it (
      • hopefully not overly expensive, but for a 100x100 map, it would imply 10 000s objects, for a memory use of maybe say 40 000 objects and each object is say 10 integers, so maybe 40 000 * 10 * 4 bytes = 400 kb of memory, it would scale linearly, so for a mega large map of 5000x5000 then you could have 25 000 000 * 4 * 10 * 4 bytes = 4 000 000 000 bytes, which is 4 000 MB, which is 4 gigs... so at that point I’d have to do something fancy :P

Game
  • Faction
    • Units, Structures
      • Each unit is an instantiation of a specific type of object
      • Position on map
      • Pointer to Ogre::entity if exists or null, update this during updateTick
      • Units have action stack and need update method
    • AI State variables
      • Relationship with each known faction
        • Expectation levels
          • Trade expectation (whether you give favourable trade deals)
          • Aid expectation (how much he thinks you’ll help if asked)
        • Perceived reputation of target
        • Current relationship strength
        • Threat factor (a number to represent a likelihood of attack)
        • military threat modifier (the ability of this faction to utilise its military power in a superior fashion)

Spaghetti Flying Monster Thoughts...

Currently, there is no event system manager.  That’s on my wish list of to-do infrastructure features with the code.  The earlier I do it, likely the less “regression” required.  I’d like to first get to the point of being able to play the game (that is, mouse clicking, resource, actions for a unit etc.) before I move onto events.  The reason is primarily due to my lack of thought on an overall design but I have an idea on it.

The map size when loaded needs to be fed into the camera system right now.  This allows the camera manager to know whether or not you’ve reached the edge of the map and then stop scrolling.  Right now, the GameState will have to somehow return that value back to the AppStateManager who then can shove it back down into the InputManager where the CameraManager lives.  If I had events, this could be part of the GAME_MAP_LOADING_COMPLETE event or some such thing (I’ve read a recommendation to use GUIDs rather than an enum list so that the different game system pieces don’t need to be recompiled when a new event is added).

Performance Thoughts...

So, the update loop in the main code will eventually tell CGameState to call its updateTick functionality.  It will then go through each unit to update them (depending on their current action they will do whatever it is they need to do, and if they have a target that target may change their current action).  A large number of units in the game (maybe >1000? I’d have to do stress testing later) could slow down a tick below 60 updates per second.  That’d make the game rather jittery and unplayable.

Now, I could definitely reduce updates to below 60 updates per second, although I think 60 is the lower bound, to ensure your eye doesn’t catch any “jumpiness”.  

First thought is to try to skip units without actions which implies the ability to queue and dequeue units from a “hasAction” list that is cheap.  In that case a linked list would be good, since enqueuing and dequeuing are O(1) actions.  I wouldn’t need random access to the linkedlist of unitsWithActions so the O(n) constraint there doesn’t matter (and I don’t need to do anything complicated about it).

As always I’m not going to bother until it’s a problem.  The first iterations of Cultura are going to be strictly set in early stone age and the first version won’t have reproduction at all.

My expectation is that graphics will take a constant amount of memory.  I won’t have “graphics settings”.  They’ll just be well tuned for 60 FPS on my box which is five years old, so hopefully it will be a good baseline for everyone (it also uses an ATI card just to be sure I don’t use any nVidia specific driver stuff and cause a blue screen on ATI computers... or it could be because an ATI card is half the price and uses a quarter the electricity... hurr!).

Game-wise, I think that each square of map implies roughly 160 bytes of memory and interesting this bounds the rest of the game object memory because they would be limited by the lack of in-game resources (no land, no food!).  Let’s say it’s like 5 objects worth of stuff, each around 10 integers worth of data, so 5 * 10 * 4 = 200 bytes.  So each square of land is like 360 bytes.

Let’s keep map sizes below the need for performance magic for now.  We’ll say I want to keep this amount of memory use to be at ~500 Mb.  So that means 1 388 889 squares and that means a square map of about 1179 x 1179.  So let’s keep a map at 1000 x 1000.  I’m concerned about the load-time on that but let’s ignore that for now.  Actually, if it turns out the load time is too slow for such a large map, then I will reduce map size to compensate.

Later on when the game is in a very excellent state (resource management, technological research, reproduction, AI, interacting with other societies), I will start performance stints to allow for larger maps and more people.  Yay.

Wednesday, February 20, 2013

Camera Control

I’ll talk a bit about the camera control since that is simple enough and my next post will detail the design of the CGameState (which is far more complex but I’ll try to do the minimal version of it to get a game working).

The camera control uses the familiar WASD and the more old-school arrow keys.  Later I may have a CConfigurationManager which maps actions to specific keys (in a settings control) similar to most games.  Since I’m not sure on how often RTS controls are customisable (I’ve really only seen pro-league RTS players do such a thing because of the vast number of keys, they try to reduce it to the 1234/QWER/ASDF control scheme to increase APM) I will put that as a later idea.

Basically, as you press W, you will move up on the map.  Your z-plane coordinates are locked.  I may implement zooming later but I’ll want to fix up the isometric view first (or not, I’m still on the fence on this since I’m not sure if it looks that much more weird using a typical 3d projection for an RTS).

CCameraController will likely be an object inside CInputController.  When the state is GAME then the CInputController will inject keys to the CCameraController object it holds.  It will inject everything to it and the CCameraController can ignore as needed (to keep it a blackbox).  Later optimisation might want to break that encapsulation but for now I’ll leave it at that.

For CCameraController I’ll simply do something like this:

  • KeyboardKeyDown
    • W or UP ARROW
      • set isMovingUp = true, unless already moving down
    • A or LEFT
      • set isMovingLeft = true, unless already moving right
    • S or DOWN
      • isMovingDown = true, unless already moving up
    • D or RIGHT
      • isMovingRight = true, unless already moving left
  • KeyboardKeyUp
    • W or UP ARROW
      • set isMovingUp = false
    • A or LEFT
      • set isMovingLeft = false
    • S or DOWN
      • isMovingDown = false
    • D or RIGHT
      • isMovingRight = false
  • FrameRenderingQueued (or something equivalent)
    • MoveCamera
      • isMoving
        • Depending on direction add x or y
        • Amount = velocity * timeSinceLastFrame
        • Do this for every direction which is true (allowing you to do up-right movement for instance)
        • Only up to max map coordinates

And that should be sufficient for a very basic camera control scheme.

Monday, February 18, 2013

Menu Transitions

Well it was a long weekend and that gave me some time to work on Cultura.

Menu transitions are now fully implemented, although I now realise I need a "new game setup" screen, but I'll ignore that for now until I actually have some kind of setup that you can worry about.  You can go from the main menu, to the game, to save/load game, quit a game, go back and so on.




The camera angle is somewhat off for an isometric view (I'm not entirely sure on the exact angle it should be at, I'm a little confuzzled to what coordinates I'm supposed to set it at since I'm trying to figure out the angles).  Here we see a map that consists of just some grass, dirt and rocks.

Next component is going to be camera controls that only operate in-game.  That hopefully will not take too long (it's just going to be WASD controls).  After that I'm going to do a design post for the CGameState which is the primary class used for storing what is going on in the game.  I was going to code it but given the hassle I had today trying to move ahead with the terrain management, I think I'm going to step back and do some more design to make sure I do this right.

Sunday, February 17, 2013

A Menu

Huzzah Jatu!  To battle.

Anyway, aside from my dream of a Cultura where you can actually create the Jatu tribes and run around rampaging through the lands in your barbarian destruction of the hardwork of civilised peoples...


I have the UIManager working and the InputManager currently injects mouse/keyboard input into the CEGUI system.  Basically, I will work on menu/state transition between saving, loading, quitting, in game menu and the game, with all the event listeners for the various menu items correctly subscribed. 

Then after that I will work on CGameState focusing primarily on saving/loading a simple map and then figure out how to update the scene to play the game.  After that I'll spawn some units.  That will involve a lot of work.

After that I'll start figuring out how allow the user to select units.  Ogre does ray casting math for me but I still need to figure out a sort of reverse hash set lookup to map Ogre's scene entities with my game entities.  Then I can start with having them push actions onto their stack.

Then once I have actions property on the stack, I start work on the UpdateTick functionality of my game.  It'll go through the list of units that need stuff to be updated and move them about, harvest resources and so on.

Lots of work to do but being able to display a menu is exciting!

Friday, February 15, 2013

Design of First Milestone

The first milestone will be the initial playable version of the game.  It will probably not be made publicly available since it’ll have a limited feature set (read as: nearly nothing :P ).  However, it’ll be a good starting point before I start putting in large maps, random map generator etc and get an actual release-able game.

I’d like the feature set to be:

  • Menu System:
    • Main Menu (Play, Load, Quit)
    • In-game menu (Continue, Save, Load, Quit to main menu)
  • Gameplay
    • WASD camera control, no zoom
    • One map, 100x100, can be entirely loaded at once
    • Trees, bushes, grass which are harvestable
      • Different graphic when exhausted
    • Resource nodes: stone, clay
    • Animals, no AI (ie. do not move)
      • Different graphic when killed
    • People, no reproduction
    • Structures, just your stash, no graphical rewards

This of course implies many technical developments in the Cultura code base.

  • InputManager
    • MainMenuInputManager
      • CEGUI Main Menu
    • InGameMenuInputManager
      • CEGUI In-game Menu
    • GameInputManager
      • Camera control
      • Selecting units, keeping track of selected units
      • Context-sensitive right-click
        • Resources imply mining actions
        • Animals imply hunting actions
        • Empty space implies movement action
  • TerrainManager
    • Store a map
    • Create a scene based on loaded map and save file information (eg. trees chopped down should have the tree invisible and the tree stump visible)
    • Update of map state (eg. trees are chopped or regrow)
    • Selective update of loaded scene (don’t want to recreate the scene every frame, just update certain entities in scene such as setting a tree invisible when it is cut down and then making the tree stump visible)
  • GameEngine
    • Main loop
      • Render graphics in vsync (60 fps for most people), no graphic setting options for now, not full screen
      • Update tick at standard interval
        • Move units that are moving
        • Objects being attacked lose health
        • Resources being mined lose resources, units mining gain those resources up to carry limit
      • Send buffered input to different InputManager depending on AppStateManager
  • AppStateManager
    • Serialisation
      • Save map
      • Save tree, bush, grass status
      • Save animal position and status
      • Save people position and status (including resources carried)
      • Save structure position and status (including resources stored)
    • State Stack
      • Push/pop states as needed (Main Menu -> Game -> In-Game Menu)
    • Input Translation
      • Send to correct InputManager depending on state
  • UIManager
    • Main Menu scheme
    • Game overlay (show selected unit status, show resources at top right)
    • In-Game Menu scheme

Here is the overall initial design:



The following classes exist and will have these definitions:

  • Main
  • GameEngine
  • AppStateManager
    • GameState
      • ResourceManager
      • TerrainManager
    • UIManager
    • InputManager
      • MainMenuHandler
      • GameInputHandler
      • InGameMenuHandler

Main
  • Main loop
    • Capture input (sends to GameEngine)
    • UpdateTick with time since last update
    • Render frame
    • Message pump

GameEngine
  • Delegate input to AppStateManager
  • UpdateTick on AppStateManager per amount of time since last update

AppStateManager
  • Delegate input to InputManager
  • Change state if requested
    • ChangeOverlay on UIManager if state is changing
    • ChangeInputHandler on InputManager if state is changing
  • UpdateTick on gameState if in game state

InputManager
  • If changing game state, call init on proper handler given window

MainMenuHandler
  • New Game, requests state game, init GameState for new game
  • Load Game, requests load game state
  • Quit, request shut down

InGameMenuHandler
  • Continue, request game state
  • Save Game, requests save game state
  • Load Game, requests load game state
  • Quit, requests main menu state

LoadGameMenuHandler
  • Load Game, init GameState with selected file then request game state
  • Back, request in game menu state

SaveGameMenuHandler
  • Save Game, serialise GameState to disk using selected file name then request game state
  • Back, request in game menu state

GameInputHandler
  • Mouse Press
    • if left-click Record location
    • If right-click and have friendly selected units
      • If on empty ground, clear action stack, push move action to all units
      • If on mineable resource, clear action stack, push move action to location, push harvest action
      • If killable non-friendly unit, clear action stack, push move action to location, push attack action, push harvest action
  • Mouse Release
    • If left-click...
      • Use location from mouse press, use this location, draw box
      • Select all units in box, all friendly, or if no friendly, first non-friendly
      • Update selected units
  • Keyboard Key Press
    • If camera movement key move camera position (check if edge reached)

UIManager
  • On request, SetGUISheet based on game state passed, load resources as needed

GameState
  • Init, tell ResourceManager to load resources (will have to optimise lazy loading later)
  • Save, given filename then serialise to disk
    • Save map with default filename based on seed
  • Load, given filename load data and populate member variables
    • Set seed on TerrainManager
    • Send to TerrainManager after loaded current gameState (for static objects, trees, bushes, grass and resource nodes)
    • Call TerrainManager load
  • UpdateTick
    • Go through all units
    • Based on action update unit
      • If move, move unit closer to location, update position in scene
        • If at location, pop action from actionStack
      • If attack, check ticks since last attack, if attack speed allows then damage target (if target too far, push moveToTarget command to unit position)
        • Check if target can react
        • Check if target dies, update scene
      • If harvest, check carry limit and resource node, harvest as much as possible, update resources in node and carried by unit (if target too far, push move command to unit)
        • Check if resource node is used up, send update to TerrainManager
        • Check if unit is full, update scene
      • If moveToTarget, update position with current position of target, then move unit closer to location, update position in scene
        • If at location pop action from actionStack
      • (For future) If attackmove, move toward location.  If attacked then push attackLimitDistance with current position and target is the attacker
      • (For future) If attackLimitDistance, check ticks since last attack, if attack speed allows then damage target (if target too far, push moveToTargetLimitDistance with current location and target unless current distance from original location exceeds threshold)
        • Check if target can react
        • Check if target dies, update scene
      • (For future) If hold position, do nothing.  If attacked then push attackLimitDistance with 0 distance threshold (meaning you do not move from location)
    • Units heal automagically
      • If unit is in an active attack action, do not heal
      • If ticks since last heal is high enough, heal unit for 1 hp, reset counter


    TerrainManager
    • Save, know own seed, write to disk based on that to generate filename
    • Load, given a seed load from disk or generate new map
    • CreateScene/ReloadScene, given location on map, if loaded then create staticGeometry as needed, then process gameState to set objects visible/invisible as needed (later optimisation: this should keep track of loaded chunks and load/unload as needed)
    • UpdateScene, check change stack, set objects visible/invisible as needed

    ResourceManager
    • Init, load resource directories into Ogre
    • LazyLoad, load meshes as they are needed (does Ogre do this?)