Programming Tutorial Based on Sample C4 Code

From C4 Engine Wiki

Jump to: navigation, search

This is intended to help people who are relatively new to C++, but who are eager to understand what's going on in the application and engine code. This first set of explanations is based on code provided in SimpleBall.cpp. This is a first draft and feedback is very welcome.

The explanations are marked as follows:

 
/*C++ 
<code snippet explanation> 
*/

Assumptions:

  • It is assumed that the reader has some programming experience and that is not totally unfamiliar with C++ structures and syntax.

To Do:

  • Add introduction
  • Check SimpleChar.cpp for other code snippets that havent been explained here
  • Check the engine code for for snippets (and C++ usage) that hasnt been addressed here

--Jimbobjames 12 February 2008

/*C++
#include is a "preprocessor directive" - the preprocessor adds the contents of the file "SimpleBall.h" directly into this 
file, before this file is actually compiled to an object file.
*/
#include "SimpleBall.h"

/*C++
To understand namespaces it is important to first understand scope, which refers to regions where a particular name or 
identifier is visible and can be used. There are several types of scope including:

*Namespace Scope - declared inside a namespace
*Block Scope - declared inside a block (with curly braces {}) - also referred to as "local" scope
*Class Scope - declared within a class (and is not in block scope) 
*File Scope - none of the above, and if not static then it is global and can be accessed from anywhere.

Two different programmers on the same project might use the same variable name (say "var"). 
Namespaces help us distinguish between the two. If one programmer works in namespace1 and the other in namespace2
then we can access the variables safely with namespace1::var and namespace2::var respectively.
By saying "using namespace C4", we don't have to write "C4::" in front of everything that follows.
Note that "::" is called the "scope operator".
*/
using namespace C4;


// This is the definition of the pointer to the Game class singleton.
// It should be initialized to nullptr, and its value will be set by
// the Game class constructor.

/*C++
"Game" is a class which is declared and defined in SimpleBall.h
TheGame (or more precisely C4::theGame) is defined here to be a pointer to a Game object. 
It is initialized with the value nullptr, the NULL pointer.
*/

Game *C4::TheGame = nullptr;

/*C++
ConstructApplication is a function. It returns a pointer to a C4::Application object. 
You may say, "No!, it returns a "Game" object!" - But below you will see that Game is a class 
that inherits (or derives) from the Application class - "Game is an Application".
*/
C4::Application *ConstructApplication(void)
{
	// This function should simply return a pointer to a new instance of
	// the Application class. Normally, the application/game module will
	// define a subclass of the Application class (in this case, the
	// Game class) and return a pointer to a new instance of that type.
	
	// This function is called exactly one time right after the
	// application/game module DLL is loaded by the engine. The returned
	// class is destroyed via the virtual destructor of the Application
	// class right before the application/game module DLL is unloaded.
	
/*C++
The function returns a new Game object (Remember that "Game" is derived from "Application")
Note that "new Game" calls the default constructor for Game. You don't need to add brackets as in "new Game()".
*/
	return (new Game);
}

/*C++
This is a constructor for the class "MovementAction", which is declared in SimpleBall.h
The class derives from the C4 class "Action" - thats why the line ends with " : Action(type)"
This constructor takes two arguments, called type and flag, both unsigned longs.
When a new object of type MovementAction is created, this constructor is called

Below is an example of a call to this constructor: 

forwardAction = new MovementAction(kActionForward, kSpectatorMoveForward);

Note that ": Action(type)" means that the first parameter, "type", (which in this example is "kActionForward"), 
is then also used when calling the constructor if the base class, Action.

In this example, the MovementAction member variable "movementFlag", is set to the value of "flag", which in this 
case is "kSpectatorMoveForward".

*/
MovementAction::MovementAction(unsigned long type, unsigned long flag) : Action(type)
{
	// Each instance of the MovementAction class represents a movement
	// in a single direction, as indicated by the flag parameter.
	// All of the MovementAction instances are constructed in the
	// Game class constructor.
	
	movementFlag = flag;
}

