Monday, February 28, 2005

This weekend I didn't really get as far as I wanted too.
Still today I'm pretty free at work - it allowed me to put all the tutorials I'm willing to put up for now. I know they need checking through but I'm not going too. I may annonce them one or two places I'm undecided.

Basically I got streaming in for loading and saving was going very much the same way. There was lots of rewriting and cleaning.
Note this has been updated see here


Aside 1 : Scripting with Lua


Contents


  • What to get?

  • Installing

  • Basic Use of Lua

  • A notch, a crank, knarc, ahctona!

  • But wait! There's more amazing where that came from!



Lua allows us to easily add scripting to our game. It's a well known widely used standard (it was used in Balders Gate). Once added it can process both compiled and uncompiled Lua scripts giving a good combination of flexibility and strength. The greatest advantage to Lua is how simply we can insert it into our C# programs. Well, hopefully now your convinced so let's get cracking.


What to get?



LuaInterface 1.2.1



Installing



Unzip LuaInterface 1.2.1 (or later version that you may have been able to download). Put it somewhere safe such as in the Visual Studio directory or some place your unlikely to accidentally delete.



Now boot up visual studio. Create a new C# Console Project.



Now go to the Solution Explorer window.


Play with my options!


(If you can't see it use this
Press me! Press me! and it should pop up!) Take your mouse over to the References icon and right click.



Choose add reference. This should all be pretty standard fare for starting a new project. We're just recovering the basics here in case you're reading this tutorial as a seperate chunk.


Press my buttons


Carefully survey your options and then choose "Browse". Then browse to where you unzipped LUA and go into the bin folder ... and select LuaInterface.dll. My copy of this .dll file is at the following location C:\LuaInterface\bin\LuaInterface.dll.



