Mini-Game: Dwarf Vs Zombies (Part 4)

Posted on February 13, 2009 by Chris at 5:04 pm

dorfie chasing the iconI’ve not forgotten this thread that I started, just got distracted with My first published game. Anyways, now that that is over with, I thought that I should finish this off a little before going back to something iPwn related. So here we go…

Articles
Part 1 - Stuff to download, getting into the development
Part 2 - Setting the Scene, and Model Drawing
Part 3 - Animation
Part 4 - Loading Maps and Moving around

Downloads
Source Code

New Feature: Mac OSX support
Ok, so maybe not that new a feature.. considering that the guts of Mac OSX looks just like linux and has all the compiler tools etc etc.. I just tried compiling the linux version on my new MacBook and it turns out that it runs perfectly. I added a couple of #ifdef thingames and put in another makefile so that those Apple users among us can compile from a folder called MacOSX. Nothing really new in the code here though.

Next Step: Loading in a Premade Map
If anyone out there knows me, they would know that I am pretty lazy when it comes down to it, and here’s some proof. I’ve decided I don’t want to be bothered with writing anything to write new maps in the game.. I’m sure it’s quite easy to add in functions that will allow me to place tiles from within the game, but I simply dont want to at this point in time. So we are stuck with our good friend “notepad.exe”.

The file format will be a simple one that simply defines the size of the map, the starting position, and what kind of blocks are where. A 0 will be used for walkable areas, and a 1 for non-walkable areas. This can easily be extended to allow for UP TO 9 DIFFERENT BLOCK TYPES!!!!.. anyhows, the file will look something like this in the text editor:

50 50
25 25
1111111111111111111111111111111111111111111111111110000000000000000000000000000
000000000000000000001

… and so on….

So here’s some code to load the map from a text file.. please no complaints about fscanf(). I know it’s dodgy, but then so is using a text file.

void MapNode::Load(const char *fileName){
	FILE *fp = NULL;
	long nWidth, nHeight;
	long x,y;
	int i,j;
	unsigned char ch;

	// open the file
	fp = fopen(fileName, "rt");

	// Load the size info
	fscanf(fp, "%d %d\n", &nWidth, &nHeight);
	fscanf(fp, "%d %d\n", &x, &y);

	// Set up the array
	m_StartX = x; m_StartY = y;
	m_Width = nWidth;
	m_Height = nHeight;
	m_Indexes = new int*[nHeight];
	for(i = 0; i < nHeight; i++){
		m_Indexes[i] = new int[nWidth];
	}		

	// load the map data
	for(i = 0; i < nWidth; i++){
		for(j = 0; j < nHeight; j++){
			ch = fgetc(fp);
			m_Indexes[j][i] = (ch == '0') ? 0 : 1;
		}
	}

	// close the file
	fclose(fp);
}

And Then: Running Around

Now that we have a funny looking map, we can start adding in some stuff to interact with. For now I’ve booted the zombie out of the game so that we can concentrate on getting our dwarf running around freely. It was quite easy to do really, just a little code here (in the main loop):

case SDL_MOUSEBUTTONDOWN:
	m_PickingNode->HandleClick((event.button.button == SDL_BUTTON_LEFT) ? 1 : 2);
	break;

and a little code there (in the picker object):

void PickingNode::SetPlayerModel(ISceneNode *pModel){
	m_PlayerModel = pModel;
}

void PickingNode::HandleClick(unsigned int nButton){
	if(abs(m_WorldY) <= 0.5){
		if(m_PlayerModel) ((ModelNode *)m_PlayerModel)->MoveToPosition(
			(float)m_WorldX, (float)m_WorldY, (float)m_WorldZ
		);
	}
}

and finally, some code in the ModelNode.cpp, to make the magic happen:

void ModelNode::MoveToPosition(float x, float y, float z){
	float x1,y1,z1;
	float x2,y2,z2;
	float vx,vy,vz;
	float dist;
	float dotprod;

	m_TargetPosition[0] = x;
	m_TargetPosition[1] = y;
	m_TargetPosition[2] = z;

	x1 = m_Position[0]; y1 = m_Position[1]; z1 = m_Position[2];
	x2 = x; y2 = y; z2 = z;
	dist = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1) + (z2 - z1)*(z2 - z1));
	vx = (x2 - x1) / dist;
	vy = (y2 - y1) / dist;
	vz = (z2 - z1) / dist;

	m_Rotation = (float)(acos(vz) * 180.0 / M_PI);
	if(vx < 0) m_Rotation = -m_Rotation;
	m_Rotation += 180.0f;

	SetCurrentAnimation(MODEL_ANIMATION_WALKING);
}