/*C++
Destructor for MovementAction - a destructor gives the programmer a chance to clean up before an object is destroyed.
Destructors can be called explicitly by the programmer (using delete[]) but they are also automatically called when 
an object goes out of scope. By "clean up", I mean release memory or close any open network or file resources.
*/
MovementAction::~MovementAction()
{
}

/*C++
"Begin" is a public method of the class MovementAction.
It returns nothing (thats why it starts with the word "void") and it takes no arguments (thats why the word "void" is in 
brackets).
*/
void MovementAction::Begin(void)
{
	// This function is called when the input control associated with this
	// particular action is activated (e.g., a key was pressed).
	
/*C++
This next line is interesting. Lets take it in pieces.

GameWorld *world = static_cast<GameWorld *>(TheWorldMgr->GetWorld());

First of all, "world" is defined to be a pointer to a "C4::GameWorld" object.

Now, at the end of this line of code we have "TheWorldMgr->GetWorld()". "TheWorldMgr" is defined and declared - in an 
engine file called C4World.h - to be a pointer to a "C4::WorldManager" object. And "GetWorld()" returns a "C4::World" 
object - the current world in fact.

But why the arrow (->) between TheWorldMgr and GetWorld()? That arrow is used to access data and methods of a pointer 
to an object (in this case "TheWorldMgr"). 

If "TheWorldMgr" was simply a "C4::WorldManager" object - and not a pointer to a "C4::WorldManager" object - then you 
would have "TheWorldMgr.GetWorld()" instead of "TheWorldMgr->GetWorld()" to access the method GetWorld(). 

The "." and "->" are called member selection operators: the dot is used with objects and the arrow is used with pointers 
to objects.

Now, because TheWorldMgr->GetWorld() returns a "C4::World" and not a "C4::GameWorld" object, we need to use a cast to 
convert the type from a pointer to a C4::World to a pointer to a C4::GameWorld. The static_cast<> keyword is used for that, 
with the name of the target type inside the angle brackets.

*/

GameWorld *world = static_cast<GameWorld *>(TheWorldMgr->GetWorld());

/*C++
If the TheWorldMgr->GetWorld() method returned a nullptr, then this check would be false (and camera would not get defined).
*/
	if (world)
	{
		// If there's currently a world loaded, apply the movement to
		// the spectator camera.
		
/*C++

SpectatorCamera *camera = world->GetSpectatorCamera();

Here, camera is here defined to be a pointer to a SpectatorCamera object, the one which is returned by the call to 
GetSpectatorCamera(). 

Again the arrow is used to access the method GetSpectatorCamera() from the object world, because world is a pointer to a 
GameWorld - and not an actual Gameworld itself.

GetSpectatorCamera() is declared in SimpleBall.h - it returns a reference to a SpectatorCamera - "return (&spectatorCamera);"

So, rewritten it would be: 

SpectatorCamera *camera = &GameWorld::spectatorCamera;

So, you would say "camera is a pointer to a SpectatorCamera object, the one who's address is &GameWorld::spectatorCamera. 
Or, you could say, "camera contains a reference to GameWorld::spectatorCamera, which is the spectatorCamera for this 
GameWorld".

When you see an "&" sign - think "address of" or "reference to". 

*/
		SpectatorCamera *camera = world->GetSpectatorCamera();

/*C++
OK, now you have the a pointer to the spectatorCamera, set some flags - the movementFlag and the flags returned by 
GetSpectatorFlags(), which are added together using the logical OR operation (denoted "|").

The flags are typically bits that you can "AND" or "OR" together - 

For Example,

bit 0, if set, has value 1; (00000001) -> 2 to the power of 0 = 1
bit 1, if set, has value 1: (00000010) -> 2 to the power of 1 = 2 
bit 2, if set, has value 4: (00000100) -> 2 to the power of 2 = 4 
bit 3, if set, has value 8: (00001000) -> 2 to the power of 3 = 8
and so on to
bit 7, if set, has value 128: (10000000) -> think 2 to the power of 7 = 128
So, bit1 OR bit3 (written bit1 | bit3) has value 1 + 8 = 9 and looks like 00001010

You typically use an "enum" to set the values of these flags so that you can associate meaningful names with these numbers.
e.g.
	enum
	{
		kSpectatorMoveForward		= 1 << 0,
		kSpectatorMoveBackward		= 1 << 1,
		kSpectatorMoveLeft		= 1 << 2,
		etc.
	}

	Here the "<<" is the shift operator and means for example that
	The number 1 corresponds to the binary 00000001
	kSpectatorMoveForward  = 00000001 = 1 //which is the number 1 with no bits shifted
	kSpectatorMoveBackward = 00000010 = 2 //1 with bit shifted 1 place to the left using the shift operator "<<"
	kSpectatorMoveLeft     = 00000100 = 4 //1 with bit shifted 2 places to the left using the shift operator "<<"

	The results is that the camera object will have the flags defined by "camera->GetSpectatorFlags() | movementFlag" 
	after the call to SetSpectatorFlags below:
*/
		camera->SetSpectatorFlags(camera->GetSpectatorFlags() | movementFlag);
	}
}

