Rochard Inspired Physics Game (C++)

Home / Portfolio / Programming / Rochard Inspired Physics Game (C++)

A physics puzzler in a 2.5D world

Responsible for: (C++)

  • Character Controls (movement and Objects control)
  • Environment mechanics
  • Energy fields
  • Spawners
  • Collision group setup
  • Triggers
  • Forcefield implementation
  • instant Save and Load

This game was an exam assignment to prove our skills with C++. The gameplay mechanics were derived from Rochard, a unity game. These mechanics include the energyfield systems and the idea of several puzzles.

The mechanics of the game consist of you as a character who has the ability to lift objects that are close enough to see. You can use these objects to activate triggers and cross gaps. The energyfields have several settings, first is the green energyfield. It has the ability to block objects but does not block the character. Second is the blue energyfield that works like the green energyfield but reversed. Last is the red energyfield that blocks all objects. The game also has forcefields that can have objects floating on them and triggers and keystones that can unlock doors or disable energyfields.

Problems:

  • Energyfield collisions: the problem I had with these was finding which exact setting had to be set right to enable the energyfield to only block the correct entities.
  • Saving & Loading: I had crashes in strange places when loading because of a pointer inside the engine (a simple, component based engine developed by school) was not correctly set when I removed my camera and added a new one. This then caused the program to write on released memory causing a crash when a new was called.

Below you can find a sample of several different classes.

The Energyfields:

#include "EnergyField.h"
#include "../Materials/TextureMaterial.h"
#include "../Materials/EnergyRippleGSMaterial.h"
#include "OverlordComponents.h"

EnergyField::EnergyField(const tstring& modelFile,const tstring& collisionFile,FieldType type):
	m_type(type),
	m_modelFile(modelFile),
	m_collisionFile(collisionFile),
	m_pMaterial(nullptr),
	m_MaxImpactTime(2.0f)
{

}

EnergyField::~EnergyField(void)
{
	SafeDelete(m_pMaterial);
}

void EnergyField::Initialize()
{
	//material
	if(m_pMaterial == nullptr){
		m_pMaterial = new EnergyRippleGSMaterial();
		m_pMaterial->SetTexture(_T("./Resources/Rochard/EnergyFieldPreEdit.png"));
	}

	//create a concave mesh
	MeshColliderComponent* collider = new MeshColliderComponent(m_collisionFile,false);

	//depending on the behaviour of the energyfield, set it's collisiongroup & colour
	switch(m_type)
	{
	case BLOCK_ALL:
		{
			// group 0 collides with everything
			collider->SetGroup(0);
			m_pMaterial->SetColor(D3DXCOLOR(1,0,0,1));
			break;
		}
	case BLOCK_OBJECTS:
		{
			// group 3 collides with all movable objects (crates, energy cores,...)
			collider->SetGroup(3);
			m_pMaterial->SetColor(D3DXCOLOR(0,1,0,1));
			break;
		}
	case BLOCK_PLAYER:
		{
			// group 5 only collides with the player
			collider->SetGroup(5);
			m_pMaterial->SetColor(D3DXCOLOR(0,0,1,1));
			break;
		}
	}

	//create a rigidbody to handle all attached meshcolliders
	RigidBodyComponent* body = new RigidBodyComponent(true);

	body->SetStatic(true);	

	//model
	auto model = new ModelComponent(m_modelFile);

	model->SetMaterial(m_pMaterial);

	//add all components to the gameobject
	this->AddComponent(body);
	this->AddComponent(collider);
	this->AddComponent(model);

	//init everything
	GameObject::Initialize();
}
void EnergyField::Update(GameContext& context)
{
	// handle all the impacts that have been created so they ripple correctly across the model

	//if there are impacts currently registered, check if any need to be erased
	if(m_Impacts.size()>0)
	{
		//if any exceed the maximum impact time, delete them
		if(m_Impacts[0].w >= m_MaxImpactTime){
			m_Impacts.erase(m_Impacts.begin());
		}
	}

	//adjust all remaining impact timers
	for(unsigned int i = 0; i < m_Impacts.size();++i)
	{
		m_Impacts[i].w += context.GameTime.ElapsedSeconds();
	}

	//update the materials internal timer
	m_pMaterial->UpdateTimer(context.GameTime.ElapsedSeconds());
}
void EnergyField::CreateImpact(D3DXVECTOR3 impactPoint)
{
	//when an object collides with the energyfield, create and impact at that point with its timer set to default 0
	m_Impacts.push_back(D3DXVECTOR4(impactPoint,0));
}

