PongPartFour

From C4 Engine Wiki

Jump to: navigation, search

Verified: C4 1.5.9


Contents

Summary

A persistent particle system is added to the ball node.

Features

  • Creates a simple particle system and attaches it to the ball node.


Header File: PongPt04.h

#ifndef PongPt04_h
#define PongPt04_h
 
 
#include "C4Application.h"
#include "C4Engine.h"
#include "C4Input.h"
#include "C4World.h"
#include "C4Cameras.h"
#include "C4Projectile.h"
#include "C4Primitives.h"
#include "C4Interface.h"
#include "C4Particles.h"
 
 
extern "C"
{
	module_export C4::Application *ConstructApplication(void);
}
 
 
namespace C4
{
 
	enum
	{
		kControllerBall		= 'ball',
		kControllerPaddle	= 'pddl',
		kControllerPlayer	= 'plyr',
 
		kActionUp		= 'frwd',
		kActionDown		= 'bkwd',
 
		kMovementUp		= 1 << 0,
		kMovementDown		= 1 << 1,
 
		kParticleSystemBall	= 'sprk'
	};
 
	class BallTrail;
 
	class MovementAction : public Action
	{
		private:
 
			unsigned long	movementFlag;
 
		public:
 
			MovementAction(unsigned long type, unsigned long flag);
			~MovementAction();
 
			void Begin(void);
			void End(void);
	};
 
	class BallController : public ProjectileController
	{
		private:
 
			BallTrail *ballTrail;
 
		public:
 
			BallController();
			~BallController();
 
			void Preprocess(void);
 
			void Move(void);
			void Travel(void);
 
			float GetBallDirection();
			Vector3D GetBallVelocity();
	};
 
 
	class PaddleController : public Controller
	{
		public:
 
			PaddleController();
			~PaddleController();
 
			void Preprocess(void);
 
			void Move(void);
	};
 
 
	class PlayerController : public Controller
	{
		private:
 
			unsigned long movementFlags;
 
 
		public:
 
			PlayerController();
			~PlayerController();
 
			void Preprocess(void);
 
			void Move(void);
 
			unsigned long GetMovementFlags(void) const;			
			void SetMovementFlags(unsigned long flags);
	};
 
 
	class ScoreInterface : public Interface
	{
		private:
 
			TextElement playerOneScore;
			TextElement playerTwoScore;
 
 
		public:
 
			ScoreInterface();
			~ScoreInterface();
 
			void SetPlayerOneScore(long score);
			void SetPlayerTwoScore(long score);
	};
 
 
	class BallTrail : public ParticleSystem
	{
		private:
 
			enum
			{
				kMaxParticleCount = 100
			};
 
			Particle		particleArray[kMaxParticleCount];
			ParticlePool<Particle>	particlePool;
 
			bool CalculateBoundingSphere(BoundingSphere *sphere) const;
 
 
		public:
 
			BallTrail();
			~BallTrail();
 
			void Preprocess(void);
			bool AnimateParticles(void);
	};
 
 
	class GameWorld : public World
	{
		private:
 
			SpectatorCamera     spectatorCamera;
 
 
		public:
 
			GameWorld(const char *name);
			~GameWorld();
 
			WorldResult Preprocess(void);
			void Render(void);
	};
 
 
	class Game : public Singleton<Game>, public Application
	{
		private:
 
			DisplayEventHandler     displayEventHandler;
 
			InputMgr::KeyProc	*prevEscapeProc;
			void                    *prevEscapeData;
 
			static World *ConstructWorld(const char *name, void *data);
			static void HandleDisplayEvent(EventType eventType, long param, void *data);
			static void EscapeProc(void *data);
 
			BallController	*ballController;
 
			PlayerController *playerController;
			PaddleController *paddleController;
 
			long playerOneScore;
			long playerTwoScore;
 
			ScoreInterface *scoreInterface;
 
 
		public:
 
			Game();
			~Game();
 
			MovementAction *upAction;
			MovementAction *downAction;
 
			BallController *GetBallController();
			PlayerController *GetPlayerController();
			PaddleController *GetPaddleController();
 
			void IncreaseScore(bool player);
	};
 
 
	extern Game *TheGame;
}
 
#endif


Source File: PongPt04.cpp

#include "PongPt04.h"
 
 
using namespace C4;
 
 
Game *C4::TheGame = nullptr;
 
C4::Application *ConstructApplication(void)
{
	return (new Game);
}
 
 
MovementAction::MovementAction(unsigned long type, unsigned long flag) : Action(type)
{	
	movementFlag = flag;
}
 