/*C++
Nothing new here - pointers, casting and member selection using "->"
*/

void MovementAction::End(void)
{
	// This function is called when the input control associated with this
	// particular action is deactivated (e.g., a key was released).
	
	GameWorld *world = static_cast<GameWorld *>(TheWorldMgr->GetWorld());
	if (world)
	{
		// If there's currently a world loaded, remove the movement from
		// the spectator camera.
		
		SpectatorCamera *camera = world->GetSpectatorCamera();

/*C++
Here the flags are "AND"ed together - as an example:

00101000 & 00001000 results in 00001000 - AND operation - only when the same bit is set in both does it appear in the 
result whereas
00100000 | 00001000 results in 00101000 - OR operation - all bits appear in the result

Note that the tilde before movementFlag (~movementFlag) means "not movementFlag" - the inverse of movementFlag

So say if movementFlag = 00010000 then ~movementFlag = 11101111

*/

		camera->SetSpectatorFlags(camera->GetSpectatorFlags() & ~movementFlag);
	}
}

/*C++
This is a constructor for the class BallController. Remember that a constructor always has the same name as the 
class itself. This constructor takes no arguments.

Now the class BallController inherits from another class called "ProjectileController", which is part of the 
engine code - see ProjectileController.h and ProjectileController.cpp.

By coding " : ProjectileController(kControllerBall, 0.1F, kProjectileStopped)" here, we are saying that the 
constructor of the parent (or base) class - ProjectileController - should be called first, and that the values 
kControllerBall, 0.1F and kProjectileStopped should be passed into that constructor.

There may be (and there are in fact) several constructors for the class ProjectileController. The one that is used 
will have 3 arguments (or parameters) with the same types used in this call - ControllerType, float and long (flags)

*/
BallController::BallController() : ProjectileController(kControllerBall, 0.1F, kProjectileStopped)
{
	// This constructor is only called when a new ball entity is created.
	// The radius of the ball projectile is 0.1 meters, and the kProjectileStopped
	// flag indicates that the ball is initially stopped. It will be put in
	// motion the first time the Move() function is called below.
	
	// The ProjectileController base class takes care of registering this
	// collidable object with the World Manager when it is preprocessed.
	// It also calculates the projectile's position delta in the
	// ProjectileController::Move() function.
}

//C++ Destructor
BallController::~BallController()
{
}