My character controller:

#include "RochardController.h"
#include "../OverlordEngine/Scenegraph/GameScene.h"
#include "BackPlane.h"
#include "../Materials/TextureMaterial.h"

RochardController::RochardController(float width, float height, float moveSpeed, int forwardKeyCode, int backwardKeyCode, int leftKeyCode, int rightKeyCode, int rotationMouseButtonCode)
	:m_Width(width),
	m_Height(height),
	m_MoveSpeed(moveSpeed),
	m_ForwardKeyCode(forwardKeyCode),
	m_BackwardKeyCode(backwardKeyCode),
	m_LeftKeyCode(leftKeyCode),
	m_RightKeyCode(rightKeyCode),
	m_RotationMouseButtonCode(rotationMouseButtonCode),
	m_pController(nullptr),
	m_TotalPitch(0.0f),
	m_TotalYaw(0.0f),
	m_RotationSpeed(static_cast(D3DX_PI/2)),
	m_Velocity(0.0f,0.0f,0.0f),
	m_MaxRunVelocity(0.5f),
	m_Gravity(1.0f),
	m_RunAccelerationTime(1.0f),
	m_JumpAccelerationTime(0.3f),
	m_RunAcceleration(m_MaxRunVelocity/m_RunAccelerationTime),
	m_JumpAcceleration(m_Gravity/m_JumpAccelerationTime),
	m_RunVelocity(0.0f),
	m_JumpVelocity(0.0f),
	m_TerminalVelocity(10.0f),
	m_JumpStartSpeed(0.7f)
{
}

RochardController::~RochardController(void)
{
	SafeDelete(m_pMaterial);
}

void RochardController::Initialize()
{
	// create the character controller and its capsule
	m_pController = new ControllerComponent(m_Width,m_Height);

	// add the charactermodel
	auto model = new ModelComponent(_T("./Resources/Rochard/robot.ovm"));

	// attach the plane needed for raycasting at the characters depth
	m_pBackPlane = new BackPlane(100,180);
	AddChild(m_pBackPlane);

	// set material
	m_pMaterial = new TextureMaterial(_T("./Resources/Rochard/robot_diff.png"));
	model->SetMaterial(m_pMaterial);

	// add the components and init everything
	AddComponent(model);
	AddComponent(m_pController);

	GameObject::Initialize();

}

