2d-sfml-game-engine / HW1 / Part 4 / homeworkOnePartFour.cpp
homeworkOnePartFour.cpp
Raw
#include <iostream>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <cmath>

// Global window size
int WINDOW_WIDTH = 800;
int WINDOW_HEIGHT = 600;

class Collider;

// Global List containing all collionable objects
std::vector<Collider*> collisionObjects;

/**
 * @brief Class for the interface of a collider
 */
class Collider {
    public:
        /**
         * @brief Destroy the Collider object
         */
        virtual ~Collider() {}

        /**
         * @brief Set Collision to be enabled or disabled
         * 
         * @param enabled whether collision is to be enabled or not
         */
        void setCollisionEnabled(bool enabled) {
            collisionEnabled = enabled;
            if(enabled) {
                collisionObjects.push_back(this);
            }
            else {
                // Remove from the global list
                auto toRemove = std::find(collisionObjects.begin(), collisionObjects.end(), this);
                if(toRemove != collisionObjects.end()) {
                    collisionObjects.erase(toRemove);
                }
            }
        }

        /**
         * @brief Get the Collision enable boolean
         * 
         * @return bool is collision is enabled
         */
        bool getCollisionEnabled() {
            return collisionEnabled;
        }

        /**
         * @brief Checks if the object collides with any other with collision on. It will resolve the object 
         * collision if needed.
         * 
         * @return bool of whether the collide object collides with any object in the collideableObjects list.
         */
        bool checkCollision() {
            if(collisionEnabled) {
                sf::FloatRect checkBounds = getGlobalBounds();

                for(Collider* collideable : collisionObjects) {
                    if(collideable == this) {
                        continue;
                    }

                    sf::FloatRect collideableBounds = collideable->getGlobalBounds();

                    if(checkBounds.intersects(collideableBounds)) {
                        resolveCollision(*this, *collideable);
                        return true;
                    }
                }
            }
            return false;
        }

        /**
         * @brief Resolves a collision by taking an object and what it collides with and moving it back outside 
         * that object.
         * 
         * @param colliderOne Object to move outside of the bounding box of the other.
         * @param colliderTwo Object being collided with.
         */
        void resolveCollision(Collider& colliderOne, Collider& colliderTwo) {
            sf::FloatRect boundsOne = colliderOne.getGlobalBounds();
            sf::FloatRect boundsTwo = colliderTwo.getGlobalBounds();

            // Find how much is being intersected in each direction.
            float intersectingX = std::min(boundsOne.left + boundsOne.width, boundsTwo.left + boundsTwo.width) - std::max(boundsOne.left, boundsTwo.left);
            float intersectingY = std::min(boundsOne.top + boundsOne.height, boundsTwo.top + boundsTwo.height) - std::max(boundsOne.top, boundsTwo.top);

            // Move outside collision
            if(intersectingX < intersectingY) {
                if(boundsOne.left > boundsTwo.left) {
                    colliderOne.move(intersectingX, 0);
                }
                else {
                    colliderOne.move(-intersectingX, 0);
                }
            }
            else {
                if(boundsOne.top > boundsTwo.top) {
                    colliderOne.move(0, intersectingY);
                }
                else {
                    colliderOne.move(0, -intersectingY);
                }
            }
        }

        /**
         * @brief Virtual method for moving within the Collider class.
         * 
         * @param xOffset amount to move in the x direction.
         * @param yOffset amount to move in the y direction.
         */
        virtual void move(float xOffset, float yOffset) = 0;

        /**
         * @brief Virtual method for moving within the Collider class.
         * 
         * @param offset amount to move given a float 2D vector.
         */
        virtual void move(sf::Vector2f offset) = 0;

        /**
         * @brief Get the Global Bounds object
         * 
         * @return sf::FloatRect global bounds of the object.
         */
        virtual sf::FloatRect getGlobalBounds() const = 0;

        /**
         * @brief Get the Movement of an object
         * 
         * @return sf::Vector2f total movement of the object in that frame
         */
        virtual sf::Vector2f getMovement() = 0;

    private:
        bool collisionEnabled; // Whether the object has collision enabled
};

/**
 * @brief Class for a static platform object
 */
