PongPartThree
From C4 Engine Wiki
Verified: C4 1.5.9
Contents |
Summary
Adds a scoreboard to the game, demonstrating a simple example of using Interface elements
Features
- Creates an Interface element which contains one quad element and two text elements
Header File: PongPt03.h
#ifndef PongPt03_h #define PongPt03_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" 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 }; 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 { public: BallController(); ~BallController(); void Preprocess(void); void Move(void); void Travel(void); float GetBallDirection(); }; 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 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: PongPt03.cpp
#include "PongPt03.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(); } 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; } 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)); } void ScoreInterface::SetPlayerTwoScore(long score) { playerTwoScore.SetText(Text::IntegerToString(score)); } 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.
ScoreInterface::ScoreInterface
The ScoreInterface class is a subclass of the Interface class defined by the engine.
float displayWidth=TheDisplayMgr->GetDisplayWidth();
This is pretty self explanatory. The display width is retrieved and stored for later use.
QuadElement *background=new QuadElement(200.0F,50.0F,ColorRGBA(0.5F,0.5F,0.5F,1.0F));
Here a QuadElement is created to act as a rectangular backboard for the text. The first two parameters are used to set a width and height of 200 and 50 pixels respectivley. The third parameter is used to set the color of the backboard to a simple gray.
background->SetElementPosition(Point3D(displayWidth/2.0F-100, 12.0F, 0.0F)); AddSubnode(background);
Once the QuadElement is initialized, it is positioned on the screen. It is centered horizontally and 12 pixels from the top. After it is positioned, it is added to the Interface using the AddSubnode command.
AutoReleaseFont font("PongFont/PongFont");
This command loads a font for use by the scoreboard. The font being referred to by the command is included in the zip file for the series.
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);
Here the TextElement for player one's score is setup and added to the Interface. The same process is similar for player two.
ScoreInterface::SetPlayerOneScore
playerOneScore.SetText(Text::IntegerToString(score));
This method is called every time player one scores to update the text representing player one's score. A similar method is used to do the same for player two.
Game::Game
scoreInterface=new ScoreInterface(); TheInterfaceMgr->AddElement(scoreInterface);
Here the interface is initialized and then registered with the Interface Manager. Note that a world does not have to be loaded to use interfaces.