bool RochardController::HasContact(D3DXVECTOR3 position) const
{
	// create a downward ray to check if the character is standing on the ground
	NxRay ray;
	ray.dir = NxVec3(0,-1,0);
	ray.orig = NxVec3(position.x, position.y, position.z);

	NxRaycastHit hit;

	// all collisiongroups to check of the character needs to sink through or not
	NxU16 levelCG = 0;
	NxU16 objCG = 1;
	NxU16 energyFieldCG = 5;

	// right shift to form the correct mask and raycast downward

	//check for the level && for energyfields blocking everything
	NxU32 currentCG = 1 << levelCG;

	auto shape1 = PhysicsManager::GetInstance()->GetClosestShape(GetGameScene()->GetPhysicsScene(),ray,currentCG,&hit,NX_STATIC_SHAPES,m_Height );
	if(shape1 != nullptr)
		return true;

	//check if standing on objects
	currentCG = 1 << objCG;

	auto shape2 = PhysicsManager::GetInstance()->GetClosestShape(GetGameScene()->GetPhysicsScene(),ray,currentCG,&hit,NX_DYNAMIC_SHAPES,m_Height );
	if(shape2 != nullptr)
		return true;

	//check if standing on an energyfield
	currentCG = 1 << energyFieldCG;

	auto shape3 = PhysicsManager::GetInstance()->GetClosestShape(GetGameScene()->GetPhysicsScene(),ray,currentCG,&hit,NX_STATIC_SHAPES,m_Height );
	if(shape3 != nullptr)
		return true;

	return false;
}
bool RochardController::HasTopContact(D3DXVECTOR3 position) const
{
	//check if the character is hitting the ceiling
	NxRay ray;
	ray.dir = NxVec3(0,1,0);
	ray.orig = NxVec3(position.x, position.y, position.z);

	NxRaycastHit hit;

	NxU16 levelCG = 0;
	NxU16 objCG = 1;
	NxU16 energyFieldCG = 5;

	// right shift to form the correct mask and raycast upward

	//check for the level && for energyfields blocking everything
	NxU32 currentCG = 1 << levelCG;

	auto shape1 = PhysicsManager::GetInstance()->GetClosestShape(GetGameScene()->GetPhysicsScene(),ray,currentCG,&hit,NX_STATIC_SHAPES,m_Height + 0.1 );
	if(shape1 != nullptr)
		return true;

	// check for objects
	currentCG = 1 << objCG;
	auto shape2 = PhysicsManager::GetInstance()->GetClosestShape(GetGameScene()->GetPhysicsScene(),ray,currentCG,&hit,NX_DYNAMIC_SHAPES,m_Height + 0.1 );
	if(shape2 != nullptr)
		return true;

	// check for energyfields blocking characters
	currentCG = 1 << energyFieldCG;
	auto shape3 = PhysicsManager::GetInstance()->GetClosestShape(GetGameScene()->GetPhysicsScene(),ray,currentCG,&hit,NX_STATIC_SHAPES,m_Height + 0.1);
	if(shape3 != nullptr)
		return true;

	return false;
}

void RochardController::Update(GameContext& context)
{

	auto diff = D3DXVECTOR3(0.0f,0.0f,0.0f);
	auto input = context.Input;

	//handle controls:
	//keyboard input
	diff.x += input->IsKeyboardKeyDown(m_RightKeyCode)?1.0f:0.0f;
	diff.x += input->IsKeyboardKeyDown(m_LeftKeyCode)?-1.0f:0.0f;
	//controller input
	if(diff.x == 0)diff.x= input->GetThumbstickPosition().x;

	//get the current position of the character
	auto transform = GetComponent();
	auto currPos = m_pController->GetTranslation();

	//if there is no input don't change anything
	if(diff.x !=0 || diff.y !=0)
	{
		//clip it
		if(m_Velocity.x > m_MaxRunVelocity)
		{
			m_Velocity.x = m_MaxRunVelocity;
		}
		else if(m_Velocity.x < -m_MaxRunVelocity)
		{
			m_Velocity.x = -m_MaxRunVelocity;
		}
		//add the acceleration to the velocity of the character
		m_Velocity.x += diff.x * m_RunAcceleration *context.GameTime.ElapsedSeconds();
	}
	else
	{
		//if there is no input the character stops moving
		m_Velocity.x = 0;
	}

	//gravity
	if(!HasContact(currPos))
	{
		//add gravity variable to the velocity when in air
		m_Velocity.y -= m_JumpAcceleration * context.GameTime.ElapsedSeconds();
		//clip it
		if(m_Velocity.y < -m_TerminalVelocity)
		{
			m_Velocity.y = -m_TerminalVelocity;
		}
	}
	else if(input->IsKeyboardKeyDown(VK_SPACE) || input->IsGamepadButtonDown(XINPUT_GAMEPAD_A))
	{
		//jump when touching the floor
		m_Velocity.y = m_JumpStartSpeed;
	}
	else if(HasTopContact(currPos))
	{
		//when you hit your head, immediatly make your vertical velocity negative
		m_Velocity.y = -m_JumpAcceleration * context.GameTime.ElapsedSeconds();
	}
	else
	{
		//in all other cases vertical velocity is 0
		m_Velocity.y = 0;
	}

	//safeguarding z position && moving
	m_Velocity.z = 0;
	m_pController->Move(m_Velocity);

	// broken childcode fix
	m_pBackPlane->GetComponent()->Translate(m_pController->GetTranslation()); 

	GameObject::Update(context);
}