class Platform : public sf::RectangleShape, public Collider {
    public:
        /**
         * @brief Construct a new Platform object with no given parameters.
         */
        Platform() {
            setPosition(0.f, 0.f);
            setSize(sf::Vector2f(50.f, 50.f));
            setFillColor(sf::Color(0, 0, 0));
        }

        /**
         * @brief Construct a new Platform object
         * 
         * @param x x position
         * @param y y position
         * @param width width of the platform
         * @param height height of the platform
         */
        Platform(float x, float y, float width, float height) {
            setPosition(x, y);
            setSize(sf::Vector2f(width, height));
            setFillColor(sf::Color(0, 0, 0));
        }

        /**
         * @brief Construct a new Platform object
         * 
         * @param x x position
         * @param y y position
         * @param width width of the platform
         * @param height height of the platform
         * @param texturePath path to the texture file
         */
        Platform(float x, float y, float width, float height, const std::string& texturePath) {
            setPosition(x, y);
            setSize(sf::Vector2f(width, height));
            
            // Load and set texture
            if (texture.loadFromFile(texturePath)) {
                setTexture(&texture);
            }
        }

        /**
         * @brief Get the Global Bounds object
         * 
         * @return sf::FloatRect global bounds of the object.
         */
        sf::FloatRect getGlobalBounds() const override {
            return sf::RectangleShape::getGlobalBounds();
        }

        /**
         * @brief Override of the move function.
         * 
         * @param xOffset amount to move in the x direction.
         * @param yOffset amount to move in the y direction.
         */
        void move(float xOffset, float yOffset) override {
            setPosition(getPosition().x + xOffset, getPosition().y + yOffset);
        }

        /**
         * @brief Override of the move function.
         * 
         * @param offset amount to move given a float 2D vector.
         */
        void move(sf::Vector2f offset) override {
            setPosition(getPosition().x + offset.x, getPosition().y + offset.y);
        }

        /**
         * @brief Get the Movement of an object
         * 
         * @return sf::Vector2f total movement of the object in that frame
         */
        sf::Vector2f getMovement() override {
            return sf::Vector2f(0.f, 0.f);
        }

    private:
        sf::Texture texture; // Texture of the platform
};

/**
 * @brief Class for a moveable platform object
 */
class MovingPlatform : public sf::RectangleShape, public Collider {
    public:
        /**
         * @brief Construct a new Moving Platform object with no given parameters.
         */
        MovingPlatform() {
            setPosition(0.f, 0.f);
            setSize(sf::Vector2f(50.f, 50.f));
            setFillColor(sf::Color(0, 0, 0));
            _speed = 40.f;
            _x = 0.f;
            _y = 0.f;
            _destX = 50.f;
            _destY = 0.f;
            movingForward = true;
            _pauseLength = 0.f;
            paused = false;
            totalMovement = sf::Vector2f(0.f, 0.f);
        }

        /**
         * @brief Construct a new Moving Platform object.
         * 
         * @param speed speed of the moving platform
         * @param x x position
         * @param y y position
         * @param destX x position to move back and forth to
         * @param destY y position to move back and forth to
         * @param width width of the platform
         * @param height height of the platform
         * @param pauseLength length to pause when reaching the destination
         */
        MovingPlatform(float speed, float x, float y, float destX, float destY, float width, float height, float pauseLength) {
            setPosition(x, y);
            setSize(sf::Vector2f(width, height));
            setFillColor(sf::Color(0, 0, 0));
            _speed = speed;
            _x = x;
            _y = y;
            _destX = destX;
            _destY = destY;
            movingForward = true;
            _pauseLength = pauseLength;
            paused = false;
            totalMovement = sf::Vector2f(0.f, 0.f);
        }

        /**
         * @brief Construct a new Moving Platform object.
         * 
         *  @param speed speed of the moving platform
         * @param x x position
         * @param y y position
         * @param destX x position to move back and forth to
         * @param destY y position to move back and forth to
         * @param width width of the platform
         * @param height height of the platform
         * @param pauseLength length to pause when reaching the destination
         * @param texturePath path to the texture file
         */
        MovingPlatform(float speed, float x, float y, float destX, float destY, float width, float height, float pauseLength, const std::string& texturePath) {
            setPosition(x, y);
            setSize(sf::Vector2f(width, height));
            _speed = speed;
            _x = x;
            _y = y;
            _destX = destX;
            _destY = destY;
            movingForward = true;
            _pauseLength = pauseLength;
            paused = false;
            totalMovement = sf::Vector2f(0.f, 0.f);
            
            // Load and set texture
            if (texture.loadFromFile(texturePath)) {
                setTexture(&texture);
            }
        }