Once you have chosen this .dll file it will pop up in the the "Selected Components" part of the window. Confirm the selection and we're ready to go. I'm sure there's a method of playing with Visual Studio where you can avoid the "Browse" button all together and have the reference preloaded in, ready to select but after a few minutes fiddling I couldn't find it :(


Your game and your game players are going to need access to the Lua dll files. So you need to have them in project directory. My version of the console application executable that we're working on is currently located here: junk\Visual Studio Projects\ConsoleApplication3\bin\Debug



Open up that directory. Also open up the directory of were you installed Lua. Go into the bin\ directory and copy across (to project directory):


  
lualib-50.0.dll
luastdcall-1.1.dll
lua-5.0.dll


Your working directory should end up looking a little like this:


Press my buttons


That concludes setting up Lua to work with this project. Not too painful.


Basic Use of Lua



Let's write some code. We want to make a C# program that's interfaces with Lua and we want to do it soon, very soon indeed.



using System;
using LuaInterface;

namespace ConsoleApplication
{
class Class1
{
static void Main(string[] args)
{

}
}
}


The above is our class skeleton. The only thing worth noting at the moment is that we're using the LuaInterface reference. We'll concentrate on the main method an make a small program that uses Lua.


  static void Main(string[] args)
{

Lua lua = new Lua();


Here we create a Lua interpreter. We can create as many as we wish and they will all be independant. But for now we'll only have one.



Next we create some Global variables. Remember we're programming in a script language so we're creating Lua global variables not C# global variables. In actual use this "programming" would be done ahead of time and then loaded in from a file. Curently we're doing it directly in the code just to get a feel of what's happening.



lua["num"] = 2;
lua["str"] = "a string";


This creates two Lua global variables. One is called num the other is called str. Note that the variables both hold different types. This is a feature of Lua it is not a strongly typed language in fact it's dynamically typed. Variables can be anything (though Lua only has a few data types). But I don't want to get pulled in to a distracting explanation of how Lua works as a programming language quite just yet. First I'd quite like to get it hooked up to C#.



Now we made these Lua varaibles let's get C# to read them. Now as you really should know by now C# is a strongly typed language. So we need to cast the variables.



double num = (double) lua["num"];
string str = (string) lua["str"];


Note that we must cast to a double here. An int won't cut it as Lua is storing double information. Now we've read this information out from the Lua interpreter, next let's read it out to the screen to see if everything is agreeable.



Console.WriteLine("str:" + str +"\tnum:" + num);
Console.ReadLine();

Agreeable indeed!


Okay so far not very exciting but we're still only getting a feel of what's going on. So let's crank it up a notch next and do something cool.


A notch, a crank, knarc, ahctona!



Okay grip the arms of your chair tightly and get ready to script!



First let's create two functions we might like our brand new scripts to make use of.


  public void DanSays(string s)
{
Console.WriteLine("Dan>" + s);
}

public void ThorSays(string s)
{
Console.WriteLine("Thor>" + s);
}


Feel free to insert your own name instead of Dan or Thor. So now we have two cool functions bursting with game playing potential.



Let's hook them into Lua - so out main method will look like below:


 class Class1
{
static void Main(string[] args)
{
Class1 c1 = new Class1();

Lua lua = new Lua();

//Register our C# functions
lua.RegisterFunction("DanSays" , c1, c1.GetType().GetMethod("DanSays" ));
lua.RegisterFunction("ThorSays", c1, c1.GetType().GetMethod("ThorSays"));



This lets the Lua scripting language make calls to our two C# functions. Incredibly easy isn't it? So in the Lua language we just make calls to DanSays("with a nice string here") or ThorSays("with an equally nice string here") and they will call the C# equivilents!


The RegisterFunction function



The RegisterFunction's first argument is what you'd like to name the function in Lua. Here we chose the same name because it's so simple. But in future function registration we might want to make the Lua name more simple or descriptive than our C# name.



The second argument is the object where the method is stored. Note the word object. That's why we have to instantiate our Class1 class. So if we have a class called people "Class People" and it has a method talk then it allows us to do something like.


   
People pete = new People();
People jeff = new People();
pete.talk("Hello");
jeff.talk("Hello");


We can the register these talk functions seperately.



The third parameter uses reflection - something I currently know nothing about. All I know is that's it's magic like pixey dust and elevators. It magically gets all the knowledge about the method - it's arguments and so forth. Then Lua can call it effectively. You have to pass in a string of the methods name and that's it. Great!


The DoString function


Add this to the end of our main method.



lua.DoString("DanSays('Hello'); ThorSays('Hi! Dan')");
Console.ReadLine();


DoString executes a line of Lua code in string form. Wonderbar!


Dan and Thor have a bit of a chin-wag

But wait! There's more amazing where that came from!



So we've got a pretty groovy scripting language all by doing relatively little.



But now let's arrange it into a bit more of a game like usefullness. Go to your working directory for this project. The place where you copied all those dll files (mine is: My Documents\Visual Studio Projects\ConsoleApplication3\bin\Debug).



Okay create a new directory all call it "scripts"


Behold the new directory!


Now in the scripts directory I've created a simple text file called "Thursdays"



It has the following not-so-witty-banter.


DanSays("Hey, Thor!");
ThorSays("Hi Dan! You know Thursdays . . . ");
DanSays("*sigh* yeah, Thor, I know Thursdays.");
ThorSays("Named after me you know!");
DanSays("Yeah, I know.");


Okay. So in scripts\Thursdays.txt with have some future-award-winning dialogue. It's written in Lua but only using custom functions designed by us because we're cool and groovy designers.


Back to El Code



Make your main method match mine pictured below:


  
static void Main(string[] args)
{
Class1 c1 = new Class1();

Lua lua = new Lua();

//Register our C# functions
lua.RegisterFunction("DanSays", c1, c1.GetType().GetMethod("DanSays"));
lua.RegisterFunction("ThorSays", c1, c1.GetType().GetMethod("ThorSays"));


lua.DoFile(@"scripts\Thursdays.txt");
Console.ReadLine();
}


The little '@' sign means take this all literally. Because now and again '/' are control characters and they mess me up. But this fine piece of programming hereafter now as "ScriptRunner3000" will run any script we call Thursdays - without recompling - yes you heard me correctly - without recompiling!



Of course if we want to get really fancy we could write some code to enumerate all the files in scripts and then produce a menu allowing you to choose which you'd like to use. But this is much more of a proof of concept deal. Anyway the output:


The extended edition


Now the tinker can be tinkered with to our hearts extent and the exutable will show those changes without having to be run. Of course it's a lot more powerful than it is here. We can uses Lua's code constructs like loops and structures and all those niceities - wasn't putting a scripting engine in really simple? :o



Now you should brush up your Lua!



Globals



Lua is simple and powerful I just wanted to show persistancy of globals then maybe we'll end this tutorial here. But later come back to really really groovy things like coroutines



  
static void Main(string[] args)
{
Class1 c1 = new Class1();

Lua lua = new Lua();

lua.DoFile(@"scripts\Thursdays.txt");

string b = (string) lua["name"];
Console.WriteLine("Name:" + b);
Console.ReadLine();

}


If in Thursdays.txt we now include the line name = "rabbits" then the above code will outpt rabbits. See the scope isn't lost in the file which is super groovy and allows us to easily insert scripts into the game loop - yay!


References used



  • LuaInterface: User’s Guide by Fabio Mascarenhas

  • LUA 5.0 Reference Manual (avaliable from www.lua.org)

Stage 6 : A Bigger Better Map




Over the course of this chapter there are two areas we should cover:




  • A map bigger than the screen.

  • Advance loading and saving of the map




Some where down the line we want Actor interaction too.





The Camera




Transforming the view matrix can be thought of as twiddle with the various functions of the camera. We can do this in DirectX using the below kind of code.




Matrix QuadMatrix = new Matrix();
QuadMatrix.Translate(-0.2f,0,0f);
device.SetTransform(TransformType.View, QuadMatrix);



So we can shift the the viewpoint rather than shifting the map with all the objects on it. This seems a little bit more efficent.




We'll create a camera class ourselves - one that we can optionally attach to a GameObject. So we could have Camera.Follow(GameObject g); Sounds powerful - good! Generally we'll have the Camera follow the PC.



The Camera Class




It seems the camera must have a process stage - it must update what it is doing. There is no better place to stick this than the PlayingGameStates Process function, currently later we may push it into the metamap it all really depends.




The camera is going to be used a lot, it should really be a singleton, a nice static class that can be called from anywhere. We could even use it in other States if we really wanted to.




The PlayingGameState process function is going to be used as below:




public void Process()
{
if (device == null)
return;

device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 1.0f, 0);

device.BeginScene();
Camera.Teleport(0,0.1f,0); //just too test!
Camera.Process(device);

map.Render(-1f,1f, device);

device.EndScene();
device.Present();

UpdateInput();
}



So this when we've created the Camera class should show us the scene with 0.1 taken off the bottom. But to see any of this we first need that camera class. The intial class with be quite bear bones but we'll slap on functionality as we go!




using System;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace EINFALL
{
///
/// Summary description for Camera.
/// Can we put a motion object in here?
///

public class Camera
{

static private Matrix position = Matrix.Identity;

static public void Teleport(float x, float y, float z)
{
ResetPosition();
position.Translate(x,y,z);
}

static public void ResetPosition()
{
position = Matrix.Identity;
}

static public void Process(Device device)
{
device.SetTransform(TransformType.View, position);
}

static public void Follow(GameObject g)
{
}

static public void Stop()
{
}

}
}



So the view matrix defaults to the identity matrix (which I believe we've dicussed briefly before).




The teleport function moves the camera to the position given in x,y,z co-ordinates, we could probably do we a 2D teleport as well or have that as the default as it's what we'll be using most.




Process just alters the view matrix on the device.




Let's see what this fantastic new class can do:



Shunt y-wise by 0.1f



The follow method!




This is pretty groovy - we need to make some changes though. We're going to take the Process function of Camera out of the main loop and have the Clock take care of it.




We need to alter the process function and get rid of the Device argument. This is because it we update the camera as often as possible it produces a nasty effect with animating the character. And we don't have any Clock ability to pass variables (although it wouldn't be overly difficult to add with a few non-descript Object types). So let's get to it.




Let's start with new variables for the camera class:




public class Camera
{

static private Matrix position = Matrix.Identity;
static private bool follow = false;
static private GameObject target = null;
static private Device device;



All pretty self explanatory I feel. Follow boolean is are we following a game object - yes or no? Target is the game object that we're following. Device is the thing we need to render the viewport correctly.




Super fantastic++ now let's get rid of the device argument in the Process function, snippity snap. . . oh and why not throw in the follow code while we're there - it's pretty simple really.




static public void Process()
{
if(follow.Equals(true))
{
position.Translate(-target.hotspotX,
-target.hotspotY,0);

}
device.SetTransform(TransformType.View, position);
}



We use the hotspot because I think it works better to follow the middlish area of the player rather than the top corner of his head. If you want to define an Actor middle or do something different it shouldn't be too hard.




The code is so simple it's pretty beautiful. It's minus X,Y because ... I'm not too sure but it's the way I want it to function and it works.




We need to actually have device to refer too. So we need some kind of setup function - it could a little like this:




static public void SetDevice(Device d)
{
device = d;
Clock.AddTimeEvent(
new TimeEvent(45, new TimeEvent.Call( Camera.Process)));
}




Oh and we put a timer in there to update the cameras postion regualarly. The number is currently in sync with how often we animate the character because otherwise the animation gets a little flickery - not sure why!





Last part stip out the stuff we put in PlayingGameState.Process, then add some intialize stuff into the constructor.




public void Process()
{
if (device == null)
return;

device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 1.0f, 0);

device.BeginScene();


map.Render(-1f,1f, device);

device.EndScene();
device.Present();

UpdateInput();
}



So the Process function is once again pretty clear.




Camera.SetDevice(device);
Camera.Follow(Player);




These two lines go at the end of the constructor.




Then notice how the player will stay in the center of the screen while the world seems to move around him!




If you still have [Enter] teleporting the player to the top corner try pressing it and watch how the camera follows him. It's pretty groovy and our gateway to larger maps.



Cameras following - woot!

That's the camera done for now - though there's lots more cool functionality we can add. Also the animation jitters bug me, I'd like to update the camera as often as I wished so I may look into circumventing that if possible.



Defining a map bigger than the screen




Is pretty simple - we merely increase the map size values in PlayingGameState




map = new Map(10,10);



Unfortunately we haven't been coding flexibly enough - there's a whole load of code assume the map is contained in a -1 to 1 box. This is no longer the case! So a load of functions get broken and I cry a bit.




The best ways to deal with this are to get a length and height functions that give the maps dimensions in directX measurement units.




Get rid of public PointF positionDifference from map, we won't be needing that where we're going.



Instead we'll pop the following Getter into Map





public PointF topLeftPoint
{
get
{
return position;
}

}



It's time to pop a few new variables into the map class:




public class Map : IStorable
{
//Hard coding the tile size
static public float tileDimension = 0.25f;


public Tile[] tiles;
public ArrayList MapObjects = new ArrayList();
private int area;
private int width, height;
public float dxWidth, dxHeight;
private PointF position = new PointF(-1f,1f);



We make reference to a tile dimension so often we should really store it in a variable. I'm going to use a static, so this isn't going to be something we can dynamically change - but I'm okay with that. I don't want the tile size to change during the game ... currently. One could imagine having a battle engine where the tile size changed. For now I'm happy just to save it here.




The next bit is the width and the height of the map in DirectX coordinates from 0. So if we're rendering from -1 we need to make sure that's where we count from to get a good end of the map in Dx coords.




So let's see the intialization:




public Map(int Width, int Height)
{
width = Width;
height = Height;
area = width * height;

dxWidth = width * Map.tileDimension;
dxHeight = -(height * Map.tileDimension);


tiles = new Tile[area];

GeneratePlain();
}



Also at this point I trawled through some of the code and tried to make sure I was consistently using Width. So any varaibles using the word length I renamed. You may also wish to do this, at the very least bare it in mind in case you come across the varaibles I've changed as we proceed.



public void Render(float x, float y, Device device). The only change in this function is to use Map.tileDimension rather than the magic number 0.25




public int GetTileIndex(float posX, float posY)
{
float percentagePosition = (posX + position.X)/(position.X+dxWidth) - position.X;
int xRowNumber = (int)Math.Floor(percentagePosition/ Map.tileDimension);

float percentagePositionY =
Math.Abs((posY + position.Y)/(position.Y+dxHeight) - position.Y);

int yRowNumber = (int)Math.Floor(percentagePositionY/Map.tileDimension);

return (yRowNumber * width) + xRowNumber;
}



This function has been changed so that it should be able to work with any map at any position but it has not been thouroughly tested due to my love of surprise bugs!




public bool CanMove(float x, float y)
{
if(x > dxWidth ||
y + position.Y > position.Y ||
x + position.X < position.X ||
y < dxHeight)
return false; //because you'd be walking out of the window

int newTile = GetTileIndex(x,y); //this is where the magic ends!
if(newTile >= tileTotal)
return false;

if(tiles[newTile].IsBlocked())
return false;

return true;
}



The area checks here changed to make sure you can't walk out of the map area, rather than before where they just stopped you walking out of a 1x1 square with a topcorner at -1,1. So much suited to our needs. The dx coords coming in are the Actors position from 0.



That pretty much finishes the required updates to map



Now for the Actor class ...




Ok Actor.SetMapPosition, this was calling Tile to discover various things. I've killed that call off because when I was debugging it was making things rather complicated. So here's the new function:




public void SetMapPosition(int tileX, int tileY)
{
try
{
PointF p = new PointF(tileX * Map.tileDimension,
-(tileY * Map.tileDimension));
motion.Y = p.Y;
motion.X = p.X;
motion.Y -= boundary[0].Y + 0.10f;
Console.WriteLine(position);
//should take in Actor height.
}
catch(Exception e)
{
Console.WriteLine("SetMapPosition produced out of bounds: " +
e.ToString());
}
}



We get the X,Y dx coords referenced from 0 by taking the width and height and multiplying by the size of one tile. Then we set this as the new motion for the player. The +0.10f to the y is just to center the NPCs betters it's a bit of rough coding that needs to be sanded down at a later date.




The next thing to change in Actor is the GetDXPosition. It looks like so:




public override PointF GetDXPosition()
{

PointF final = new PointF(
position.X + map.topLeftPoint.X,
position.Y + map.topLeftPoint.Y);

return final;
}



There references to -1,1 as an important point in space are removed. The position of the player is reference from were ever the top corner of the map is.




One last place that's worth making changes is here:




//Tile shaped boundary
static public PointF[] CreateTileBoundary()
{
return new PointF[4] { new PointF(0f, 0f),
new PointF(0f, -Map.tileDimension),
new PointF(+Map.tileDimension, 0f),
new PointF(+Map.tileDimension, -Map.tileDimension)};
}



We use the nice new reference to the all important tile size value.




All those minor fixes and boom! We have a map larger than the screen that we can explore! The camera follows us as we look around! Yay! We can make a pretty big map with this too, so knock yourself out.



A bigger map



Loading and Saving




Currently we can load and save this map, all tile information will be saved. But we want to save more than that we want to save the Map objects and we want to save the tile occupants too!




Where the same object appears in different places on the map then efficent loading and saving can get a bit tricky - not too tricky we can't deal with though. For instance we could create our object Rock and then have it occupy many different tiles.




Before dealing with that though let us save the mapObjects. This should be much easier.




We create the bush by doing the following:




Actor bush = new Actor(map, "plains",
new NoMotion(s.GetOffset("Bush")),
Actor.CreateHumanBoundary());



So the map part is no problem we don't need to save that.




We do need to save the textureSet, type of motion, offset info and boundary info. So we want to save datablock that looks like the following:




[textureSet name]
[type of motion]
[motion offset - / idle state of textureSet]
[boundary type]
[X position]
[Y position]



We want the Actor to be able to write and read such a collection of data.





public class Actor : MapObject, IStorable
{



Here VS.net has put the skeleton functions in for us:




#region IStorable Members

public void Read(object o)
{
// TODO: Add Actor.Read implementation
}

public void Write(object o)
{
// TODO: Add Actor.Write implementation
}

#endregion



To know what texture set we're working with we're going to have to explicitly store the information. At least for now:




protected string ioTextureSetName;

public Actor(Map mapIn, string TexSet, IMotion m, PointF[] bounds)
: base(mapIn)
{

ioTextureSetName = TexSet;



Now we can write this information out! And read it in. Notable this writing and reading is still going to be fragile and rather weak code. Also The whole idiom probably needs a revamp. A static loader / reader is probably required to do this thing properly.




We must make change to Actor, and changes to map:




Actor needs a new simple constructor so the rest can be filled in by the reader. Yes it sucks yes we need something different at some point.




public Actor(Map mapIn) : base(mapIn)
{
}



The rest of the actor information will be filled in by the read method.




This is a good point to glance over the read record:




public void Read(object o)
{
StreamReader reader = (StreamReader) o;

ioTextureSetName = reader.ReadLine();
string motionType = reader.ReadLine();
int idle = int.Parse(reader.ReadLine());
string boundaryInfo = reader.ReadLine();

PointF[] bounds = null;
sprite = TextureManager.BufferTextureSet(ioTextureSetName);

switch(boundaryInfo)
{
case "Human Boundary":
{
bounds = Actor.CreateHumanBoundary();
}break;

case "Tile Boundary":
{
bounds = Actor.CreateTileBoundary();
}break;

default:
{
//load in all bounds information
}break;
}

boundary = bounds;
//assume it's been given the map

if(motionType.Equals("EINFALL.Motions.NoMotion"))
{
motion = new NoMotion(idle);
}
else if(motionType.Equals("EINFALL.Motions.PlayerMotion"))
{

motion = new PlayerMotion(sprite,idle);
}
else
{
Console.WriteLine("Error loading Actor");
}

motion.X = position.X = float.Parse(reader.ReadLine());
motion.Y = position.Y = float.Parse(reader.ReadLine());

}



It's pretty self explanatory and not very flexible. It's brittle code that needs working upon. But it will suit our purposes just fine.





The write code is a bit like the reverse of the read.



public void Write(object o)
{
StreamWriter writer = (StreamWriter) o;
writer.WriteLine(ioTextureSetName);
//Need to find out motion type!

writer.WriteLine(motion.ToString());


//Need to write idle - this is a hack!
motion.setState("standing");
int idle = motion.offset;
writer.WriteLine(idle);

//Need to write bounds
//This is really bad coding :D
if(boundary[0].Y.Equals(-0.41f))
{
writer.WriteLine("Human Boundary");
}
else
{
writer.WriteLine("Tile Boundary");
}

writer.WriteLine(position.X);

writer.WriteLine(position.Y);

}



The clever bit here is the motion.ToString() bit. Even though we only have an IMotion reference and this could be any implementation if you tell it to ToString it writes out what particular implementation it is - this is good. Saves us work.



Saving the map with map objects



So Actors can now write themselves out and read themselves in. But this isn't a lot of use unless these functions get called. So to the IStorable members of the map!




public void Write(object o)
{
String outputFile = (string) o;

try
{
using (StreamWriter sw = new StreamWriter(outputFile))
{
sw.WriteLine("Plain"); // should be a variable somewhere :0
sw.WriteLine(width);
sw.WriteLine(height);

sw.WriteLine(MapObjects.Count);

foreach(Actor a in MapObjects)
{
a.Write(sw);
}


foreach(IStorable t in tiles)
{
t.Write(sw);
}
}
}
catch(Exception e)
{

MessageBox.Show("Error writing map file: " + e.ToString(),
"Write Error");
}
}



Writing is pretty easy but not that we're writing out Actors! We may have many different GameObjects for true functionality we should be writing out Load and Store as implemented by all GameObject types!




Reading is a little more complicated.




public void Read(object o)
{
String inputFile = (string) o;

using (StreamReader sr = new StreamReader(inputFile))
{
string textureSet = sr.ReadLine();
int newHeight = int.Parse(sr.ReadLine());
int newWidth = int.Parse(sr.ReadLine());

height = newHeight;
width = newWidth;
area = width * height;

int numMapObjs = int.Parse(sr.ReadLine());

MapObjects.Clear(); // We're loading not appending
for(int i = 0; i < numMapObjs; i++)
{
Actor a = new Actor(this);
a.Read(sr);
AddMapObject(a);
}


tiles = new Tile[area];
for(int i = 0; i < area; i++)
tiles[i] = new Tile("plains", this);


foreach(IStorable t in tiles)
{
t.Read(sr);
}

}
}



Nothing too hard. Now load the game up and press [ALT-S] and it will save the map and the map objects to c:\MapFile.map. Mine looks like below:




Plain
30
30
4
player
EINFALL.Motions.PlayerMotion
0
Human Boundary
0.25
0.31
plains
EINFALL.Motions.NoMotion
12
Human Boundary
0.5
-0.19
plains
EINFALL.Motions.NoMotion
8
Tile Boundary
1.25
-1.35
NPC
EINFALL.Motions.PlayerMotion
0
Human Boundary
1
-1.19
Grass
False
Grass

... Grass and False FOR AGES! ...

Grass
False



Reasonably sweet. Of course we've saved the player, which for general map loading saving we'd wish to avoid.




Don't alter the map file. We're going to mess with the code then we'll load our data from the map file.




First let us rather inelegantly open up a protected member of Actor. (Really the GameObject should give a uniquie id or at least id name to each object but on such luck at moment).




public string ioTextureSetName;



With that done let us change the constructor of PlayingGameState so that it appears as the below:




public PlayingGameState(Device d, GameStateManager g, DXInput.Device dInput)
{
device = d;


gameStateManager = g;
inputDevice = dInput;


map = new Map(30,30);
/**
ISprite s = TextureManager.BufferTextureSet("player");

Player = new Actor(map, "player", new PlayerMotion(s,0),
Actor.CreateHumanBoundary());

s = TextureManager.BufferTextureSet("NPC");

NPC = new Actor(map, "NPC", new PlayerMotion(s,0),
Actor.CreateHumanBoundary());

s = TextureManager.BufferTextureSet("plains");

Actor rock = new Actor(map, "plains",
new NoMotion(s.GetOffset("Rock")),
Actor.CreateTileBoundary());

Actor bush = new Actor(map, "plains",
new NoMotion(s.GetOffset("Bush")),
Actor.CreateHumanBoundary());

Player.SetMapPosition(1,0);
NPC.SetMapPosition(4,6);
rock.SetMapPosition(5,5);
bush.SetMapPosition(2,2);

map.AddMapObject(Player);
map.AddMapObject(NPC);
map.AddMapObject(rock);
map.AddMapObject(bush);
//map.tiles[22].AddOccupant(rock);
**/

map.Read(@"c:\MapFile.txt");

foreach(Actor a in MapObjects)
if(a.ioTextureSetName.Equals("player"))
Player = a;

Camera.SetDevice(device);
Camera.Follow(Player);
}



Notice the massive chunk we've been able to block out by loading from a map file! Yay for us.




Load it up - and oh my we're loading map files with Actor / GameObject data from a file! Woo we could now design cool maps and we have a format to load from. Albiet a crappy format but who cares it works!



The final count down - duh duh duh




I hate that song, so much.




Right all that's left is to save a game object associated with a tile. This is tricky because we may have the same gameobject associated with many tiles! It may also be a member of the MapObjects. All things we need to conisider to make a nice tight map format.



How do we deal with this?




First we make a list of all the unique occupiers on the map. We disregard any items on the list that are in the MapObjects list. Then we write out this list of unique occupiers. Then for each tile we reference which occupiers it has by reference to the order the occupiers where written down. A tad tricky but not out of our reach.




[normal tile stuff]
[int : number of occupiers]
[int : reference no. of 1st occupier]
...
[int : reference no. of nth occupier]



Then to load we build a list of the unique occupiers and add them to the relevant tiles using the offset number - easy now just the implemetation details :)



The map and the writing function




So to write out first we count up our uniques put them in a list. Write out how long it is followed by all the elements in it.




public void Write(object o)
{
String outputFile = (string) o;

try
{
using (StreamWriter sw = new StreamWriter(outputFile))
{
sw.WriteLine("Plain"); // should be a variable somewhere :0
sw.WriteLine(width);
sw.WriteLine(height);

sw.WriteLine(MapObjects.Count);

foreach(Actor a in MapObjects)
{
a.Write(sw);
}

ArrayList occupiers = new ArrayList();

foreach(Tile t in tiles)
{
if(!t.Occupants.Count.Equals(0))
{
//limiting ourselves to Actor
foreach(Actor a in t.Occupants)
{
if(!MapObjects.Contains(a))
{
if(!occupiers.Contains(a))
{
occupiers.Add(a);
}
}
}
}
}

sw.WriteLine(occupiers.Count);
foreach(Actor a in occupiers)
{
a.Write(sw);
}





It's worth noting that we're also making sure the unqiues aren't in the mapObjects - that means when we index the tile stuff we index by taking into account the concatination of both these lists.




So each tile will say - how many occupiers it has and then it will list the reference numbers.




[TileInfo]
[int : number of occupiers
[int : occupier 1]
...
[int : occupier n]



So our map files are getting pretty confusing with plenty of numbers! we could use XML or binary. XML would be nice because then the data is descriptive and easier for us to edit - but also bloated and easy for a user to edit which may be undesirable.





Grass
true
1
3




So let's do the tile writing stuff - this would be nice if it was in Tile.Write but that obviously does not serve our purposes because we wouldn't have access to the list.




foreach(Tile t in tiles)
{
t.Write(sw);
sw.WriteLine(t.Occupants.Count);


foreach(Actor a in t.Occupants)
{
if(MapObjects.Contains(a))
{
sw.WriteLine(MapObjects.IndexOf(a));
}
else if(occupiers.Contains(a))
{
sw.WriteLine(occupiers.IndexOf(a)
+ MapObjects.Count);
}
else
{
Console.WriteLine("Something has gone horribly wrong in constructing"
+" our list of uniques");
}
}

}



Now we written it all out we have to read it in! I'm just going to lay this bad boy of function down. It's pretty obvious if somehat tricky to code without making errors.




public void Read(object o)
{
String inputFile = (string) o;

using (StreamReader sr = new StreamReader(inputFile))
{
string textureSet = sr.ReadLine();
int newHeight = int.Parse(sr.ReadLine());
int newWidth = int.Parse(sr.ReadLine());

height = newHeight;
width = newWidth;
area = width * height;

int numMapObjs = int.Parse(sr.ReadLine());

MapObjects.Clear(); // We're loading not appending
for(int i = 0; i < numMapObjs; i++)
{
Actor a = new Actor(this);
a.Read(sr);
AddMapObject(a);
}

int uniqueOccupiers = int.Parse(sr.ReadLine());

ArrayList uniqueOccList = new ArrayList();
for(int i = 0; i < uniqueOccupiers; i++)
{
Actor a = new Actor(this);
a.Read(sr);
uniqueOccList.Add(a);
}

tiles = new Tile[area];
for(int i = 0; i < area; i++)
tiles[i] = new Tile("plains", this);


int occCount;
foreach(Tile t in tiles)
{
t.Read(sr);
occCount = int.Parse(sr.ReadLine());

for(int i = 0; i < occCount; i++)
{
int occRef = int.Parse(sr.ReadLine());
if(occRef > MapObjects.Count)
{
occRef -= MapObjects.Count;
t.AddOccupant((GameObject)uniqueOccList[occRef]);

}
else
{
t.AddOccupant((GameObject)MapObjects[occRef]);
}
}
}

}
}




Yes it's a beast.




Thus ends this volume of the tutorial. You have your walking, bounded, animated man, with sceney and loadable / savable maps in an simple format.



What needs to be done




  • Loading saving needs to be robust.

  • Scripting, and extending Actors to allow it

  • Inventory data type

  • Items to pick up

  • Combat system

  • Some kind of plot

  • The various inspired details that will make your RPG unique.




Out of all of these decent scripting is probably going to be the hardest. I know how to make a simple system through blood sweat and coding it myself tears. But I hear one can do it easily usin C# reflection abilities. This doesn't make sense to me as all I know about reflection is that you can use it to read attributes in the code. So there's a place to investigate.




The rest should be fairly trival to implement yourselves :D Just be stubborn and clever where possible but most of all stubborn and you'll see your game come alive. If you've read this far you should be able to take the ball and run with it - good luck, please send me anything you finished or that is cool. Please send questions and pleas for help to a patient message board. While I'd love to help you all I'm not going to! Other than release updated versions of this if necessary.



Will there be more?




Quite possibly. Though you must remember this is the code base or the start of the code base of my own fine game. That I would like to be commerically viable at some point, so I'm not going to give away all the source just yet.


Stage 5 - 4 : Getting A Really Good Rock





Most rocks aren't actors they're more potential actors. Should we have another class. Or should we polish the actor class until it's really general? This is the question the plagues us!




We should at least start with allowing boundaries to be adjusted! That way we can have small rocks and big monsters surely the best of both worlds.



New Project




We'll call this project really good rock. As usuakl copy over all the data from the previous project!



Basic Namespace structure




Now my projects code name is EINFALL, so that's the master namespace I'm going to house all my files in. So choose your name, if you really must you can copy mine - but if your going into Game Design the least you could think of is a name for your game.




Okay, notably Clock, TimeEvent and some other's aren't in any namespace, they should make sure every CS file has it's root namespace as EINFALL or whatever your choosing to call your game. After you do this make sure it compiles!



Sub Namespace GameStates




GameName.GameStates is the order of the day, so in here we'll put playing game state and title screen state and game state itself.




using EINFALL.GameStates;



The above must be added to the main .cs file that has the main function and to the GameStateManager.cs file too. Once again check if it compiles.



Sub NameSpace Motions




In here we'll throw NoMotion, PlayerMotion and IMotion.




using EINFALL.Motions;



Add to PlayingGameState and Actor to achieve compiliability.




In PlayingGameState I have the following includes:




using System;
using EINFALL.Motions;
using Microsoft.DirectX.Direct3D;
using DXInput = Microsoft.DirectX.DirectInput;



Generally there should be no includes that aren't being used relics from early code should be killed off.




That cleans up a bit of our code but really there needs to be a great number of well defined namespaces. I'm delaying putting some more in now because I'm unsure of the best place to put so I'll defer until later.




The real greatness of namespaces is using them as boxes to put "done" code that you can than forget about and just keep in mind what really should be worked on at the moment - currently our project desk is a bit messy



Do we need a scenery class?




Rocks currently in their and working fine, the only thing wrong with it is it's boundary. Well boundaries are going to be a potential problem from many different shaped actors, so we should make the actor constructor take in boundary info.




Note: boundaries are still a fuzzy concept they're hard programmed to have only four values yet we set it up so they can potentially have more. For now we'll keep the fuzziness but this is something we may wish to lock down as our game stares to emerge from the ether of bits.



Loading in boundaries




So in Actor we have the following variables:




public class Actor : MapObject
{
protected ISprite sprite;
protected IMotion motion;

protected PointF[] boundary;
protected PointF position = new PointF(0,0);
protected int idle = 0;



We can remove idle that information is now covered by motion. Here we're concerned with the boundary.




The boundary is intialized in the constructor as below:




public Actor(Map mapIn, string TexSet, IMotion m)
: base(mapIn)
{

sprite = TextureManager.BufferTextureSet(TexSet);

motion = m;

blocking = true;

boundary = new PointF[4] {new PointF(+0.04f, -0.41f),
new PointF(+0.04f, -0.49f),
new PointF(+0.19f, -0.41f),
new PointF(+0.19f, -0.49f)};

}



Okay so let's add a new static function that will create just such a boundary. Why not a static variable? Well we may want to dynamically change an Actors boundary so this seems wisest!




static public PointF[] CreateHumanBoundary()
{
return new PointF[4] { new PointF(+0.04f, -0.41f),
new PointF(+0.04f, -0.49f),
new PointF(+0.19f, -0.41f),
new PointF(+0.19f, -0.49f)};
}



A little wave of my magic wand and the constructor looks like this:




public Actor(Map mapIn, string TexSet, IMotion m)
: base(mapIn)
{

sprite = TextureManager.BufferTextureSet(TexSet);

motion = m;

blocking = true;

boundary = Actor.CreateHumanBoundary();
}



Okay so let's alter the constructor so we have to pass this value in:




public Actor(Map mapIn, string TexSet, IMotion m, PointF[] bounds)
: base(mapIn)
{

sprite = TextureManager.BufferTextureSet(TexSet);

motion = m;

blocking = true;

boundary = bounds;
}



Of course this sets off various kinds of problems in the PlayingGameState class. So let's fix them.




Player = new Actor(map, "player", new PlayerMotion(s,0),
Actor.CreateHumanBoundary());

s = TextureManager.BufferTextureSet("NPC");

NPC = new Actor(map, "NPC", new PlayerMotion(s,0),
Actor.CreateHumanBoundary());

s = TextureManager.BufferTextureSet("plains");

Actor rock = new Actor(map, "plains",
new NoMotion(s.GetOffset("Rock")),
Actor.CreateHumanBoundary());



Okay so we're still giving the Rock the human bounds but at least now we could change it if we wish.



On needing a scenery class revisited (again) :D




So now Actor could potentially do a perfect rock, member of tile or not member of tile.




The only thing a tile based piece of scenery wouldn't need is the elaborate bounds checking. Which isn't done anyway because it occurs in Render(device) and all tile based objects are going to use Render(x,y,device).




So for the time being no particular scenery object is needed. We can just use Actor. This may change if we increase the amount of information in Actor. Notably we might add a "brain" at some point as well as "action/reaction" code.



Testing out Actor as scenry use




We'll fix up rocks boundary add it to the main map objects and see how it fares this will allow us to pin point bugs and correct on short sightedness that we've had.




First we're going to need an appropiate boundary. So in Actor we'll create a new static function like so:




static public PointF[] CreateTileBoundary()
{
return new PointF[4] { new PointF(0f, 0f),
new PointF(0f, -0.25f),
new PointF(+0.25f, -0f),
new PointF(+0.25f, -0.25f)};
}



So we shouldn't really be pulling these numbers out of the air, but for now we are, maybe when we can query the map, or Tile for the numbers we'll do that, but currently that functionality is missing.




Okay in the PlayingGameState classes constructor we do the following:




Actor rock = new Actor(map, "plains",
new NoMotion(s.GetOffset("Rock")),
Actor.CreateTileBoundary());

Player.SetMapPosition(4,4);
NPC.SetMapPosition(4,6);
rock.SetMapPosition(5,5);

map.AddMapObject(Player);
map.AddMapObject(NPC);
map.AddMapObject(rock);



So we're adding it as an MapObject so we can test if the boundaries work. And work they do - but there is a rather noticable problem:



Oh noes.


So the rock should be behind the player - why is this not happening. Well the player is being reference from the top let corner - that's his "hotspot". So he's actually behind the rock if he's references from the head, but he should be being reference from the feet.



Where is all this voodoo happening?




Well cast your mind back and you will see that we put a CompareTo into the map object class. Let's have a look at it:




public int CompareTo(Object rhs)
{
GameObject g = (GameObject)rhs;
return g.GetDXPosition().Y.CompareTo(this.GetDXPosition().Y);
}



Notice that it just get the DirectX position of the Y. We of course want to modify this so we'll stick a Getter into the MapObject class.




public abstract float hotspotY
{
get;
}



For completenesses sake we'll whack the X version in there to:




public abstract float hotspotX
{
get;

}



Now we must implement them in both Tile and Actor. The tile one is very simple its just the top corner so we make a call to getDxPosition.




public override float hotspotX
{
get
{
return GetDXPosition().X;
}
}

public override float hotspotY
{
get
{
return GetDXPosition().Y;
}
}



Okay now to Actor, this where we change things a little.





public override float hotspotX
{
get
{
return GetDXPosition().X + boundary[0].X;
}
}

public override float hotspotY
{
get
{
return GetDXPosition().Y + boundary[0].Y;
}
}



Finally to MapObject where we change the comparison function. But ... the compare function takes in GameObject. Okay no problem we'll just move the get/setters up to GameObject and everything should be be okay.



Remove the abstract hotspot functions from map object and place them into the game object.




Then alter the comparision function like so:





public int CompareTo(Object rhs)
{
GameObject g = (GameObject)rhs;
return g.hotspotY.CompareTo(this.hotspotY);
}



With that done the rock now is hidden as we desired!



Yay

Moving map objects with the map




Before I demonstrated how we could move the map to be rendered from a different X,Y position. The only draw back was that the mapObjects in the map did not move, they remained fixed. This shouldn't be too hard to solve.




Map has a render function, this is where we'll want to strike - so let's have a look at it.




public void Render(float x, float y, Device device)
{
float xLimit = x;

for(int i = 0; i < tileTotal; i++)
{
Tile t = (Tile) tiles[i];
t.Render(x,y, device);

if(((i+1) % rowLength) == 0)
{
x = xLimit;
y -= 0.25f;
}
else
{
x += 0.25f;
}
}
}



Okay so currently it's not rendering it's GameObjects, this is being done by playingGameState, so we wish to rectify this!




So we simply cut the code out of playing game state and drop it into our map render function. Omiting all the map object references. Like so:



public void Render(float x, float y, Device device)
{
float xLimit = x;

for(int i = 0; i < tileTotal; i++)
{
Tile t = (Tile) tiles[i];
t.Render(x,y, device);

if(((i+1) % rowLength) == 0)
{
x = xLimit;
y -= 0.25f;
}
else
{
x += 0.25f;
}
}

MapObjects.Sort();
foreach(MapObject m in MapObjects)
{
m.Render(device);
}

}



Okay now we want to store the base values of X and Y that are passed into this render function. This way we know where in the screen we'll be drawing the map from. Easy enough:




public void Render(float x, float y, Device device)
{
float xBase = x;
float yBase = y;


for(int i = 0; i < tileTotal; i++)
{
Tile t = (Tile) tiles[i];
t.Render(x,y, device);

if(((i+1) % rowLength) == 0)
{
x = xBase;
y -= 0




Now the place we should render is the Actor position plus the original X,Y of the map. We could do this by calling the render(x,y,device) function. If we choose to do this though we have to move the boundary code.




Also the Actors X,Y position will be incorrect, which will work no problem for collisions as everything is relevant but it will cause issues with say ... mouse intersection!




We should have a MapPosition that we set in the render loop then MapObjects can include this when they get their DX position. So we take the map positions from (-1,1) the top corner of the map and then if there's any difference we add this on when we give out the directX coordinates of our Actors and Tiles.




Ok so let's have a map postion and an accessor that gives the difference - they'll look like so:




private PointF position = new PointF(-1f,1f);

//The difference of position from top left -1,1
public PointF positionDifference
{
get
{
PointF difference = new PointF(-1 - position.X,
1 - position.Y);

return difference;




Now let's slightly edit Maps render function to update it, to this change.




public void Render(float x, float y, Device device)
{
float xBase = x;
position.X = x;
position.Y = y;


for(int i = 0; i < tileTotal; i++)
{
Tile t = (Tile) tiles[i];
t.Render(x,y, device);

if(((i+1) % rowLength) == 0)
{
x = xBase;
y -= 0.25f;
}
else
{
x += 0.25f;
}
}

MapObjects.Sort();
foreach(MapObject m in MapObjects)
{
m.Render(device);
}
}



There, beautiful.




Next change is in the GetDXPosition in Actor.





public override PointF GetDXPosition()
{

PointF final = new PointF(
position.X - map.positionDifference.X,
position.Y - map.positionDifference.Y);

return final;
}



That works perfectly try changing the PlayingGameState map rendering co-ords to the following:




map.Render(-0.5f,1.3f, device);


Woot!


Nice, notice how all the Actors are now relative to the map - makes me happy.




Also the bounds on the map prevent you walking in black black space - pretty groovy no?



Let's not forget Tile while we're here




Tile should also give correct DX position, Currently it's assuming -1,1 values so this needs to be updated!




public override System.Drawing.PointF GetDXPosition()
{

int index = map.GetTileIndex(this);
float tileLength = 2f/map.rowLength; // in directX co-ords

/*
* It's 2f because X goes from -1 to 1
*/
int ylevel = System.Math.Abs(index / map.rowLength);
int xlevel = index - (ylevel * map.rowLength);

PointF p = new PointF( -1 + (xlevel * tileLength) , //DXcoords
-ylevel * tileLength +1);

p.X = p.X - map.positionDifference.X;
p.Y = p.Y - map.positionDifference.Y;

return p;
}



That's fine but setting tile position isn't working! I don't know why I think because of the muddles of the offset coordinate system. So I'll have to think about that ...




... okay the problem is we store potential coordinates in Motion. That's a pretty easy fix the remaining problem is in the SetMapPosition, there's a magic number that centers the NPc on a tile. Also the rock isn't position correctly because NoMotion doesn't actually store any numbers we'll have to add these. So the more correct name will NoMotion-apart-from-sudden-teleportation.




Let's start with the IMotion implementation - NoMotion. I'ts now easy to find as it's under the Motions namespaces. It needs a bit of altering so let's go:




public class NoMotion : IMotion
{

int idle;
float internalX;
float internalY;




Then we need to alter the Getters / Setters.




public float X
{
get
{
return internalX;
}
set
{
internalX = value;
}
}

public float Y
{
get
{
return internalY;
}
set
{
internalY = value;
}
}



Okay now to Tile. In Tile we need to give the coords of the tile in reference to the map position so let's do the following:




public override System.Drawing.PointF GetDXPosition()
{

int index = map.GetTileIndex(this);
float tileLength = 2f/map.rowLength; // in directX co-ords

/*
* It's 2f because X goes from -1 to 1
*/
int ylevel = System.Math.Abs(index / map.rowLength);
int xlevel = index - (ylevel * map.rowLength);

PointF p = new PointF(-1 + (xlevel * tileLength) , //DXcoords
-ylevel * tileLength + 1);

p.X = p.X - map.positionDifference.X;
p.Y = p.Y - map.positionDifference.Y;

return p;
}



Now we have to alter how we change the positions in Actor. So first SetDXPosition:




public void SetDXPosition(float X, float Y)
{
motion.X = X;
motion.Y = Y;
}



Next on to SetMapPosition




public void SetMapPosition(int tileX, int tileY)
{
try
{
int tileIndex = tileX + (map.rowLength * tileY);
PointF p = map.tiles[tileIndex].GetDXPosition();
motion.Y = p.Y;
motion.X = p.X;
motion.Y -= boundary[0].Y + 0.10f;
Console.WriteLine(position);
//should take in Actor height.
}
catch(Exception e)
{
Console.WriteLine("SetMapPosition produced out of bounds: " +
e.ToString());
}
}



That's all fixed - finally so we can now move the map around the screen and all the mapobject will tag along with it.




Rock is now fully working, it's helped prove the flexiblity of the Actor class. You could say we've managed to get a really good rock



Okay very nice but what about some bush related promises?



Shoddily crafted bush


It's a shoddily crafted bush - and jebus it's two tiles tall!.




I've stuck it in my plains .tga file. Though it's not what I'd conventionally think of when thinking of a bush on the plains but it will do!




So just to make my texture file look nice I've put the tree right over on the far end of the file. Over to the right. So in the Sprite Set file this will have to be taken into account.




Also two tiles tall - this means we don't have a preset vertex buffer to use, we either create a custom one and the code to handle this or create a new present one. Before doing all that though let's just settle for the single tile size and to check if we're actually managing to pick out the right texture.




\plains2.tga
16
Stone



We need to add another four to the vertex buffer.




Bush
tileshape
1.0
0
1.0
0.250
0.875
0.250
0.875
0



This should work for the measurements of my textue file. So let's check if it works.




Go to playing game state and let's change the offset name for the rock so that the bush texture is loaded. It creates quite a cool small bush:




Actor rock = new Actor(map, "plains",
new NoMotion(s.GetOffset("Bush")),
Actor.CreateTileBoundary());


Small shoddily crafted bush


If we want to keep the bush at this size we could just keep the TextureSet info and add a new set of info for a our full size bush, so we could have bush and dwarf bush or something.



Getting a full size bush




The bush is the same size as an NPC I only just noticed so we can use a preset to load it we just modify the TextureSet info like so:




Bush
base character shape
1.0
0
1.0
0.250
0.875
0.250
0.875
0



Run it again and you get the following:



Normal size shoddily crafted bush


Blocking is present too ... but it's blocking a tilesworth from the top of the sprite. It should be blocking a tile's worth from the bottom :( We can hack this together by using the Players blocking code, the feet are around the same area as the root's so it will work perfectly!




Let's write the code to put all the elements together. We'll do this in the PlayingGameState constructor:




Actor rock = new Actor(map, "plains",
new NoMotion(s.GetOffset("Rock")),
Actor.CreateTileBoundary());

Actor bush = new Actor(map, "plains",
new NoMotion(s.GetOffset("Bush")),
Actor.CreateHumanBoundary());

Player.SetMapPosition(6,5);
NPC.SetMapPosition(4,6);
rock.SetMapPosition(5,5);
bush.SetMapPosition(2,2);

map.AddMapObject(Player);
map.AddMapObject(NPC);
map.AddMapObject(rock);
map.AddMapObject(bush);
//map.tiles[22].AddOccupant(rock);
}




We get this and all the bounds work nicely.



Where's the hero?


Oddly the tree doesn't look like it did on the .tga file. I believe this maybe because they way we've set up transparency only takes into account binary transparency rather than the graded system - I'm not entirely sure how to resolve this - I guess reading of some kind :D

Stage 5 - 3 : Bush and a Rock



What we are going to do!



In this section we're going to add some eye candy, notably a bush and a rock!




  • Cleaning, always cleaning!

  • Sorting Animation and refining the TextureManager

  • Lots of other good stuff



Do we have tiles hold gameobjects too? Tile based objects this is probably a good idea.




After I reach stage 1 of my Masterplan I will construct a vaguely pretty (I say pretty here when I more accurately mean is won't be drawn in MSPaint using only the colours red and yellow). So I've added ROCK to the plain tile set, though it's not a tile on it's own, it's more like tile eye candy. So the question is do we have it as a "GameObject" or do we have it as tile object. If it's a tile object the collisions can be done so much more easily so ... let's do that!




First you may need a hand adding it to the tile class. It's not the easiest thing in the world at the moment we really need to have a go at some groovy tools soon. Add a nice ROCK image next to the grass and stone tiles. I've drawn mine like so:



Rock go rock.


I'm not putting the .tga here because I'm lazy muhahaha ... maybe when / if I review these pages.




Okay we need to do things ... first let's update the old .tex file so our fantastic game engine can handle this new "tile". Super extra fantastic. We need to append the following:




Rock
tileshape
0.375
0
0.375
0.125
0.250
0.125
0.250
0



Also if you've given your new texture set a new name (I've called mine plains2.tga) then thou must remember to change the reference at the top of this file. Get some pratice about how the computer must feel you ungrateful swine.




That's it it's loaded in and working (try it out by setting a tiles flavour to "Rock" and then amaze your friends. So if you do that you'll note the black background - that's right we need to have this "tile" placed on an actually tile. But how Holmes how?! Well we'll give the class tiles a shot in the arm that will bring it shooting into the tehnological future! We'll make an arraylist of occupiers! (which will be oh so private!)



First some 'shuffling'



Yes getting to hate that word aren't you?




Basically the selfish tile class has a monopoly on blocking code! We're going to bump the blocking stuff up to the map object and let it take care of it, so then actors can have their blocking info turned off and the like.




First to the MapObject class.




//MapObject Class : Adding some stuff
public abstract class MapObject : GameObject, IComparable
{
protected Map map;
protected bool blocking = false;

public bool block
{
set
{
blocking = value;
}
}

abstract public bool IsBlocked();




So this is all cut and pasted over from the Tile class. Apart from the IsBlocked function which is just made into a prototype for the MapObject class. Also a changed the B on blocking to lower case so it fits my coding style, which results in a few changes to the Tile and Actor class too - we need to add the following function to both:




//Tile and Actor Classes : Add this

public override bool IsBlocked()
{
return blocking;
}



Okay the blocking flag - isn't taken into account yet by the Actor class so we should fix that while it's still fresh in our memories.




First let's make sure our default values are up to scratch:




public Actor(Device device, Map mapIn, string TexSet)
: base(mapIn)
{

blocking = true;



Most actors are going to block so we make that standard. We also need to handle the case where an actor might now be blocking, this occurs in the obstruction code - therefore in the Map class - let's check it out.




public bool movementObstructed(PointF[] boundary, GameObject focus,
PointF pos)
{

bool intersect= false;
foreach(GameObject g in MapObjects)
{

if (g.Equals(focus))
continue;



We have this so we need to attempt to cast to MapObjects and then check blocking stuff. First we check if the focus is a blocking by doing the following:




public bool movementObstructed(PointF[] boundary, GameObject focus,
PointF pos)
{

bool intersect= false;

MapObject m;

m = focus as MapObject;
if(m!=null)
{
if(!m.IsBlocked())
return false;
}


foreach(GameObject g in MapObjects)
{



Then we'll do it for each GameObject too.





foreach(GameObject g in MapObjects)
{


if (g.Equals(focus))
continue;

m = g as MapObject;
if(m!=null)
{
if(!m.IsBlocked())
break;
}




Okay that's the blocking code in for Actor and Tiles now - great!





We can do a quick test by turning the NPCs blocking off and seeing if we can walk through him!




//PlayingGameState Class : Constructor
Player.SetDXPosition(0.5f,0.5f);

map.AddMapObject(Player);
map.AddMapObject(NPC);
NPC.block = false;
}



and we can run through him again - truely GURU programming. Now let's get back to the problem of adding that rock!



Occupancy List




We'll allow any items on the old occupancy list, any items that inherit from GameObjects, that is. So let's do that now! Let's go to the tile class!




//Tile Class adding variables

public class Tile : MapObject, IStorable
{

private string flavour;
private ArrayList Occupants = new ArrayList();
public ISprite sprite;



We need some Add/Remove code now too.




Blocking is a state that might change, for instance a door way might open and go from blocking to not blocking, the tile needs to consider this fact about it's occupiers list! So there's every reason we should be considering whether things block - each loop or each time obstruction is called. So we're going to write the following functions:




//Tile Class

public void AddOccupant(GameObject g)
{
Occupants.Add(g);

MapObject m = g as MapObject;
if(m != null)
{
if(m.IsBlocked())
this.blocking = true;
}
}

public void RemoveOccupant(GameObject g)
{
Occupants.Remove(g);
}

public bool DoOccupantsBlock()
{
bool local_blocking = blocking;

foreach(MapObject m in Occupants)
if(m.IsBlocked().Equals(true))
local_blocking = true;

return local_blocking;
}



The we modify the IsBlocked function like so:




public override bool IsBlocked()
{
blocking = DoOccupantsBlock();
return blocking;
}



So we have a cool new occupancy list - what next. Well how should rocks really be represented - a scenery, eyecandy or item class. Well probably but I fancy making the rock and actor for now and so that's what we're going to do. Cos we can have anything that's a a GameObject - and god help me I'm feeling a little bit crazy!




But maybe we're running ahead of the old hypothetical horse here - first we need to be renderin' these Occupiers! Currently the Tile rendering code is a mess, it's all in the GamePlayingState, it's like map has it's intestines half inside and half outside it's body - it's not nice. But for the moment we don't care we just wanna see that fine fine rock on a nice tile. So let's do this the quick and dirty way!




We have to turn this public:




public class Tile : MapObject, IStorable
{

private string flavour;
public ArrayList Occupants = new ArrayList();



Then over in PlayingGameState we have to do this:




map = new Map(8,8);

Player = new Actor(device, map, "player");
NPC = new Actor(device, map, "NPC");

Actor rock = new Actor(device, map, "plains");


Player.SetDXPosition(0.5f,0.5f);

map.AddMapObject(Player);
map.AddMapObject(NPC);
map.AddMapObject(rock);
}



First we're adding the Actor to the GameObjects just because we want to take small steps.




On running this you may notice everythin going odd - it's because idle state for actors is 0, always! :o





Then notice how there's a flashing tile, it's an error due to the idle state in Actor assuming the idle state will always be true. This is not necessarily correct so this is something we'll change.




To do this require a bit of minor tinkering with actor. In the future we'll need to pull out the big guns and make an animation/state class that can be loaded into Actors. For now we'll simply overload the constructor.




public class Actor : MapObject
{
protected PointF position = new PointF(0,0);
protected PointF[] boundary;
protected ISprite sprite;

int idle = 0;



That's going to be the offset for idle.




private void Animate()
{
switch(currentState)
{
case States.standing:
{
vbOffset = idle;
}break;



Then we add this constructor:





public Actor(Device device, Map mapIn, string TexSet, string Idle)
: base(mapIn)
{
blocking = true;

sprite = TextureManager.BufferTextureSet(TexSet);


Clock.AddTimeEvent(
new TimeEvent(95, new TimeEvent.Call(Animate)));

Clock.AddTimeEvent(
new TimeEvent(45, new TimeEvent.Call(Move)));


boundary = new PointF[4] {new PointF(+0.04f, -0.41f),
new PointF(+0.04f, -0.49f),
new PointF(+0.19f, -0.41f),
new PointF(+0.19f, -0.49f)};

idle = sprite.GetOffset(Idle);

}



This code will get the rock in:




map = new Map(8,8);

Player = new Actor(device, map, "player");
NPC = new Actor(device, map, "NPC");

//Actor rock = new Actor(device, map, "plains");
Actor rock = new Actor(device, map, "plains", "Rock");
//rock.vbOffset = rock.GetSprite().GetOffset("Rock");
//map.tiles[1].AddOccupant(rock);

Player.SetDXPosition(0.5f,0.5f);
rock.SetDXPosition(0f, 0.5f);

map.AddMapObject(Player);
map.AddMapObject(NPC);
map.AddMapObject(rock);
}



Okay more problems, the boundary is the Actor boundary for the stadard NPC style size! Maybe we need some kind of text file to define NPCs too - or maybe we're using a really stupid class to represent a rock :D So the Actor boundary is below the rock - where it's feet would be. We must bear in mind that this really does need changing but for now we need to do two things:



New Class and Cleaning up Maps rendering




Let's get maps crap out of PlayingGameState.




Below is all the crap.




float x = -1f; //Remember we're using Cartesian
float y = 1f;
for(int i = 0; i < map.tileTotal; i++)
{
Tile t = (Tile) map.tiles[i];

QuadMatrix.Translate(x,y, 0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, t.sprite.GetTexture());
device.DrawPrimitives(PrimitiveType.TriangleFan,t.vbOffset, 2);



if(((i+1) % map.rowLength) == 0)
{
x = -1f;
y -= 0.25f;
}
else
{
x += 0.25f;
}
}

map.MapObjects.Sort();
foreach(MapObject m in map.MapObjects)
{
m.Render(device);
}



In map we will create a wonderful skeleton function called Render - not like GameObjects render it will differ slightly. Den-den-dur ...




//Somewhere ever far far away in the Map Class

//x,y are DX coords
public void Render(float x, float y, Device device)
{

}



Yes groovy indeed. Now we transfer crap for PlayingState to this function and the world becomes a happier place. (may need to add various references).




map.Render(-1f,1f);

... (crap we've yet to move)



First stage let's get the tile rendering out of there! So in PlayingState we have the following:




public void Process()
{
if (device == null)
return;

device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 1.0f, 0);

device.BeginScene();

map.Render(-1f,1f, device);

map.MapObjects.Sort();
foreach(MapObject m in map.MapObjects)
{
m.Render(device);
}

device.EndScene();
device.Present();

UpdateInput();
}



Okie, let's give get our Map render function a gander:




//x,y are DX coords
public void Render(float x, float y, Device device)
{
device.SetStreamSource( 0, tiles[0].sprite.GetVB(), 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

Matrix QuadMatrix = new Matrix();
QuadMatrix = Matrix.Identity;


for(int i = 0; i < tileTotal; i++)
{
Tile t = (Tile) tiles[i];

QuadMatrix.Translate(x,y, 0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, t.sprite.GetTexture());
device.DrawPrimitives(PrimitiveType.TriangleFan,t.vbOffset, 2);



if(((i+1) % rowLength) == 0)
{
x = -1f;
y -= 0.25f;
}
else
{
x += 0.25f;
}
}

}




Okay now try moving the map. Like so:




public void Process()
{
if (device == null)
return;

device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 1.0f, 0);

device.BeginScene();

map.Render(-0.9f,1f, device);



Notice the glaring error!



Errororororoooooaaaaar


We're reset X to -1f, not the value we feed in, but as we're Captain Fixit this proves a minor problem by doing the following:




First a new variable.




public void Render(float x, float y, Device device)
{
float xLimit = x;



Then let us use this fine piece of variable power:




for(int i = 0; i < tileTotal; i++)
{
Tile t = (Tile) tiles[i];

QuadMatrix.Translate(x,y, 0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, t.sprite.GetTexture());
device.DrawPrimitives(PrimitiveType.TriangleFan,t.vbOffset, 2);



if(((i+1) % rowLength) == 0)
{
x = xLimit;
y -= 0.25f;
}



Now we're rollin' try the following in PlayingGameState.




map.Render(-0.9f,0.9f, device);



Cool we get the following picture, as expected:



Fixed Error


But why you may think - why why why why why - patience. Maybe we wish to stitch together several maps! Or do some thing groovy and special. Notice how we move the map yet most stuff doesn't crap up isn't it wonderful! Of course it would crap up if we moved if far enough.




  • Problem1: GameObjects are not strongly linked to the map




They have DX coords - being map objects they should have something different, like a tile number and offset or similar!



Map should be feeding the render coordinates to it's childen



Parent Child Bonding



We must note if something leaves a map! (maybe don't give DXx,y at start but something that creates the X,y for you from map info)




So we may want to set a tile objects position in reference to a tile on the map. So we can do this in X,Y ints or maybe passing a tile. The way we should really do this is to have each tile store it's X,Y because it would make our life easier and the computer can take it (we're no longer programming for the C64). But obviously this time I decided easy wasn't good enough and went for moderately hard. Each tile should be able to tell us it's X,Y DX position in fact I even wrote the code for this.




Of course I never checked it and therefore the code is wrong. Only slightly wrong though. So go into the tile class and replace your function with the below:




public override System.Drawing.PointF GetDXPosition()
{

int index = map.GetTileIndex(this);
float tileLength = 2f/map.rowLength; // in directX co-ords

/*
* It's 2f because X goes from -1 to 1
*/
int ylevel = System.Math.Abs(index / map.rowLength);
int xlevel = index - (ylevel * map.rowLength);

PointF p = new PointF( -1 + (xlevel * tileLength) , //DXcoords
-ylevel * tileLength +1);
return p;
}



So each tile can now tell us where it is in DX co-ords. Or at least tell us where its top corner is, and we can infer the rest.




So in actor to complement SetDXPosition, we're going to have SetMapPosition. It's going to look a little like below:




public void SetMapPosition(int tileX, int tileY)
{
int tileIndex = tileX + (map.rowLength * tileY);
position = map.tiles[tileIndex].GetDXPosition();
}



To check this was working okay. I went into playing game state and used the OnPressingEnter code and played about with some values. There is no out of bounds checking or anything like that which would be nice to have!




private void UpdateInput()
{
DXInput.KeyboardState state = inputDevice.GetCurrentKeyboardState();
if (state[DXInput.Key.Return])
{
Player.SetMapPosition(7,7);
}



It currently takes the actors top left as the area to move but really we want the area just above the feet. So let's look at our new function in Actor again.





public void SetMapPosition(int tileX, int tileY)
{
try
{

int tileIndex = tileX + (map.rowLength * tileY);
position = map.tiles[tileIndex].GetDXPosition();

position.Y -= boundary[0].Y + 0.10f;
//should take in Actor height.
}
catch(Exception e)
{
Console.WriteLine("SetMapPosition produced out of bounds");
}
}



We use the boundary to get the top part of the feet but because the boundary is rather tight this doesn't place the feet into the center of the tile :( So I add a little to pad it out. This is rather hacky. (A better soultion would be to place from the middle of the tile.



Little bit of neating




In playing GameState let's use this new function to set the position of our actors - of course Rock will be incorrect because it's boundary is wrong.





Player.SetMapPosition(4,4);
NPC.SetMapPosition(4,6);
rock.SetMapPosition(5,5);


map.AddMapObject(Player);
map.AddMapObject(NPC);
map.AddMapObject(rock);
}



Okay to get these Actors to sit on tiles and move with the tiles when we move the map we need to do two things:


  • Have an tile offset, distance from top corner

  • New render method that uses X,Y in GameObject





I think it be time to stratify the class hierarchy a little more. First came GameObjects, which where a huge success. Then MapObjects, also highly useful and now we may have TileObjects! A skeleton may look like below:




namespace Animate
{

public abstract class TileObject : MapObject
{
PointF offset = new PointF(0,0);

public TileObject(Map mapIn) : base(mapIn)
{

}
}
}



And what a nice skeleton class it is too. We're going to do nothing with it for the moment though. First to GameObject and let's add another Render function.




public abstract void Render(Matrix QuadMatrix, Device device);



Then of course this needs to be implemented in both Tile and Actor. We'll make a skeleton function in both of them for now:




public override void Render(Matrix QuadMatrix, Device device)
{

}



Once these are in place our code will compile again.




Let's start tinkering with the tile one.




public override void Render(float X, float Y, Device device)
{
device.SetStreamSource( 0, sprite.GetVB(), 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

Matrix QuadMatrix = new Matrix();
QuadMatrix = Matrix.Identity;

QuadMatrix.Translate(X,Y,0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, sprite.GetTexture());
device.DrawPrimitives(PrimitiveType.TriangleFan,vbOffset, 2);

foreach(GameObject g in Occupants)
g.Render(X,Y, device);
}



Things are starting to look good! Of course we also need to tinker with Maps render loop and remove a few lines of code from there.




public void Render(float x, float y, Device device)
{
float xLimit = x;

for(int i = 0; i < tileTotal; i++)
{
Tile t = (Tile) tiles[i];
t.Render(x,y, device);

if(((i+1) % rowLength) == 0)
{
x = xLimit;
y -= 0.25f;
}
else
{
x += 0.25f;
}
}
}



We're calling SetStreamSource a lot more now that upsets me - but it's the simplest way to support a large number of texture sets. I'm sure there's a clever way where the tiles are rendered in "chunks" according to which texture set they use. If speed becomes a problem then this will be an area worth examining for bottle necks.




So now each tile render will also render anything that's occupying it. At the moment though nothing is occupying any of the tiles and Actors render function is only a skeleton. Let's just throw in some of the code we used for the tile rendering.




public override void Render(float X, float Y, Device device)
{
device.SetStreamSource( 0, sprite.GetVB(), 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

Matrix QuadMatrix = new Matrix();
QuadMatrix = Matrix.Identity;

QuadMatrix.Translate(X,Y,0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, sprite.GetTexture());
device.DrawPrimitives(PrimitiveType.TriangleFan,vbOffset, 2);
}



Now let's change rock so that's it's not a map object but it's an occupier of a tile.




map.AddMapObject(Player);
map.AddMapObject(NPC);
map.tiles[22].AddOccupant(rock);



Cool that works. The only problem is it won't work so well for our Actors, as the move pixel by pixel they change the tile that they're on! Should we even have them as tile occupiers? Maybe not for now. Of course Rock isn't perfect because it's a blocking Actor on the tile, the tile turns to blocking as but this gives a the rock at much larger blocking range. Maybe this is for the best maybe not. It's something we can leave for now and just have tile blocking. Let's change the generate plain function:




public void GeneratePlain()
{
//TextureManager.BufferTextures("Plain");
for(int i = 0; i < area; i++)
{
tiles[i] = new Tile("plains", this);
tiles[i].setFlavour("Grass");
}


//((Tile)tiles[22]).setFlavour("Stone");
//((Tile)tiles[22]).block = true;
}


The rock in action!

So we have a nice rock that's on tile and it blocks our passage. Seems pretty good to me.



The actor class though is overkill for the rock, it's totally static. What we'd rather have is a scenery class. Something bare bones that can maybe have a script attached if necessary. I can see three classes currently for the map: Actors, Scenery, Items. Very simple.



Quick Aside




Currently we're tinkering about and seeing that perhaps the code would be better done in different ways. It works for now and I want to only change previous code gradually as more and more gets done. Anywho while experiment I noticed a bug in the Actor class, namely the intersect method the code should be as following:




public override bool Intersect(PointF p)
{
PointF pt0 = new PointF(position.X + boundary[0].X, position.Y + boundary[0].Y);
PointF pt1 = new PointF(position.X + boundary[1].X, position.Y + boundary[1].Y);
PointF pt2 = new PointF(position.X + boundary[2].X, position.Y + boundary[2].Y);
PointF pt3 = new PointF(position.X + boundary[3].X, position.Y + boundary[3].Y);

bool inX = (p.X >= pt0.X && p.X <= pt2.X);
bool inY = (p.Y <= pt0.Y && p.Y >= pt1.Y);

return inX && inY;
}



If you where on the exact same y you could walk one Actor through another - oops. Good job I caught it now!



Scenery Class




We want something pretty stripped down for this.




Yes animations would be nice but we're not putting any of that code in for now. All we want is a sprite and a render method. That will do.



The GameObject




We want the gameobject to be as general as possible. So it's worth reviewing at this time where we're thinking about extending its lineage.




public abstract class GameObject
{
public abstract ISprite GetSprite();
protected Matrix QuadMatrix = Matrix.Identity;



Two things here, first we should just assign this to the Identity rather than having it empty. Second in the tile class and possible Actor class we use a variable of the same name - we can get rid of that!





//Tile Class : Render(x,y,device) method : Removing unecessary code
public override void Render(float X, float Y, Device device)
{
device.SetStreamSource( 0, sprite.GetVB(), 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

//Matrix QuadMatrix = new Matrix(); /**POOF**/
//QuadMatrix = Matrix.Identity; /**POOF**/

QuadMatrix.Translate(X,Y,0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, sprite.GetTexture());
device.DrawPrimitives(PrimitiveType.TriangleFan,vbOffset, 2);

foreach(GameObject g in Occupants)
g.Render(X,Y, device);
}



and as Actor is basically a copy we can do the same removal.




public override void Render(float X, float Y, Device device)
{
device.SetStreamSource( 0, sprite.GetVB(), 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

//Matrix QuadMatrix = new Matrix(); /**POOF**/
//QuadMatrix = Matrix.Identity; /**POOF**/

QuadMatrix.Translate(X,Y,0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, sprite.GetTexture());
device.DrawPrimitives(PrimitiveType.TriangleFan,vbOffset, 2);
}



Okay let's continue looking through GameObject.




protected int vbOffset = 0;



Let's close down that access. Next ...




public abstract PointF GetDXPosition();


public abstract bool Intersect(PointF p);
public bool Intersect(float X, float Y)
{
return Intersect(new PointF(X,Y));
}



Okay every GameObject should have a DX position. And intersect is very useful, say to detect if the mouse is over any of our game objects! So we'll keep that in but let's get rid of the code and have the children implement both of these. That way its more consitent and less clutter.




public abstract bool Intersect(PointF p);
public abstract bool Intersect(float X, float Y);



and in Actor we have:




public override bool Intersect(PointF p)
{
return Intersect(p.X,p.Y);
}

public override bool Intersect(float X, float Y)
{
PointF pt0 = new PointF(position.X + boundary[0].X, position.Y + boundary[0].Y);
PointF pt1 = new PointF(position.X + boundary[1].X, position.Y + boundary[1].Y);
PointF pt2 = new PointF(position.X + boundary[2].X, position.Y + boundary[2].Y);
PointF pt3 = new PointF(position.X + boundary[3].X, position.Y + boundary[3].Y);

bool inX = (X >= pt0.X && X <= pt2.X);
bool inY = (Y <= pt0.Y && Y >= pt1.Y);

return inX && inY;
}



and in tile something similar:




public override bool Intersect(float X, float Y)
{
return (this.Equals(map.tiles[map.GetTileIndex(X,Y)]));
}


public override bool Intersect(System.Drawing.PointF p)
{
return Intersect(p.X,p.Y);
}


Let us continue to look at GameObject.





public GameObject()
{
}



Constructor that does nothing - don't really need that so remove it.



Then:



public abstract void Render(float X, float Y, Device device);



Our latest addition very good!




and finally:




public void Render(Device device)
{
ISprite s = GetSprite();

device.SetStreamSource(0,s.GetVB() ,0);
device.RenderState.AlphaTestEnable = true;
device.RenderState.AlphaFunction = Compare.NotEqual;
device.SetTexture(0, s.GetTexture());
QuadMatrix.Translate(GetDXPosition().X,GetDXPosition().Y, 0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.DrawPrimitives(PrimitiveType.TriangleFan, vbOffset, 2);
}



This is the only function we have left, we may as well get rid of it and bump it down. Just to make GameObject lighter. MapObject can take care of it for now. So in goes:




public abstract void Render(Device device);



and into MapObject we put:




public override void Render(Device device)
{
ISprite s = GetSprite();

device.SetStreamSource(0,s.GetVB() ,0);
device.RenderState.AlphaTestEnable = true;
device.RenderState.AlphaFunction = Compare.NotEqual;
device.SetTexture(0, s.GetTexture());
QuadMatrix.Translate(GetDXPosition().X,GetDXPosition().Y, 0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.DrawPrimitives(PrimitiveType.TriangleFan, vbOffset, 2);
}



This makes GameObject almost an interface, which is really what I wanted it to be in the first place. Something a bit lightweight that everything in the game will have.




The only thing stopping it being an interface are the two variables that are left:




vbOffset
QuadMatrix



vbOffset is going to be used by all GameObjects so I don't really want to get rid of it. It's used internally as well so I don't want to replace it the a get/setter. Maybe I could put it in MapClass. Really it should be bound up in Animation but while Animation is much better than it was the final system isn't finialized yet. For now it stays here!




QuadMatrix can go down to map object, especially as the render functions are now defferred.




//MapObject Class
protected Matrix QuadMatrix = Matrix.Identity;



So the only thing stopping GameObject becoming a funky interface is the development of the animation system. Well it will still be a while until we're prepared to tackle that one. But we can lay the ground work ... to lay the ground work requires a lot of good shuffling and writing without anything new happening :(



Let the crying and wailing begin ... also que poor thinking and bad programming!




Okay we have these Actors but they all have to conform to the same animations and rates of movement! Same speeds etc. We don't want this we want the animations and possible "states" to be all different. Somethings just want to flash. Some things want complicated movements. And that's cool we want that to be possible. So first we'll hard code different types of modules yet leave it open if in the future we wish to have dynamic info or loading from files.




We need to have a close look at Actor and see how we can rip a module out of there!




In Actor the module we're thinking about comprises of the following:



Modulizing motion


States are hard coded in a enumeration, they include Idle | Move_Left | Move_Up ... . But imagine a machine that spins the only states it might have are Spin | Broken. Actor isn't helpful here it's hard coded for something that's going to be walking about. We might want something that bobs up and down.




So we can't have a hard coded enumeration, we need something more flexible. In this case we're going to use strings. So we'll have string State and we'll say if State = "Spin" then advance the spin animation. If we want a bit of a bob we might say in the move function "if State == "Spin"
then move up a bit". We should be free to do what we like for each type of Actor we produce.




So we have movement, animation and states all in one place. This a good thing and we can call it the Motion module. So we'll do the clever trick we've done before we'll have IMotion that exposes a few basic functions to the Actor. Then we'll have a few detailed implementations of IMotion and these will be fairly free in their implementation - they might load the data from a file for instance or be hard coded for simple human movement - it's pretty flexible and should be more than powerful enough for our needs.



Defining IMotion




using System;
using System.Drawing;

namespace Animate
{
public interface IMotion
{
void animateUpdate();
void positionUpdate();
void setState(string state);

float X
{
get;
set;
}

float Y
{
get;
set;
}
}
}



The way this works we'll have the Actor keep the blocking code. It will check the new position motion suggests and then see if it's blocked or not, if not it will accept it, otherwise it resets it. This is good because being blocked is not necessarily anything to do with basic motion. Also it limits the amount of stuff we need to pass into the particular motion class we intend to use.




In Actor we can now add:




public class Actor : MapObject
{
protected PointF position = new PointF(0,0);
protected PointF[] boundary;
protected ISprite sprite;

IMotion motion;


CanMove Function




Now we'll add a simple function, that is basically just copied from the end of the Move function.




private bool CanMove(float newX, float newY)
{
if( map.CanMove(newX + boundary[0].X, newY + boundary[0].Y)
& map.CanMove(newX + boundary[1].X, newY + boundary[1].Y)
& map.CanMove(newX + boundary[2].X, newY + boundary[2].Y)
& map.CanMove(newX + boundary[3].X, newY + boundary[3].Y))
{
if(!map.movementObstructed(boundary,this, new PointF(newX,newY)))
{
return true;
}
}

return false;
}



With that in we can now simplify the Move procedure itself - even though we ultimately intend to delete it!




case States.standing:
{
return;
}
}

//Check the bounding box around the feet
if(CanMove(newX,newY))
{
position.Y = newY;
position.X = newX;
}



}


Implement an IMotion object




Okay the IMotion object we're going to create is not going to big or particularly clever. In fact it's mainly going to be ripped off from parts of Actor - but at this point it really doesn't matter because we're crossing the threshold to modularity.




Let's take a look at the entire beast!




using System;
using System.Drawing;

namespace Animate
{
public class PlayerMotion : IMotion
{

protected ISprite sprite;
protected string currentState = "idle";
protected PointF position = new PointF(0,0);
protected int idle = 0; //Offset for VB idle look
protected int vbOffset;

public PlayerMotion(ISprite s, int idleOffset)
{
sprite = s;
idle = idleOffset;
}


#region IMotion Members

public void positionUpdate()
{
switch(currentState)
{
case "down":
{
position.Y -= 0.045f;
}break;

case "right":
{
position.X += 0.04f;
}break;

case "left":
{
position.X -= 0.04f;
}break;

case "up":
{
position.Y += 0.045f;
}break;

case "standing":
{
return;
}
}
}

public void animateUpdate()
{
switch(currentState)
{
case "standing":
{
//Check previous state and get last facing direction
vbOffset = idle;
}break;

case "down":
{
vbOffset =
sprite.GetAnimation("walking down").Advance();
}break;

case "right":
{
vbOffset =
sprite.GetAnimation("walking right").Advance();
}break;

case "left":
{
vbOffset =
sprite.GetAnimation("walking left").Advance();

}break;
}
}

public void setState(string state)
{
currentState = state;
}

public float X
{
get
{
return position.X;
}

set
{
position.X = value;
}
}

public float Y
{
get
{
return position.Y;
}

set
{
position.Y = value;
}
}

#endregion
}
}




What's worth noting above is the change to strings. Also the lack of blocking code ... instead of blocking code we just update the Position variable. The position variable is accessed via the IMotion interface.



Notes




Okay so we're calling these things all the time and we're comparing all these strings, it maybe better to make things little more efficent but only if it becomes an issue! first make it work (and I'm talking 'bout the entire game) then make it fast!




So now we have a funky ass motion module all ready to be slotted in! We'll do the slotting in slowly, slowly is always best.



Slowly slotting it in ;)




First we need to pull Render(Device device) down from Map. This is for basic consitency but this is also where we're going to be putting out blocking code. It's best to get it out the way first.



Pulling down Render




Put this function into both Tile and Actor and remove it from MapObject!





public override void Render(Device device)
{

ISprite s = GetSprite();

device.SetStreamSource(0,s.GetVB() ,0);
device.RenderState.AlphaTestEnable = true;
device.RenderState.AlphaFunction = Compare.NotEqual;
device.SetTexture(0, s.GetTexture());
QuadMatrix.Translate(GetDXPosition().X,GetDXPosition().Y, 0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.DrawPrimitives(PrimitiveType.TriangleFan, vbOffset, 2);
}



Adding to Actor's Constructor




For now we're putting Motion inside the constructor, of course in the future there should be some choice in which motion object you're actually plugging in.




public Actor(Device device, Map mapIn, string TexSet)
: base(mapIn)
{

motion = new PlayerMotion(
TextureManager.BufferTextureSet(TexSet),
0);




There are two constructors. We can cut some code out of the second constructor by doing the following:




public Actor(Device device, Map mapIn, string TexSet, string Idle)
: this(device, mapIn, TexSet)
{
idle = sprite.GetOffset(Idle);
}




So it calls the other constructor and then does it's jazz. Nice simple and tight. So we're creating a nice new Motion object.



Throwaway function



The next thing we need to do is utilize it and for this we're first going to overload another function in Actor:




public void SetMovementType(string move)
{
motion.setState(move);
}



So this is a total throwaway function that's not really doing anything but for now it's fine.



PlayingGameState Update!




We need to have the key's being pressed send the correct signals to our Actor. Signals in the form of strings!




Slowly slowly at first so the old state update remains as well!




private void UpdateInput()
{
DXInput.KeyboardState state = inputDevice.GetCurrentKeyboardState();
if (state[DXInput.Key.Return])
{
Player.SetMapPosition(0,0);
}

if(state[DXInput.Key.LeftAlt] && state[DXInput.Key.S])
{
map.Write(@"c:\MapFile.txt");
}

if(state[DXInput.Key.LeftAlt] && state[DXInput.Key.L])
{
map.Read(@"c:\MapFile.txt");
}

if(state[DXInput.Key.RightArrow])
{
Player.SetMovementType(Actor.States.right);
Player.SetMovementType("right");
}
else if(state[DXInput.Key.LeftArrow])
{
Player.SetMovementType(Actor.States.left);
Player.SetMovementType("left");
}
else if(state[DXInput.Key.UpArrow])
{
Player.SetMovementType(Actor.States.up);
Player.SetMovementType("up");
}
else if(state[DXInput.Key.DownArrow])
{
Player.SetMovementType(Actor.States.down);
Player.SetMovementType("down");
}
else
{
Player.SetMovementType(Actor.States.standing);
Player.SetMovementType("standing");
}



Now of course we need to make sure that we take the new position given to us by motion. We check we can move there. If we can the update the Actors position to that one.



Adding the Timing Hook




So back to Actor's Constructor and let's do a little fiddling.




We're going to remove Actors local Animate function as a time event and in it's sted we'll place our motion one.





//Clock.AddTimeEvent(
// new TimeEvent(45, new TimeEvent.Call(Move)));

Clock.AddTimeEvent(
new TimeEvent(45, new TimeEvent.Call(motion.positionUpdate)));




Now motion is being periodically called to produce a new position. Of course we need to act on this information, so to the Render functions - which we'll be able to shorten in the same way we managed with the constructors.



GameObject from Render to Process




public override void Render(float X, float Y, Device device)
{


device.SetStreamSource( 0, sprite.GetVB(), 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

device.RenderState.AlphaTestEnable = true;
device.RenderState.AlphaFunction = Compare.NotEqual;

QuadMatrix.Translate(X,Y,0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, sprite.GetTexture());
device.DrawPrimitives(PrimitiveType.TriangleFan,vbOffset, 2);
}

public override void Render(Device device)
{

if(CanMove(motion.X,motion.Y))
{
position.X = motion.X;
position.Y = motion.Y;
}
else
{
motion.X = position.X;
motion.Y = position.Y;
}

Render(device, GetDXPosition().X, GetDXPosition().Y);
}



This seems a bit odd no? The first function never checks what motions position is! This means motion will build an increasingly inaccurate position. This is okay at the moment because we're using the simpler render device so we always get the up to date motion info - or reset it if it's going to an unmovable area. Well if we move the Actor so he's a tile object we would use the first Render function probably with an offset value - so not to worry for now, a potential piece of code has yet to appear so it looks like an area for possible bugs. Luckily at note like this will remind us what was going on.





Now upon running you'll be in control! Using motions abilities.



Cleaning out the Movement stuff. So we kill actors Move procedure and give ourselves a bit more space. We can also remove the associated TimerEvent in the constructor if we have not already done so.



Honing the Animation




IMotion must communicate Animation changes to Actor. This information will result in index to an offset buffer and texture.




Sprite generally encapsulates information about the current animation of the GameObject. We currently have two sprite references! One is in Actor and one is Motion.




First!




public interface IMotion
{
void animateUpdate();
void positionUpdate();
void setState(string state);

float X
{
get;
set;
}

float Y
{
get;
set;
}

int offset
{
get;
set;
}

}



That's right we're moving the offset into IMotion and away from animation.




So we need to update player motion let's go:




public class PlayerMotion : IMotion
{

protected ISprite sprite;
protected string currentState = "idle";
protected PointF position = new PointF(0,0);
protected int idle = 0; //Offset for VB idle look
protected int vbOffset;

public PlayerMotion(ISprite s, int idleOffset)
{
sprite = s;
idle = idleOffset;
vbOffset = idle;
}



and the all important accessor functions!




public int offset
{
get
{
return this.vbOffset;
}

set
{
vbOffset = value;
}
}



Now let's start storing stuff in that varaible!




public void animateUpdate()
{
switch(currentState)
{
case "standing":
{
vbOffset =
idle;
}break;

case "down":
{
vbOffset =
sprite.GetAnimation("walking down").Advance();
}break;

case "right":
{
vbOffset =
sprite.GetAnimation("walking right").Advance();
}break;

case "left":
{
vbOffset =
sprite.GetAnimation("walking left").Advance();

}break;
}
}



Okay looks good to me, lets hook it into Actor.




First Actor's render function:




public override void Render(float X, float Y, Device device)
{
device.SetStreamSource( 0, sprite.GetVB(), 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

device.RenderState.AlphaTestEnable = true;
device.RenderState.AlphaFunction = Compare.NotEqual;

QuadMatrix.Translate(X,Y,0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, sprite.GetTexture());
device.DrawPrimitives(PrimitiveType.TriangleFan,motion.offset, 2);
}



Then if you haven't already done so (I can't remember cos I've made so many frickin changes to get something that works and isn't butt ugly) edit Actors constructor like so:




public Actor(Device device, Map mapIn, string TexSet)
: base(mapIn)
{

motion = new PlayerMotion(
TextureManager.BufferTextureSet(TexSet),
0);

blocking = true;

sprite = TextureManager.BufferTextureSet(TexSet);


//Clock.AddTimeEvent(
// new TimeEvent(95, new TimeEvent.Call(Animate)));

Clock.AddTimeEvent(
new TimeEvent(95, new TimeEvent.Call(motion.animateUpdate)));





Run this it should work, if it doesn't it's because I've missed out a step, please treat this as a quiz and see if you can fix it for yourself :D



Cleaning Up Actor




Now actor has junk functions that IMotion objects take care of.




So kill off the commented out TimerEvents in the constructor.




And while we're killing things let's move on to a few function I don't like the look of.




private void Animate()



Goodbye!




Let's go to PlayingGameState and remove the input duality we've been suffering from!




That leaves us with:




if(state[DXInput.Key.RightArrow])
{
Player.SetMovementType("right");
}
else if(state[DXInput.Key.LeftArrow])
{
Player.SetMovementType("left");
}
else if(state[DXInput.Key.UpArrow])
{
Player.SetMovementType("up");
}
else if(state[DXInput.Key.DownArrow])
{
Player.SetMovementType("down");
}
else
{
Player.SetMovementType("standing");
}



So let's get rid of the old SetMovementType




public void SetMovementType(States movementInfo)



We can get rid of all those -oh so- inflexible states too. Bye bye state enumeration.




public enum States



Once they've bitten the dust may as well throw out currentState as well. And that's it for our tidying. Of course there are a few lurking problems (where has our beautiful rock gone?!)



A few notes on IMotion




It needs a little improving. We shouldn't have two references to Sprite in Actor and IMotion. The only way I can think of resolving this is have a get texture function, which I'd rather not do for now. Also IMotion should maybe take care of setting up the Timers.




Actors should load in IMotions.



Where is the rock?




It's gone because in motion we default idle to zero and rock is a 8 or 12 or something. This is a problem due to class intialization.




First things first let's actually have the IMotion implementation take care of the timers!




public PlayerMotion(ISprite s, int idleOffset)
{
sprite = s;
idle = idleOffset;
vbOffset = idle;

Clock.AddTimeEvent(
new TimeEvent(95, new TimeEvent.Call(animateUpdate)));

Clock.AddTimeEvent(
new TimeEvent(45, new TimeEvent.Call(positionUpdate)));
}



Of course remove these from Actor!




Rearrange code so that the sprite is created first.




public Actor(Device device, Map mapIn, string TexSet)
: base(mapIn)
{

sprite = TextureManager.BufferTextureSet(TexSet);

motion = new PlayerMotion(
sprite,
0);



In the second constructor we need to add another motion constructor - yes I know we are creating the object twice - but it's something we should be passing in ready made anyway ... I think.




public Actor(Device device, Map mapIn, string TexSet, string Idle)
: this(device, mapIn, TexSet)
{
idle = sprite.GetOffset(Idle);
motion = new PlayerMotion(
sprite,
idle);

}



And wonderfully the rock returns! Glory Glorly Glorly.



Final Tidying Up




Yes I lied about the bush, we're sticking with the rocks - it's been hard enough as it is :D




So the rock being asked to Update and it has all these animation timers - but it's just freaking rock! It's not going to do crap all ever! (maybe someday we could hit it with a hammer or it could turn into a rock creature, put this is something that can be done without checking if each rock needs to do something each turn!)



New Class NoMotion




We've had player motion - now comes no motion. Yes it is that amazing. You'll also see the awesome power of the IMotion interface and maybe be slightly less inclined to think that I'm batshit-crazy. So hold on to your keyboard tightly here we go.




New class called "NoMotion" jump to to it, Suzi.




(Just like no-e-motion, like robots, like our rock . . . like the future!)




This is the whole class:





public class NoMotion : IMotion
{

int idle;
public NoMotion(int idleOffset)
{
idle = idleOffset;
}
#region IMotion Members

public void animateUpdate()
{
// Nothing as there is "NO MOTION"
}

public void positionUpdate()
{
// Nothing as there is "NO MOTION"
}

public void setState(string state)
{
// NOMOTION NO STATES!
}

public float X
{
get
{
return 0;
}
set
{
// NOMOTION nothing to update
}
}

public float Y
{
get
{
return 0;
}
set
{
// NOMOTION nothing to update
}
}

public int offset
{
get
{
return idle;
}
set
{
idle = value;
}
}

#endregion
}
}



This seems a base class that may be useful especially for testing and building phases such as we're in. In the game most "Actors" will be motive to some degree and so I don't know how often this bare bones class will be used - notice too how our project sprawls we need to get in there with some namespace organization (next chapter!)




To get this bad boy chugging along we need the Actor constructor to take in IMotion objects, rather that create them - so let's do that now!




If we do this it will no longer need the "idle" input so we can replace that! Also a quick look at the Actor constructors and it's apparent that we're passing in Device for no good reason at all so before anything else let's cut that out:




public Actor(Map mapIn, string TexSet, string Idle)
: this(mapIn, TexSet)
{
idle = sprite.GetOffset(Idle);
motion = new PlayerMotion(
sprite,
idle);

}

public Actor(Map mapIn, string TexSet)
: base(mapIn)
{

sprite = TextureManager.BufferTextureSet(TexSet);

motion = new PlayerMotion(
sprite,
0);

blocking = true;

boundary = new PointF[4] {new PointF(+0.04f, -0.41f),
new PointF(+0.04f, -0.49f),
new PointF(+0.19f, -0.41f),
new PointF(+0.19f, -0.49f)};
}



We must also update these changes in PlayingGameState:





{
device = d;


gameStateManager = g;
inputDevice = dInput;

map = new Map(8,8);

Player = new Actor(map, "player");
NPC = new Actor(map, "NPC");

Actor rock = new Actor(map, "plains", "Rock");


Player.SetMapPosition(4,4);
NPC.SetMapPosition(4,6);
rock.SetMapPosition(5,5);

map.AddMapObject(Player);
map.AddMapObject(NPC);
map.tiles[22].AddOccupant(rock);
}


So that's a little neater - okay next we comment out the first smaller constructor and add Imotion m as an argument to the second! You should have something like below:





/**
public Actor(Map mapIn, string TexSet, string Idle)
: this(mapIn, TexSet)
{
idle = sprite.GetOffset(Idle);
motion = new PlayerMotion(
sprite,
idle);

}
**/

public Actor(Map mapIn, string TexSet, IMotion m)
: base(mapIn)
{

sprite = TextureManager.BufferTextureSet(TexSet);

//motion = new PlayerMotion(
// sprite,
// 0);

motion = m;



So now we go to PlayingGameState, and here things get a bit messy. Each Actor needs (generally) and unique IMotion implementation as well as the TexSet name! We could include IMotion all in TexSet. For instance have a different TeXSet address each Actor but for now we're not going! All we do is create the correct sprite objects and pass in the correct stuff.




public PlayingGameState(Device d, GameStateManager g, DXInput.Device dInput)
{
device = d;


gameStateManager = g;
inputDevice = dInput;

map = new Map(8,8);

ISprite s = TextureManager.BufferTextureSet("player");

Player = new Actor(map, "player", new PlayerMotion(s,0));

s = TextureManager.BufferTextureSet("NPC");

NPC = new Actor(map, "NPC", new PlayerMotion(s,0));

s = TextureManager.BufferTextureSet("plains");

Actor rock = new Actor(map, "plains",
new NoMotion(s.GetOffset("Rock")));

Player.SetMapPosition(4,4);
NPC.SetMapPosition(4,6);
rock.SetMapPosition(5,5);

map.AddMapObject(Player);
map.AddMapObject(NPC);
map.tiles[22].AddOccupant(rock);
}



Now everything should compile and rock is just a simple plain ol' rock. It works just as it should but ... but but but but it still has a secret lurking away. It has a "boundary" and this boundary is just as big as would be expected by a player character! That should not be the case! But for now we can deal everything is happy and shiny and while this lesson works let's create a new project and scrape over our code landscape again.


Review




It's nice to see how light the classes such as PlayingGameState are, it makes them pretty easy to follow.




A lot of code introduced this time needs a bit more care and bit more molding. But it works and its what we want. The problems where generally that this isn't something I've tackled before and I definetly blundered into it. It came out okay. The codes feeling more flexible, something that will, once neaten, be able to support a game.



We're getting pretty near one of the major way points. In fact if we reach one I may end these tutorials for the public there - as I don't want my revolutionary game becoming immediately mimiced around the interweb!




Next time we'll think about that scenery class once more. Try to remove anything redundant, consolidate some code that needs consolidating (I'm taking about Slices and TextureSet here). Add some NameSpaces and try to be as cool and groovy as possible.




Then making sure that map objects more relative to the map! I'm looking at you "Player" and NPC". After this there are many ways to go and I might develop both in parrallel because I'm mad.