Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

unrays/Spark

Open more actions menu

Repository files navigation

Crystal Engine

This project is a personal showcase developed at the end of my second year studying Computer Science Techniques at Cégep de Rimouski.
It is a minimalist 2D game engine written in C++ using SDL2, built entirely from scratch.

The project is still a work in progress and currently contained in a single source file.
It demonstrates my technical skills in object-oriented programming, input handling, graphics rendering, and software architecture.

This engine serves as a foundation for future improvements and projects I plan to develop.

Please note: In hindsight, this project presents numerous architectural errors. It generally does not apply SOLID principles correctly and breaks many of these rules. If I had to do it again, there would be numerous modifications to be made to the architecture. I will not touch it since it is the archive of my very first step into 2D game engines and it means a lot to me. written on September 27, 2025

// Copyright (c) May 2025 Félix-Olivier Dumas. All rights reserved.
// Licensed under the terms described in the LICENSE file

#include <SDL.h>
#include <SDL_image.h>
#include <iostream>
#include <vector>
#include <cstdint>
#include <optional>
#include <cmath>
#include <tuple>

std::vector<SDL_Rect> sceneObjects;

class IPrintable {
public:
    virtual ~IPrintable() = default;
    virtual void print(std::ostream& stream) const = 0;
};

inline std::ostream& operator<<(std::ostream& stream, const IPrintable& obj) {
    obj.print(stream);
    return stream;
}

class IOperable {
public:
    virtual ~IOperable() = default;
    virtual std::unique_ptr<IOperable> operator+(const IOperable& other) const = 0;
    virtual std::unique_ptr<IOperable> operator-(const IOperable& other) const = 0;
    virtual std::unique_ptr<IOperable> operator*(const IOperable& other) const = 0;
    virtual std::unique_ptr<IOperable> operator/(const IOperable& other) const = 0;

    virtual IOperable& operator+=(const IOperable& other) = 0;
    virtual IOperable& operator-=(const IOperable& other) = 0;
    virtual IOperable& operator*=(const IOperable& other) = 0;
    virtual IOperable& operator/=(const IOperable& other) = 0;
};

class IVector : public virtual IPrintable {
public:
    virtual ~IVector() = default;

    void print(std::ostream& stream) const = 0;

    virtual void setXY(const float x, const float y) = 0;

    virtual float getX() const = 0;
    virtual float getY() const = 0;

    virtual void setX(float x) = 0;
    virtual void setY(float y) = 0;
};

class Vector2 : public IVector {
private:
    float _x, _y;

public:
    Vector2(float x = 0.0f, float y = 0.0f) : _x(x), _y(y) {}

    void setXY(const float x, const float y) override { _x = x; _y = y; }

    void print(std::ostream& stream) const override { stream << "(" << _x << ", " << _y << ")" << std::endl; }

    float getX() const override { return _x; }
    void setX(float x) override { _x = x; }

    float getY() const override { return _y; }
    void setY(float y) override { _y = y; }

    Vector2 operator+(const Vector2& other) const { return Vector2(_x + other.getX(), _y + other.getY()); }
    Vector2 operator-(const Vector2& other) const { return Vector2(_x - other.getX(), _y - other.getY()); }
    Vector2 operator*(const Vector2& other) const { return Vector2(_x * other.getX(), _y * other.getY()); }
    Vector2 operator/(const Vector2& other) const { return Vector2(_x / other.getX(), _y / other.getY()); }

    Vector2& operator+=(const Vector2& other) { this->setXY(this->_x += other.getX(), this->_y += other.getY()); return *this; }
    Vector2& operator-=(const Vector2& other) { this->setXY(this->_x -= other.getX(), this->_y -= other.getY()); return *this; }
    Vector2& operator*=(const Vector2& other) { this->setXY(this->_x *= other.getX(), this->_y *= other.getY()); return *this; }
    Vector2& operator/=(const Vector2& other) { this->setXY(this->_x /= other.getX(), this->_y /= other.getY()); return *this; }
};