        /**
         * @brief Get the Global Bounds object
         * 
         * @return sf::FloatRect global bounds of the object.
         */
        sf::FloatRect getGlobalBounds() const override {
            return sf::RectangleShape::getGlobalBounds();
        }

        /**
         * @brief Override of the move function.
         * 
         * @param xOffset amount to move in the x direction.
         * @param yOffset amount to move in the y direction.
         */
        void move(float xOffset, float yOffset) override {
            setPosition(getPosition().x + xOffset, getPosition().y + yOffset);
        }

        /**
         * @brief Override of the move function.
         * 
         * @param offset amount to move given a float 2D vector.
         */
        void move(sf::Vector2f offset) override {
            setPosition(getPosition().x + offset.x, getPosition().y + offset.y);
        }

        /**
         * @brief Get the Movement of an object
         * 
         * @return sf::Vector2f total movement of the object in that frame
         */
        sf::Vector2f getMovement() override {
            return totalMovement;
        }

        /**
         * @brief Update each frame, transforming the object based on time.
         * 
         * @param time time elapsed since the last frame
         */
        void update(float time) {
            totalMovement = sf::Vector2f(0.f, 0.f);

            // If the platform is currently paused
            if(paused && _pauseLength != 0.f) {
                pauseTimer -= time;

                // If timer is done
                if(pauseTimer <= 0.f) {
                    paused = false;
                    pauseTimer = 0.f;
                    movingForward = !movingForward;
                }

                return;
            }

            sf::Vector2f currentPosition = getPosition();

            if(movingForward) { // Move forward
                // Calculate the normalized vector towards destination
                sf::Vector2f directionVector(_destX - currentPosition.x, _destY - currentPosition.y);
                // Get direction vector length
                float directionLength = std::sqrt(std::pow(directionVector.x, 2.f) + std::pow(directionVector.y, 2.f));
                if(directionLength > 0) { // Normalize
                    directionVector /= directionLength;
                }

                float xOffset = _speed * time * directionVector.x;
                float yOffset = _speed * time * directionVector.y;
                if(currentPosition.x + xOffset >= _destX || currentPosition.y + yOffset >= _destY) {
                    totalMovement = sf::Vector2f(_destX - currentPosition.x, _destY - currentPosition.y);
                    setPosition(_destX, _destY);
                    paused = true;
                    pauseTimer = _pauseLength;
                }
                else {
                    totalMovement = sf::Vector2f(xOffset, yOffset);
                    move(totalMovement);
                }
            }
            else { // Move backward
                // Calculate the normalized vector towards origin
                sf::Vector2f directionVector(_x - currentPosition.x, _y - currentPosition.y);
                // Get direction vector length
                float directionLength = std::sqrt(std::pow(directionVector.x, 2.f) + std::pow(directionVector.y, 2.f));
                if(directionLength > 0) { // Normalize
                    directionVector /= directionLength;
                }

                float xOffset = _speed * time * directionVector.x;
                float yOffset = _speed * time * directionVector.y;
                if(currentPosition.x - xOffset <= _x || currentPosition.y - yOffset <= _y) {
                    totalMovement = sf::Vector2f(_x - currentPosition.x, _y - currentPosition.y);
                    setPosition(_x, _y);
                    paused = true;
                    pauseTimer = _pauseLength;
                }
                else {
                    totalMovement = sf::Vector2f(xOffset, yOffset);
                    move(totalMovement);
                }
            }
        }

    private:
        float _speed; // Speed of the moving platform
        float _x; // X Position of the moving platform
        float _y; // Y Position of the moving platform
        float _destX; // X Position of the destination the platform moves to
        float _destY; // Y Position of the destination the platform moves to
        bool movingForward; // Whether the platform is moving forward
        bool paused; // If the platform is currently paused
        float pauseTimer; // Timer of how long the platform is going to be paused
        float _pauseLength; // How long to pause the platform before it begins in the other direction
        sf::Vector2f totalMovement; // Movement of the platform in this frame;
        sf::Texture texture; // Texture of the platform
};

/**
 * @brief Class for a controlled player character
 */