MovementAction::~MovementAction()
{
}
 
void MovementAction::Begin(void)
{	
	PlayerController *controller = TheGame->GetPlayerController();
	if (controller) controller->SetMovementFlags(controller->GetMovementFlags() | movementFlag);
}
 
void MovementAction::End(void)
{
	PlayerController *controller = TheGame->GetPlayerController();
	if (controller) controller->SetMovementFlags(controller->GetMovementFlags() & ~movementFlag);
}
 
 
BallController::BallController() : ProjectileController(kControllerBall, 1.001F, kProjectileStopped)
{
}
 
BallController::~BallController()
{
}
 
void BallController::Preprocess(void)
{
	ProjectileController::Preprocess();
 
	ballTrail=new BallTrail();
	ballTrail->SetNodePosition(GetTargetNode()->GetNodePosition());
	GetTargetNode()->AddNewSubnode(ballTrail);
}
 
void BallController::Move(void)
{
	if (GetProjectileFlags() == kProjectileStopped)
	{	
		SetProjectileFlags(0);
		SetInitialState(Point3D(0.0F,0.0F,16.0f), Vector3D(Math::RandomFloat(6.0F)-3.0F, 0.0F, Math::RandomFloat(2.0F)-1.0F).Normalize() * 0.05F);
	}
 
	if (GetTargetNode()->GetNodePosition().x > 25)
	{
		SetInitialState(Point3D(0.0F,0.0F,16.0f), Vector3D(Math::RandomFloat(6.0F)-3.0F, 0.0F, Math::RandomFloat(2.0F)-1.0F).Normalize() * 0.05F);
		TheGame->IncreaseScore(true);
	}
	else if (GetTargetNode()->GetNodePosition().x < -25)
	{
		SetInitialState(Point3D(0.0F,0.0F,16.0f), Vector3D(Math::RandomFloat(6.0F)-3.0F, 0.0F, Math::RandomFloat(2.0F)-1.0F).Normalize() * 0.05F);
		TheGame->IncreaseScore(false);
	}
 
	ProjectileController::Move();
}
 
void BallController::Travel(void)
{
	ProjectileController::Travel();
 
	Node *node = GetTargetNode();
	CollisionState state = GetCollisionState();
 
	if (state == kCollisionStateNone)
	{
		node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * GetFinalPosition());
	}
	else
	{
		Vector3D velocity;
 
		const CollisionData *data = GetCollisionData();
		const Vector3D& normal = data->normal;
 
		if (state == kCollisionStateGeometry)
		{
			velocity = GetCollisionVelocity() - normal * (2.0F * (normal * GetCollisionVelocity()));
 
			if (Fabs(velocity.z) < 0.02F ) velocity.z*=2.0F;
			if (Fabs(velocity.x) < 0.02F ) velocity.x*=2.0F;
			velocity=velocity.Normalize()*0.05F;
		}
 
		const Point3D& position = GetFinalPosition();
		SetInitialState(position, velocity);
		node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * position);
	}
}
 
float BallController::GetBallDirection()
{
	return GetVelocity(0.0F).x;
}
 
Vector3D BallController::GetBallVelocity()
{
	return GetVelocity(0.0F);
}
 
 
PaddleController::PaddleController() : Controller(kControllerPaddle)
{
}
 
PaddleController::~PaddleController()
{
}
 
void PaddleController::Preprocess(void)
{
	Controller::Preprocess();
}
 
void PaddleController::Move(void)
{
	Node *node=GetTargetNode();
	float ballZ=TheGame->GetBallController()->GetTargetNode()->GetWorldPosition().z;
	float ballDirection=TheGame->GetBallController()->GetBallDirection();
	Point3D paddlePosition=node->GetSuperNode()->GetWorldTransform() * node->GetNodePosition();
 
	if (ballDirection>0.0F)
	{
		if (ballZ>paddlePosition.z+3)
		{
			node->Invalidate();
			node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * Point3D(paddlePosition.x,paddlePosition.y,Fmin(paddlePosition.z+Fmin(0.025F,ballZ-paddlePosition.z),25.0F)));
		}
		else if (ballZ<paddlePosition.z+3)
		{
			node->Invalidate();
			node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * Point3D(paddlePosition.x,paddlePosition.y,Fmax(paddlePosition.z-Fmin(0.025F,paddlePosition.z-ballZ+3),1.0F)));
		}
	}
	else
	{
		if (paddlePosition.z>13)
		{
			node->Invalidate();
			node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * Point3D(paddlePosition.x,paddlePosition.y,Fmin(paddlePosition.z-Fmin(0.025F,paddlePosition.z+3-16),25.0F)));
		}
		else if (paddlePosition.z<13)
		{
			node->Invalidate();
			node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * Point3D(paddlePosition.x,paddlePosition.y,Fmax(paddlePosition.z+Fmin(0.025F,16-paddlePosition.z+3),1.0F)));
		}
	}
 
	Controller::Move();
}
 
 
PlayerController::PlayerController() : Controller(kControllerPlayer)
{
	movementFlags=0;
}
 