plus a bit more in the model’s update routine

	// do the movement stuff
	float x1,y1,z1;
	float x2,y2,z2;
	float dist, vx, vy, vz;
	x1 = m_Position[0];
	y1 = m_Position[1];
	z1 = m_Position[2];
	x2 = m_TargetPosition[0];
	y2 = m_TargetPosition[1];
	z2 = m_TargetPosition[2];
	if(x1 != x2 || y1 != y2 || z1 != z2){
		// normalize the vector
		dist = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1) + (z2 - z1)*(z2 - z1));
		vx = (x2 - x1) / dist;
		vy = (y2 - y1) / dist;
		vz = (z2 - z1) / dist;

		if(dist < m_MovementSpeed * fTime){
			m_Position[0] = x2;
			m_Position[1] = y2;
			m_Position[2] = z2;
			SetCurrentAnimation(MODEL_ANIMATION_IDLE);
		} else {
			m_Position[0] += vx * fTime * m_MovementSpeed;
			m_Position[1] += vy * fTime * m_MovementSpeed;
			m_Position[2] += vz * fTime * m_MovementSpeed;
		}
	}

Mini-Game: Dwarf vs Zombies (Part 3)

Posted on November 6, 2008 by Chris at 3:01 pm

Welcome to Part 3 of Dwarf vs Zombie. In this weeks episode we will be prodding those lazy little models in the back side and telling them to get to work. You will remember the anoying maths that we needed to do for the quaternions?, well you can be thankful that all that is over and we wont need to look at it again :D

Articles
Part 1 - Stuff to download, getting into the development
Part 2 - Setting the Scene, and Model Drawing
Part 3 - Animation

Downloads
Source: Click Here

Code Updates
There isn’t really much to talk about here that hasn’t already been talked about. I added a few classes to do the maths (Quaternion, Matrix), added a new Joint class, and got it all working together. Here’s a list below if you want to download and look at the code.

* Added Joint Rendering to Render() routine
* Added Joint setup to SetModelData() routine
* Added Joint class
* Added Quaternion class
* Added Matrix class

Performance Tuning
I mucked around a little with the Model.Render() routine, and the Model.Update() routines a bit to try and get some more frames out, and managed to get it running at 60fps on my system.. I know it can do more, but just cant figure out what is slowing it down. Is only 2 models… and a total of 2831 triangles being rendered each frame. Surely we can do better than that!

And finally, some magic
Look at that pretty picture at the top. If it’s animating the way it should be, you will see our two models doing a bit of moon-walking :P

Mini-Game: Dwarf vs Zombie (Part 2)

Posted on October 2, 2008 by Chris at 6:21 pm

Well, here I am again writing my next part of the Dwarf Vs Zombie game. Feels like months since I’ve actually been able to get to sit down and do some more work on this.

This one’s not going to be a big update, even though it took more work than Part 1, there isn’t really much to say.

Articles
   Part 1 - Stuff to download, getting into the development
   Part 2 - Setting the Scene, and Model Drawing

Downloads
   Source: Click Here

Setting the Scene

Before you look too far, you will notice that I wrote about a Scene Graph back in Building a Solar System in XNA (Part 2). It’s quite a simple system to implement, and is powerful enough to handle whatever I throw at it.

I’ve been at it again, this time writing one in C++ to handle the 3d environment we are working with. Here’s a little code that shows you how we can move recursively through the graph to draw the entire tree from bottom to top, without needing to have any additional code in the ISceneNode interface.

void SceneGraph::Render(){
   Render(m_BaseNode);
}

void SceneGraph::Render(ISceneNode *node){
  SceneNodeIterator it;
  node->Render();
  it = node->begin();
  while(it != node->end()){
    Render(*it);
    it++;
  }
}

