The following code represents a "little" 2D engine I developed to play around a bit. The code went into one header which isn't the best practice, but I wanted to have the engine as a plug and play header so I won't have to add multiple files into a project.
The engine is targeted on windows and works with GDI+ as graphics library.
I decided not to include the "regular" physic part which works with regular gravity, since that would have overblown the post even more. The only physics possible at the moment are kind of zero-gravity physics. The physic calculations aren't reality-conform but they do an ok job for the intended usage.
The engine is pretty limited, but should be absolutly sufficient to roll smaller games like Space Invaders like games or tetris and pong. With the gravity part it would even be able to handle smaller sidescroll shooter.
I activly decided against the use of sprite sheets due to laziness but I am aware, that using sprite sheets would lead to better performance overall.
The engine has a really minimalistic component system which can be extended pretty easy and the utility::Actor can easily be replaced with a customized class which inherits from the regular Actor.
#include <Windows.h>
#include <stdio.h>
#include <gdiplus.h>
#include <dshow.h>
#include <string>
#include <memory>
#include <vector>
#include <algorithm>
#include <conio.h>
#include <ctime>
#include <functional>
#include <map>
#pragma comment(lib,"gdiplus.lib")
#pragma comment(lib, "winmm.lib")
#pragma warning( disable : 4996 )
namespace utility
{
//I shifted the Vector2 struct into a seperate namespace so I can
//make the operators global. Later I loaded Vector2 into the
//utility namespace so it's as easy accessible as the rest.
//Vector2 is a 2D vector thought to store positions and motions-
namespace operators
{
struct Vector2;
inline Vector2 operator + (const Vector2& a, const Vector2& b);
inline Vector2 operator - (const Vector2& a, const Vector2& b);
inline Vector2 operator * (const Vector2& a, const double& d);
inline Vector2 operator / (const Vector2& a, const double& d);
struct Vector2
{
Vector2(int x = 0, int y = 0)
: X(x), Y(y)
{}
Vector2& operator += (const Vector2& a)
{
Vector2& me = *const_cast<Vector2*>(this);
me = me + a;
return me;
}
Vector2& operator -= (const Vector2& a)
{
Vector2& me = *const_cast<Vector2*>(this);
me = me - a;
return me;
}
Vector2& operator *= (const double& d)
{
Vector2& me = *const_cast<Vector2*>(this);
me = me * d;
return me;
}
Vector2& operator /= (const double& d)
{
Vector2& me = *const_cast<Vector2*>(this);
me = me / d;
return me;
}
int X;
int Y;
};
inline Vector2 operator + (const Vector2& a, const Vector2& b)
{
return Vector2(a.X + b.X, a.Y + b.Y);
}
inline Vector2 operator - (const Vector2& a, const Vector2& b)
{
return Vector2(a.X - b.X, a.Y - b.Y);
}
inline Vector2 operator * (const Vector2& a, const double& d)
{
return Vector2((int)(a.X * d), (int)(a.Y * d));
}
inline Vector2 operator / (const Vector2& a, const double& d)
{
return Vector2((int)(a.X / d), (int)(a.Y / d));
}
}
using Vector2 = operators::Vector2;
//The FrameBuffer contains frame informations and the GDI graphics
struct FrameBuffer
{
FrameBuffer(Vector2 res, HWND window = GetConsoleWindow())
{
wnd = window;
Memhdc = 0;
hdc = GetDC(wnd);
GetClientRect(wnd, &Client_Rect);
Memhdc = CreateCompatibleDC(hdc);
Membitmap = CreateCompatibleBitmap(hdc, res.X, res.Y);
SelectObject(Memhdc, Membitmap);
Graphics = Gdiplus::Graphics::FromHDC(Memhdc);
}
~FrameBuffer()
{
HDC Memhdc = 0;
HDC hdc = 0;
HBITMAP Membitmap = 0;
HWND wnd = 0;
SendMessage(wnd, WM_CLOSE, 0, 0);
}
RECT Client_Rect;
HDC Memhdc;
HDC hdc;
HBITMAP Membitmap;
HWND wnd;
Gdiplus::Graphics* Graphics;
};
//Simple object to initalize GDI
class GDI
{
public:
GDI()
{
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
}
~GDI()
{
Gdiplus::GdiplusShutdown(gdiplusToken);
}
private:
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
};
inline const wchar_t * to_wchar(const char *c)
{
const size_t cSize = strlen(c) + 1;
wchar_t* wc = new wchar_t[cSize];
mbstowcs(wc, c, cSize);
return wc;
}
//Simple representation of a 2D sprite
class Sprite
{
public:
using GSprite = std::shared_ptr<Gdiplus::Image>;
Sprite(Vector2 position, Vector2 resolution)
: m_Bitmap(nullptr), m_Position(position), m_Resolution(resolution)
{}
Sprite(const Sprite& spr)
: m_Bitmap(spr.m_Bitmap), m_Position(spr.m_Position), m_Resolution(spr.m_Resolution)
{}
Sprite& operator = (const Sprite& spr)
{
if (m_Bitmap)
{
m_Bitmap.reset();
}
m_Bitmap = spr.m_Bitmap;
m_Position = spr.m_Position;
m_Resolution = spr.m_Resolution;
return *const_cast<Sprite*>(this);
}
~Sprite()
{}
void loadSprite(std::string name, std::string path = "")
{
std::wstring nameAndPath = to_wchar((path + name + std::string(".png")).c_str());
m_Bitmap = std::make_shared<Gdiplus::Image>(nameAndPath.c_str());
}
void draw(Gdiplus::Graphics* graphics) const
{
Gdiplus::RectF ImgRect((Gdiplus::REAL)m_Position.X, (Gdiplus::REAL)m_Position.Y, (Gdiplus::REAL)m_Resolution.X, (Gdiplus::REAL)m_Resolution.Y);
graphics->DrawImage(m_Bitmap.get(), ImgRect);
}
void clear(Gdiplus::Graphics* graphics)
{
graphics->Clear(RGB(0, 0, 0));
}
void setPosition(Vector2 position)
{
m_Position = position;
}
void move(Vector2 motion)
{
m_Position += motion;
}
Vector2 getResolution() const
{
return m_Resolution;
}
Vector2 getPosition() const
{
return m_Position;
}
private:
GSprite m_Bitmap;
Vector2 m_Position;
Vector2 m_Resolution;
};
//Contains a collection of sprites and data to paly animations
class Animation
{
public:
Animation(std::string animationName, Vector2 position, Vector2 resolution)
: m_AnimationName(animationName), m_Position(position), m_Resolution(resolution), m_CurrentFrame(0)
{}
Animation(const Animation& anim)
: m_Frames(anim.m_Frames), m_AnimationName(anim.m_AnimationName), m_Position(anim.m_Position), m_Resolution(anim.m_Resolution), m_CurrentFrame(0)
{}
Animation& operator = (const Animation& anim)
{
m_Frames = anim.m_Frames;
m_AnimationName = anim.m_AnimationName;
m_Position = anim.m_Position;
m_Resolution = anim.m_Resolution;
m_CurrentFrame = 0;
return *const_cast<Animation*>(this);
}
void draw(Gdiplus::Graphics* graphics) const
{
m_Frames.at(m_CurrentFrame)->draw(graphics);
}
void update()
{
m_CurrentFrame + 1 == m_Frames.size() ? m_CurrentFrame = 0 : ++m_CurrentFrame;
}
void loadAnimation(std::string name, int numberOfFrames, std::string path = "")
{
for (int frameToLoad = 0; numberOfFrames > frameToLoad; ++frameToLoad)
{
m_Frames.push_back(std::make_shared<Sprite>(m_Position, m_Resolution));
m_Frames.back()->loadSprite(name + std::to_string(frameToLoad), path);
}
}
void setPosition(Vector2 position)
{
std::for_each(m_Frames.begin(), m_Frames.end(), [&](std::shared_ptr<Sprite> sprite)->void { sprite->setPosition(position); });
}
void move(Vector2 motion)
{
std::for_each(m_Frames.begin(), m_Frames.end(), [&](std::shared_ptr<Sprite> sprite)->void { sprite->move(motion); });
}
Vector2 getResolution() const
{
return m_Resolution;
}
Vector2 getPosition() const
{
return m_Position;
}
private:
std::vector<std::shared_ptr<Sprite> > m_Frames;
std::string m_AnimationName;
Vector2 m_Position;
Vector2 m_Resolution;
int m_CurrentFrame;
};
//General object for graphical interactions like drawing, etc
class GraphicController
{
public:
GraphicController(HWND window, Vector2 resolution)
: m_Resolution(resolution), m_Buffer(m_Resolution, window), m_GDI(InitGDI())
{}
template <class T> void draw(const T& sprite) const
{
sprite.draw(m_Buffer.Graphics);
}
void present()
{
BitBlt(m_Buffer.hdc, 0, 0, m_Resolution.X, m_Resolution.Y, m_Buffer.Memhdc, 0, 0, SRCCOPY);
}
void clearScreen()
{
m_Buffer.Graphics->Clear(Gdiplus::Color::Black);
present();
}
void clearBuffer()
{
m_Buffer.Graphics->Clear(Gdiplus::Color::Black);
}
private:
static std::shared_ptr<GDI> InitGDI()
{
static std::shared_ptr<GDI> StaticGDI(new GDI);
return StaticGDI;
}
Vector2 m_Resolution;
std::shared_ptr<GDI> m_GDI;
FrameBuffer m_Buffer;
};
//checks if key was pressed
inline int check_key()
{
return _kbhit();
}
//get key input
inline int get_key()
{
return _getch();
}
//returns mouse position
inline Vector2 get_mouse(HWND window = GetConsoleWindow())
{
POINT pos;
GetCursorPos(&pos);
ScreenToClient(window, &pos);
return Vector2(pos.x, pos.y);
}
//Inverts capital to no capital and vice versa
inline char invert_character(char character)
{
return character ^ 0x20;
}
//starts playing a sound
inline void playSound(std::string path)
{
std::string command = "play ";
command.append(path);
mciSendString(command.c_str(), NULL, 0, NULL);
}
//stops playing last started sound
inline void stopSound(std::string path)
{
std::string command = "stop ";
command.append(path);
mciSendString(command.c_str(), NULL, 0, NULL);
}
//Simple stop watch
class Timer
{
public:
Timer()
: m_Total(0.0), m_Start(0), m_End(0), m_Running(false)
{}
double totalTime() const
{
return m_Total;
}
void reset()
{
m_Total = 0;
m_Start = 0;
m_End = 0;
}
void start()
{
if (!m_Running)
{
m_Start = clock();
m_Running = true;
}
}
void stop()
{
if (m_Running)
{
m_End = clock();
m_Total = ((double)(m_End - m_Start)) / CLOCKS_PER_SEC;
m_Running = false;
}
}
void tick()
{
if (m_Running)
{
m_Total = ((double)(clock() - m_Start)) / CLOCKS_PER_SEC;
}
}
private:
double m_Total;
clock_t m_Start;
clock_t m_End;
bool m_Running;
};
//Function to resize the console
inline void resizeConsole(Vector2 resolution)
{
HWND console = GetConsoleWindow();
RECT r;
GetWindowRect(console, &r);
MoveWindow(console, r.left, r.top, resolution.X, resolution.Y, TRUE);
}
}
namespace utility
{
class Actor;
class Component
{
public:
friend Actor;
Component(const std::string& name)
: m_Name(name)
{}
virtual ~Component()
{}
virtual void Update() = 0;
virtual void Move(utility::Vector2 motion) = 0;
virtual void SetPosition(utility::Vector2 pos) = 0;
virtual utility::Vector2 GetPosition() const = 0;
virtual std::string GetName() const
{
return m_Name;
}
Actor* GetOwner() const
{
return m_Owner;
}
private:
void SetOwner(Actor* actor)
{
m_Owner = actor;
}
std::string m_Name;
Actor* m_Owner;
};
}
namespace utility
{
//Info struct which is needed to load animations
struct AnimationInfo
{
AnimationInfo(int id, const std::string& name, int nFrames, const std::string& path)
: animationID(id), fileName(name), numFrames(nFrames), filePath(path)
{}
int animationID;
std::string fileName;
int numFrames;
std::string filePath;
};
class Graphic : public utility::Component
{
public:
Graphic(utility::Vector2 position, utility::Vector2 resolution)
: Component("graphic"), m_Position(position), m_Resolution(resolution), m_FrameDuration(1), m_CurrentFrame(0)
{}
~Graphic()
{
std::for_each(m_Animations.begin(), m_Animations.end(), [&](std::pair<const int, utility::Animation*>& an)->void { delete an.second; });
}
void Load(std::vector<AnimationInfo> animations)
{
std::for_each(animations.begin(), animations.end(), [&](AnimationInfo& info)
{
utility::Animation* tmp = new utility::Animation(std::to_string(info.animationID), m_Position, m_Resolution);
tmp->loadAnimation(info.fileName, info.numFrames, info.filePath);
m_Animations.insert(std::make_pair(info.animationID, tmp));
});
}
void Draw()
{
if (m_Animations.size() != 0)
{
GetController().draw(*m_Animations.at(m_ActiveAnimation));
}
}
void Update()
{
if (m_Animations.size() != 0)
{
if (m_CurrentFrame == m_FrameDuration)
{
m_Animations.at(m_ActiveAnimation)->update();
m_CurrentFrame = 0;
}
else
{
++m_CurrentFrame;
}
}
}
void SetFrameDuration(int n)
{
m_FrameDuration = n;
}
void Move(utility::Vector2 motion)
{
std::for_each(m_Animations.begin(), m_Animations.end(), [&](std::pair<const int, utility::Animation*>& an)->void { an.second->move(motion); });
m_Position += motion;
}
void SetPosition(utility::Vector2 pos)
{
std::for_each(m_Animations.begin(), m_Animations.end(), [&](std::pair<const int, utility::Animation*>& an)->void { an.second->setPosition(pos); });
m_Position = pos;
}
utility::Vector2 GetPosition() const
{
return m_Position;
}
utility::Vector2 GetResolution() const
{
return m_Resolution;
}
void SetActiveAnimation(int animationID)
{
if (m_Animations.find(animationID) != m_Animations.end())
{
m_ActiveAnimation = animationID;
}
}
int GetActiveAnimation() const
{
return m_ActiveAnimation;
}
static utility::GraphicController& GetController()
{
static utility::GraphicController* gc = nullptr;
if (!gc)
{
gc = new utility::GraphicController(GetConsoleWindow(), utility::Vector2(640, 640));
}
return *gc;
}
private:
std::map<int, utility::Animation*> m_Animations;
utility::Vector2 m_Position;
utility::Vector2 m_Resolution;
int m_ActiveAnimation;
int m_FrameDuration;
int m_CurrentFrame;
};
}
namespace utility
{
class Collision : public utility::Component
{
public:
Collision(utility::Vector2 position, utility::Vector2 resolution)
: Component("collision"), m_Position(position), m_Resolution(resolution)
{}
bool CollidesWith(Collision* target)
{
utility::Vector2 p1 = target->GetPosition();
bool condition1 = p1.X >= m_Position.X && p1.X <= m_Position.X + m_Resolution.X && p1.Y >= m_Position.Y && p1.Y <= m_Position.Y + m_Resolution.Y;
utility::Vector2 p2 = target->GetPosition();
p2.X += target->m_Resolution.X;
bool condition2 = p2.X >= m_Position.X && p2.X <= m_Position.X + m_Resolution.X && p2.Y >= m_Position.Y && p2.Y <= m_Position.Y + m_Resolution.Y;
utility::Vector2 p3 = target->GetPosition();
p3.Y += target->m_Resolution.Y;
bool condition3 = p3.X >= m_Position.X && p3.X <= m_Position.X + m_Resolution.X && p3.Y >= m_Position.Y && p3.Y <= m_Position.Y + m_Resolution.Y;
utility::Vector2 p4 = target->GetPosition();
p4.X += target->m_Resolution.X;
p4.Y += target->m_Resolution.Y;
bool condition4 = p4.X >= m_Position.X && p4.X <= m_Position.X + m_Resolution.X && p4.Y >= m_Position.Y && p4.Y <= m_Position.Y + m_Resolution.Y;
return condition1 || condition2 || condition3 || condition4;
}
bool CollidesWith(utility::Vector2 position)
{
utility::Vector2 p1 = position;
bool condition = p1.X >= m_Position.X && p1.X <= m_Position.X + m_Resolution.X && p1.Y >= m_Position.Y && p1.Y <= m_Position.Y + m_Resolution.Y;
return condition;
}
void Update()
{}
void Move(utility::Vector2 motion)
{
m_Position += motion;
}
void SetPosition(utility::Vector2 pos)
{
m_Position = pos;
}
utility::Vector2 GetPosition() const
{
return m_Position;
}
private:
utility::Vector2 m_Position;
utility::Vector2 m_Resolution;
};
}
namespace utility
{
template <class Actor>
class EPhysic : public utility::Component
{
public:
EPhysic(utility::Vector2 position)
: Component("physic"), m_Position(position), m_XSpeed(0.0f), m_YSpeed(0.0f)
{}
void Update()
{
m_XMotionTimer.tick();
m_YMotionTimer.tick();
if (m_XSpeed > 0)
{
double speed = m_XSpeed + (-AirResistance()) * m_XMotionTimer.totalTime();
if (speed > 0)
{
((Actor*)GetOwner())->Move(utility::Vector2((int)std::round(speed), 0));
}
else
{
m_XSpeed = 0;
m_XMotionTimer.stop();
m_XMotionTimer.reset();
}
}
if (m_XSpeed < 0)
{
double speed = m_XSpeed + AirResistance() * m_XMotionTimer.totalTime();
if (speed < 0)
{
((Actor*)GetOwner())->Move(utility::Vector2((int)std::round(speed), 0));
}
else
{
m_XSpeed = 0;
m_XMotionTimer.stop();
m_XMotionTimer.reset();
}
}
if (m_YSpeed > 0)
{
double speed = m_YSpeed + (-AirResistance()) * m_YMotionTimer.totalTime();
if (speed > 0)
{
((Actor*)GetOwner())->Move(utility::Vector2(0, (int)std::round(speed)));
}
else
{
m_YSpeed = 0;
m_YMotionTimer.stop();
m_YMotionTimer.reset();
}
}
if (m_YSpeed < 0)
{
double speed = m_YSpeed + AirResistance() * m_YMotionTimer.totalTime();
if (speed < 0)
{
((Actor*)GetOwner())->Move(utility::Vector2(0, (int)std::round(speed)));
}
else
{
m_YSpeed = 0;
m_YMotionTimer.stop();
m_YMotionTimer.reset();
}
}
}
void SetXSpeed(double speed)
{
m_XSpeed = speed;
m_XMotionTimer.stop();
m_XMotionTimer.reset();
m_XMotionTimer.start();
}
void SetYSpeed(double speed)
{
m_YSpeed = speed;
m_YMotionTimer.stop();
m_YMotionTimer.reset();
m_YMotionTimer.start();
}
void Move(utility::Vector2 motion)
{
m_Position += motion;
}
void SetPosition(utility::Vector2 pos)
{
m_Position = pos;
}
utility::Vector2 GetPosition() const
{
return m_Position;
}
static void SetAirResistance(double ar)
{
AirResistance() = ar;
}
static double GetAirResistance()
{
return AirResistance();
}
private:
static double& AirResistance()
{
static double airResistance = 1.81;
return airResistance;
}
utility::Timer m_XMotionTimer;
utility::Timer m_YMotionTimer;
utility::Vector2 m_Position;
double m_XSpeed;
double m_YSpeed;
};
}
namespace utility
{
using Physic = EPhysic<Actor>;
class Actor
{
public:
Actor(const std::string& name, utility::Vector2 position, bool graphic, bool physic, bool collision, utility::Vector2 resolution = utility::Vector2())
: m_Name(name), m_Position(position), m_Destroyed(false), m_Delay(0)
{
if (graphic)
{
AddComponent(new Graphic(position, resolution));
}
if (physic)
{
AddComponent(new Physic(position));
}
if (collision)
{
AddComponent(new Collision(position, resolution));
}
}
virtual void LoadAnimations(int n, AnimationInfo arg1, ...)
{
std::vector<AnimationInfo> animations;
va_list argument_list;
va_start(argument_list, n);
for (int currentArg = 0; n > currentArg; ++currentArg)
{
AnimationInfo info = va_arg(argument_list, AnimationInfo);
animations.push_back(info);
}
va_end(argument_list);
struct graphic_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "graphic";
}
};
std::vector<Component*>::iterator component = std::find_if(m_Components.begin(), m_Components.end(), graphic_finder());
if (component != m_Components.end())
{
((utility::Graphic*)*component)->Load(animations);
}
}
virtual ~Actor()
{
std::for_each(m_Components.begin(), m_Components.end(), [&](Component* cp)->void { delete cp; });
}
virtual std::string GetName()
{
return m_Name;
}
virtual void Draw()
{
struct graphic_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "graphic";
}
};
std::vector<Component*>::iterator component = std::find_if(m_Components.begin(), m_Components.end(), graphic_finder());
if (component != m_Components.end())
{
((utility::Graphic*)*component)->Draw();
}
}
virtual void SetActiveAnimation(int animationID)
{
struct graphic_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "graphic";
}
};
std::vector<Component*>::iterator component = std::find_if(m_Components.begin(), m_Components.end(), graphic_finder());
if (component != m_Components.end())
{
((utility::Graphic*)*component)->SetActiveAnimation(animationID);
}
}
virtual int GetActiveAnimation()
{
struct graphic_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "graphic";
}
};
std::vector<Component*>::iterator component = std::find_if(m_Components.begin(), m_Components.end(), graphic_finder());
if (component != m_Components.end())
{
return ((utility::Graphic*)*component)->GetActiveAnimation();
}
return -1;
}
virtual utility::Vector2 GetResolution()
{
struct graphic_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "graphic";
}
};
std::vector<Component*>::iterator component = std::find_if(m_Components.begin(), m_Components.end(), graphic_finder());
if (component != m_Components.end())
{
return ((utility::Graphic*)*component)->GetResolution();
}
return utility::Vector2();
}
virtual void SetFrameDuration(int n)
{
struct graphic_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "graphic";
}
};
std::vector<Component*>::iterator component = std::find_if(m_Components.begin(), m_Components.end(), graphic_finder());
if (component != m_Components.end())
{
((utility::Graphic*)*component)->SetFrameDuration(n);
}
}
virtual bool CollidesWith(Actor* actor)
{
struct collision_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "collision";
}
};
std::vector<Component*>::iterator componentMe = std::find_if(m_Components.begin(), m_Components.end(), collision_finder());
std::vector<Component*>::iterator componentHe = std::find_if(actor->m_Components.begin(), actor->m_Components.end(), collision_finder());
if (componentMe != m_Components.end() && componentHe != actor->m_Components.end())
{
return ((utility::Collision*)*componentMe)->CollidesWith((utility::Collision*)*componentHe);
}
else
{
return false;
}
}
virtual bool CollidesWith(utility::Vector2 point)
{
struct collision_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "collision";
}
};
std::vector<Component*>::iterator componentMe = std::find_if(m_Components.begin(), m_Components.end(), collision_finder());
if (componentMe != m_Components.end())
{
return ((utility::Collision*)*componentMe)->CollidesWith(point);
}
else
{
return false;
}
}
virtual void SetXSpeed(double speed)
{
struct physic_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "physic";
}
};
std::vector<Component*>::iterator componentMe = std::find_if(m_Components.begin(), m_Components.end(), physic_finder());
if (componentMe != m_Components.end())
{
((utility::EPhysic<Actor>*)*componentMe)->SetXSpeed(speed);
}
}
virtual void SetYSpeed(double speed)
{
struct physic_finder
{
bool operator()(Component* ptr)
{
return ptr->GetName() == "physic";
}
};
std::vector<Component*>::iterator componentMe = std::find_if(m_Components.begin(), m_Components.end(), physic_finder());
if (componentMe != m_Components.end())
{
((utility::EPhysic<Actor>*)*componentMe)->SetYSpeed(speed);
}
}
virtual void Update()
{
std::for_each(m_Components.begin(), m_Components.end(), [&](Component* cp)->void { cp->Update(); });
if (m_Destroyed)
{
--m_Delay;
}
}
virtual void Move(utility::Vector2 motion)
{
std::for_each(m_Components.begin(), m_Components.end(), [&](Component* cp)->void { cp->Move(motion); });
m_Position += motion;
}
virtual void SetPosition(utility::Vector2 pos)
{
std::for_each(m_Components.begin(), m_Components.end(), [&](Component* cp)->void { cp->SetPosition(pos); });
m_Position = pos;
}
virtual void AddComponent(Component* component)
{
component->SetOwner(this);
m_Components.push_back(component);
}
virtual utility::Vector2 GetPosition() const
{
return m_Position;
}
virtual bool Destroyed() const
{
return m_Destroyed && m_Delay == 0;
}
virtual void Destroy()
{
m_Destroyed = true;
}
virtual void DestroyDelayed(int frames)
{
if (!m_Destroyed)
{
m_Destroyed = true;
m_Delay = frames;
}
}
private:
std::vector<Component*> m_Components;
std::string m_Name;
utility::Vector2 m_Position;
int m_Delay;
bool m_Destroyed;
};
}
namespace utility
{
class Engine
{
public:
Engine()
: m_OnFrame(place_holder), m_OnCollision(place_holder_cl), m_OnInit(place_holder)
{
utility::resizeConsole(utility::Vector2(640, 640));
Graphic::GetController().clearBuffer();
}
void Run()
{
m_OnInit();
m_Timer.start();
m_LastFrame = 0.0;
m_Running = true;
while (m_Running)
{
m_Timer.tick();
if (m_Timer.totalTime() - m_LastFrame >= 1.0f / 60.0f)
{
m_LastFrame = m_Timer.totalTime();
m_OnFrame();
Clear();
Update();
Collision();
Draw();
Present();
}
}
}
void AddActor(Actor* actor)
{
m_Actors.push_back(actor);
}
template <class T> void SetOnFrame(T fnc)
{
m_OnFrame = fnc;
}
template <class T> void SetOnCollision(T fnc)
{
m_OnCollision = fnc;
}
template <class T> void SetOnInit(T fnc)
{
m_OnInit = fnc;
}
Actor* GetActor(const std::string& name)
{
struct actor_finder
{
actor_finder(const std::string& n)
: m_ActorName(n)
{}
bool operator()(Actor* ptr)
{
return ptr->GetName() == m_ActorName;
}
std::string m_ActorName;
};
std::vector<Actor*>::iterator actor = std::find_if(m_Actors.begin(), m_Actors.end(), actor_finder(name));
if (actor != m_Actors.end())
{
return *actor;
}
else
{
return nullptr;
}
}
std::vector<Actor*>& GetActors()
{
return m_Actors;
}
void Exit()
{
m_Running = false;
}
static Engine& GetEngine()
{
static Engine utility;
return utility;
}
private:
void Collision()
{
for (unsigned int currentIndexA = 0; m_Actors.size() > currentIndexA; ++currentIndexA)
{
for (unsigned int currentIndexB = currentIndexA + 1; m_Actors.size() > currentIndexB; ++currentIndexB)
{
if (m_Actors.at(currentIndexA)->CollidesWith(m_Actors.at(currentIndexB)))
{
m_OnCollision(m_Actors.at(currentIndexA), m_Actors.at(currentIndexB));
}
}
}
}
void Update()
{
std::for_each(m_Actors.begin(), m_Actors.end(), [](Actor* actor)->void { actor->Update(); });
}
void Draw()
{
std::for_each(m_Actors.begin(), m_Actors.end(), [](Actor* actor)->void { actor->Draw(); });
}
void Clear()
{
for (unsigned int currentIndex = 0; m_Actors.size() > currentIndex; ++currentIndex)
{
if (m_Actors.at(currentIndex)->Destroyed())
{
delete m_Actors.at(currentIndex);
m_Actors.erase(m_Actors.begin() + currentIndex);
--currentIndex;
}
}
utility::Graphic::GetController().clearBuffer();
}
void Present()
{
utility::Graphic::GetController().present();
}
static void place_holder_cl(Actor* a, Actor* b)
{
}
static void place_holder()
{
}
std::vector<Actor*> m_Actors;
std::function<void(Actor* a, Actor* b)> m_OnCollision;
std::function<void()> m_OnFrame;
std::function<void()> m_OnInit;
utility::Timer m_Timer;
double m_LastFrame;
bool m_Running;
};
}
I will give a little space invaders game as an example on how the engine works.
int main()
{
utility::Engine& Engine = utility::Engine::GetEngine();
Engine.SetOnInit(init);
Engine.SetOnFrame(frame);
Engine.SetOnCollision(collision);
Engine.Run();
return 0;
}
I decided to give the user access to three important functions. Init(), Frame()
andCollision()
. All three functions can be defined and then simply added to the engine over the SetOn"FunctionName"(xyz)
functions. That way you can take a lot of trouble away from the developer. All three functions will be called by the engine when needed.
Init is thought to setup the game and add things like a background and a player for example. It will called before the game actually starts.
void createBackground()
{
utility::Engine& Engine = utility::Engine::GetEngine();
utility::Actor* actor = new utility::Actor("background", utility::Vector2(0, -600), true, true, false, utility::Vector2(640, 1200));
actor->LoadAnimations(1, utility::AnimationInfo(AnimationIDs::idle, "background", 1, "Sprites\\misc\\"));
Engine.AddActor(actor);
}
void createPlayer(utility::Vector2 position)
{
utility::Engine& Engine = utility::Engine::GetEngine();
utility::Actor* actor = new utility::Actor("player", utility::Vector2(100, 400), true, true, true, utility::Vector2(72, 72)); //48
actor->LoadAnimations(2, utility::AnimationInfo(AnimationIDs::idle, "ship", 1, "Sprites\\ships\\"),
utility::AnimationInfo(AnimationIDs::explode, "explosion", 5, "Sprites\\effects\\"));
Engine.AddActor(actor);
}
void init()
{
utility::Engine& Engine = utility::Engine::GetEngine();
createBackground();
createPlayer(utility::Vector2(100, 400));
utility::Physic::SetAirResistance(1.1);
}
I divided the init function into 2 subfunctions. The createBackground()
function will load a background and set it's position while the createPlayer()
function will setup the player actor. To create an actor you will have to give the constructor a name, a position (represented in a Vector2 struct), three bool values depending on which components you want on your actor and a Vector2 with the size of the collision box which you want to create.
The next step is to actually load some sprites (if you want a graphics component). The first parameter in the function indicates how many animations you want to load, the rest will be filled with AnimationInfo structs. The function will load n sprites, while n is the number of frames you give to the AnimationInfo. The function can load .png files named like spritename + currentFrameNumber.png. If your animation is named ShipIdle of instance you would have to name the frames ShipIdle0.png, ShipIdle1.png and so on.
The last step would be to add the Actor to the Engine.
The next function to address is the Frame()
function.
void frame()
{
utility::Engine& Engine = utility::Engine::GetEngine();
utility::Actor* player = Engine.GetActor("player");
if (!player)
{
std::cout << "game over";
utility::Engine::GetEngine().Exit();
utility::get_key();
return;
}
controls(player);
playerMovement(player);
backgroundMovement();
actorMovement();
spawn();
}
This function is called every frame and is though to contain the actual game logic.
The first subfunction I created has been the controls(actor*)
function, which will apply user input to the player actor.
void controls(utility::Actor* player)
{
utility::Engine& Engine = utility::Engine::GetEngine();
if (utility::check_key())
{
char key = utility::get_key();
switch (key)
{
case 'w':
{
player->SetYSpeed(-3);
break;
}
case 'a':
{
player->SetXSpeed(-3);
break;
}
case 's':
{
player->SetYSpeed(3);
break;
}
case 'd':
{
player->SetXSpeed(3);
break;
}
case' ':
{
shoot(player, true);
break;
}
case 'x':
{
Engine.Exit();
break;
}
default:
{
break;
}
}
}
}
It contains a simple switch case which will accellerate the actor in the desired direction if the player or exit the program presses a key, while the next function will prevent the player from leaving the visible screen.
void playerMovement(utility::Actor* player)
{
if (player->GetPosition().X > 560)
{
player->SetPosition(utility::Vector2(560, player->GetPosition().Y));
}
if (player->GetPosition().X < 0)
{
player->SetPosition(utility::Vector2( 0, player->GetPosition().Y));
}
if (player->GetPosition().Y > 500)
{
player->SetPosition(utility::Vector2(player->GetPosition().X, 500));
}
if (player->GetPosition().Y < 0)
{
player->SetPosition(utility::Vector2(player->GetPosition().X, 0));
}
}
And here are the functions to spawn shots and enemies.
void shoot(utility::Actor* actor, bool player)
{
utility::Engine& Engine = utility::Engine::GetEngine();
utility::Actor* shot = nullptr;
std::string name = "";
utility::Vector2 position = utility::Vector2();
if (player)
{
name = "playerShot";
position = utility::Vector2(actor->GetPosition().X + (actor->GetResolution().X / 2) - 10, actor->GetPosition().Y - 21);
}
else
{
name = "enemyShot";
position = utility::Vector2(actor->GetPosition().X + (actor->GetResolution().X / 2) - 10, actor->GetPosition().Y + actor->GetResolution().Y + 21);
}
shot = new utility::Actor(name, position, true, true, true, utility::Vector2(20, 20));
shot->LoadAnimations(1, utility::AnimationInfo(0, "laser_basic", 1, "Sprites\\effects\\"));
Engine.AddActor(shot);
}
void spawnEnemy(utility::Timer& timer)
{
static utility::random_engine rnd(0, 540);
static double spawnInterval = 2.2;
static double lastSpawn = 0.0;
if (timer.totalTime() - lastSpawn >= spawnInterval)
{
lastSpawn = timer.totalTime();
utility::Vector2 pos(rnd.generate(), 0);
createEnemy(pos);
}
}
utility::Actor* getRandomEnemy(std::vector<utility::Actor*>& actors)
{
static utility::random_engine rnds(0, (int)utility::Engine::GetEngine().GetActors().size() - 1);
rnds.changeMax(actors.size() - 1);
int tries = 0;
utility::Actor* actor = nullptr;
while (tries != 30 && (actor == nullptr || actor->GetName() != "enemy"))
{
actor = actors.at(rnds.generate());
++tries;
}
return actor->GetName() == "enemy" ? actor : nullptr;
}
void spawnShot(utility::Timer& timer)
{
static double shootInterval = 1.0;
static double lastShot = 0.0;
std::vector<utility::Actor*>& actors = utility::Engine::GetEngine().GetActors();
if (timer.totalTime() - lastShot >= shootInterval && actors.size() > 1)
{
lastShot = timer.totalTime();
utility::Actor* actor = getRandomEnemy(actors);
if (actor)
{
shoot(actor, false);
}
}
}
void spawn()
{
static utility::Timer timer;
static bool once = false;
if (!once)
{
timer.start();
}
timer.tick();
spawnEnemy(timer);
spawnShot(timer);
}
Our next two functions will move/reset the background and the other actors in the game (like enemies and fired shots).
void backgroundMovement()
{
utility::Engine& Engine = utility::Engine::GetEngine();
utility::Actor* background = Engine.GetActor("background");
if (background)
{
background->SetYSpeed(1);
if (background->GetPosition().Y >= 0)
{
background->SetPosition(utility::Vector2(0, -600));
}
}
}
void actorMovement()
{
utility::Engine& Engine = utility::Engine::GetEngine();
std::vector<utility::Actor*>& actors = Engine.GetActors();
std::for_each(actors.begin(), actors.end(), [](utility::Actor* actor)->void { if (actor->GetName() == "playerShot") { actor->SetYSpeed(-6); } });
std::for_each(actors.begin(), actors.end(), [](utility::Actor* actor)->void { if (actor->GetName() == "enemyShot") { actor->SetYSpeed( 4); } });
std::for_each(actors.begin(), actors.end(), [](utility::Actor* actor)->void { if (actor->GetName() == "enemy") { actor->SetYSpeed( 1); } });
std::for_each(actors.begin(), actors.end(), [](utility::Actor* actor)->void { if ((actor->GetPosition().Y > 680 || actor->GetPosition().Y < -90) && actor->GetName() != "background") { actor->Destroy(); } });
}
The last two functions to address are the collision(actor*, actor*)
function and determinCollision(actor*)
function.
void determinCollision(utility::Actor* actor)
{
if (actor->GetName().find("Shot") == std::string::npos)
{
actor->SetActiveAnimation(AnimationIDs::explode);
actor->SetFrameDuration(3);
actor->DestroyDelayed(15);
}
else
{
actor->Destroy();
}
}
void collision(utility::Actor* a, utility::Actor* b)
{
if ((a->GetName().find("Shot") != std::string::npos && b->GetName().find("Shot") != std::string::npos) || (a->GetName() == "background" || b->GetName() == "background"))
{
return;
}
determinCollision(a);
determinCollision(b);
}
The collision function will be called each time, two actors collide. It's purpose is to give the developer the opportunity to decide what will happen during our collision.
In our case it will call the determinCollision(actor)
function to decide which actor shall be destroyed.
An actor must not be destroyed by delete
, it shall be killed by actor::destroy()
or actor::destroyDelayed(n)
to give the engine the opportunity to clean it if it's not longer required.
That's our finished game which could look somewhat like this (depending on the sprites):