PlayerController::~PlayerController()
{
}
 
void PlayerController::Preprocess(void)
{
	Controller::Preprocess();
}
 
void PlayerController::Move(void)
{
	Node *node=GetTargetNode();
	Point3D paddlePosition=node->GetSuperNode()->GetWorldTransform() * node->GetNodePosition();
 
	static const float directionTable[4] =
	{
		0.0F, 1.0F, -1.0F, 0.0F
	};
 
	node->Invalidate();
	node->SetNodePosition(node->GetSuperNode()->GetInverseWorldTransform() * Point3D(paddlePosition.x,paddlePosition.y,paddlePosition.z+(directionTable[movementFlags]*0.025F)));
 
	Controller::Move();
}
 
unsigned long PlayerController::GetMovementFlags(void) const
{
	return (movementFlags);
}
 
void PlayerController::SetMovementFlags(unsigned long flags)
{
	movementFlags = flags;
}
 
 
ScoreInterface::ScoreInterface()
{
	float displayWidth=TheDisplayMgr->GetDisplayWidth();
 
	QuadElement *background=new QuadElement(200.0F,50.0F,ColorRGBA(0.5F,0.5F,0.5F,1.0F));
	background->SetElementPosition(Point3D(displayWidth/2.0F-100, 12.0F, 0.0F));
	AddSubnode(background);
 
	AutoReleaseFont font("PongFont/PongFont");
 
	playerOneScore.SetText("0");
	playerOneScore.SetFont(font);
	playerOneScore.SetTextColor(ColorRGBA(1.0F, 0.7F, 0.1F, 1.0F));	
	playerOneScore.SetElementPosition(Point3D(displayWidth/2.0F-55.0F, 12.0F, 0.0F));
	AddSubnode(&playerOneScore);
 
	playerTwoScore.SetFont(font);
	playerTwoScore.SetText("0");
	playerTwoScore.SetTextColor(ColorRGBA(1.0F, 0.7F, 0.1F, 1.0F));
	playerTwoScore.SetElementPosition(Point3D(displayWidth/2.0F+45.0F, 12.0F, 0.0F));
	AddSubnode(&playerTwoScore);
}
 
ScoreInterface::~ScoreInterface()
{
}
 
void ScoreInterface::SetPlayerOneScore(long score)
{
	playerOneScore.SetText(Text::IntegerToString(score));
 
	Invalidate();
}
 
 
void ScoreInterface::SetPlayerTwoScore(long score)
{
	playerTwoScore.SetText(Text::IntegerToString(score));
}
 
 
BallTrail::BallTrail() :
		ParticleSystem(kParticleSystemBall, &particlePool, kParticlePoint),
		particlePool(kMaxParticleCount, particleArray)
{
}
 
BallTrail::~BallTrail()
{
}
 
void BallTrail::Preprocess(void)
{
	ParticleSystem::Preprocess();
}
 