/*C++
This is a public method of the class BallController.

Its declaration in SimnplBall.h is:

static bool ValidNode(const Node *node);

The "const Node *node" in brackets must be read from right to left - "node is a pointer to a Node constant".
This means that the node object which is passed into this method, cannot be changed in the method. It is "read-only". 
If you try to change it (say for example with node->SetNodeName("MyNode");), then you will get a compiler error.

The return type here is bool (boolean, true or false). Below in the implementation we have: 

return (node->GetNodeType() == kNodeEntity);

Now "return (a == b)" returns true if a equals b and false if a is not equal to b.

Note that "return (a = b)" is different. In the brackets, you are assigning the value of b to a and if that is 
allowed and works, then a will equal b, and the result of the operation is true - even if a was'nt equal to b 
before the operation.

And finally, the static keyword in the declaration means that the method can be called EVEN IF you dont have an 
instance of the (in this case BallController) object. The way to call a static member function (if you dont have 
an object of the class) is using the class name and scope operator,

ClasName::staticMemberFunction() - in this case BallController::ValidNode(node)

Note that static member functions can only call static member functions and can only use static data members of
the class.

Finally, "kNodeEntity" is defined in C4Node.h - it is one of several types of node defined in an "enum" or enumeration.

An enumeration is used to give understandable and useful names to constant integer values.

eg.

enum fruit
{
	apple,
	orange,
	pear,
	banana,
	peach
};

would assign the integer 0 to apple, 1 to orange and so on up to 4 to peach. You would access apple by fruit::apple.

Note that you don't have to name the enum. You can also assign values inside the enum like this:

enum
{
	apple = 10,
	orange, //orange will automatically get the value 11
	pear = 30,
	banana = 30, // you can use the same value for two separate names - just the names must be unique
	peach = 40
};

In the engine, the enum names (like node type such as kNodeEntity) are set to 4 character literals like 'abcd'.
I am assuming these evaluate to integers.

Enums are preferred in C++ to using "#define" preprocessor directive, because they obey the C++ scope rules. 
You could also use "const int" or "static const int", but enum is the preferred method for assigning names to constant 
integers in C++.

*/

bool BallController::ValidNode(const Node *node)
{
	// This function is called by the engine to determine whether
	// this particular type of controller can control the particular
	// node passed in through the node parameter. This function should
	// return true if it can control the node, and otherwise it should
	// return false. In this case, the controller can only be applied
	// to entity nodes.
	
	return (node->GetNodeType() == kNodeEntity);
}

void BallController::Preprocess(void)
{
	// This function is called once before the target node is ever
	// rendered or moved. The base class Preprocess() function should
	// always be called first, and then the subclass can do whatever
	// preprocessing it needs to do. In this case, we don't do anything
	// extra, so this function is only overridden for illustrative purposes.
	
	ProjectileController::Preprocess();
}

void BallController::Move(void)
{
	// This function is called once per frame to allow the controller to
	// move its target node. The base class Move() function should always
	// be called.
	
	if (GetProjectileFlags() == kProjectileStopped)
	{
		// If the ball isn't moving yet, then initialize its state
		// and set it in motion.
		
		SetProjectileFlags(kProjectileGravity);
		SetInitialState(GetTargetNode()->GetWorldPosition(), Math::RandomUnitVector() * 0.001F);
	}
	
	ProjectileController::Move();
}

