1. BGE: An open-source framework
for teaching component
based games development in C++
Dr Bryan Duggan
DIT School of Computing
bryan.duggan@dit.ie
@ditcomputing
http://facebook.com/ditschoolofcomputing
2. Questions we will answer today
•
•
•
•
•
•
•
What is BGE?
Motivation
How are 3D Graphics rendered?
Calculating the world transform
Calculating the view & projection transforms
Component based development
Examples in BGE
4. How does BGE Work?
• OpenGL for rendering
– Vertex shaders & Fragment shaders (OpenGL 4)
• GLEW
– The OpenGL Extension Wrangler Library (GLEW) is a crossplatform open-source C/C++ extension loading library.
GLEW provides efficient run-time mechanisms for
determining which OpenGL extensions are supported on
the target platform. OpenGL core and extension
functionality is exposed in a single header file. GLEW has
been tested on a variety of operating systems, including
Windows, Linux, Mac OS X, FreeBSD, Irix, and Solaris.
• GLM
– OpenGL Maths Library
5. • SDL - Simple DirectMedia Library
– A cross-platform multimedia library designed to provide fast access to
the graphics framebuffer and audio device.
– Initialises OpenGL
– Creates the OpenGL context
– Provides an abstraction for keyboard/mouse/joystick
– SDL_TTF for TTF Font support
• FMOD – Closed source Xplatform audio library
– FMOD is a programming library and toolkit for the creation and
playback of interactive audio.
– MP3/WAV/MIDI playback
– 3D Audio
– Occlusion/doppler/effects etc
– Free for non-commercial use
6. • Bullet
– Bullet 3D Game Multiphysics Library provides state of the
art collision detection, soft body and rigid body dynamics.
– Rigid bodies, constraints etc
– A solver
• C++
– Smart pointers – shared pointers
– STL
• git
– Labs, lectures, solutions
– Git/github is amazing. You should use it.
– Mandated for all DT228/DT211 students now
7. How are 3D Graphics Rendered
in BGE?
Vertex data
in world
space
Vertex
shader
Textures
Model/World Matrix
View Matrix
Projection Matrix
Normal Matrix
MVP Matrix
Fragment
Shader
Screen
8. I prefer…
Vertices
The vertices
as they come out
of a 3D modelling
program.
The centre of
the model is
usually the
origin
Model
/World
Places the model
in the world
relative to all the
other objects
View
Transforms
everything
relative to the
camera (0,0,0)
looking down
the –Z Axis
Projection
Viewport
Clipping
Projects
Often does
everything
nothing special
onto a
but can be
2D plane.
a different
Far away
render target
objects are (such as a texture)
smaller
9. Calculating the world transform
• Combination of the position, orientation and
scale
– Position & scale & vectors
– Orientation is a quaternion
• world = glm::translate(glm::mat4(1), position)
* glm::mat4_cast(orientation) *
glm::scale(glm::mat4(1), scale);
13. The Game loop
• Initialise()
• While (true)
– Update(timeDelta)
– Draw()
• End while
• Cleanup()
14. What are design patterns?
• A very famous book
• Published in 1994
• Common solutions to
problems in software
developmnent
• Part 1
– The capabilities and
pitfalls of object-oriented
programming
• Part 2
– 23 classic software design
patterns
15. Principles
•
Program to an 'interface', not an 'implementation‘
–
–
•
•
Interfaces lead to dynamic binding and polymorphism (central features of objectoriented programming).
Inheritance is referred to as white-box reuse
–
•
•
The internals of parent classes are often visible to subclasses
Object composition is black-box reuse
–
•
Clients are unaware of the specific types of objects they use, as long as the object adheres to the
interface
Clients remain unaware of the classes that implement these objects; clients only know about the
abstract class(es) defining the interface
No internal details of composed objects need be visible in the code using them
"Because inheritance exposes a subclass to details of its parent's implementation,
it's often said that 'inheritance breaks encapsulation'“
Using inheritance is recommended mainly when adding to the functionality of
existing components, reusing most of the old code and adding relatively small
amounts of new code
17. Some more!
• Lazy loading
– Loading on demand. Defer initialization of an
object until the point at which it is needed
• Instancing
– Rendering multiple copies of the same mesh in a
scene at once
• Controller
– Update a game objects state based on
circumstance
18. Some goals for BGE
(or any other game engine)
• Keep track of everything in the scene
• Allow game objects to be assembled from
components – increase code reuse and flexibility
• Allow easy creation of composite objects such as
physics objects
• Allow lazy loading of game assets
• Support instancing
• State management of game components
19. Object Oriented Game Engines
• Are terrible. I know I made one (Dalek World)
• Consider:
20. Problems!
• Each new piece of functionality you want to
add to a class becomes a new (more specific
class)
• Too many classes
• No flexibility
• Tight coupling
21. A better approach
• The aggregate design pattern
Game Component
Initialise()
Update(timeDelta)
Draw()
Cleanup()
Attach(GameComponent c)
list<GameComponent> children
0..*
22. Architecture?
• GameObjects, GameComponents and Transforms
– Circular dependancies
– Difficult to compile in C++
• GameComponents with own transforms
– Rules for copying transforms
– Lots of duplication
• GameComponents with shared transforms
– BGENA2
– How to store transform hierarchies?
– Duplicated calculations
23. Component Based Games Engines
• Everything in BGE is a component
• Most things extend GameComponent
–
–
–
–
virtual bool Initialise();
virtual void Update(float timeDelta);
virtual void Draw();
virtual void Cleanup();
• GameComponent’s keep track of a list of children
components & parent component
– std::list<std::shared_ptr<GameComponent>>
children;
• This is known as the aggregate design pattern
25. The base class GameComponent
•
•
•
•
•
Holds a list of GameComponent children references
Use Attach() to add something to the list.
Calls Initialise, Update and Draw on all children
All subclasses do their own work first then
Must call the base class member function so that the
children get Initialised, Updated and Drawn!
– Are these depth first or breadth first?
• This means that the scene is a graph of objects each
contained by a parent object
• The parent object in BGE is the Game instance
26. bool GameComponent::Initialise()
{
// Initialise all the children
std::list<std::shared_ptr<GameComponent>>::iterator it = children.begin();
while (it != children.end())
{
(*it ++)->initialised = (*it)->Initialise();
}
return true;
}
27. void GameComponent::Cleanup()
{
// Cleanup all the children
std::list<std::shared_ptr<GameComponent>>::iterator it =
children.begin();
while (it != children.end())
{
(*it ++)->Cleanup();
}
}
void GameComponent::Draw()
{
// Draw all the children
std::list<std::shared_ptr<GameComponent>>::iterator it =
children.begin();
while (it != children.end())
{
if ((*it)->worldMode == GameComponent::from_parent)
{
(*it)->parent = this;
(*it)->UpdateFromParent();
}
(*it ++)->Draw();
The child object is
}
}
controlled by the parent
it is attached to
An example is a model
28. void GameComponent::Update(float timeDelta) {
switch (worldMode)
{
case world_modes::from_self:
world = glm::translate(glm::mat4(1), position) * glm::mat4_cast(orientation) * glm::scale(glm::mat4(1), scale);
break;
case world_modes::from_self_with_parent:
world = glm::translate(glm::mat4(1), position) * glm::mat4_cast(orientation) * glm::scale(glm::mat4(1), scale);
if (parent != NULL)
{
world = (glm::translate(glm::mat4(1), parent->position) * glm::mat4_cast(parent->orientation)) *
world;
}
break;
case world_modes::to_parent:
world = glm::translate(glm::mat4(1), position) * glm::mat4_cast(orientation) *
parent->world = glm::scale(world, parent->scale);
parent->position = this->position;
parent->up = this->up;
parent->look = this->look;
parent->right = this->right;
parent->orientation = this->orientation;
glm::scale(glm::mat4(1), scale);
break;
}
RecalculateVectors();
moved = false;
// Update all the children
std::list<std::shared_ptr<GameComponent>>::iterator it = children.begin();
while (it != children.end())
{
if (!(*it)->alive)
{
it = children.erase(it);
}
else
{
(*it ++)->Update(timeDelta);
}
}
}
The parent is
controlled by a child
The child is known
as a Controller
29.
30. Attaching!
• You can attach a component to another component:
child->parent = this;
// All my children share the same
transform if they dont already have one...
if (child->transform == nullptr)
{
child->transform = transform;
}
children.push_back(child);
32. Member functions
• void Strafe(float units);
• void Fly(float units);
• void Walk(float units);
• void Pitch(float angle); // rotate on right
vector
• void Yaw(float angle); // rotate on up vector
• void Roll(float angle); // rotate on look vector
33. Yaw Example
• void GameComponent::Yaw(float angle)
• {
• // A yaw is a rotation around the global up
vector
• glm::quat rot = glm::angleAxis(angle,
GameComponent::basisUp);
• orientation = rot * orientation;
• }
34.
35. Making game objects from
components
• You can use these rules to assemble
composite objects together. For example:
– A component with a model attached and a
steeringcontroller attached
– The steeringcontroller shares the parent world
transform
– The model gets its world from the parent
– You can attach different controllers to get different
effects.
– Examples…
36.
37. Using steeringbehaviours
• SteeringController implements lots of cool
steering behaviours such as follow_path, seek,
obstacle_avoidance
• Can be attached to anything and it will update
the world transform of the thing it’s attached
to
• See 9 & 11 for examples
• 12 is just a textured model. Nothing special
• An example in code…
38. •
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
//// from_self_with_parent
station = make_shared<GameComponent>();
station->worldMode = world_modes::from_self;
station->ambient = glm::vec3(0.2f, 0.2, 0.2f);
station->specular = glm::vec3(0,0,0);
station->scale = glm::vec3(1,1,1);
std::shared_ptr<Model> cmodel = Content::LoadModel("coriolis", glm::rotate(glm::mat4(1),
90.0f, GameComponent::basisUp));
station->Attach(cmodel);
station->Attach(make_shared<VectorDrawer>(glm::vec3(5,5,5)));
Attach(station);
// Add a child to the station and update by including the parent's world transform
std::shared_ptr<GameComponent> ship1 = make_shared<GameComponent>();
ship1->worldMode = world_modes::from_self_with_parent;
ship1->ambient = glm::vec3(0.2f, 0.2, 0.2f);
ship1->specular = glm::vec3(1.2f, 1.2f, 1.2f);
std::shared_ptr<Model> ana = Content::LoadModel("anaconda", glm::rotate(glm::mat4(1),
180.0f, GameComponent::basisUp));
ship1->Attach(ana);
ship1->position = glm::vec3(0, 0, -10); // NOTE the ship is attached to the station at an offset
of 10
station->Attach(ship1);.
39. VR
• Render the scene twice
– Left eye and right eye
• Split the screen in two
• Combine quaternion from Xbox controller
with quaternion from rift