13
\$\begingroup\$

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):

enter image description here

\$\endgroup\$
1
  • \$\begingroup\$ Just one comment: I like your code-style. Easy to read variable and function names, proper Allman bracing and good formatting in other ways (spacing etc). \$\endgroup\$
    – Almo
    Apr 5, 2018 at 14:35

3 Answers 3

10
\$\begingroup\$

That's a lot of code. Here's what I have noticed, but I didn't look at absolutely everything and no doubt have missed things. There area also some general notes that are mentioned once or twice but not every time they occur.

In your namespace operators, you don't need the inline keyword when forward declaring your operators. You only need that when you define them later.

Every place you pass a parameter by const double &, this can just be a double.

In your Vector2 class, the const_cast used within the operators is superfluous since *this is not a const value. The use of me doesn't really improve things. You could just use *this = *this + a; return *this;.

Within ~FrameBuffer, you declare several unused local variables that shadow class members. I don't think this is what you intended. Omit the types from all 4 declarations. You're also zeroing out wnd before you call SendMessage, so the message won't go to the right place.

Moving on to class Sprite, the assignment operator is not safe for self assignment. And the const_cast in the return statement can be omitted.

In the one place you call to_wchar, you have a string object that you convert to a C-string. Then in to_wchar you call strlen. Just pass the string object to to_wchar so you'll already have the length available and won't have to count characters.

Sprite::clear can be a static member function, since it does not access any of the member variables or other member functions of Sprite.

You pass a lot of Vector2 objects by const &, but in setPosition and move you pass by value, which makes a copy you don't need to make. These two functions should take the position as const Vector2& position.

The Animation class has several of the same issues as Sprite; I won't reiterate them. In update, you're abusing the conditional operator as an if statement. Just use an if, or write an expression that uses the % operator instead of a conditional.

You can use emplace_back instead of push_back in loadAnimation. In setPosition and move, you don't need to explicitly state that your lambda functions return void. This will be inferred by the compiler.

In your GraphicController constructor, you should list the initializer values in the order they are declared (m_Resolution, m_GDI, m_Buffer) since this is the order the compiler will call the constructors. If your warning level is set to /W4 you should be getting a warning about this.

Your Component constructor does not set an initial (nullptr) value for m_Owner.

Graphic::Load gets its parameter by value, which makes a copy of the vector. This should be passed in by const reference.

The Graphic::Draw function checks for an empty m_Animations map, but does not verify that the requested animation is already there. This can potentially cause a null pointer dereference.

In Graphic::GetController, you can initialize gc in its declaration, rather initializing it to nullptr and checking to see if it has been created.

Collision::CollidesWith can just return true when it finds a true condition, rather than always calculating all 4 and checking at the end. p4 can be eliminated by reusing p2 or p3 with an appropriate adjustment.

Your use of Actor as a template parameter name in EPhysic is confusing, since there is also an Actor class.

The Actor constructor has m_Destroyed and m_Delay swapped. You're declaring virtual functions in it, but I don't see where it is used as a base class.

You have multiple identical declarations of helper classes graphic_finder, collision_finder, and physic_finder. There should only be one definition for those to avoid the repetition. Additionally, create a single FindComponent function to use in Draw, SetActiveAnimation, etc., rather than repeating all that find code.

Actor::GetName() can be a const function.

Engine::Engine again has member constructors specified in the wrong order.

playerMovement could use a better way to change the position of the player. Since you only set either the X or Y values there should be an easy way to do that. Alternatively, pass in the bounds and let the player object check and update its own position.

In actorMovement, you can collapse several passes thru the actors vector into one, checking the type of each actor as you go. This would be easier if the way to identify the actor's type (GetName) used something like an enum value instead.

\$\endgroup\$
1
  • \$\begingroup\$ Allright, first of all, I want to thank you for your effort, there are a lot of really good points which I really appreciate and which I plan to change :) But I have some comments to two special points you mentioned. First: The Actor has virtual functions, yes. That's fully intentional. It shall give the dev the possibility to implement a custom actor instead of using the component system. (I desided against it in my invaders). Second: The name of an actor is stored as string to allow generic names like "enemy48" and also for logging purposes if one should deside to log in larger projects. \$\endgroup\$
    – Mango
    Apr 6, 2018 at 6:32
3
\$\begingroup\$

A few comments on 'things I wouldn't do'...

I'm assuming the code you have given is in a header file. If so, that's an awful lot of code for a single header file. Perhaps one header for each class?

Another general thing is that you shouldn't inline anything beyond simple getters and setters, unless you can determine that the inlining produces a significant performance improvement. It keeps your interface a lot cleaner and doesn't cause your clients to get surprising headers delivered to them. It also means that you have more leeway in changing your implementation without causing client code to be rebuilt.

More specifically:

Why have this? predeclaring these isn't very useful

    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);

just declare them inline when you define the method (which as you declare them in the class, you don't need to do anyway)

This constructor:

        Vector2(int x = 0, int y = 0)
            : X(x), Y(y)
        {}

allows you to declare a Vector2 x(5). Is that what you really want people to do? Also, as you haven't made it explicit, you can do Vector2 x = 5 which is almost definitely wrong. I'd think two separate constructors with no defaulted parameters would be better.

You also have a lot of this:

            Vector2& me = *const_cast<Vector2*>(this);

            me = me - a;

            return me;

Firstly, the const_cast is unnecessary as this isn't const (as the method isn't). Secondly, using const_cast is a code smell. If you actually need to do it you should explain why, as it can cause all sorts of unpleasant behaviour.

You have a using statement

using Vector2 = operators::Vector2;
enter code here

Don't do this in a header file. You might as well have put Vector2 in the enclosing namespace. It's really not a kind thing to do to people who use the header.

    FrameBuffer(Vector2 res, HWND window = GetConsoleWindow())
    {

Again, this should be explicit for safety. Again, IMO this should be two separate constructors, as this means your header has to pull in whichever windows header defines GetConsoleWindow, which your clients probably don't want to know about.

        wnd = window;

        Memhdc = 0;
        hdc = GetDC(wnd);

Use the initialiser list to initialise members when you can. The compiler HAS to initialise all the members so you will end up with default initialisation followed by assignment. And remember to order the initialiser list in the same order as your member variables.

        GetClientRect(wnd, &Client_Rect);

        Memhdc = CreateCompatibleDC(hdc);
        Membitmap = CreateCompatibleBitmap(hdc, res.X, res.Y);

        SelectObject(Memhdc, Membitmap);

        Graphics = Gdiplus::Graphics::FromHDC(Memhdc);

This is an awful lot of code for an inline function and pulls in a lot of headers into the code that your clients really don't need to know about.

    }

    ~FrameBuffer()
    {
        HDC          Memhdc = 0;
        HDC          hdc = 0;
        HBITMAP      Membitmap = 0;
        HWND         wnd = 0;

One - this declares local variables with 0 values. It doesn't zero the values in the class. Two: It's the destructor. This object will never be accessible again. Why bother? Three: Destructors should not be inline. A destructor has to destroy all the member variables, and that can unexpectedly generate a lot of code in every client.

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;
}

Not that I have significant problems with this, but you should probably return a unique_ptr or similar, otherwise the client is liable to leak the returned value.

       if (m_Animations.size() != 0)

use if (not m_Animations.empty()) - it's guaranteed O(1), whereas size is not

This:

   static utility::GraphicController& GetController()
    {
        static utility::GraphicController* gc = nullptr;

        if (!gc)
        {
            gc = new utility::GraphicController(GetConsoleWindow(), utility::Vector2(640, 640));
        }

        return *gc;
    }

leaks a single instance of a GraphicController. You should avoid singletons. Or the client should be responsible for the lifetime.

You have a lot of C-style casts in EPhysic class. Please avoid them, they are unchecked and could hide issues. use static_cast (or other appropriate cast)

I haven't checked any more as I have to get back to work, and the scrolling up and down is beginning to get to me...

\$\endgroup\$
3
  • \$\begingroup\$ I would be more harsh about casting away constness. In the best scenario its just a nasty smell, but in most cases it causes undefined behavior. \$\endgroup\$ Apr 6, 2018 at 19:25
  • \$\begingroup\$ relevant: github.com/isocpp/CppCoreGuidelines/blob/master/… for to_wchar. \$\endgroup\$
    – JDługosz
    Apr 7, 2018 at 0:14
  • \$\begingroup\$ @JDługos Yeah, I kind of recognized the issue with the to_wchar function, I changed it by now, thanks for pointing out! \$\endgroup\$
    – Mango
    Apr 9, 2018 at 14:18
1
\$\begingroup\$

Don't have time for a full review.

But you doing the operator overloading the wrong way around. You are defineing operator += in terms of operator +. This causes a extra copies, you should do it the other way around and define operator + in terms of operator +=.

Original Code:

    Vector2& operator += (const Vector2& a)
    {
        // This is particularly smelly.
        // Never case away constness.
        Vector2& me = *const_cast<Vector2*>(this);

        me = me + a;

        return me;
    }
    inline Vector2 operator + (const Vector2& a, const Vector2& b)
    {
        return Vector2(a.X + b.X, a.Y + b.Y);
    }

I would do it like this:

   Vector2& operator += (Vector2 const& a) {
       X += a.X;
       Y += a.Y;
       return *this;
   }
   Vector2 operator + (Vector2 const& a, Vector2 const& b) {
       Vector2 result(a);
       return result += b;
   }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.