I'm writing a C++ game engine and I have a scene class that I'd like to present as a code example to a team that is interested in my skills.
The scene class holds games entities, updating and rendering them using a camera.
I'm interested in the stylistic or functional improvements that could be made to improve this class, as well as any possible optimisations. In particular, I was told by an industry professional that smart pointers (like shared_ptr
) are frowned upon in game engines because of instances where reference counting adds an overhead. Is there truth in this?
Scene.h
#pragma once
#include <memory>
#include "Entity.h"
#include "Renderer.h"
#include "Window.h"
#include "Components\Camera.h"
namespace Core
{
// A class that represents a level or screen within the game. It maintains
// a collection of entities which make up the objects in the scene. These are
// updated and rendered each frame. The scene must be given a specified camera
// entity to view the world.
class Scene
{
public:
// An exception used by a scene to declare that an entity is missing
// a required component.
class MissingComponentException : public std::runtime_error
{
public:
MissingComponentException(const std::string& component, const std::string& message)
: std::runtime_error(component + " component missing: " + message){}
};
// Construct a new empty scene
Scene();
// Deconstructs the scene and any entities that it contains
~Scene();
// Adds an entity to the current scene. The scene will delete this
// entity when it is deleted.
void AddEntity(Core::Entity* entity);
// Gets an entity from the scene by name and returns it by pointer.
Core::Entity* GetEntity(std::string name);
// Sets the entity which will be used as the camera for rendering the
// scene. This entity must have a camera component otherwise
// MissingComponentException will be thrown.
void SetCameraEntity(Core::Entity* newCameraEntity);
// Updates the scene, should be called every frame. Calls update on
// all the entities that the scene contains.
void Update();
// Renders the scene using the given renderer and window.
void Render(std::shared_ptr<Core::Renderer> renderer, std::shared_ptr<Core::Window> window);
// Post updates the scene, removing any entities marked for deletion and
// adding any entities in the entitiesToAdd list.
void Post();
protected:
// Use of raw pointers chosen here specifically for efficiency,
// i.e. a reduce overhead when passing objects around over
// std::shared_ptr.
Components::Camera* camera;
// Use of raw pointers chosen here specifically for efficiency,
// i.e. a reduce overhead when passing objects around over
// std::shared_ptr.
// Use of std::vector here as the number of entities is likely
// to vary a lot during the scenes life time.
std::vector<Core::Entity*> entities;
// Entities that will be added in the Post phase of the scene
// update.
std::vector<Core::Entity*> entitiesToAdd;
// The projection transform used to define perspective, it requires:
// - The y field of view
// - The aspect ratio
// - The near plane distance
// - The far plane distance
// When a camera component is suppled it is retrieved from this
// the component during render.
glm::mat4 projectionTransform;
// The view transform used to define where the view is looking from
// and to, it requires:
// - An eye position
// - A focus position
// - An up vector
// When a camera component is suppled it is retrieved from this
// the component during render.
glm::mat4 viewTransform;
};
}
Scene.cpp
#include "Scene.h"
#include <iostream>
#include "glm\glm.hpp"
#include "glm\gtc\matrix_transform.hpp"
#include "Components\Model.h"
#include "Components\Transform.h"
#include "Components\Camera.h"
#include "Structures\Mesh.h"
#include "Utility\Maths.h"
namespace Core
{
Scene::Scene() :
camera(nullptr),
projectionTransform(glm::perspective(Utility::Maths::PI / 4.0f, 1.0f, 0.1f, 100.0f)), // A default projection transform (should never be used)
viewTransform(glm::lookAt(glm::vec3(1,0,0),glm::vec3(0,0,0), glm::vec3(0,1,0))) // A default view transform (should never be used)
{}
Scene::~Scene()
{
for(unsigned int i = 0; i < entities.size(); i++)
{
delete entities[i];
}
entities.clear();
}
void Scene::AddEntity(Core::Entity* entity)
{
entity->SetScene(this);
// This entity will be added in the Post phase of the update cycle
entitiesToAdd.push_back(entity);
}
Core::Entity* Scene::GetEntity(std::string name)
{
Entity* matchingEntity = nullptr;
for(unsigned int i = 0; i < entities.size() && matchingEntity == nullptr; i++)
{
if(entities[i]->GetName().compare(name) == 0)
{
matchingEntity = entities[i];
}
}
return matchingEntity;
}
void Scene::SetCameraEntity(Core::Entity* cameraEntity)
{
if(cameraEntity->GetComponent<Components::Camera>() == nullptr)
{
throw MissingComponentException("Camera", "Provided camera entity not does not have a camera component.");
}
camera = cameraEntity->GetComponent<Components::Camera>();;
}
void Scene::Update()
{
for(unsigned int i = 0; i < entities.size(); i++)
{
entities[i]->Update();
}
}
void Scene::Render(std::shared_ptr<Core::Renderer> renderer, std::shared_ptr<Core::Window> window)
{
if(camera == nullptr)
{
// Attempting to render without a camera... give up.
return;
}
projectionTransform = camera->GetProjectionMatrix(window);
viewTransform = camera->GetViewMatrix();
for(unsigned int i = 0; i < entities.size(); i++)
{
if(entities[i]->HasComponent<Components::Model>())
{
Components::Model* model = entities[i]->GetComponent<Components::Model>();
glm::mat4 modelTransform = glm::mat4(1.0f);
if(entities[i]->HasComponent<Components::Transform>())
{
Components::Transform* transform = entities[i]->GetComponent<Components::Transform>();
modelTransform = transform->GetMatrix();
}
renderer->RenderMesh(
model->GetMesh(),
model->GetTexture(),
model->GetShader(),
projectionTransform,
viewTransform,
modelTransform
);
}
}
}
void Scene::Post()
{
for(unsigned int i = 0; i < entities.size(); i++)
{
if(entities[i]->IsDestroyed())
{
entities.erase(entities.begin() + i);
delete entities[i];
}
}
for(unsigned int i = 0; i < entitiesToAdd.size(); i++)
{
entities.push_back(entitiesToAdd[i]);
}
entitiesToAdd.clear();
}
}