void BallController::Travel(void)
{
	// Once per frame, this function is called to actually move the ball or respond
	// to any collision that occurred. The base class Travel() function must be
	// called first because it calculates the velocity at the time of collision.
	
	ProjectileController::Travel();
	
	Node *node = GetTargetNode();
	CollisionState state = GetCollisionState();
	
	if (state == kCollisionStateNone)
	{
		// No collision occurred, so move the ball through the entire delta for this frame.
		node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * GetFinalPosition());
	}
	else
	{
		Vector3D	velocity;
		
		// Some kind of collision occurred, so grab some information about it.
		
		const CollisionData *data = GetCollisionData();
		const Vector3D& normal = data->normal;
		
		if (state == kCollisionStateGeometry)
		{
			// The ball hit environmental geometry. Bounce off of it by
			// reflecting the velocity vector about the contact normal.
			
			velocity = GetCollisionVelocity() - normal * (2.0F * (normal * GetCollisionVelocity()));
			
			// The following line just ensures that the ball has a significant
			// amount of upward velocity after it bounces off of the floor.
			
			if ((normal.z > 0.9F) && (velocity.z < 0.001F)) velocity.z = 0.001F;
		}
		else
		{
			// The ball hit another collider, which must be a ball in this application.
			// In this case we transfer the other ball's normal component of its velocity
			// to this ball. When the Travel() function is called for the other ball, it
			// will take the normal component of this ball's velocity and transfer it to itself.
			
			float v1 = GetCollisionVelocity() * normal;
			Vector3D perpComponent = GetCollisionVelocity() - normal * v1;
			float v2 = data->collider->GetColliderDelta() * normal / TheTimeMgr->GetFloatDeltaTime();
			
			velocity = normal * v2 + perpComponent;
			
			// Add a sound effect and some sparks to the zone containing the ball.
			// Transform the contact point into the zone's local coordinate system.
			
			Zone *zone = node->GetOwningZone();
			Point3D zonePosition = zone->GetInverseWorldTransform() * data->position;
			
			OmniSource *source = new OmniSource("model/Blaster/Reflect", 50.0F);
			source->SetNodePosition(zonePosition);
			zone->AddNewSubnode(source);
			
			SparkSystem *sparks = new SparkSystem(20);
			sparks->SetNodePosition(zonePosition);
			zone->AddNewSubnode(sparks);
		}
		
		// Move the ball only as far as it could go before it hit something.
		
		const Point3D& position = GetFinalPosition();
		SetInitialState(position, velocity);
		node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * position);
	}
}


SparkSystem::SparkSystem(long count) :
	
		// Initialize the base class, tell it where the particle pool is,
		// and tell it what kind of particle we're drawing (lines).
		ParticleSystem(kParticleSystemSpark, &particlePool, kParticleLine),
		
		// Initialize the particle pool by telling it where the array of
		// Particle structs is and how big it is.
		particlePool(kMaxParticleCount, particleArray)
{
	// The SparkEffect node creates a small burst of sparks and
	// then self-destructs when all of them have burned out.
	
	sparkCount = count;
	SetParticleSystemFlags(kParticleSystemSelfDestruct);
}

SparkSystem::SparkSystem() :
	
		// Initialize the base class, tell it where the particle pool is,
		// and tell it what kind of particle we're drawing (lines).
		ParticleSystem(kParticleSystemSpark, &particlePool, kParticleLine),
		
		// Initialize the particle pool by telling it where the array of
		// Particle structs is and how big it is.
		particlePool(kMaxParticleCount, particleArray)
{
	// This constructor gets used when the particle effect is being loaded from
	// a saved game. In this case, we don't need to initialize anything.
}

SparkSystem::~SparkSystem()
{
}

void SparkSystem::Preprocess(void)
{
	// This function creates the spark particles.
	
	// Always call the base class Preprocess() function.
	ParticleSystem::Preprocess();
	
	// If there's already a particle in the system, then the effect was loaded
	// from a saved game. We only create new particles if the system is empty.
	
	if (!GetFirstParticle())
	{
		// Calculate the world-space center.
		Point3D center = GetSuperNode()->GetWorldTransform() * GetNodePosition();
		
		const float *sine = Math::GetSineTable();
		const float *cosine = Math::GetCosineTable();
		
		long count = sparkCount;
		for (natural a = 0; a < count; a++)
		{
			// Grab a new unused particle from the pool.
			Particle *p = particlePool.NewParticle();
			if (!p) break;
			
			p->emitTime = 0;						// Particle appears right away.
			p->lifeTime = 500 + Math::Random(750);	// Particle lives 500-1250 milliseconds.
			p->radius = 0.025F;						// The radius is 25 mm.
			p->color.Set(1.0F, 1.0F, 0.1F, 1.0F);	// It's yellow.
			p->orientation = 0;						// This doesn't matter for line                              
			p->position = center;					// It starts at the effect's center.
			
			// Calculate a random velocity in a random direction.
			long phi = Math::Random(128);
			long theta = Math::Random(256);
			float speed = Math::RandomFloat(0.004F);
			float s = sine[phi] * speed;
			float c = cosine[phi] * speed;
			p->velocity.Set(cosine[theta] * s, sine[theta] * s, c);
			
			// Add the particle to the particle system.
			AddParticle(p);
		}
	}
}