You might have recognised the sneaky use of STL to implement the node list.. why re-invent the list, when you can simply inherit it :). Here’s the code for our SceneNode interface. Please DO remember the virtual destructor, unless you would prefer some wild and zany adventures with memory leaks later.

class ISceneNode : public std::list<ISceneNode *>{
public:
  virtual ~ISceneNode();
  virtual void Render() = 0;
  virtual void Update(float fStep) = 0;
};

typedef std::list<ISceneNode *>::iterator SceneNodeIterator;

Playing with Models (again)

So, what good is a Scene Graph without stuff to fill it?.. The next step is to get our models that we downloaded a couple of months ago showing up in the scene. To do this, I’ve written the ModelNode class that is responsible for the drawing of the models, and an MS3DLoader class that can read the data from a ‘.ms3d’ file.

I’ve basically inferred the MS3D file format from the MilkShape 3D Binary Model Viewer source code that you can download from the MilkShape site, and ignored the parts that I don’t intend to use. So, if you are after an MS3D loader, I’d suggest that you go there to find a full featured one.

Once the data is loaded, we can then use our ModelNode to render the models into our scene.

First, we will position and scale our models, so that they are about the right size and place. It is one of the problems with using art from multiple places, they are usually all different scales, and often face different directions when you get them.. this is just a simple hack to get them the same size and direction.

void ModelNode::Render(){
  int nTriangleIndex;
  int nVertIndex;
  int nMatIndex;

  glPushMatrix(); // push the matrix onto the stack
  glTranslatef(m_InitialPosition[0],m_InitialPosition[1],m_InitialPosition[2]);
  glRotatef(m_InitialRotation, 0.0f, 1.0, 0.0f);
  glScalef(m_Scale,m_Scale,m_Scale);

  // Do the rest of the rendering in here
  [...]

  glPopMatrix(); // pop the matrix we pushed earlier
}

Now we iterate through each of the model’s groups, and set the material information for each one. I’m almost certain that we use the glMaterial function for this.. but being that neither of the models do anything interesting with the materials, we are unlikely to find out if this is a mistake right now or not.

We also tell the system to use the appropriate texture here. If you look at the code, you will notice that I dont use the texture information from the ms3d file, but rather allocate it manually during the loading process.

  glColor3f( 1.0f, 1.0f, 1.0f);
  for(unsigned int i = 0; i < m_Data.Groups.size(); i++){
    // get the material information
    nMatIndex = m_Data.Groups[i].MaterialIndex;
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, m_Data.Materials[nMatIndex].Ambient);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, m_Data.Materials[nMatIndex].Diffuse);
    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, m_Data.Materials[nMatIndex].Emissive);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, m_Data.Materials[nMatIndex].Shininess);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, m_Data.Materials[nMatIndex].Specular);

    // set up the texture
    if(glIsTexture(m_Data.Materials[nMatIndex].TextureId)){
      glEnable(GL_TEXTURE_2D);
      glBindTexture(GL_TEXTURE_2D, m_Data.Materials[nMatIndex].TextureId);
    } else {
      glDisable(GL_TEXTURE_2D);
    }

    // we draw the triangles in here
    [...]
  }

Finally, within each group we draw the individual triangles, complete with normals, and texture co-ordinates from the loaded data. You can just ignore the funky business going on with the texture coordinates..

I thought I was being smart earlier when I wrote the loading process, but it loaded them in the order s1-s2-s3-t1-t2-t3 rather s1-t1-s2-t2-s3-t3, so I couldn’t use the glTexCoord3fv. Not that it really matters, they are all there.

    glBegin(GL_TRIANGLES);
    for(unsigned int j = 0; j < m_Data.Groups[i].Indices.size(); j++){
      nTriangleIndex = m_Data.Groups[i].Indices[j];

      // get the vertex information
      for(int k = 0; k < 3; k++){
        nVertIndex = m_Data.Triangles[nTriangleIndex].Indeces[k];

        glNormal3fv(&m_Data.Triangles[nTriangleIndex].Normals[k * 3]);
        glTexCoord2f(
          m_Data.Triangles[nTriangleIndex].TextureCoords[k],
          m_Data.Triangles[nTriangleIndex].TextureCoords[k + 3]);
        glVertex3fv(m_Data.Vertices[nVertIndex].verts);
      }
    }
    glEnd();