A Simple Tank

From C4 Engine Wiki

Jump to: navigation, search

This tutorial is the second in a series of tutorials on how to make a simple game with C4. The first tutorial is A Simple Projectile and the results of that tutorial are used here to make a simple tank, which can shoot those simple projectiles.

A Simple Tank model is available in the opening post on the forums.

You can also make your own Tank model using the World Editor.

Here are the steps:

1. Open your C4 solution file, which is in the directory ..\C4\VisualStudio2010\C4, in Visual Studio and then open SimpleChar.cpp and SimpleChar.h.

2. Now open SimpleBall.cpp and SimpleBall.h and save them as SimpleProjectile.cpp and SimpleProjectile.h. In SimpleProjectile, we are going to throw away all the Game and Application code and only leave the classes for the Projectile and the SparkSystem. We are finished with the SimpleBall files, so close those now.

3. Right click on SimpleChar project in the solution explorer and chose Add and then select Existing Item. Now mark the SimpleProjectile.h and SimpleProjectile.cpp files and add them to the SimpleChar project. Open these files.

4. In SimpleProjectile.cpp, delete the blocks of code beginning with

C4::Application *ConstructApplication(void)
MovementAction
GameWorld::GameWorld(const char *name)

and

Game::Game() 

5. In SimpleProjectile.h file delete the blocks of code beginning with

extern "C"

the "Action" enums

	enum
	{
		kActionForward			= 'frwd',
		kActionBackward			= 'bkwd',
		kActionLeft				= 'left',
		kActionRight			= 'rght',
		kActionUp				= 'jump',
		kActionDown				= 'down'
	};
MovementAction
class GameWorld : public World
class Game : public Application, public Singleton<Game>

and

extern Game *TheGame;

6. Back in SimpleChar.cpp, modify the void MovementAction::Begin(void) function as follows:

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). We respond to
	// such an event by setting a movement flag in the soldier controller.
	
	SoldierController *controller = TheGame->GetSoldierController();
	if (controller) controller->SetMovementFlags(controller->GetMovementFlags() | movementFlag);

		GameWorld *world = static_cast<GameWorld *>(TheWorldMgr->GetWorld());
		ChaseCamera *camera = world->GetChaseCamera();//new

		if (movementFlag & 16) {//Space Bar (Up) pressed, so fire rockets!
			Controller *controller1;
			Model *model1 = nullptr;
			Point3D zonePosition;
			float speed = 100.0F;// increase or decrease to change the speed
			Vector3D direction = camera->GetWorldTransform()[2];
			Point3D startPos = camera->GetWorldPosition() + Point3D(0.0F,0.0F,0.0F);
			Zone *zone = world->FindZone(startPos);
			controller1 = new BallController((speed * direction));
			model1 = Model::Get(kModelBall);
			if (model1)
			{
				model1->SetController(controller1);
				zonePosition = zone->GetInverseWorldTransform() * (startPos);
				model1->SetNodePosition(zonePosition);
				zone->AddNewSubnode(model1);
				model1->Update();
			}

		} 
}

7. Now add

#include "SimpleProjectile.h" 

at the top of the SimpleChar.h file, just after the line

#include "C4Engine.h"

8. OK, now change the file game.cfg which is under D:\C4-2.7\Data\Game to use the SimpleChar game code

$gameModuleName = "SimpleChar";

and save it.

If you compile and run now, it won't work because we need to register the ball model and the ball and spark controllers! So..

9. Add this code to SimpleChar.cpp, just after the line

displayEventHandler(&HandleDisplayEvent),
		ballModelReg(kModelBall, "Bouncing Ball", "model/Ball", kModelPrecache, kControllerBall),
		
		controllerReg(kControllerBall, "Bouncing Ball"),
		
		sparkSystemReg(kParticleSystemSpark, "Sparks"),

10. Back in the SimpleChar.h file, add these lines after the line beginning with DisplayEventHandler

			ModelRegistration		ballModelReg;//new
			ControllerReg<BallController>	controllerReg;//new
			ParticleSystemReg<SparkSystem>	sparkSystemReg;//new

11. If you now run SimpleChar in the Water world and you'll notice that the projectiles are fired when you press the Space Bar on your keyboard, but they are fired from the camera, not from the gun!

12. So change these lines to start the projectile where the soldier is and to use the Soldier's direction, not that of the Camera:

Vector3D direction = controller->GetTargetNode()->GetWorldTransform()[0];
Point3D startPos = controller->GetTargetNode()->GetWorldPosition() + Point3D(0.0F,0.0F,2.0F);//Starts above the soldiers head!!

13. OK, let's change the soldier model for a simple tank, by commenting out the Soldier animation code in the following lines

//frameAnimator.SetTargetModel(soldier);
//soldier->SetRootAnimator(&frameAnimator);

//soldier->GetWorld()->AddInteractor(&soldierInteractor);

//GetTargetNode()->Animate();