bool SparkSystem::CalculateBoundingSphere(BoundingSphere *sphere) const
{
	// Just return a sphere that's big enough to always enclose
	// all of the particles. This is in local coordinates.
	
	sphere->SetCenter(0.0F, 0.0F, 0.0F);
	sphere->SetRadius(20.0F);
	return (true);
}

bool SparkSystem::AnimateParticles(void)
{
	// This function is called once per frame to move the particles.
	
	// Times are in milliseconds.
	long dt = TheTimeMgr->GetDeltaTime();
	float fdt = TheTimeMgr->GetFloatDeltaTime();
	
	long particleCount = 0;
	Particle *particle = GetFirstParticle();
	while (particle)
	{
		// Get the next particle now in case the current one is removed from the system.
		Particle *next = particle->nextParticle;
		
		long life = (particle->lifeTime -= dt);
		if (life > 0)
		{
			// Update velocity with gravity.
			particle->velocity.z += K::gravity * fdt;
			
			// Move the particle and see if it hit the floor plane at z=0.
			float z1 = particle->position.z - particle->radius;
			particle->position += particle->velocity * fdt;
			float z2 = particle->position.z - particle->radius;
			if (z1 * z2 <= 0.0F)
			{
				// The particle hit the floor, so reflect its velocity and remove some energy.
				particle->position.z = 0.05F - z2;
				particle->velocity.z *= -0.5F;
			}
			
			// If the particle is nearing the end of its life, fade it out.
			if (life < 100) particle->color.alpha = (float) life * 0.01F;
			particleCount++;
		}
		else
		{
			// Particle burned out.
			FreeParticle(particle);
		}
		
		particle = next;
	}
	
	// If no particles survived, then return false to indicate that it's time to self-destruct.
	return (particleCount != 0);
}

/*C++
This is the constructor for the GameWorld object. Again, name is "read-only" in here.
The " : " indicates that members of the object are to be initialized with the values given. 
The base (or parent) constructor - world(const char *name) - is to be called with the value "name".
The member spectatorCamera is to be constructed with the float values, 2.0f, 1.0f and 0.3f.
That constructor can be found in the engine class C4Cameras.cpp
*/

GameWorld::GameWorld(const char *name) :
		World(name),
		spectatorCamera(2.0F, 1.0F, 0.3F)
{
	// This constructor is called when the Game::ConstructWorld() function is
	// called to create a new world class. The world hasn't actually been loaded
	// from disk yet when we get here.
}

GameWorld::~GameWorld()
{
}