bool BallTrail::CalculateBoundingSphere(BoundingSphere *sphere) const
{
	sphere->SetCenter(0.0F, 0.0F, 0.0F);
	sphere->SetRadius(50.0F);
	return (true);
}
 
 
bool BallTrail::AnimateParticles(void)
{
	long dt = TheTimeMgr->GetDeltaTime();
	float fdt = TheTimeMgr->GetFloatDeltaTime();
 
	Particle *particle = GetFirstParticle();
	while (particle)
	{
		Particle *next = particle->nextParticle;
 
		long life = (particle->lifeTime -= dt);
		if (life > 0)
		{
			particle->position += particle->velocity * fdt;
			if (life < 100) particle->color.alpha = (float) life * 0.01F;
		}
		else
		{
			FreeParticle(particle);
		}
 
		particle = next;
	}
 
	Point3D center = GetSuperNode()->GetNodePosition();
 
	Vector3D particleVelocity=-TheGame->GetBallController()->GetBallVelocity()*0.1F;
 
	static const ColorRGBA colorTable[6]=
	{
		ColorRGB(1.0F,0.0F,0.0F),
		ColorRGB(0.0F,1.0F,0.0F),
		ColorRGB(0.0F,0.0F,1.0F),
		ColorRGB(1.0F,1.0F,0.0F),
		ColorRGB(1.0F,0.0F,1.0F),
		ColorRGB(0.0F,1.0F,1.0F)
	};
 
	for (natural a = 0; a < 50; a++)
	{
		Particle *p = particlePool.NewParticle();
		if (!p) break;
 
		p->emitTime = 0;						
		p->lifeTime = 500 + Math::Random(750);
		p->radius = 1.5F;
		p->color.Set(colorTable[Math::Random(6)], 1.0F);
		p->orientation = 0;
		p->position = center;
 
		p->velocity.Set(particleVelocity.x+(Math::RandomFloat(0.02F)-0.01F),particleVelocity.y+(Math::RandomFloat(0.02F)-0.01F),particleVelocity.z+(Math::RandomFloat(0.02F)-0.01F));
 
		AddParticle(p);
	}
 
	return (true);
}
 
 
GameWorld::GameWorld(const char *name) : World(name),spectatorCamera(2.0F, 1.0F, 0.0F)
{
}
 
GameWorld::~GameWorld()
{
}
 
WorldResult GameWorld::Preprocess(void)
{
	WorldResult result = World::Preprocess();
	if (result != kWorldOkay) return (result);
 
	spectatorCamera.SetCameraAzimuth(K::pi_over_2);
 
	spectatorCamera.SetNodePosition(Point3D(0.0F, -50.0F, 16.0F));
 
	SetCamera(&spectatorCamera);
 
	Zone *root = GetRootNode();
	Node *node=root;
	do
	{
		const char *nodeName=node->GetNodeName();
		if ((nodeName) && Text::CompareText(nodeName,"Ball"))
		{
			BallController *tempBallController=TheGame->GetBallController();
			node->SetController(tempBallController);
			tempBallController->Preprocess();
		}
		else if ((nodeName) && Text::CompareText(nodeName,"Paddle1"))
		{
			PlayerController *tempPlayer=TheGame->GetPlayerController();
			node->SetController(tempPlayer);
			tempPlayer->Preprocess();
		}
		else if ((nodeName) && Text::CompareText(nodeName,"Paddle2"))
		{
			PaddleController *tempPaddle=TheGame->GetPaddleController();
			node->SetController(tempPaddle);
			tempPaddle->Preprocess();
		}
 
		node = root->GetNextNode(node);
	}while(node);
 
	return (kWorldOkay);
}
 
void GameWorld::Render(void)
{
	World::Render();
}
 
 
Game::Game() :
        Singleton<Game>(TheGame),
        displayEventHandler(&HandleDisplayEvent)
{
	TheDisplayMgr->InstallDisplayEventHandler(&displayEventHandler);
 
	prevEscapeProc = TheInputMgr->GetEscapeProc();
	prevEscapeData = TheInputMgr->GetEscapeData();
	TheInputMgr->SetEscapeProc(&EscapeProc, this);
 
	TheWorldMgr->SetWorldConstructor(&ConstructWorld);
 
	ballController=new BallController();
 
	playerController=new PlayerController();
	paddleController=new PaddleController();
 
	upAction = new MovementAction(kActionUp, kMovementUp);
	downAction = new MovementAction(kActionDown, kMovementDown);
 
	TheInputMgr->AddAction(upAction);
	TheInputMgr->AddAction(downAction);
 
	playerOneScore=0;
	playerTwoScore=0;
 
	scoreInterface=new ScoreInterface();
	TheInterfaceMgr->AddElement(scoreInterface);
 
	TheMessageMgr->BeginSinglePlayerGame();
 
	TheWorldMgr->LoadWorld("PongWorld/PongWorld");
}
 
Game::~Game()
{
	TheWorldMgr->UnloadWorld();
	TheWorldMgr->SetWorldConstructor(nullptr);
 
	delete downAction;
	delete upAction;
 
	delete scoreInterface;
 
	TheMessageMgr->EndGame();
 
	TheInputMgr->SetEscapeProc(prevEscapeProc, prevEscapeData);
}
 
World *Game::ConstructWorld(const char *name, void *data)
{
	return (new GameWorld(name));
}
 
void Game::HandleDisplayEvent(EventType eventType, long param, void *data)
{
	if (eventType == kEventDisplayChange)
	{
 
	}
}
 