class Player : public sf::Sprite, public Collider {
    public:
        /**
         * @brief Construct a new Player object.
         * 
         * @param texturePath path to the texture
         */
        Player(const std::string& texturePath) {
            setPosition(0.f, 0.f);
            _speed = 50.f;
            _gravity = 9.81f;
            _jumpSpeed = 10.f;
            jumpVelocity = 0.f;
            isJumping = false;
            totalMovement = sf::Vector2f(0.f, 0.f);
            onPlatform = false;
            
            // Load and set texture
            if (texture.loadFromFile(texturePath)) {
                setTexture(texture);
            }
        }

        /**
         * @brief Construct a new Player object
         * 
         * @param texturePath path to the texture file
         * @param x x position
         * @param y y position
         * @param speed speed of the moving platform
         * @param gravity gravity to be applied once in the air
         * @param jumpSpeed power of a jump
         */
        Player(const std::string& texturePath, float x, float y, float speed, float gravity, float jumpSpeed) {
            setPosition(x, y);
            _speed = speed;
            _gravity = gravity;
            _jumpSpeed = jumpSpeed;
            jumpVelocity = 0.f;
            isJumping = false;
            onPlatform = false;
            totalMovement = sf::Vector2f(0.f, 0.f);
            
            // Load and set texture
            if (texture.loadFromFile(texturePath)) {
                setTexture(texture);
            }
        }

        /**
         * @brief Construct a new Player object
         * 
         * @param texturePath path to the texture file
         * @param x x position
         * @param y y position
         * @param speed speed of the moving platform
         * @param gravity gravity to be applied once in the air
         * @param jumpSpeed power of a jump
         * @param scaleX amount to scale the sprite by horizontally
         * @param scaleY amount to scale the sprite by vertically
         */
        Player(const std::string& texturePath, float x, float y, float speed, float gravity, float jumpSpeed, float scaleX, float scaleY) {
            setPosition(x, y);
            setScale(sf::Vector2f(scaleX, scaleY));
            _speed = speed;
            _gravity = gravity;
            _jumpSpeed = jumpSpeed;
            jumpVelocity = 0.f;
            isJumping = false;
            onPlatform = false;
            totalMovement = sf::Vector2f(0.f, 0.f);
            
            // Load and set texture
            if (texture.loadFromFile(texturePath)) {
                setTexture(texture);
            }
        }

        /**
         * @brief Get the Global Bounds object
         * 
         * @return sf::FloatRect global bounds of the object.
         */
        sf::FloatRect getGlobalBounds() const override {
            return sf::Sprite::getGlobalBounds();
        }

        /**
         * @brief Override of the move function.
         * 
         * @param xOffset amount to move in the x direction.
         * @param yOffset amount to move in the y direction.
         */
        void move(float xOffset, float yOffset) override {
            setPosition(getPosition().x + xOffset, getPosition().y + yOffset);
        }

        /**
         * @brief Override of the move function.
         * 
         * @param offset amount to move given a float 2D vector.
         */
        void move(sf::Vector2f offset) override {
            setPosition(getPosition().x + offset.x, getPosition().y + offset.y);
        }

        /**
         * @brief Get the Movement of an object
         * 
         * @return sf::Vector2f total movement of the object in that frame
         */
        sf::Vector2f getMovement() override {
            return totalMovement;
        }