class IColor : public virtual IPrintable {
public:
    virtual ~IColor() = default;

    virtual Uint8 getR() const = 0;
    virtual Uint8 getG() const = 0;
    virtual Uint8 getB() const = 0;
    virtual Uint8 getA() const = 0;

    virtual void setColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a = 255) = 0;

    virtual void print(std::ostream& stream) const = 0;
};

class ColorRGBA : public IColor {
private:
    Uint8 _r, _g, _b, _a;

public:
    static constexpr Uint8 MAX_COLOR_VALUE = 255;
    static constexpr Uint8 MIN_COLOR_VALUE = 0;

    ColorRGBA(Uint8 r = 0, Uint8 g = 0, Uint8 b = 0, Uint8 a = 255)
        : _r(r), _g(g), _b(b), _a(a) {}

    void setColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a) { _r = r; _g = g; _b = b; _a = a; }

    Uint8 getR() const { return _r; }
    Uint8 getG() const { return _g; }
    Uint8 getB() const { return _b; }
    Uint8 getA() const { return _a; }

    Uint8 setR(Uint8 r) { _r = r; }
    Uint8 setG(Uint8 g) { _g = g; }
    Uint8 setB(Uint8 b) { _b = b; }
    Uint8 setA(Uint8 a) { _a = a; }

    void print(std::ostream& stream) const override { stream << "(" << _r << ", " << _g << ", " << _b << ", " << _a << ")"; }

    ColorRGBA operator+(const ColorRGBA& other) const {
        return ColorRGBA((_r + other.getR() > MAX_COLOR_VALUE) ? MAX_COLOR_VALUE : _r + other.getR(),
                         (_g + other.getG() > MAX_COLOR_VALUE) ? MAX_COLOR_VALUE : _g + other.getG(),
                         (_b + other.getB() > MAX_COLOR_VALUE) ? MAX_COLOR_VALUE : _b + other.getB(),
                         (_a + other.getA() > MAX_COLOR_VALUE) ? MAX_COLOR_VALUE : _a + other.getA());
    }

    ColorRGBA operator-(const ColorRGBA& other) const {
        return ColorRGBA((_r - other.getR() < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : _r - other.getR(),
                         (_g - other.getG() < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : _g - other.getG(),
                         (_b - other.getB() < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : _b - other.getB(),
                         (_a - other.getA() < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : _a - other.getA());
    }

    ColorRGBA& operator+=(const ColorRGBA& other) {
        this->setColor((_r + other.getR() > MAX_COLOR_VALUE) ? MAX_COLOR_VALUE : _r + other.getR(),
                       (_g + other.getG() > MAX_COLOR_VALUE) ? MAX_COLOR_VALUE : _g + other.getG(),
                       (_b + other.getB() > MAX_COLOR_VALUE) ? MAX_COLOR_VALUE : _b + other.getB(),
                       (_a + other.getA() > MAX_COLOR_VALUE) ? MAX_COLOR_VALUE : _a + other.getA());
        return *this;
    }

    ColorRGBA& operator-=(const ColorRGBA& other) {
        this->setColor((_r - other.getR() < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : _r - other.getR(),
                       (_g - other.getG() < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : _g - other.getG(),
                       (_b - other.getB() < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : _b - other.getB(),
                       (_a - other.getA() < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : _a - other.getA());
        return *this;
    }
};

class ISizable {
public:
    virtual ~ISizable() = default;
    virtual void scale(const float& factor) = 0;
};

class Size : public ISizable {
private:
    float _width, _height;

public:
    ~Size() = default;
    Size(const float width, const float height)
        : _width(width), _height(height) {}

    float getWidth()  const { return _width; }
    float getHeight() const { return _height; }

    void scale(const float& factor) override {
        _width *= factor; _height *= factor;
    }
};

class IMovable {
public:
    virtual ~IMovable() = default;
    virtual void move(const Vector2& offset) = 0;
    virtual void setPosition(const Vector2& position) = 0;
    virtual Vector2 currentPosition() const = 0;
};

class IRotatable {
public:
    virtual ~IRotatable() = default;
    virtual void setRotation(const float& angle) = 0;
    virtual void rotate(const float& angle) = 0;
};

class Position : public virtual IMovable, public virtual IPrintable {
private:
    Vector2 _coordinates;

public:
    Position() = default;
    Position(const Vector2& coords) : _coordinates(coords) { }

    float getX() const { return _coordinates.getX(); }
    float getY() const { return _coordinates.getY(); }

    void setX(float x) { _coordinates.setX(x); }
    void setY(float y) { _coordinates.setX(y); }

    void setPosition(const Vector2& newPosition) override { _coordinates = newPosition; }
    Vector2 currentPosition() const override { return _coordinates; }

    void move(const Vector2& offset) override { _coordinates.setXY(_coordinates.getX() + offset.getX(), _coordinates.getY() + offset.getY()); }

    void print(std::ostream& stream) const override { stream << _coordinates; };

    Position operator+(const Position& other) const { return Position(_coordinates + other.currentPosition()); }
    Position operator-(const Position& other) const { return Position(_coordinates - other.currentPosition()); }
    Position operator*(const Position& other) const { return Position(_coordinates * other.currentPosition()); }
    Position operator/(const Position& other) const { return Position(_coordinates / other.currentPosition()); }

    Position& operator+=(const Position& other) { _coordinates += other.currentPosition(); return *this; }
    Position& operator-=(const Position& other) { _coordinates -= other.currentPosition(); return *this; }
    Position& operator*=(const Position& other) { _coordinates *= other.currentPosition(); return *this; }
    Position& operator/=(const Position& other) { _coordinates /= other.currentPosition(); return *this; }
};

class Rotation : public virtual IRotatable, public virtual IPrintable {
private:
    float _angle;

public:
    static constexpr float MAX_ANGLE = 360.0f;
    static constexpr float MIN_ANGLE = 360.0f;

    Rotation(const float& angle) : _angle(angle) { }

    void setRotation(const float& newAngle) override { _angle = (newAngle < MAX_ANGLE) ? newAngle : 0.0f; }

    void print(std::ostream& stream) const override { stream << _angle; };

    void rotate(const float& angle) override {
        const float range = MAX_ANGLE - MIN_ANGLE;

        _angle += angle;
        _angle = std::fmod(_angle - MIN_ANGLE, range);

        if (_angle < 0) {
            _angle += range;
            _angle += MIN_ANGLE;
        }
    }
};

class ICollidable {
public:
    virtual ~ICollidable() = default;
    virtual void collideWith(const ICollidable& object) = 0;
    virtual const Position& getHitboxPosition() const = 0;
};

class IPhysicsApplicable {
public:
    virtual ~IPhysicsApplicable() = default;
};

class IRenderable {
public:
    virtual void render(SDL_Renderer* renderer) = 0;
    virtual ~IRenderable() = default;
};

class ISpriteable {
public:
    virtual ~ISpriteable() = default;
    virtual void changeSprite(SDL_Renderer* renderer, const std::string& imagePath) = 0;
};

class IAnimatable {
public:
    virtual ~IAnimatable() = default;
    virtual void changeAnimation(SDL_Renderer* renderer, const Vector2& resolution, const Vector2& gridSize, const std::string& imagePath) = 0;
    virtual void setAnimationFrame(const int row, const int column) = 0;
};

class ITexturable {
public:
    virtual ~ITexturable() = default;
    virtual void loadFromFile(const std::string& imagePath) = 0;
    virtual void convertToTexture(SDL_Renderer* renderer) = 0;
};

class IControllable {
public:
    virtual ~IControllable() = default;
    virtual void handleInput(SDL_Renderer* renderer, const SDL_Event& input) = 0;
};

class ITargetable : public virtual IMovable {
public:
    virtual ~ITargetable() = default;
};

class IHealthable {
public:
    virtual ~IHealthable() = default;
    virtual void heal(const Uint32 healAmount) = 0;
    virtual void takeDamage(const Uint32 damageAmount) = 0;
    virtual bool isAlive() const = 0;
    virtual Uint32 getHealth() const = 0;
};

struct IZoomable {
public:
    virtual ~IZoomable() = default;
    virtual void zoom(const float& factor) = 0;
};

class Camera : public virtual IMovable, public virtual IRotatable, public IZoomable {
private:
    std::optional<ITargetable*> _target;
    Position _position;
    Rotation _angle;

    bool _isTargeting = false;
    Position _displacement;

    Vector2 _lastPosition;

public:
    Camera(Position& position, Rotation& angle, ITargetable* target = nullptr)
        : _position(position), _angle(angle) {
        if (target) { _target = target; _isTargeting = true; }
    }

    void setTarget(ITargetable& target) { _target = &target; _isTargeting = true; }
    void clearTarget() { _target.reset(); _isTargeting = false; }

    void update() {
        if (!_target) {
            _target.emplace();
            std::cerr << "[WARNING] No initialized target for update\n";
            return;
        }
        _lastPosition = _position.currentPosition();

        Vector2 newPos = _target.value()->currentPosition() + _displacement.currentPosition();
        _position.setPosition(newPos);

        _displacement.setPosition(Vector2(0.f, 0.f));
    }


    void setPosition(const Vector2& position) override { _position.setPosition(position); }
    Vector2 currentPosition() const override { return _position.currentPosition(); }
    Vector2 lastPosition() const { return _lastPosition; }

    void move(const Vector2& offset) override { _displacement += offset; }


    void setRotation(const float& angle) override { _angle.setRotation(angle); }
    void rotate(const float& angle) override { _angle.rotate(angle); }

    void zoom(const float& factor) override { /* [À FINIR] */ }
};

class Sprite : public virtual ITexturable {
private:
    SDL_Surface* _surface = nullptr;
    SDL_Texture* _texture = nullptr;
    std::string  _imagePath;

public:
    Sprite() = default;
    Sprite(const std::string& imagePath) : _imagePath(imagePath) { }

    void loadFromFile(const std::string& imagePath = "") override {
        const std::string loadedPath = (imagePath.empty() ? _imagePath : imagePath);
        _surface = IMG_Load(loadedPath.c_str());

        if (!_surface) { std::cerr << "[ERROR] Can't load the surface: " << IMG_GetError() << std::endl; }
    }

    void convertToTexture(SDL_Renderer* renderer) override {
        _texture = SDL_CreateTextureFromSurface(renderer, _surface);
        if (!_texture) { std::cout << "[ERROR] SDL_CreateTextureFromSurface: " << SDL_GetError() << std::endl; }
        else { SDL_FreeSurface(_surface); _surface = nullptr; }
    }

    void draw(SDL_Renderer* renderer, const Position& position, const Size& size) {
        SDL_Rect destination = { static_cast<int>(position.getX()), static_cast<int>(position.getY()),
                                 static_cast<int>(size.getWidth()), static_cast<int>(size.getHeight()) };
        SDL_RenderCopy(renderer, _texture, NULL, &destination);
    }
};

class SpriteSheet : public virtual ITexturable {
private:
    SDL_Rect     m_clip;
    SDL_Surface* _surface = nullptr;
    SDL_Texture* _texture = nullptr;
    std::string  _imagePath;
    int _spriteWidth = 0;
    int _spriteHeight = 0;
    int _padding = 0;
    int _columns = 0;
    int _rows = 0;

public:
    SpriteSheet() = default;
    SpriteSheet(const std::string& imagePath) : _imagePath(imagePath) { }

    void setSpriteSheetDimensions(int sheetWidth, int sheetHeight, int rows, int columns, int padding = 0) {
        _rows = rows;
        _columns = columns;
        _padding = padding;
        _spriteWidth = (sheetWidth - (columns - 1) * padding) / columns;
        _spriteHeight = (sheetHeight - (rows - 1) * padding) / rows;
        m_clip = { 0, 0, _spriteWidth, _spriteHeight };
    }

    void loadFromFile(const std::string& imagePath = "") override {
        const std::string loadedPath = imagePath.empty() ? _imagePath : imagePath;
        _surface = IMG_Load(loadedPath.c_str());
        if (!_surface) {
            std::cerr << "[ERROR] Can't load the surface: " << IMG_GetError() << std::endl;
        }
    }

    void convertToTexture(SDL_Renderer* renderer) override {
        _texture = SDL_CreateTextureFromSurface(renderer, _surface);
        if (!_texture) {
            std::cerr << "[ERROR] SDL_CreateTextureFromSurface: " << SDL_GetError() << std::endl;
        }
        else {
            SDL_FreeSurface(_surface);
            _surface = nullptr;
        }
    }

    void selectSprite(int x, int y) {
        m_clip.x = x * (_spriteWidth + _padding);
        m_clip.y = y * (_spriteHeight + _padding);
    }

    void selectSpriteByTime(int fps = 10, int frameCount = 1, int row = 0) {
        Uint32 ticks = SDL_GetTicks();
        Uint32 spriteIndex = (ticks / (1000 / fps)) % frameCount;

        int x = spriteIndex % _columns;
        int y = row;

        selectSprite(x, y);
    }



    void draw(SDL_Renderer* renderer, const Position& position, const Size& size) {
        if (!_texture) {
            std::cerr << "[ERROR] No texture loaded, impossible to draw." << std::endl;
            return;
        }

        SDL_Rect destRect = {
            static_cast<int>(position.getX()),
            static_cast<int>(position.getY()),
            static_cast<int>(size.getWidth()),
            static_cast<int>(size.getHeight())
        };
        SDL_RenderCopy(renderer, _texture, &m_clip, &destRect);
    }
};



class GameObject : public IRenderable, public ISizable, public virtual IMovable {
protected:
    Position _position;
    Size _size;

public:
    GameObject(Position& position, const Size& size) 
        : _position(position), _size(size) { }

    virtual ~GameObject() = default;

    void scale(const float& factor) { _size.scale(factor); }

    void setPosition(const Vector2& position) override { _position.setPosition(position); }
    Vector2 currentPosition() const override { return _position.currentPosition(); }

    void move(const Vector2& offset) override { _position.move(offset); }
};

class Actor : public GameObject, public ISpriteable, public IAnimatable, public ITargetable {
protected:
    std::optional<Sprite> _sprite;
    std::optional<SpriteSheet> _animation;

public:
    Actor(Position& position, const Size& size, std::optional<Sprite> sprite = std::nullopt, std::optional<SpriteSheet> animation = std::nullopt)
        : GameObject(position, size), _sprite(std::move(sprite)), _animation(std::move(animation)) { }

    void render(SDL_Renderer* renderer) override {
        if (_sprite) { _sprite->draw(renderer, _position, _size); }
        else if (_animation) { _animation->draw(renderer, _position, _size); }
    }

    void changeSprite(SDL_Renderer* renderer, const std::string& imagePath) override {
        if (!_sprite) _sprite.emplace();

        _sprite->loadFromFile(imagePath);
        _sprite->convertToTexture(renderer);
    }

    void changeAnimation(SDL_Renderer* renderer, const Vector2& resolution, const Vector2& gridSize, const std::string& imagePath) override {
        if (!_animation) _animation.emplace();

        _animation->loadFromFile(imagePath);
        _animation->convertToTexture(renderer);
        _animation->setSpriteSheetDimensions(static_cast<int>(resolution.getX()), static_cast<int>(resolution.getY()),
                                             static_cast<int>(gridSize.getX()), static_cast<int>(gridSize.getY()));
    }

    void setAnimationFrame(const int row, const int column) override {
        if (!_animation) _animation.emplace();

        _animation->selectSprite(row, column);
    }

    void testAnim() {
        if (!_animation) _animation.emplace();

        _animation->selectSpriteByTime(4, 8, 1);
    }
};

class Player : public virtual Actor, public virtual IHealthable, public IControllable, public virtual ICollidable {
private:
    Uint32 _health; Uint32 _maxHealth;

public:
    Player(Position& position, const Size& size, Uint32 health = 100, Uint32 maxHealth = 100,
        std::optional<Sprite> sprite = std::nullopt, std::optional<SpriteSheet> spriteSheet = std::nullopt)
        : Actor(position, size, sprite, spriteSheet), _health(health), _maxHealth(maxHealth) {}

    void heal(const Uint32 healAmount) override { _health = (_health + healAmount <= _maxHealth) ? (_health + healAmount) : _maxHealth; }
    void takeDamage(const Uint32 damageAmount) override { _health -= damageAmount; }
    bool isAlive() const override { return _health > 0;  }
    Uint32 getHealth() const override { return _health; }

    void collideWith(const ICollidable& object) override {
        /* Detecter les collisions hors d'ici
           Et gerer les effets de cette collision
           Dans cette methode (genre ne pas permettre
           De traverser l'objet)... Peut-être penser
           à faire une classe abstraite quelque part pour
           move, scale et tout car ils sont communs [FAIT] */
    }

    const Position& getHitboxPosition() const override { return _position; }

    void handleInput(SDL_Renderer* renderer, const SDL_Event& event) override {
        if (event.type == SDL_KEYDOWN) {
            if (event.key.keysym.sym == SDLK_SPACE) {
                std::cout << "Espace pressée !" << std::endl;
            }
            else if (event.key.keysym.sym == SDLK_w) {
                //move(Vector2(0, -15));
                //changeSprite(renderer, "assets/images/mario_jump_sprite.png");
            }
            else if (event.key.keysym.sym == SDLK_s) {
                //move(Vector2(0, 15));
            }
            else if (event.key.keysym.sym == SDLK_a) {
                //move(Vector2(-15, 0));
            }
            else if (event.key.keysym.sym == SDLK_d) {
                //move(Vector2(15, 0));
                //_animation->selectSpriteByTime(10, 8, 4);
            }
            else {
                testAnim();
            }

        }
    }
};

class Enemy : public virtual Actor, public virtual IHealthable, public virtual ICollidable {
private:
    Uint32 _health; Uint32 _maxHealth;

public:
    Enemy(Position& position, const Size& size, Uint32 health = 100, Uint32 maxHealth = 100,
        std::optional<Sprite> sprite = std::nullopt, std::optional<SpriteSheet> spriteSheet = std::nullopt)
        : Actor(position, size, sprite, spriteSheet), _health(health), _maxHealth(maxHealth) {}

    void heal(const Uint32 healAmount) override { _health = (_health + healAmount <= _maxHealth) ? (_health + healAmount) : _maxHealth; }
    void takeDamage(const Uint32 damageAmount) override { _health -= damageAmount; }
    bool isAlive() const override { return _health > 0; }
    Uint32 getHealth() const override { return _health; }

    const Position& getHitboxPosition() const override { return _position; }

    void collideWith(const ICollidable& object) override {
    }
};

struct configuration {
    int width;
    int height;
};

void handleKeyboardInput(SDL_Renderer* renderer, SDL_Event& event, Player& player, bool& isRunning) { // [USELESS]
    switch (event.type) {
    case SDL_QUIT:
        isRunning = false;
        break;

    case SDL_KEYDOWN:
        if (SDL_GetScancodeName(event.key.keysym.scancode) == std::string("W")) {
            player.move(Vector2(0, -15));
        }
        if (SDL_GetScancodeName(event.key.keysym.scancode) == std::string("S")) {
            player.move(Vector2(0, 15));
        }
        if (SDL_GetScancodeName(event.key.keysym.scancode) == std::string("A")) {
            player.move(Vector2(-15, 0));
        }
        if (SDL_GetScancodeName(event.key.keysym.scancode) == std::string("D")) {
            player.move(Vector2(15, 0));
        }

        std::cout << "[KEYBOARD INPUT] Scancode: " << SDL_GetScancodeName(event.key.keysym.scancode) << std::endl;
        break;


    default:
        break;
    }
}

float stickX = 0.0f;
float stickY = 0.0f;

void handleControllerInput(SDL_Renderer* renderer, SDL_Event &event, Player& player, bool &isRunning) { // [USELESS]
    switch (event.type) {
    case SDL_QUIT:
        isRunning = false;
        break;

    case SDL_CONTROLLERBUTTONDOWN:
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_A) {
            std::cout << "[CONTROLLER] A PRESSED" << std::endl;

            playerColorR = 255;


        }
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_B) {
            std::cout << "[CONTROLLER] B PRESSED" << std::endl;
        }
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_X) {
            std::cout << "[CONTROLLER] X PRESSED" << std::endl;
        }
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_Y) {
            std::cout << "[CONTROLLER] Y PRESSED" << std::endl;
        }

        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP) {
            std::cout << "[CONTROLLER] DPAD UP PRESSED" << std::endl;
            //playerActor.y -= 15;

            player.move(Vector2(0, -5));
        }
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) {
            std::cout << "[CONTROLLER] DPAD DOWN PRESSED" << std::endl;
            //playerActor.y += 15;

            player.move(Vector2(0, 5));
        }
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) {
            std::cout << "[CONTROLLER] DPAD LEFT PRESSED" << std::endl;
            //playerActor.x -= 15;

            player.move(Vector2(-5, 0));
        }
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
            std::cout << "[CONTROLLER] DPAD RIGHT PRESSED" << std::endl;
            //playerActor.x += 15;

            player.move(Vector2(5, 0));
        }

        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) {
            std::cout << "[CONTROLLER] RB PRESSED" << std::endl;
        }
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) {
            std::cout << "[CONTROLLER] LB PRESSED" << std::endl;
        }

        if (event.cbutton.button == SDL_CONTROLLERAXISMOTION) {

        }
        break;

    case SDL_CONTROLLERAXISMOTION:
        if (event.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
            if (event.caxis.value == 32767) {
                std::cout << "[CONTROLLER] RT PRESSED: " << std::endl;
            }
        }
        if (event.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) {
            if (event.caxis.value == 32767) {
                std::cout << "[CONTROLLER] LT PRESSED: " << std::endl;
            }
        }

        

        if (event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX) {
            stickX = event.caxis.value;
            player.move(Vector2((stickX / 10000), 0));
            
            
           
        }
        if (event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY) {
            stickY = event.caxis.value;
            player.move(Vector2(0, (stickY / 10000)));


        }
        break;

    default:
        break;
    }
}