void Game::EscapeProc(void *data)
{
	TheEngine->Quit();
}
 
BallController *Game::GetBallController()
{
	return ballController;
}
 
PlayerController *Game::GetPlayerController()
{
	return playerController;
}
 
PaddleController *Game::GetPaddleController()
{
	return paddleController;
}
 
void Game::IncreaseScore(bool player)
{
	if (player) 
	{
		playerOneScore++;
		scoreInterface->SetPlayerOneScore(playerOneScore);
	}
	else
	{
		playerTwoScore++;
		scoreInterface->SetPlayerTwoScore(playerTwoScore);
	}
}


Code Breakdown

This section attempts to explain what certain parts of the code actually do. The title of each subsection is the method where the commands being explained are located.


BallTrail::BallTrail

The constructor for the particle system. It initializes the particle pool and sets the particle type to point particles. It also tells the engine where the particle pool is


BallTrail::AnimateParticles

This method is called every frame to allow the program to do what it needs to the particles. This program uses it to update existing particles and create new particles.

long dt = TheTimeMgr->GetDeltaTime();
float fdt = TheTimeMgr->GetFloatDeltaTime();

Here the time between the current frame and the last frame is retrieved as a both an int and a float and stored locally for later use.

Particle *particle = GetFirstParticle();
while (particle)
{
	Particle *next = particle->nextParticle;
        ...
	particle = next;
}

Here the first particle is retrieved and then the program iterates through the rest so that the program can do what it needs to them. The next particle is retrieved at the beginning of the loop because the current particle can be deleted mid-loop if it times out.

long life = (particle->lifeTime -= dt);
if (life > 0)
{
	particle->position += particle->velocity * fdt;
	if (life < 100) particle->color.alpha = (float) life * 0.01F;
}
else
{
	FreeParticle(particle);
}

Retrieve the life of the current particle in the loop and reduce it by the change in time since the last frame. If the particle's lifeTime is greater than 0, then move it. If the particle is still alive but has less than 100 milliseconds left, start to fade it out so that when it dies it doesn't just dissapear suddenly. If the particle's life has expired, then it is freed using the FreeParticle command.

Point3D center = GetSuperNode()->GetNodePosition();
 
Vector3D particleVelocity=-TheGame->GetBallController()->GetBallVelocity()*0.1F;

The first line is used to get the position where new particles are going to start from. The second line is used to determine the general velocity that the particles should move when created. It is basically the ball's velocity negated so that they move in the opposite direction. Multiplying by 0.1F keeps them from moving to fast.

static const ColorRGBA colorTable[6]=
{
	ColorRGB(1.0F,0.0F,0.0F),
	ColorRGB(0.0F,1.0F,0.0F),
	ColorRGB(0.0F,0.0F,1.0F),
	ColorRGB(1.0F,1.0F,0.0F),
	ColorRGB(1.0F,0.0F,1.0F),
	ColorRGB(0.0F,1.0F,1.0F)
};

This array is used as a lookup table which is drawn from randomly to set particle colors.

for (natural a = 0; a < 50; a++)
{
	Particle *p = particlePool.NewParticle();
	if (!p) break;
        ...
}

This where the particles are created every frame. The pointers for them are drawn from the particle pool created earlier. If a pointer isn't available then the rest of the loop is skipped.

p->emitTime = 0;						
p->lifeTime = 500 + Math::Random(750);
p->radius = 1.5F;
p->color.Set(colorTable[Math::Random(6)], 1.0F);
p->orientation = 0;
p->position = center;

These are the values that define every particle. emitTime is how long the particle waits before it is shown. lifeTime represents how long the particle lasts in milliseconds. radius defines the size of the particle. color is the color of the particle, and a random integer from 0 to 5 is passed to the colorTable created earlier to set it.

p->velocity.Set(particleVelocity.x+(Math::RandomFloat(0.02F)-0.01F),particleVelocity.y+(Math::RandomFloat(0.02F)-0.01F),particleVelocity.z+(Math::RandomFloat(0.02F)-0.01F));

This sets the velocity for the particle. The velocity is slightly altered with small random values so that all of the particles will spread out instead of going the same direction.

AddParticle(p);

Simple enough, add the particle to the system.


BallController::Preprocess

This is where the particle system is initialized

ballTrail=new BallTrail();
ballTrail->SetNodePosition(GetTargetNode()->GetNodePosition());
GetTargetNode()->AddNewSubnode(ballTrail);

Here the particle system is initialized and attached to the ball node.

Personal tools