        /**
         * @brief Update each frame, transforming the object based on time and keyboard input.
         * 
         * @param time time elapsed since the last frame
         */
        void update(float time) {
            totalMovement = sf::Vector2f(0.f, 0.f);

            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) || sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
                // Left or A key is pressed: move the player to the left
                totalMovement.x -= _speed * time;
            }
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) || sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
                // Right or D key is pressed: move the player to the right
                totalMovement.x += _speed * time;
            }
            if ((sf::Keyboard::isKeyPressed(sf::Keyboard::Space) || sf::Keyboard::isKeyPressed(sf::Keyboard::W) || sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) && !isJumping) {
                // Space or W key is pressed: the player jumps
                isJumping = true;
                jumpVelocity = -_jumpSpeed;
            }

            bool isColliding = checkCollision();

            if(onPlatform && !isJumping) {
                move(collidingPlatform->getMovement());
            }

            // Fall down by gravity
            totalMovement.y = jumpVelocity * time;
            jumpVelocity += _gravity * sqrt(time);

            move(totalMovement);

            checkWindowCollision();

            if (onPlatform) {
                isJumping = false;
                jumpVelocity = 0.f;
            }
            
        }

        /**
         * @brief An override of the Collision function that adds logic of whether the player is on a platform.
         * 
         * @return whether there was a collision
         */
        bool checkCollision() {
            if(getCollisionEnabled()) {
                sf::FloatRect checkBounds = getGlobalBounds();

                bool bottomCollision = false;

                for(Collider* collideable : collisionObjects) {
                    if(collideable == this) {
                        continue;
                    }

                    sf::FloatRect collideableBounds = collideable->getGlobalBounds();

                    if(checkBounds.top + checkBounds.height < collideableBounds.top + collideableBounds.height && onPlatform) {
                        bottomCollision = true;
                    }

                    if(checkBounds.intersects(collideableBounds)) {
                        if(checkBounds.top + checkBounds.height >= collideableBounds.top && checkBounds.top < collideableBounds.top) {
                            onPlatform = true;
                        }
                        collidingPlatform = collideable;
                        resolveCollision(*this, *collideable);
                        return true;
                    }
                }
                if(bottomCollision && !isJumping) {
                    // Constants are tested to have additional movement due to collision not being directly detected every frame.
                    move(collidingPlatform->getMovement().x * 1.1f, collidingPlatform->getMovement().y * 1.5f);
                }
            }
            onPlatform = false;
            collidingPlatform = nullptr;
            return false;
        }

        /**
         * @brief Checks if the player is against the window edge, not allowing it to go further
         */
        void checkWindowCollision() {
            if(getPosition().x < 0) {
                setPosition(0.f, getPosition().y);
            }
            if(getPosition().y < 0) {
                setPosition(getPosition().x, 0.f);
            }
            if(getPosition().x + getGlobalBounds().width > WINDOW_WIDTH) {
                setPosition(WINDOW_WIDTH - getGlobalBounds().width, getPosition().y);
            }
            if(getPosition().y + getGlobalBounds().height > WINDOW_HEIGHT) {
                setPosition(getPosition().x, WINDOW_HEIGHT - getGlobalBounds().height);
            }
        }

    private:
        float _speed; // Speed of the player
        float _gravity; // Amount that gravity affects the player
        float _jumpSpeed; // Power of the jump of the player
        float jumpVelocity; // Current jump velocity
        bool isJumping; // Is the character currently jumping?
        bool onPlatform; // Is the player on a platform?
        Collider* collidingPlatform; // Pointer to the collider of the platform the player is on
        sf::Vector2f totalMovement; // Total movement of the player
        sf::Texture texture; // Texture of the platform
};

/**
 * @brief Jayden Sansom, jksanso2
 * HW 1 Part 4
 * 
 * @return int exit code
 */
int main() {
    // Create window
    sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "CSC 481 Game Engine Foundations HW 1 Part 4");
    // Get running desktop and set window to be positioned in the middle of the screen
    sf::VideoMode desktop = sf::VideoMode::getDesktopMode();
    window.setPosition(sf::Vector2i(desktop.width / 2 - window.getSize().x / 2, 
                           desktop.height / 2 - window.getSize().y / 2));
    window.setVerticalSyncEnabled(false);
    window.setFramerateLimit(60);

    // Start clock
    sf::Clock clock;

    // Create floor
    Platform floor = Platform(0.f, 550.f, 800.f, 50.f);
    floor.setCollisionEnabled(true);
    // Create moving platform
    MovingPlatform movingPlatform = MovingPlatform(30.f, 400.f, 160.f, 600.f, 450.f, 180.f, 50.f, 1.f);
    movingPlatform.setCollisionEnabled(true);
    // Create Player
    Player player = Player("wolfie.png", 250.f, 430.f, 100.f, 50.f, 300.f, 0.3f, 0.3f);
    player.setCollisionEnabled(true);

    // While open loop
    while(window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        sf::Time elapsed = clock.restart();

        window.clear(sf::Color(255, 255, 255, 0));

        // Draw scene objects
        window.draw(floor);
        movingPlatform.update(elapsed.asSeconds());
        window.draw(movingPlatform);
        player.update(elapsed.asSeconds());
        window.draw(player);

        window.display();
    }

    return 0; // Return on end
}