int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {
        SDL_Log("Erreur SDL_Init : %s", SDL_GetError());
        return 1;
    }

    SDL_GameController* controller = nullptr;
    if (SDL_NumJoysticks() > 0 && SDL_IsGameController(0)) {
        controller = SDL_GameControllerOpen(0);
        std::cout << ((controller ? "Controller detected and open" : "No controller detected or failed to open")) << std::endl;
    }

    SDL_Window* window = SDL_CreateWindow("SDL2 Setup Simple",
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        800, 600,
        SDL_WINDOW_SHOWN);

    if (!window) {
        SDL_Log("Erreur SDL_CreateWindow : %s", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if (!renderer) {
        SDL_Log("Erreur SDL_CreateRenderer : %s", SDL_GetError());
        SDL_DestroyWindow(window);
        SDL_Quit();
        return 1;
    }

    bool running = true;
    SDL_Event event;

    configuration configuration;
    SDL_GL_GetDrawableSize(window, &configuration.width, &configuration.height);
    std::cout << "" << configuration.width << std::endl << configuration.height << std::endl;

    static float offset = 0;
    //SDL_Rect rect = { 100, 100, 200, 150 };
    //sceneObjects.push_back(rect);
    sceneObjects.push_back(playerActor);

    /*********************************************************/

    SpriteSheet playerSpriteSheet("assets/images/sprite_sheet_new.png");

    playerSpriteSheet.loadFromFile();
    playerSpriteSheet.setSpriteSheetDimensions(1200, 510, 6, 8);
    playerSpriteSheet.convertToTexture(renderer);

    playerSpriteSheet.selectSprite(0, 5);


    /*********************************************************/

    Sprite playerSprite("");

    playerSprite.loadFromFile("assets/images/mario_sprite.png");
    playerSprite.convertToTexture(renderer);

    //playerSprite.testImageLoad("C:/Users/XXXXXXXXXX/Documents/Programmation/Crystal Engine/assets/images/sprite.png", renderer);

    Position playerPosition(Vector2(-200.0f, -100.0f));
    Size playerSize(775.0f, 600.0f);

    //Player player(std::move(playerPosition), playerSize, 100, 100, playerSprite, std::nullopt);
    Player player(playerPosition, playerSize, 100, 100, std::nullopt, playerSpriteSheet);


    Sprite fActorSprite("");

    fActorSprite.loadFromFile("assets/images/mario_sprite.png");
    fActorSprite.convertToTexture(renderer);

    Position fActorPosition(Vector2(static_cast<float>(configuration.width / 2), 50.0f));
    Size fActorSize(500.0f, 500.0f);

    Enemy fActor(fActorPosition, fActorSize, 100, 100, fActorSprite, std::nullopt);

    /*********************************************************/

    Position cameraStart(Vector2(0, 0));
    Rotation cameraAngle(0.0f);

    Camera camera(cameraStart, cameraAngle, &player);

    //camera.move(Vector2(10, 0));

    /*********************************************************/

    std::vector<GameObject*> renderQueue;
    //renderQueue.push_back(&player);
    renderQueue.push_back(&fActor);

    static int sprite = 0;

    while (running) {
        //rect.x = (configuration.width / 2) - (rect.w / 2);
        //rect.y = (configuration.height / 2) - (rect.h / 2);

        const Position& playerHitBox = player.getHitboxPosition();

        /*if (std::round(rect.x) == std::round(playerPositionX)) {
            std::cout << "Collision Detected" << std::endl;
        }

        if (playerPositionX >= (rect.x) && playerPositionX <= (rect.x + rect.w) &&
            playerPositionY >= (rect.y) && playerPositionY <= (rect.y + rect.h)) {
            std::cout << "Le point est dans la zone !" << std::endl;
        }*/

        //std::cout << playerPositionX << ", " << playerPositionY << std::endl;
        //std::cout << rect.x << ", " << rect.y << std::endl;

        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) running = false;

            handleKeyboardInput(renderer, event, player, running);
            handleControllerInput(renderer, event, player, running);

            player.handleInput(renderer, event);
        }
        
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);

        camera.update();

        Vector2 cameraPos = camera.currentPosition();
        Vector2 screenCenter(configuration.width / 2.f, configuration.height / 2.f);

        for (GameObject* object : renderQueue) {
            Vector2 worldPos = object->currentPosition();
            Vector2 screenPos = worldPos - cameraPos + screenCenter;


            object->setPosition(worldPos);
            object->render(renderer);

            std::cout << worldPos << std::endl;
        }




        //playerSpriteSheet.selectSprite(sprite, 0);

        //player.setAnimationFrame(sprite, 0);
        player.testAnim();

        player.render(renderer);
        fActor.render(renderer);

        camera.update();

        Vector2 camPos = camera.currentPosition();
        std::cout << "Camera position: (" << camPos.getX() << ", " << camPos.getY() << ")\n";
        
        //player.updateJump(1.0f / 60.0f);


        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        //SDL_RenderFillRect(renderer, &rect);

        SDL_SetRenderDrawColor(renderer, playerColorR, playerColorG, playerColorB, 255);
        SDL_RenderFillRect(renderer, &playerActor); // remplacer

        SDL_RenderPresent(renderer);

        sprite++;

        SDL_Delay(16);
    }

    if (controller) SDL_GameControllerClose(controller);

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

About

A small 2D game engine made with SDL2 in C++ meant to reflect my learning journey in programming and object-oriented design

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.