14. Now change the line which registers the Soldier model to register a Tank model instead:

soldierModelReg(kModelSoldier, nullptr, "model/Tank", kModelPrecache | kModelPrivate, kControllerSoldier),//new

15. The Tank model has a marker node called "Barrel", which is used to mark the position where the missiles should be fired from. At the end of the SoldierController::Preprocess() function add this to find the Barrel Node:

        //start at the root although you could also start
        //at the target node with GetTargetNode()
	Node *root = TheWorldMgr->GetWorld()->GetRootNode(); 
	Node *thisnode = root;
	String<30> nodeName = "NoName"; 
	bool found = false;
	do
	{
		nodeName = "NoName"; 
		if (thisnode->GetNodeName() != NULL)
			nodeName = thisnode->GetNodeName();
		if ((Text::CompareText(nodeName, "Barrel")) ) {
			BarrelNode = thisnode;
			found = true;
		}
		thisnode = root->GetNextNode(thisnode);
	} while (thisnode && !found);

We use the position of this marker node as the starting point for the projectiles which the tank fires.

16. Add a global at the top of the file to capture this Node, just after Game *C4::TheGame = nullptr;

Node *BarrelNode;

17. OK, compile and run that and open the Water world. You should now be able to shoot missiles from your Tank!

Sparks

18. Let's make some sparks when the missile hits another piece of geometry. Add this function to SimpleProjectile.cpp

	
RigidBodyStatus BallController::HandleNewGeometryContact(const GeometryContact *contact)
{
	// This function is called when the ball makes contact with any geometry.

	// Add a sound effect and some sparks to the zone containing the ball.
	// Transform the contact point into the zone's local coordinate system.
		
	Node *node = GetTargetNode();
	Zone *zone = node->GetOwningZone();
	Point3D zonePosition = node->GetWorldPosition();//();
		
	OmniSource *source = new OmniSource("model/Ball", 50.0F);
	source->SetNodePosition(zonePosition);
	zone->AddNewSubnode(source);
		
	SparkSystem *sparks = new SparkSystem(20);
	sparks->SetNodePosition(zonePosition);
	zone->AddNewSubnode(sparks);
	
	delete node;
	return (kRigidBodyDestroyed);
}

19. Almost done. In SimpleProjectile.h, just after the line

	
RigidBodyStatus HandleNewRigidBodyContact(const RigidBodyContact *contact, RigidBodyController *contactBody);

declare the HandleNewGeometryContact function as follows:

RigidBodyStatus HandleNewGeometryContact(const GeometryContact *contact);//declare Handler for Geometry Contact

Try it out! Now the missiles should create sparks when they hit geometry and instead of bouncing around, they now disappear.

20. Finally, change the code to make the tank fire missiles when the mouse button is clicked instead of when the space bar is pressed. You simply need to move the firing code from void MovementAction::Begin(void) to the function void UseAction::Begin(void). Your new UseAction function will look like this:

void UseAction::Begin(void)
{
	// The player has pressed the fire/use button. If we are currently interacting with
	// a node in the scene and that node has a controller, then we send an activate event
	// to that controller to let it know that the player is doing something with it.

	SoldierController *controller = TheGame->GetSoldierController();
	if (controller)
	{
		const SoldierInteractor *interactor = controller->GetSoldierInteractor();
		const Node *interactionNode = interactor->GetInteractionNode();
		if (interactionNode)
		{
			Controller *interactionController = interactionNode->GetController();
			if (interactionController)
			{
				interactionController->HandleInteractionEvent(kInteractionEventActivate, &interactor->GetInteractionPosition(), controller->GetTargetNode());
			}
		}
	}
	//New for Simple Tank
	GameWorld *world = static_cast<GameWorld *>(TheWorldMgr->GetWorld());
	ChaseCamera *camera = world->GetChaseCamera();//new
	Controller *controller1;
	Model *model1 = nullptr;
	Point3D zonePosition;
	float speed = 200.0F;// increase or decrease to change the speed
	Vector3D direction = controller->GetTargetNode()->GetWorldTransform()[0];
	Point3D startPos = BarrelNode->GetWorldPosition(); 
	Zone *zone = world->FindZone(startPos);
	controller1 = new BallController((speed * direction));
	model1 = Model::Get(kModelBall);
	if (model1)
	{
		model1->SetController(controller1);
		zonePosition = zone->GetInverseWorldTransform() * (startPos);
		model1->SetNodePosition(zonePosition);
		zone->AddNewSubnode(model1);
		model1->Update();
	}
}

Your turn:

1. Change the model for the projectiles to be more like a real missile

2. Add sound effects for when the missile is in flight and to simulate an explosion

3. Tweak the Spark effect to be more like an explosion (if you didnt already at the end of the first tutorial!)

4. Did you notice that the missiles hit the barrels in the Water world, but go straight through the crates without collision? Why is that and can you change the code to make the missiles collide with the crates too?

Personal tools