WorldResult GameWorld::Preprocess(void)
{
	// The Preprocess() function is called after the world has been constructed.
	// We must always call the base class Preprocess() function first.
	
	WorldResult result = World::Preprocess();
	if (result != kWorldOkay) return (result);
	
	// The world is now completely loaded. We search for a locator node that
	// represents the spectator camera's starting position. It will have a locator
	// type of kLocatorSpectator. We only look in the root zone of the world.
	
	Zone *zone = GetRootZone();
	if (zone)
	{
		// Iterate through all of the markers in the zone.
		
		const Marker *marker = zone->GetFirstMarker();
		while (marker)
		{
			MarkerType type = marker->GetMarkerType();
			if (type == kMarkerLocator)
			{

/*C++
First (marker)->GetLocatorType() is evaluated. 
This method returns a "C4:LocatorType" which is then cast into a pointer to a LocatorMarker.
That is compared to kLocatorSpectator, which is defined as 'spec' in SimpleBall.h
*/
				if (static_cast<const LocatorMarker *>(marker)->GetLocatorType() == kLocatorSpectator)
				{
					// The marker is of the correct type for the spectator camera.
					// So set the spectator camera's initial position and orientation.
					
					const Vector3D direction = marker->GetWorldTransform()[0];
					float azimuth = Atan(direction.y, direction.x);
					float altitude = 
                                        Atan(direction.z, Sqrt(direction.x * direction.x + direction.y * direction.y));
/*C++
Here are examples of using the dot to access a member functions.
Since spectatorCamera is an object of type SpectatorCamera, and not a pointer to an object of that type, the dot 
is used to access the member functions here and NOT the arrow (->).
*/

					spectatorCamera.SetCameraAzimuth(azimuth);
					spectatorCamera.SetCameraAltitude(altitude);
					spectatorCamera.SetNodePosition(marker->GetWorldPosition());
				}
			}
			
			// Get the next marker in the list. (Markers are elements of both a list
			// of nodes and a list of markers, so we need to explicitly specify which
			// one we're asking for with the ListElement<Marker>::Next() expression.)
			
/*C++

marker is a pointer to a C4::Marker (in this case constant) object - from a few lines up:

const Marker *marker = zone->GetFirstMarker();

C4::Marker is defined in the engine code in C4Markers.h.

Now C4:Marker inherits from Node and represents a (marker) node in the scene graph.

But C4::Marker also from ListElement<class type>, which basically means "is a" ListElement - and that means simply that 
a marker can be stored in a list - Objects of type "ListElement" can be stored in a List.
See C4List.h for more info on the class ListElement.

Note that the angle brackets here indicate that ListElement is a template class. This basically means that the class
needs only to be defined once, but can generate a separate class on compilation, depending on paramters you use for 
the class. In this example, the type Marker is provided so we will get a ListElement object for Markers.

See more info on ListElement class in the API: http://www.terathon.com/c4engine/doco/Utilities/ListElement.html
"The ListElement class should be declared as a base class for objects that need to be stored in a list."

Now, marker itself doesn't have a method called Next(). But its parent, ListElement, does. 
ListElement<Marker>::Next() is explicitly calling the Next() method of ListElement<Marker>.
And Next() returns a pointer to the type used for the template, in this case a C4:Marker.

*/
			marker = marker->ListElement<Marker>::Next();
		}
	}
	else
	{
		spectatorCamera.SetNodePosition(Point3D(0.0F, 0.0F, 1.0F));
	}
	
	// Set the world's current camera to be our spectator camera.
	// The world will not render without a camera being set.
	
	SetCamera(&spectatorCamera);
	
	return (kWorldOkay);
}

void GameWorld::Render(void)
{
	// This function is called once per frame to render the world.
	// The subclass may do whatever it needs to before or after rendering,
	// but at some point must call World::Render().
	
	World::Render();
}


/*C++

The Game constructor again has the " : " to provide a list of initializers separated by commas.
See the declaration of the Game class in SimpleBall.h.

class Game : public Singleton<Game>, public Application

Game inherits from the Singleton template which means that there will only be max one Game object. 
It also inherits from Application.

These private data members of the Game Class

		private:
			
			DisplayEventHandler				displayEventHandler;
			
			EntityRegistration				ballEntityReg;
			ControllerReg<BallController>	controllerReg;
			ParticleSystemReg<SparkSystem>	sparkSystemReg;
			LocatorRegistration				locatorReg;
			
are all initialized here in the constructor (behind the " : " and before the first curly brace "{".

*/

Game::Game() :
		
		// This is the constructor for the main application/game module class.
		// This class is constructed by the ConstructApplication() function,
		// which is called right after the application/game DLL is loaded by
		// the engine. There is only one instance of this class, so it inherits
		// from the Singleton template, which we initialize first.
		
		Singleton<Game>(TheGame),
		
		// The display event handler encapsulates a function that gets called
		// when the Display Manager changes something like the screen resolution.
		
		displayEventHandler(&HandleDisplayEvent),
		
		// An entity registration represents a model that can be instanced.
		// This particular declaration associates the kEntityBall type with
		// the model named "Ball.mdl". The third parameter tells the engine
		// to pre-cache the model resource, and the last parameter specifies
		// the default controller type to assign to entities of type kEntityBall.
		
		ballEntityReg(kEntityBall, "model/Ball", kEntityPrecache, kControllerBall),
		
		// A controller registration tells the engine about an application-defined
		// type of controller and registers its name and validation function.
		// In this case, the name "Bouncing Ball" will appear in the list of
		// available controllers in the World Editor for any node that is determined
		// to be a valid target node by the BallController::ValidNode() function.
		
		controllerReg(kControllerBall, "Bouncing Ball"),
		
		// A particle system registration tells the engine about an application-defined
		// type of particle system and registers its name.
		
		sparkSystemReg(kParticleSystemSpark, "Sparks"),
		
		// Locator markers are registered so that the World Editor
		// can display their names in the Get Info dialog box.
		
		locatorReg(kLocatorSpectator, "Spectator Camera")
{
	// This installs an event handler for display events. This is only
	// necessary if we need to perform some action in response to
	// display events for some reason.
	
	TheDisplayMgr->InstallDisplayEventHandler(&displayEventHandler);
	
	// The following lines set the radius, height, depth, and color that the
	// ball entity has when placed in the World Editor.
	
	ballEntityReg.SetEntitySize(0.125F, 0.125F, 0.125F);
	ballEntityReg.SetEntityColor(ColorRGB(0.0F, 1.0F, 0.0F));
	
	// This sets the function that is called when the user hits the
	// escape key during gameplay. We save the old function so that
	// it can be restored when the game DLL is unloaded.
	
	prevEscapeProc = TheInputMgr->GetEscapeProc();
	prevEscapeData = TheInputMgr->GetEscapeData();
	TheInputMgr->SetEscapeProc(&EscapeProc, this);
	
	// This registers our world class constructor with the World Manager.
	// We only need to do this if we have defined a subclass of the World
	// class that holds extra information.
	
	TheWorldMgr->SetWorldConstructor(&ConstructWorld);
	
	// These create the movement actions that are used to fly the
	// spectator camera around.
	
	forwardAction = new MovementAction(kActionForward, kSpectatorMoveForward);
	backwardAction = new MovementAction(kActionBackward, kSpectatorMoveBackward);
	leftAction = new MovementAction(kActionLeft, kSpectatorMoveLeft);
	rightAction = new MovementAction(kActionRight, kSpectatorMoveRight);
	upAction = new MovementAction(kActionUp, kSpectatorMoveUp);
	downAction = new MovementAction(kActionDown, kSpectatorMoveDown);
	
	// These register our new actions with the Input Manager.
	
	TheInputMgr->AddAction(forwardAction);
	TheInputMgr->AddAction(backwardAction);
	TheInputMgr->AddAction(leftAction);
	TheInputMgr->AddAction(rightAction);
	TheInputMgr->AddAction(upAction);
	TheInputMgr->AddAction(downAction);
	
	// Put the Message Manager in single-player mode.
	
	TheMessageMgr->BeginSinglePlayerGame();
}

Game::~Game()
{
	// When the game DLL is about to be unloaded, this destructor is called.
	
	TheWorldMgr->UnloadWorld();
	TheWorldMgr->SetWorldConstructor(nullptr);
	
	// Tell the Message Manager to clean up.
	
	TheMessageMgr->EndGame();
	
	delete downAction;
	delete upAction;
	delete rightAction;
	delete leftAction;
	delete backwardAction;
	delete forwardAction;
	
	// Restore the previous escape key handling function.
	
	TheInputMgr->SetEscapeProc(prevEscapeProc, prevEscapeData);
}

World *Game::ConstructWorld(const char *name, void *data)
{
	// This function is called when a new world is being loaded. It should
	// return a pointer to a newly constructed subclass of the World class.
	
	return (new GameWorld(name));
}

void Game::HandleDisplayEvent(EventType eventType, long param, void *data)
{
	// This function is called when a display event occurs (because we
	// registered it in the Game constructor).
	
	if (eventType == kEventDisplayChanged)
	{
		// The screen resolution has changed. Handle accordingly.
	}
}

void Game::EscapeProc(void *data)
{
	// This function is called when the user hits the escape key in gameplay
	// mode because we registered it using the InputMgr::SetEscapeProc() function.
}
Personal tools