diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a4fc976..367e543 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,7 +20,8 @@ "source=./scripts,target=/app/scripts,type=bind", "source=./dist,target=/app/dist,type=bind", "source=./assets,target=/app/assets,type=bind", - "source=./include,target=/app/include,type=bind" + "source=./include,target=/app/include,type=bind", + "source=./.vscode,target=/app/.vscode,type=bind" ], "customizations": { @@ -29,7 +30,7 @@ "extensions": [ "esbenp.prettier-vscode", "ms-vscode.cpptools-extension-pack", - "jeff-hykin.better-cpp-syntax" + "vadimcn.vscode-lldb" ] } }, diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml new file mode 100644 index 0000000..013c05f --- /dev/null +++ b/.gitea/workflows/deploy.yaml @@ -0,0 +1,14 @@ +name: Jelly Rescue Deployment +run-name: ${{ gitea.actor }} is deploying Jelly Rescue +on: + push: + branches: + - main + +jobs: + JellyRescue Runner: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - run: docker compose up -d --build diff --git a/.gitea/workflows/testing.yaml b/.gitea/workflows/testing.yaml new file mode 100644 index 0000000..18267d8 --- /dev/null +++ b/.gitea/workflows/testing.yaml @@ -0,0 +1,13 @@ +name: Jelly Rescue Testing +run-name: ${{ gitea.actor }} is testing Jelly Rescue +on: [push] + +jobs: + JellyRescue Runner: + runs-on: ubuntu-latest + steps: + - run: docker --version + - run: docker compose version + - name: Check out repository code + uses: actions/checkout@v4 + # Run tests diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e6964e4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug with LLDB", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/dist/jelly_rescue", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "miDebuggerPath": "/usr/bin/lldb-mi", + "preLaunchTask": "build jelly_rescue", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..aed8e9a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,64 @@ +{ + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "format": "cpp", + "text_encoding": "cpp", + "ratio": "cpp", + "cinttypes": "cpp", + "__verbose_abort": "cpp", + "cfenv": "cpp", + "cstring": "cpp", + "fstream": "cpp" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..403f3c9 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,20 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build jelly_rescue", + "type": "shell", + "command": "bash", + "args": [ + "-c", + "g++ -std=c++20 -g -O0 $(find src/ -name \"*.cpp\") -o dist/jelly_rescue -I./include -L$LD_LIBRARY_PATH -lraylib -lGL -lm -lpthread -ldl -lrt -lX11" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": ["$gcc"], + "detail": "Compiling the jelly_rescue project" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 25c0935..2de1b50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apt-get update \ cmake \ libasound2-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev \ libglu1-mesa-dev libxcursor-dev libxinerama-dev libwayland-dev libxkbcommon-dev \ - sudo \ + sudo lldb \ alsa-utils \ && rm -rf /var/cache/apt/* @@ -63,4 +63,39 @@ RUN cd libs/raylib/src && \ emar rcs libraylib.a rcore.o rshapes.o rtextures.o rtext.o rmodels.o utils.o raudio.o && \ cd ../.. -CMD ["bash"] +# CMD ["bash"] + +FROM development as build + +RUN mkdir dist html src include assets + +COPY include include +COPY src src +COPY html html +COPY assets assets + +# CMD ["tail", "-f", "/dev/null"] +# Run the production build command to generate game.js and game.wasm +RUN source /app/libs/emsdk/emsdk_env.sh && \ + emcc $(find src/ -name "*.cpp") -o dist/game.js -O3 -Wno-missing-braces \ + -I./include -I/app/libs/raylib/build/raylib/include \ + -L/app/libs/raylib/src /app/libs/raylib/src/libraylib.a \ + -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=536870912 -s MAXIMUM_MEMORY=4294967296 \ + -s --preload-file assets@/assets -s USE_GLFW=3 -s ASSERTIONS=2 -DPLATFORM_WEB + +# CMD ["tail", "-f", "/dev/null"] + +FROM nginx:alpine as production + +# Remove the default nginx index.html +RUN rm -rf /usr/share/nginx/html/* + +# Copy the generated files from the production stage into the Nginx html directory +COPY --from=build /app/dist/ /usr/share/nginx/html/ +COPY --from=build /app/html/index.html /usr/share/nginx/html/ + +# Expose port 80 to allow Nginx to serve the files +EXPOSE 80 + +# Start Nginx server +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/README.md b/README.md index 0d2989a..e1aca3d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ # WebJelly-Rescue -Jelly Rescue, but now for the web! +[Jelly Rescue](https://github.com/Ninjaws/JellyGame), but now for the web! + +## Installation +### Install Docker +- On Windows and Mac +``` +Install Docker Desktop +Windows: https://docs.docker.com/desktop/install/windows-install/ +Mac: https://docs.docker.com/desktop/install/mac-install/ +``` +- On Linux +```bash +# For Debian-based systems +sudo apt install -y docker.io +sudo apt install docker-compose + +#For Arch-based systems +sudo pacman -S docker.io +sudo pacman -S docker-compose + +# Making Docker run now and on startup +sudo systemctl start docker +sudo systemctl enable docker + +# Using the CLI without sudo +sudo usermod -aG docker $USER +sudo reboot +``` + +## How to run +- Navigate to the root of the project +- Open a terminal +- Start the container: +```bash +docker compose up #(might take a few minutes to build) +``` +- Visit https://localhost:4600 to play the game locally +- Or visit play it live [here!](https://jelly.ianvink.nl) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e3345e3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + frontend: + build: + context: . + dockerfile: Dockerfile + target: production + image: jelly-rescue-prod + container_name: jelly-rescue-prod-cont + ports: + - "4600:80" + restart: always + cpu_period: 100000 # Default is 100,000 microseconds (100ms) + cpu_quota: 30000 # 5% of the total CPU time \ No newline at end of file diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..728fa2a --- /dev/null +++ b/html/index.html @@ -0,0 +1,114 @@ + + + + + + + Jelly Rescue + + + + + +
+ + +
+

+ + + + + diff --git a/include/entities/AmmoPack.h b/include/entities/AmmoPack.h new file mode 100644 index 0000000..3c631ea --- /dev/null +++ b/include/entities/AmmoPack.h @@ -0,0 +1,43 @@ +#ifndef AMMO_PACK_H +#define AMMO_PACK_H + +#include "raylib.h" +#include "services/AssetService.h" +#include "entities/TextureWrapper.h" + +class AmmoPack { + public: + AmmoPack() { + this->object = TextureWrapper(AssetService::getInstance().getSprite(ESprite::AMMOPACK),{32,32}); + } + void logic(){ + } + + void draw() { + DrawTexture(object.getTexture(), object.getPosition().x, object.getPosition().y, WHITE); + } + + int getAmmoAmount() { + return this->ammoAmount; + } + + void setPickedUp(bool pickedUp) { + this->pickedUp = pickedUp; + } + + bool hasBeenPickedUp() { + return this->pickedUp; + } + + TextureWrapper& getObject() { + return this->object; + } + + private: + TextureWrapper object; + unsigned int ammoAmount = 10; // Amount of ammo replenished by the pack + bool pickedUp; // When the player has picked up the pack +}; + + +#endif \ No newline at end of file diff --git a/include/entities/Background.h b/include/entities/Background.h index dc9ad8b..d8d892a 100644 --- a/include/entities/Background.h +++ b/include/entities/Background.h @@ -8,38 +8,43 @@ #include "services/AssetService.h" #include "services/StateService.h" -class Background { - public: - Background() { - +class Background +{ +public: + Background() + { } - Background(EBackground background){ - // Texture2D texture = AssetService::getInstance().getBackground(background); - TextureWrapper wrapper = TextureWrapper(AssetService::getInstance().getBackground(background), StateService::getInstance().getScreenSize(), {0,0}); - wrapper.setSourceRect({0,0,wrapper.getSize().x, wrapper.getSize().y}); - textures.push_back(wrapper); + Background(EBackground background) + { + TextureWrapper wrapper = TextureWrapper(AssetService::getInstance().getBackground(background), StateService::getInstance().getScreenSize(), {0, 0}); + wrapper.setSourceRect({0, 0, wrapper.getSize().x, wrapper.getSize().y}); + textures.push_back(wrapper); + } + Background(std::vector backgrounds) + { + for (int i = 0; i < backgrounds.size(); i++) + { + this->textures.push_back(backgrounds[i]); } - Background(std::vector backgrounds){ - for(int i = 0; i < backgrounds.size(); i++) { - this->textures.push_back(backgrounds[i]); - } + } + ~Background() {} + void draw() + { + for (int i = 0; i < textures.size(); i++) + { + TextureWrapper texture = textures[i]; + DrawTextureRec(texture.getTexture(), texture.getSourceRect(), texture.getPosition(), WHITE); } - ~Background(){} - // Vector2 getPosition(); - // void updatePosition(Vector2 deltaPos); - // void setPosition(Vector2 position); - void draw(){ - for(int i = 0; i < textures.size(); i++) { - TextureWrapper texture = textures[i]; - // Rectangle sourceRect = {0, 0, texture.getSize().x, texture.getSize().y}; - DrawTextureRec(texture.getTexture(), texture.getSourceRect(), texture.getPosition(),WHITE); - } - } - private: - std::vector textures; - // Vector2 position; - // Vector2 size; + } + + Vector2 getBackgroundSize() + { + TextureWrapper lastbg = this->textures[this->textures.size() - 1]; + return {lastbg.getPosition().x + lastbg.getSize().x, lastbg.getPosition().y + lastbg.getSize().y}; + } + +private: + std::vector textures; }; - #endif \ No newline at end of file diff --git a/include/entities/Bullet.h b/include/entities/Bullet.h new file mode 100644 index 0000000..1c44314 --- /dev/null +++ b/include/entities/Bullet.h @@ -0,0 +1,57 @@ +#ifndef BULLET_H +#define BULLET_H + +#include "raylib.h" +#include "entities/TextureWrapper.h" +#include "entities/Vector2i.h" +#include + +class Bullet +{ +public: + Bullet() {} + Bullet(Vector2 pos, float direction) + { + this->position = pos; + this->direction = direction; + } + + void logic() + { + move(); + hasCollided = false; + objCollision(); + envCollision(); + // // detectCollision(); + } + + void draw() + { + DrawCircleGradient(position.x, position.y, size, color, ORANGE); + } + + bool getHasCollided() + { + return this->hasCollided; + } + +protected: + Color color = RED; + Vector2 position; + int size = 7; + bool hasCollided = false; + +private: + float direction; + float velocity = 15.0f; + + void move() + { + position.x += velocity * cos(direction); + position.y += velocity * sin(direction); + } + void envCollision(); + virtual void objCollision() = 0; +}; + +#endif \ No newline at end of file diff --git a/include/entities/Crate.h b/include/entities/Crate.h new file mode 100644 index 0000000..3e9ccb5 --- /dev/null +++ b/include/entities/Crate.h @@ -0,0 +1,49 @@ +#ifndef CRATE_H +#define CRATE_H + +#include "services/AssetService.h" +#include "entities/TextureWrapper.h" + +class Crate +{ +public: + Crate() + { + this->texture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::CRATE), {32, 32}); + } + + Crate(Vector2 pos) + { + this->texture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::CRATE), {32, 32}, pos); + } + + void logic() + { + } + + bool hasBeenHit() + { + return this->hit; + } + + void setHit(bool hit) + { + this->hit = hit; + } + + void draw() + { + DrawTexture(this->texture.getTexture(), this->texture.getPosition().x, this->texture.getPosition().y, WHITE); + } + + TextureWrapper &getObject() + { + return this->texture; + } + +private: + TextureWrapper texture; + bool hit = false; // Whether a PBullet has hit the crate +}; + +#endif \ No newline at end of file diff --git a/include/entities/EBullet.h b/include/entities/EBullet.h new file mode 100644 index 0000000..9d76b9b --- /dev/null +++ b/include/entities/EBullet.h @@ -0,0 +1,22 @@ +#ifndef EBULLET_H +#define EBULLET_H + +#include "services/AssetService.h" +#include "entities/Bullet.h" +#include + +class EBullet : public Bullet +{ +public: + EBullet() {} + EBullet(Vector2 pos, float angle) : Bullet(pos, angle) + { + this->color = RED; + } + +private: + uint8_t damage = 1; + void objCollision() override; +}; + +#endif \ No newline at end of file diff --git a/include/entities/Enemy.h b/include/entities/Enemy.h new file mode 100644 index 0000000..a3c4d38 --- /dev/null +++ b/include/entities/Enemy.h @@ -0,0 +1,98 @@ +#ifndef ENEMY_H +#define ENEMY_H + +#include "raylib.h" +#include "services/AssetService.h" +#include "entities/TextureWrapper.h" +#include "entities/Vector2i.h" +#include +#include + +class Enemy +{ +public: + Enemy() + { + this->texture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::ENEMY), {32, 32}); + this->gunRight = TextureWrapper(AssetService::getInstance().getSprite(ESprite::ENEMY_GUN_RIGHT), {97, 27}); + this->gunLeft = TextureWrapper(AssetService::getInstance().getSprite(ESprite::ENEMY_GUN_LEFT), {97, 27}); + prevFrame = GetTime(); + lastShot = GetTime(); + + /** Randomizes between 0 and 1 (+1 to get Left and Right in directions), so they dont all move in the same direction */ + int randDir = (int)((int)(GetTime() * 1000000) % 2); + spriteAnimation = {0, randDir + 1}; + health = maxHealth; + } + Enemy(Vector2 startPos) + { + this->texture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::ENEMY), {32, 32}, startPos); + prevFrame = GetTime(); + + int randDir = (int)((int)(GetTime() * 1000000) % 2); + spriteAnimation = {0, randDir + 1}; + health = maxHealth; + } + + void logic(); + + void draw() + { + drawGun(); + drawHealthBar(); + DrawTextureRec(this->texture.getTexture(), this->texture.getSourceRect(), this->texture.getPosition(), WHITE); + } + + TextureWrapper &getObject() + { + return this->texture; + } + + uint8_t getHealth() + { + return this->health; + } + + void takeDamage() + { + this->health = std::max(health - 1, 0); + } + +private: + TextureWrapper texture; + TextureWrapper gunLeft; + TextureWrapper gunRight; + uint8_t health; + uint8_t maxHealth = 5; + Vector2 velocity = {0, 0}; + float spottingRange = 300; // From how far away enemies can spot the player + float gravity = 0.6f; + float moveSpeed = 1.5f; + float animationDelay = 0.2f; // In seconds + double prevFrame; // Time since the last frame + bool hasCollided = false; + Vector2i spriteAnimation; // Keeps track of (1) Sprite cycling and (2) Enemy direction + enum Direction + { + Down, + Left, + Right, + Up + }; // Used in conjuction with spriteAnimation, makes sure sprite direction matches with enemy direction + float shotsPerSecond = 8.0f; + double lastShot; + bool playerSpotted = false; + float aimSpeed = 2.0f; // How quickly the enemy can angle its gun to match the player (and to return to default) + + void walk(); + void checkForCollision(); + std::vector getCorners(); + void animate(); + void drawHealthBar(); + void drawGun(); + void fall(); + void detectPlayer(); + void shoot(); +}; + +#endif \ No newline at end of file diff --git a/include/entities/Flag.h b/include/entities/Flag.h new file mode 100644 index 0000000..7215236 --- /dev/null +++ b/include/entities/Flag.h @@ -0,0 +1,48 @@ +#ifndef FLAG_H +#define FLAG_H + +#include "raylib.h" +#include "services/AssetService.h" +#include "services/StateService.h" +#include "entities/TextureWrapper.h" + +class Flag +{ +public: + Flag() + { + this->texture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::FLAG), {40, 40}); + } + Flag(Vector2 startPos) + { + this->texture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::FLAG), {40, 40}, startPos); + } + + void logic() + { + // if (touched) + // { + // // StateService::getInstance().setScreen(EScreen::VICTORY); + // } + } + + void draw() + { + DrawTextureEx(texture.getTexture(), texture.getPosition(), 0.0f, texture.getSize().x/texture.getTexture().width, WHITE); + } + + TextureWrapper &getObject() + { + return this->texture; + } + + void setTouched(bool touched) { + this->touched = touched; + } + +private: + TextureWrapper texture; + bool touched = false; // Whether the player has touched the flag +}; + +#endif \ No newline at end of file diff --git a/include/entities/Game.h b/include/entities/Game.h index 71491ce..d0360c8 100644 --- a/include/entities/Game.h +++ b/include/entities/Game.h @@ -7,19 +7,28 @@ #include "services/AudioService.h" #include "services/MapService.h" #include "services/InputService.h" -#include "entities/Player.h" +#include "services/GameService.h" +#include "services/CollectableService.h" +#include "services/EnemyService.h" +#include "services/PlayerService.h" +#include "services/BulletService.h" +#include "entities/Text.h" +#include +#include class Game { public: Game() { - TextureWrapper wrapper1 = TextureWrapper(AssetService::getInstance().getBackground(EBackground::GAME1), {GetScreenWidth()*2.0f,(float)GetScreenHeight()}, {0,0}); - wrapper1.setSourceRect({0,0,wrapper1.getSize().x, wrapper1.getSize().y}); - TextureWrapper wrapper2 = TextureWrapper(AssetService::getInstance().getBackground(EBackground::GAME2), {864.0f,(float)GetScreenHeight()}, {wrapper1.getSize().x,0.0}); - wrapper2.setSourceRect({0,200,wrapper2.getSize().x, 200+wrapper2.getSize().y}); - TextureWrapper wrapper3 = TextureWrapper(AssetService::getInstance().getBackground(EBackground::GAME3), {1792.0f,(float)GetScreenHeight()}, {wrapper1.getSize().x+wrapper2.getSize().x,0.0}); - wrapper3.setSourceRect({64,200,64+wrapper3.getSize().x, 200+wrapper3.getSize().y}); + GameService::getInstance().resetGame(); + + TextureWrapper wrapper1 = TextureWrapper(AssetService::getInstance().getBackground(EBackground::GAME1), {GetScreenWidth() * 2.0f, (float)GetScreenHeight()}, {0, 0}); + wrapper1.setSourceRect({0, 0, wrapper1.getSize().x, wrapper1.getSize().y}); + TextureWrapper wrapper2 = TextureWrapper(AssetService::getInstance().getBackground(EBackground::GAME2), {864.0f, (float)GetScreenHeight()}, {wrapper1.getSize().x, 0.0}); + wrapper2.setSourceRect({0, 200, wrapper2.getSize().x, 200 + wrapper2.getSize().y}); + TextureWrapper wrapper3 = TextureWrapper(AssetService::getInstance().getBackground(EBackground::GAME3), {1728.0f, (float)GetScreenHeight()}, {wrapper1.getSize().x + wrapper2.getSize().x, 0.0}); + wrapper3.setSourceRect({64, 200, 64 + wrapper3.getSize().x, 200 + wrapper3.getSize().y}); std::vector wrappers; wrappers.push_back(wrapper1); @@ -35,56 +44,347 @@ public: camera.target = (Vector2){screenSize.x / 2.0f, screenSize.y / 2.0f}; // Initially at the center of the world camera.rotation = 0.0f; camera.zoom = 1.0f; - this->camera = camera; + GameService::getInstance().setCamera(camera); + BulletService::getInstance().initBullets(); + PlayerService::getInstance().initPlayer(); + this->crosshair = TextureWrapper(AssetService::getInstance().getSprite(ESprite::CROSSHAIR), {15, 15}, {0, 0}, {0, 0, 15, 15}); - this->player = Player(); - this->player.getTexture().setPosition({200,350}); + CollectableService::getInstance().initCollectables(); + std::cout << "Initting" << std::endl; - InputService::getInstance().setKeysToWatch({KEY_SPACE, KEY_A, KEY_D}, {MOUSE_BUTTON_LEFT}); + initHud(); + initPauseScreen(); + initGameOverScreen(); + EnemyService::getInstance().initEnemies(); + InputService::getInstance().setKeysToWatch({KEY_A, KEY_W, KEY_S, KEY_D, KEY_ENTER}, {MOUSE_BUTTON_LEFT}); AudioService::getInstance().setMusic(EMusic::GAME); AudioService::getInstance().playMusic(); } + ~Game() { } void logic() { - // this->camera.offset.x -= 1.5; - this->player.logic(); - if(this->player.getTexture().getPosition().x > (StateService::getInstance().getScreenSize().x / 2.0f)) { - this->camera.offset = {(StateService::getInstance().getScreenSize().x / 2.0f) +((StateService::getInstance().getScreenSize().x / 2.0f) - this->player.getTexture().getPosition().x), StateService::getInstance().getScreenSize().y / 2.0f}; + this->crosshair.setPosition(InputService::getInstance().getMousePos()); + if (GameService::getInstance().isPaused()) + { + pausedLogic(); + return; + } + else if (GameService::getInstance().isGameOver()) + { + gameOverLogic(); + return; + } + + if (InputService::getInstance().isKeyPressed(KEY_ENTER)) + { + GameService::getInstance().setPaused(true); + return; + } + + BulletService::getInstance().bulletLogic(); + CollectableService::getInstance().collectableLogic(); + EnemyService::getInstance().enemyLogic(); + + auto &player = PlayerService::getInstance().getPlayer(); + player.logic(); + if (player.getTexture().getPosition().x > (StateService::getInstance().getScreenSize().x / 2.0f) && + player.getTexture().getPosition().x + (StateService::getInstance().getScreenSize().x / 2.0f) < background.getBackgroundSize().x) + { + GameService::getInstance().getCamera().offset = {(StateService::getInstance().getScreenSize().x / 2.0f) + ((StateService::getInstance().getScreenSize().x / 2.0f) - player.getTexture().getPosition().x), StateService::getInstance().getScreenSize().y / 2.0f}; + } + if (player.isFinishReached()) + { + StateService::getInstance().setScreen(EScreen::VICTORY); } } void draw() { BeginDrawing(); - BeginMode2D(this->camera); + BeginMode2D(GameService::getInstance().getCamera()); this->background.draw(); + + BulletService::getInstance().drawBullets(); this->map.draw(); - this->player.draw(); + CollectableService::getInstance().drawCollectables(); + EnemyService::getInstance().drawEnemies(); + PlayerService::getInstance().getPlayer().draw(); EndMode2D(); + + drawHud(); + if (GameService::getInstance().isPaused()) + { + drawPaused(); + } + else if (GameService::getInstance().isGameOver()) + { + drawGameOver(); + } + else + { + drawCrosshair(); + } EndDrawing(); } private: - Camera2D camera; Background background; Map map; - Player player; - // std::vector enemies - // std::vector crates - // std::vector playerBullets - // std::vector enemyBullets - // std::vector jellies - // std::vector ammo - // std::vector healthpacks - // Powerup powerup + TextureWrapper crosshair; + std::vector enemies; + + std::vector pauseScreenButtons; + int hoveredPauseScreenButton = 0; + + Text gameOverText; + std::vector gameOverScreenButtons; + int hoveredGameOverScreenButton = 0; + + Text hudJellyText; + TextureWrapper hudJellyTexture; + + void initHud() + { + hudJellyTexture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::JELLY), {32, 32}, {500, 5}); + hudJellyText = Text("0/3", EFont::SANSATION, 30, 0, RED, {hudJellyTexture.getPosition().x + hudJellyTexture.getSize().x * 1.5f, hudJellyTexture.getPosition().y + 5}); + } + + void initPauseScreen() + { +#if defined(PLATFORM_WEB) + const int TOP_GAP = 100; + const int BOTTOM_GAP = 100; + const int AMNT_OF_BUTTONS = 2; +#else + const int TOP_GAP = 100; + const int BOTTOM_GAP = 50; + const int AMNT_OF_BUTTONS = 4; +#endif + + const int TOTAL_SPACE = GetScreenHeight() - (TOP_GAP + BOTTOM_GAP); + const int SPACE_PER_ITEM = TOTAL_SPACE / AMNT_OF_BUTTONS; + + for (int i = 0; i < AMNT_OF_BUTTONS; i++) + { + Text txt = Text("Continue", EFont::SANSATION, 30, 0, WHITE); + pauseScreenButtons.push_back(txt); + } + pauseScreenButtons[0].setColor(RED); + +#if defined(PLATFORM_WEB) + pauseScreenButtons[1].setText("Quit to Main Menu"); +#else + pauseScreenButtons[1].setText("Save Game"); + pauseScreenButtons[2].setText("Quit to Main Menu"); + pauseScreenButtons[3].setText("Quit to Desktop"); +#endif + + for (int i = 0; i < AMNT_OF_BUTTONS; i++) + { +#if defined(PLATFORM_WEB) + pauseScreenButtons[i].setPosition({(float)(GetScreenWidth() - pauseScreenButtons[i].getDimensions().x) / 2.0f, (float)TOP_GAP + SPACE_PER_ITEM * i + SPACE_PER_ITEM / 2.0f}); + +#else + pauseScreenButtons[i].setPosition({(float)(GetScreenWidth() - pauseScreenButtons[i].getDimensions().x) / 2.0f, (float)TOP_GAP + SPACE_PER_ITEM * i}); + +#endif + } + } + + void initGameOverScreen() + { + this->gameOverText = Text("Game Over", EFont::SANSATION, 60, 0, WHITE); + this->gameOverText.setPosition({(float)(GetScreenWidth() - gameOverText.getDimensions().x) / 2.0f, (float)(GetScreenHeight() - gameOverText.getDimensions().y) / 3.0f}); + + const int TOP_POS = GetScreenHeight() / 1.8; + const int X_GAP = 90; + const int AMNT_OF_BUTTONS = 2; + const int TOTAL_SPACE = GetScreenWidth() - (X_GAP + X_GAP); + const int SPACE_PER_ITEM = TOTAL_SPACE / AMNT_OF_BUTTONS; + for (int i = 0; i < AMNT_OF_BUTTONS; i++) + { + Text txt = Text("Play Again", EFont::SANSATION, 30, 0, BLACK); + gameOverScreenButtons.push_back(txt); + } + gameOverScreenButtons[0].setColor(WHITE); + gameOverScreenButtons[1].setText("Quit to Main Menu"); + for (int i = 0; i < AMNT_OF_BUTTONS; i++) + { + gameOverScreenButtons[i].setPosition({(float)X_GAP + SPACE_PER_ITEM * i, (float)TOP_POS}); + } + } + + void pausedLogic() + { + if (InputService::getInstance().isKeyPressed(KEY_S) || InputService::getInstance().isKeyPressed(KEY_W)) + { + pauseScreenButtons[hoveredPauseScreenButton].setColor(WHITE); + /** Move up or down depending on the pressed button. +4 is used so that -1 becomes 3 (the last button) */ + hoveredPauseScreenButton = ((hoveredPauseScreenButton - (InputService::getInstance().isKeyPressed(KEY_S) ? -1 : 1)) + pauseScreenButtons.size()) % pauseScreenButtons.size(); + pauseScreenButtons[hoveredPauseScreenButton].setColor(RED); + } + + if (InputService::getInstance().isKeyPressed(KEY_ENTER)) + { + + std::string selectedButton = pauseScreenButtons[hoveredPauseScreenButton].getText(); + + if (selectedButton == "Continue") + { + GameService::getInstance().setPaused(false); + AudioService::getInstance().playMusic(); + } + else if (selectedButton == "Save Game") + { + } + else if (selectedButton == "Quit to Main Menu") + StateService::getInstance().setScreen(EScreen::MAIN); + else if (selectedButton == "Quit to Desktop") + StateService::getInstance().setScreen(EScreen::CLOSE); + } + } + + void gameOverLogic() + { + if (InputService::getInstance().isKeyPressed(KEY_D) || InputService::getInstance().isKeyPressed(KEY_A)) + { + gameOverScreenButtons[hoveredGameOverScreenButton].setColor(BLACK); + /** Move up or down depending on the pressed button. +4 is used so that -1 becomes 3 (the last button) */ + hoveredGameOverScreenButton = ((hoveredGameOverScreenButton - (InputService::getInstance().isKeyPressed(KEY_D) ? -1 : 1)) + gameOverScreenButtons.size()) % gameOverScreenButtons.size(); + gameOverScreenButtons[hoveredGameOverScreenButton].setColor(WHITE); + } + + if (InputService::getInstance().isKeyPressed(KEY_ENTER)) + { + std::string selectedButton = gameOverScreenButtons[hoveredGameOverScreenButton].getText(); + + if (selectedButton == "Play Again") + { + StateService::getInstance().setScreen(EScreen::GAME); + } + else if (selectedButton == "Quit to Main Menu") + { + StateService::getInstance().setScreen(EScreen::MAIN); + } + GameService::getInstance().setGameOver(false); + } + } + + void drawPaused() + { + // 100, 100, 255, 60 + DrawRectangle(0, 0, StateService::getInstance().getScreenSize().x, StateService::getInstance().getScreenSize().y, {0, 0, 0, 200}); + if (pauseScreenButtons.size() > 0) + { + for (int i = 0; i < pauseScreenButtons.size(); i++) + { + pauseScreenButtons[i].draw(); + } + } + } + + void drawGameOver() + { + DrawRectangle(0, 0, StateService::getInstance().getScreenSize().x, StateService::getInstance().getScreenSize().y, {100, 0, 0, 200}); + + gameOverText.draw(); + + for (int i = 0; i < gameOverScreenButtons.size(); i++) + { + gameOverScreenButtons[i].draw(); + } + } + + void drawCrosshair() + { + DrawTextureEx( + this->crosshair.getTexture(), + {InputService::getInstance().getMousePos().x - this->crosshair.getSize().x / 2.0f, InputService::getInstance().getMousePos().y - this->crosshair.getSize().y / 2.0f}, + 0, + 1.2f, + RED); + } + + void drawHud() + { + drawHudHealth(); + drawHudBullets(); + drawHudJellies(); + } + + void drawHudBullets() + { + Vector2i pixel_pos = Vector2i(5, 40); + Texture2D ammoSprite = AssetService::getInstance().getSprite(ESprite::HUD_AMMO); + Vector2i display_pos; + for (int i = 0; i < PlayerService::getInstance().getPlayer().getAmmo(); i++) + { + if (i <= 49) + { + display_pos = Vector2i(pixel_pos.x + ammoSprite.width * i, pixel_pos.y); + } + if (i > 49 && i <= 99) + { + display_pos = Vector2i(pixel_pos.x + ammoSprite.width * (i - (50)), pixel_pos.y + ammoSprite.height); + } + else if (i > 99 && i <= 149) + { + display_pos = Vector2i(pixel_pos.x + ammoSprite.width * (i - (100)), pixel_pos.y + ammoSprite.height * 2); + } + else if (i > 149 && i <= 199) + { + display_pos = Vector2i(pixel_pos.x + ammoSprite.width * (i - (150)), pixel_pos.y + ammoSprite.height * 3); + } + else if (i > 199 && i <= 249) + { + display_pos = Vector2i(pixel_pos.x + ammoSprite.width * (i - (200)), pixel_pos.y + ammoSprite.height * 4); + } + DrawTexture(ammoSprite, display_pos.x, display_pos.y, WHITE); + } + } + + void drawHudHealth() + { + Vector2i pixel_pos = Vector2i(5, 5); + Texture2D noHealthSprite = AssetService::getInstance().getSprite(ESprite::HUD_HEART_EMPTY); + Texture2D healthSprite = AssetService::getInstance().getSprite(ESprite::HUD_HEART); + // sVector2f coord_pos = GetWindow().mapPixelToCoords(pixel_pos); + for (int i = 0; i < PlayerService::getInstance().getPlayer().getMaxHealth(); i++) + { + Vector2i pos = Vector2i(pixel_pos.x + noHealthSprite.width * i, pixel_pos.y); + // noHealthSprite[i].setPosition(coord_pos.x + noHealthTexture.getSize().x * i, coord_pos.y); + DrawTexture(noHealthSprite, pos.x, pos.y, WHITE); + } + for (int i = 0; i < PlayerService::getInstance().getPlayer().getHealth(); i++) + { + // healthSprite[i].setPosition(coord_pos.x + healthTexture.getSize().x * i, coord_pos.y); + // DrawTexture(ammoSprite, display_pos.x, display_pos.y, WHITE); + Vector2i pos = Vector2i(pixel_pos.x + healthSprite.width * i, pixel_pos.y); + // noHealthSprite[i].setPosition(coord_pos.x + noHealthTexture.getSize().x * i, coord_pos.y); + DrawTexture(healthSprite, pos.x, pos.y, WHITE); + } + } + + void drawHudJellies() + { + std::stringstream stream; + stream.clear(); + stream << CollectableService::getInstance().getAmountOfJelliesFreed(); + stream << "/"; + stream << CollectableService::getInstance().getMaxAmountOfJellies(); + hudJellyText.setText(stream.str()); + + DrawTexture(hudJellyTexture.getTexture(), hudJellyTexture.getPosition().x, hudJellyTexture.getPosition().y, WHITE); + hudJellyText.draw(); + } }; #endif \ No newline at end of file diff --git a/include/entities/HealthPack.h b/include/entities/HealthPack.h new file mode 100644 index 0000000..dad0083 --- /dev/null +++ b/include/entities/HealthPack.h @@ -0,0 +1,43 @@ +#ifndef HEALTH_PACK_H +#define HEALTH_PACK_H + +#include "raylib.h" +#include "services/AssetService.h" +#include "entities/TextureWrapper.h" + +class HealthPack { + public: + HealthPack() { + this->object = TextureWrapper(AssetService::getInstance().getSprite(ESprite::HEALTHPACK),{32,32}); + } + void logic(){ + } + + void draw() { + DrawTexture(object.getTexture(), object.getPosition().x, object.getPosition().y, WHITE); + } + + int getHealAmount() { + return this->healAmount; + } + + void setPickedUp(bool pickedUp) { + this->pickedUp = pickedUp; + } + + bool hasBeenPickedUp() { + return this->pickedUp; + } + + TextureWrapper& getObject() { + return this->object; + } + + private: + TextureWrapper object; + unsigned int healAmount = 2; // Amount of health replenished by the pack + bool pickedUp; // When the player has picked up the pack +}; + + +#endif \ No newline at end of file diff --git a/include/entities/Jelly.h b/include/entities/Jelly.h new file mode 100644 index 0000000..b221a37 --- /dev/null +++ b/include/entities/Jelly.h @@ -0,0 +1,49 @@ +#ifndef JELLY_H +#define JELLY_H + +#include "raylib.h" +#include "services/AssetService.h" +#include "entities/TextureWrapper.h" + +class Jelly +{ +public: + Jelly(){ + this->texture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::JELLY),{32,32}); + // AssetService::getInstance().playSound(ESound::JELLY_FREED); + } + Jelly(Vector2 startPos) + { + this->startPosition = startPos; + this->texture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::JELLY),{32,32},startPos); + // AssetService::getInstance().playSound(ESound::JELLY_FREED); + } + + void logic() + { + if (texture.getPosition().y >= startPosition.y) + { + velocity.y = -jumpSpeed; + } + else + { + velocity.y += gravity; + } + + texture.move(velocity); + } + + void draw() + { + DrawTexture(texture.getTexture(), texture.getPosition().x, texture.getPosition().y, WHITE); + } + +private: + TextureWrapper texture; + float jumpSpeed = 7.0f; + float gravity = 0.6f; + Vector2 velocity = {0, 0}; + Vector2 startPosition = {0,0}; +}; + +#endif \ No newline at end of file diff --git a/include/entities/Map.h b/include/entities/Map.h index 7aeece0..acdbf7b 100644 --- a/include/entities/Map.h +++ b/include/entities/Map.h @@ -1,9 +1,10 @@ #ifndef MAP_H #define MAP_H -#include #include "raylib.h" #include "entities/TextureWrapper.h" +#include "entities/Vector2i.h" +#include #include /** @@ -18,12 +19,20 @@ public: void draw(); /** Checks and returns which corners of the object have been collided with */ - std::unordered_map checkForCollision(TextureWrapper texture); + // std::unordered_map checkForCollision(TextureWrapper texture); int getTileSize() { return this->tileSize; } + std::vector> getColMap() { + return this->colMap; + } + + void updateColmapTile(Vector2i tile, bool collision) { + this->colMap[tile.y][tile.x] = collision ? 1 : 0; + } + private: /** Holds the number for every single tile, which refers to a certain sprite in the tileset */ std::vector> map; @@ -41,7 +50,7 @@ private: /** * Converts the corners of the object to the matching tiles, so that they can be checked for collision */ - std::unordered_map cornersToTilePos(std::unordered_map corners); + // std::unordered_map cornersToTilePos(std::unordered_map corners); }; #endif \ No newline at end of file diff --git a/include/entities/PBullet.h b/include/entities/PBullet.h new file mode 100644 index 0000000..4dfa76f --- /dev/null +++ b/include/entities/PBullet.h @@ -0,0 +1,20 @@ +#ifndef PBULLET_H +#define PBULLET_H + +#include "services/AssetService.h" +#include "entities/Bullet.h" + +class PBullet : public Bullet +{ +public: + PBullet() {} + PBullet(Vector2 pos, float angle) : Bullet(pos, angle) + { + this->color = YELLOW; + } + +private: + void objCollision() override; +}; + +#endif \ No newline at end of file diff --git a/include/entities/Player.h b/include/entities/Player.h index eea1a0b..d4e0263 100644 --- a/include/entities/Player.h +++ b/include/entities/Player.h @@ -2,36 +2,54 @@ #define PLAYER_H #include "raylib.h" -#include -#include "services/AssetService.h" -#include "services/MapService.h" -#include "services/InputService.h" -#include "entities/TextureWrapper.h" #include "enums/ECorner.h" +#include "enums/ESound.h" +#include "services/AssetService.h" +#include "entities/TextureWrapper.h" +#include "entities/Vector2i.h" +#include +#include +#include + +class PBullet; class Player { public: Player() { - TextureWrapper wrapper = TextureWrapper(AssetService::getInstance().getSprite(ESprite::PLAYER), {32, 32}, {200, 200}); - wrapper.setSourceRect({0, 0, 32, 32}); - this->texture = wrapper; + this->texture = TextureWrapper(AssetService::getInstance().getSprite(ESprite::PLAYER), {32, 32}, {200, 200}); + prevFrame = GetTime(); + spriteAnimation = {0, Right}; + this->gunRight = TextureWrapper(AssetService::getInstance().getSprite(ESprite::PLAYER_GUN_RIGHT), {58, 29}, {0, 0}); + this->gunLeft = TextureWrapper(AssetService::getInstance().getSprite(ESprite::PLAYER_GUN_LEFT), {58, 29}, {0, 0}); + this->ammo = max_ammo; + this->prevShot = GetTime(); + this->health = max_health; } + ~Player() { } void logic() { - gravity(); + /** Fall out of the map when the jelly has been killed */ + if(this->health == 0) { + fall(); + return; + } + rotateGun(); + shoot(); movement(); - handleCollision(); + outofboundsCheck(); + consumableCollision(); } void draw() { - DrawTextureRec(this->texture.getTexture(), this->texture.getSourceRect(), this->texture.getPosition(), WHITE); + drawGun(); + DrawTextureRec(this->texture.getTexture(), this->texture.getSourceRect(), this->texture.getPosition(), gainedPowerup ? RED : WHITE); } TextureWrapper &getTexture() @@ -39,85 +57,80 @@ public: return this->texture; } + void setPosition(Vector2 pos) + { + this->texture.setPosition(pos); + } + + int getAmmo() + { + return this->ammo; + } + + uint8_t getHealth() + { + return this->health; + } + + uint8_t getMaxHealth() + { + return this->max_health; + } + + void takeDamage(uint8_t damage) { + this->health = std::max(health - damage, 0); + } + + bool isFinishReached() { + return this->finishReached; + } + protected: private: TextureWrapper texture; + TextureWrapper gunLeft; + TextureWrapper gunRight; Vector2 velocity = {0, 0}; + float jumpPower = 7.2f; + float moveSpeed = 5.0f; + float gravity = 0.6f; + float animationDelay = 0.2f; // In seconds + double prevFrame; // Time since the last frame + Vector2i spriteAnimation; // Keeps track of (1) Sprite cycling and (2) Player direction + enum Direction + { + Down, + Left, + Right, + Up + }; // Used in conjuction with spriteAnimation, makes sure sprite direction matches with player direction + std::vector tiles; // Holds eight points of the player for accurate collision std::vector timesCollision; // Holds the amount of times the player has collided with a tile, and which corner of the player is touching a tile bool groundCollision; // Keeps track of whether or not the player has collided with the ground - bool crateTopCollision; - /** - * Holds the corners of the player - * Is used for precise collision detection - */ + bool crateTopCollision; // Whether the player is standing on top of a crate - void gravity() - { - if (!groundCollision) - { - velocity.y += 0.6f; - } - else - { - // velocity.y = 0.0f; - } - } + uint8_t ammo; // Current ammo of the player + uint8_t max_ammo = 100; // Max ammo of the player + float firingRate = 0.2f; // Delay in seconds + double prevShot; // The last time the player fired a bullet + uint8_t health; // Current health of the player + uint8_t max_health = 10; // Max health of the player + bool gainedPowerup = false; + bool finishReached = false; - void movement() - { - velocity.x = 0.0f; - if (InputService::getInstance().isKeyDown(KEY_A)) - { - velocity.x -= 5.0f; - } - else if (InputService::getInstance().isKeyDown(KEY_D)) - { - velocity.x += 5.0f; - } - - if (InputService::getInstance().isKeyPressed(KEY_SPACE) && groundCollision) - { - velocity.y -= 7.2f; - } - } - - /** - * Stores the current position - * Applies the current velocity - * Checks for collision, and removes all velocities that are causing collisions - * Set the object exactly against the object it is colliding with position - */ - void handleCollision() - { - Vector2 tempPos = this->texture.getPosition(); - this->texture.move(velocity); - - groundCollision = false; - std::unordered_map collisions = MapService::getInstance().getMap().checkForCollision(this->texture); - - if (collisions[ECorner::BOTTOM_LEFT] || collisions[ECorner::BOTTOM_RIGHT]) - { - velocity.y = 0.0f; - groundCollision = true; - this->texture.setPosition({this->texture.getPosition().x, floorf(this->texture.getPosition().y / MapService::getInstance().getMap().getTileSize()) * MapService::getInstance().getMap().getTileSize()}); - } - if (collisions[ECorner::LEFT_TOP] && collisions[ECorner::LEFT_BOTTOM]) - { - velocity.x = 0.0f; - this->texture.setPosition({ceilf(this->texture.getPosition().x / MapService::getInstance().getMap().getTileSize()) * MapService::getInstance().getMap().getTileSize(), this->texture.getPosition().y}); - } - if (collisions[ECorner::TOP_LEFT] || collisions[ECorner::TOP_RIGHT]) - { - velocity.y = 0.0f; - this->texture.setPosition({this->texture.getPosition().x, (ceilf(this->texture.getPosition().y / MapService::getInstance().getMap().getTileSize())) * MapService::getInstance().getMap().getTileSize()}); - } - if (collisions[ECorner::RIGHT_TOP] && collisions[ECorner::RIGHT_BOTTOM]) - { - velocity.x = 0.0f; - this->texture.setPosition({floorf(this->texture.getPosition().x / MapService::getInstance().getMap().getTileSize()) * MapService::getInstance().getMap().getTileSize(), this->texture.getPosition().y}); - } - } + void applyGravity(); + void movement(); + void move(Vector2 velocity); + void animate(); + void positionUpdates(); + void handleCollision(); + void consumableCollision(); + void outofboundsCheck(); + void rotateGun(); + void drawGun(); + void shoot(); + void fall(); // Lets the player fall out of the map when he dies }; #endif diff --git a/include/entities/Powerup.h b/include/entities/Powerup.h new file mode 100644 index 0000000..837996b --- /dev/null +++ b/include/entities/Powerup.h @@ -0,0 +1,59 @@ +#ifndef POWERUP_H +#define POWERUP_H + +#include "raylib.h" +#include "services/AssetService.h" +#include "entities/TextureWrapper.h" +#include + +class Powerup { + public: + Powerup() { + this->object = TextureWrapper(AssetService::getInstance().getSprite(ESprite::POWERUP),{50,55}); + } + void logic(){ + animate(); + } + + void draw() { + DrawTextureRec(object.getTexture(), object.getSourceRect(), object.getPosition(), WHITE); + } + + float getExtraJumpPower() { + return this->extraJumpPower; + } + + void setPickedUp(bool pickedUp) { + this->pickedUp = pickedUp; + } + + bool hasBeenPickedUp() { + return this->pickedUp; + } + + TextureWrapper& getObject() { + return this->object; + } + + private: + TextureWrapper object; + float extraJumpPower = 2.0f; // Jump power gained by grabbing the powerup + bool pickedUp = false; // When the player has picked up the pack + float animationDelay = 0.08f; // How long to wait in between animation frames (in seconds) + double lastFrame; // Time since the last frame + uint8_t currentFrame = 0; // Current frame of the animation + uint8_t amountOfFrames = 8; // Total amount of animation frames + + void animate() { + if(GetTime() - lastFrame > animationDelay) { + currentFrame ++; + currentFrame %= amountOfFrames; + + object.setSourceRect({(float)currentFrame * 81 + 16, 10,object.getSize().x, object.getSize().y}); + lastFrame = GetTime(); + } + } +}; + + +#endif \ No newline at end of file diff --git a/include/entities/Text.h b/include/entities/Text.h index 21dd3b0..e667f11 100644 --- a/include/entities/Text.h +++ b/include/entities/Text.h @@ -3,46 +3,53 @@ #include "raylib.h" #include "enums/EFont.h" -#include -#include #include "services/AssetService.h" +#include -class Text { - public: - Text(std::string text, EFont font, int fontSize = 30, int spacing = 0, Color color = WHITE, Vector2 position = {100,100}); - ~Text(){} +class Text +{ +public: + Text() {} + Text(std::string text, EFont font, int fontSize = 30, int spacing = 0, Color color = WHITE, Vector2 position = {100, 100}); + ~Text() { + } - Vector2 getDimensions(); + Vector2 getDimensions(); - void setPosition(Vector2 position) { - this->position = position; - } + void setPosition(Vector2 position) + { + this->position = position; + } - void setColor(Color color) { - this->color = color; - } + void setColor(Color color) + { + this->color = color; + } - void setText(std::string text) { - this->text = text; - } + void setText(std::string text) + { + this->text = text; + } - Vector2 getPosition() { - return position; - } + Vector2 getPosition() + { + return position; + } - std::string getText() { - return this->text; - } + std::string getText() + { + return this->text; + } - void draw(); + void draw(); - private: - std::string text; - Font font; - int fontSize; - int spacing; - Color color; - Vector2 position; +private: + std::string text; + Font font; + int fontSize; + int spacing; + Color color; + Vector2 position; }; #endif \ No newline at end of file diff --git a/include/entities/TextureWrapper.h b/include/entities/TextureWrapper.h index accd1c8..129858c 100644 --- a/include/entities/TextureWrapper.h +++ b/include/entities/TextureWrapper.h @@ -3,7 +3,6 @@ #include "raylib.h" #include "enums/ECorner.h" -#include class TextureWrapper { public: @@ -12,9 +11,9 @@ class TextureWrapper { this->texture = texture; this->size = size; this->position = position; + this->sourceRect = sourceRect; } ~TextureWrapper() { - } Texture2D getTexture() { @@ -33,6 +32,10 @@ class TextureWrapper { return this->position; } + float getRotation() { + return this->rotation; + } + void setSourceRect(Rectangle rect) { this->sourceRect = rect; } @@ -41,28 +44,12 @@ class TextureWrapper { this->position = position; } - void move(Vector2 deltaPos) { - this->position = {this->position.x + deltaPos.x, this->position.y + deltaPos.y}; + void setRotation(float rot) { + this->rotation = rot; } - /** Returns the 4 corners of the texture */ - std::unordered_map getCorners() { - std::unordered_map corners; - float top = this->position.y; - float right = this->position.x+this->size.x; - float bottom = this->position.y + this->size.y; - float left = this->position.x; - float margin = 8; // Space to the corner - corners[ECorner::TOP_LEFT] = {left+margin, top}; - corners[ECorner::TOP_RIGHT] = {right-margin, top}; - corners[ECorner::RIGHT_TOP] = {right, top+margin}; - corners[ECorner::RIGHT_BOTTOM] = {right, bottom-margin}; - corners[ECorner::BOTTOM_RIGHT] = {right-margin, bottom}; - corners[ECorner::BOTTOM_LEFT] = {left+margin, bottom}; - corners[ECorner::LEFT_BOTTOM] = {left, bottom-margin}; - corners[ECorner::LEFT_TOP] = {left, top+margin}; - - return corners; + void move(Vector2 deltaPos) { + this->position = {this->position.x + deltaPos.x, this->position.y + deltaPos.y}; } private: @@ -70,6 +57,7 @@ class TextureWrapper { Rectangle sourceRect; Vector2 size; Vector2 position; + float rotation; }; #endif \ No newline at end of file diff --git a/include/entities/Vector2i.h b/include/entities/Vector2i.h new file mode 100644 index 0000000..950533a --- /dev/null +++ b/include/entities/Vector2i.h @@ -0,0 +1,15 @@ +#ifndef VECTOR2I_H +#define VECTOR2I_H + +class Vector2i { + public: + Vector2i(){} + Vector2i (int x, int y) { + this->x = x; + this->y = y; + } + int x; + int y; +}; + +#endif \ No newline at end of file diff --git a/include/enums/ESound.h b/include/enums/ESound.h new file mode 100644 index 0000000..d70f1a4 --- /dev/null +++ b/include/enums/ESound.h @@ -0,0 +1,18 @@ +#ifndef ESOUND_H +#define ESOUND_H + +enum class ESound { + SHOOT, + NO_AMMO, + CRATE_BREAK, + JELLY_FREED, + HEAL_PICKUP, + AMMO_PICKUP, + POWERUP, + PLAYER_DAMAGE, + ENEMY_DEFEATED, + VICTORY, + PLAYER_SPOTTED +}; + +#endif \ No newline at end of file diff --git a/include/enums/ESprite.h b/include/enums/ESprite.h index 5e70281..77f4950 100644 --- a/include/enums/ESprite.h +++ b/include/enums/ESprite.h @@ -15,7 +15,9 @@ enum class ESprite { POWERUP, HUD_HEART, HUD_HEART_EMPTY, - HUD_AMMO + HUD_AMMO, + CROSSHAIR, + FLAG, }; #endif \ No newline at end of file diff --git a/include/screens/Screen.h b/include/screens/Screen.h index 90270ec..9acc00c 100644 --- a/include/screens/Screen.h +++ b/include/screens/Screen.h @@ -5,7 +5,8 @@ class Screen { public: Screen(){} - ~Screen() {} + ~Screen() { + } /** * Execute all logic for this frame diff --git a/include/screens/VictoryScreen.h b/include/screens/VictoryScreen.h new file mode 100644 index 0000000..600f790 --- /dev/null +++ b/include/screens/VictoryScreen.h @@ -0,0 +1,30 @@ +#ifndef VICTORY_SCREEN_H +#define VICTORY_SCREEN_H + +#include "raylib.h" +#include "screens/Screen.h" +#include "services/AssetService.h" +#include "entities/TextureWrapper.h" +#include "entities/Text.h" +#include "entities/Background.h" +#include + +class VictoryScreen : public Screen +{ + +public: + VictoryScreen(); + ~VictoryScreen() { + } + void logic() override; + void draw() override; + +private: + std::vector text; + std::vector textures; + std::vector buttons; + int hoveredButton = 0; + Background background; +}; + +#endif \ No newline at end of file diff --git a/include/services/AssetService.h b/include/services/AssetService.h index 5eec69e..42080cf 100644 --- a/include/services/AssetService.h +++ b/include/services/AssetService.h @@ -10,6 +10,7 @@ #include "enums/EBackground.h" #include "enums/EMap.h" #include "enums/ESprite.h" +#include "enums/ESound.h" #include "services/Service.h" class AssetService: public Service @@ -32,6 +33,10 @@ public: return this->sprites[sprite]; } + void playSound(ESound sound) { + PlaySound(sounds[sound]); + } + protected: AssetService(); @@ -41,7 +46,7 @@ private: std::string assetDir; std::unordered_map fonts; - std::vector sounds; + std::unordered_map sounds; std::unordered_map music; std::unordered_map backgrounds; std::unordered_map maps; diff --git a/include/services/BulletService.h b/include/services/BulletService.h new file mode 100644 index 0000000..ab9ee19 --- /dev/null +++ b/include/services/BulletService.h @@ -0,0 +1,57 @@ +#ifndef BULLET_SERVICE_H +#define BULLET_SERVICE_H + +#include "raylib.h" +#include "services/Service.h" +#include "services/AssetService.h" +#include "entities/Bullet.h" +#include "entities/PBullet.h" +#include "entities/EBullet.h" +#include +#include + +class BulletService : public Service +{ +public: + void initBullets() + { + this->bullets.clear(); + } + + void bulletLogic() + { + for (auto it = this->bullets.begin(); it != this->bullets.end();) + { + (*it)->logic(); + if ((*it)->getHasCollided()) + it = this->bullets.erase(it); + else + ++it; + } + } + + void drawBullets() + { + for (auto it = this->bullets.begin(); it != this->bullets.end();) + { + (*it)->draw(); + ++it; + } + } + + void addPlayerBullet(Vector2 position, float direction) + { + this->bullets.push_back(std::make_unique(position, direction)); + } + + void addEnemyBullet(Vector2 position, float direction) + { + this->bullets.push_back(std::make_unique(position, direction)); + } + +protected: +private: + std::vector> bullets; +}; + +#endif \ No newline at end of file diff --git a/include/services/CollectableService.h b/include/services/CollectableService.h new file mode 100644 index 0000000..94ef4d6 --- /dev/null +++ b/include/services/CollectableService.h @@ -0,0 +1,114 @@ +#ifndef COLLECTABLE_SERVICE_H +#define COLLECTABLE_SERVICE_H + +#include "raylib.h" +#include "services/Service.h" +#include "entities/Vector2i.h" +#include "entities/Crate.h" +#include "entities/Jelly.h" +#include "entities/AmmoPack.h" +#include "entities/HealthPack.h" +#include "entities/Powerup.h" +#include "entities/Flag.h" +#include + +/** + * Service that manages the collectables (jellies), consumables (Ammo, health, powerups), and the finish flag + */ +class CollectableService : public Service +{ +public: + void initCollectables() + { + initCrates(); + initJellies(); + initAmmoPacks(); + initHealthPacks(); + initPowerups(); + initFlag(); + } + + void collectableLogic() + { + crateLogic(); + jellyLogic(); + ammoPackLogic(); + healthPackLogic(); + powerupLogic(); + flag.logic(); + } + + void drawCollectables() + { + drawCrates(); + drawJellies(); + drawAmmoPacks(); + drawHealthpacks(); + drawPowerups(); + flag.draw(); + } + + std::vector &getCrates() + { + return this->crates; + } + + std::vector &getAmmoPacks() + { + return this->ammoPacks; + } + + std::vector &getHealthPacks() + { + return this->healthPacks; + } + + std::vector &getPowerups() { + return this->powerUps; + } + + Flag& getFlag() { + return this->flag; + } + + int getMaxAmountOfJellies() + { + return this->amountOfCrates; + } + + int getAmountOfJelliesFreed() + { + return this->jellies.size(); + } + +private: + int amountOfCrates = 3; + int amountOfAmmoPacks = 4; + int amountOfHealthPacks = 4; + int amountOfPowerups = 1; + std::vector crates; + std::vector ammoPacks; + std::vector healthPacks; + std::vector jellies; + std::vector powerUps; + Flag flag; + + void initCrates(); + void initJellies(); + void initAmmoPacks(); + void initHealthPacks(); + void initPowerups(); + void initFlag(); + void crateLogic(); + void jellyLogic(); + void ammoPackLogic(); + void healthPackLogic(); + void powerupLogic(); + void drawCrates(); + void drawJellies(); + void drawAmmoPacks(); + void drawHealthpacks(); + void drawPowerups(); +}; + +#endif \ No newline at end of file diff --git a/include/services/EnemyService.h b/include/services/EnemyService.h new file mode 100644 index 0000000..ee56a85 --- /dev/null +++ b/include/services/EnemyService.h @@ -0,0 +1,22 @@ +#ifndef ENEMY_SERVICE_H +#define ENEMY_SERVICE_H + +#include "raylib.h" +#include "services/Service.h" +#include "entities/Enemy.h" +#include + +class EnemyService : public Service +{ +public: + void initEnemies(); + void enemyLogic(); + void drawEnemies(); + std::vector &getEnemies(); + +protected: +private: + std::vector enemies; +}; + +#endif \ No newline at end of file diff --git a/include/services/GameService.h b/include/services/GameService.h new file mode 100644 index 0000000..9b6b42a --- /dev/null +++ b/include/services/GameService.h @@ -0,0 +1,77 @@ +#ifndef GAME_SERVICE_H +#define GAME_SERVICE_H + +#include "raylib.h" +#include "services/Service.h" +#include "services/AudioService.h" +#include "services/InputService.h" +#include +#include + +class GameService : public Service +{ +public: + bool isPaused() + { + return this->paused; + } + + bool isGameOver() + { + return this->gameOver; + } + + void setPaused(bool paused) + { + this->paused = paused; + if (this->paused) + AudioService::getInstance().pauseMusic(); + } + + void setGameOver(bool gameOver) + { + this->gameOver = gameOver; + if (this->gameOver) + { + AudioService::getInstance().setMusic(EMusic::GAME_OVER); + AudioService::getInstance().playMusic(); + } + } + + void setCamera(Camera2D camera) + { + this->camera = camera; + } + + Camera2D &getCamera() + { + return this->camera; + } + + Vector2 getMouseWorldPos() + { + Vector2 mouseWorldPos = {InputService::getInstance().getMousePos().x + camera.target.x - camera.offset.x, + InputService::getInstance().getMousePos().y}; + return mouseWorldPos; + } + + Vector2 toWorldPos(Vector2i pixelPos) + { + Vector2 worldPos = {(float)pixelPos.x + camera.target.x - camera.offset.x, (float)pixelPos.y}; + return worldPos; + } + + void resetGame() + { + paused = false; + gameOver = false; + // playerBullets.clear(); + } + +private: + bool paused = false; + bool gameOver = false; + Camera2D camera; +}; + +#endif \ No newline at end of file diff --git a/include/services/InputService.h b/include/services/InputService.h index e7f537b..de793f7 100644 --- a/include/services/InputService.h +++ b/include/services/InputService.h @@ -1,8 +1,8 @@ #ifndef INPUT_SERVICE_H #define INPUT_SERVICE_H -#include "Service.h" #include "raylib.h" +#include "Service.h" #include #include #include @@ -57,13 +57,26 @@ public: for (auto button : watchedMouseButtons) { mouseButtonsPressed[button] = false; + mouseButtonsDown[button] = false; if (IsMouseButtonPressed(button)) { mouseButtonsPressed[button] = true; } + if (IsMouseButtonDown(button)) + { + mouseButtonsDown[button] = true; + } } } + void checkMousePos() { + this->mousePosition = GetMousePosition(); + } + + Vector2 getMousePos() { + return this->mousePosition; + } + bool isKeyPressed(KeyboardKey key) { return keysPressed[key]; @@ -78,22 +91,30 @@ public: return mouseButtonsPressed[button]; } + bool isMouseButtonDown(MouseButton button) { + return mouseButtonsDown[button]; + } + private: + Vector2 mousePosition {0,0}; std::unordered_set watchedKeys; std::unordered_map keysPressed; std::unordered_map keysDown; std::unordered_set watchedMouseButtons; std::unordered_map mouseButtonsPressed; + std::unordered_map mouseButtonsDown; void setMouseButtonsToWatch(std::vector mouseButtonsToWatch) { this->watchedMouseButtons.clear(); this->mouseButtonsPressed.clear(); + this->mouseButtonsDown.clear(); for (int i = 0; i < mouseButtonsToWatch.size(); i++) { this->watchedMouseButtons.insert(mouseButtonsToWatch[i]); this->mouseButtonsPressed[mouseButtonsToWatch[i]] = false; + this->mouseButtonsDown[mouseButtonsToWatch[i]] = false; } } }; diff --git a/include/services/PlayerService.h b/include/services/PlayerService.h new file mode 100644 index 0000000..c0b4eca --- /dev/null +++ b/include/services/PlayerService.h @@ -0,0 +1,27 @@ +#ifndef PLAYER_SERVICE_H +#define PLAYER_SERVICE_H + +#include "raylib.h" +#include "services/Service.h" +#include "entities/Player.h" +#include + +class PlayerService : public Service +{ +public: + void initPlayer() + { + this->player = std::make_unique(); + this->player->setPosition({200, 350}); + } + + Player& getPlayer() { + return *this->player; + } + +protected: +private: + std::unique_ptr player; +}; + +#endif \ No newline at end of file diff --git a/include/services/StateService.h b/include/services/StateService.h index 01960b6..750a8fb 100644 --- a/include/services/StateService.h +++ b/include/services/StateService.h @@ -1,16 +1,17 @@ #ifndef STATE_SERVICE_H #define STATE_SERVICE_H -#include -#include -#include "services/Service.h" -#include "screens/Screen.h" -#include "enums/EScreen.h" #include "raylib.h" +#include "enums/EScreen.h" +#include "services/Service.h" +#include "services/InputService.h" +#include "screens/Screen.h" +#include #if defined(PLATFORM_WEB) #include #endif +// #include /** * Handles switching between screens @@ -32,30 +33,23 @@ public: * Start the game loop */ void startGame(); - static void staticStep() { - if (IsKeyPressed(KEY_ENTER) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) - { - std::cout << "Input!" << std::endl; - } // Access the singleton instance and call step getInstance().step(); } - + private: std::unique_ptr currentScreen; - bool gameRunning = true; - Vector2 screenSize; /** * One step of the gameloop */ void step(); - void handleMusic(); + }; #endif \ No newline at end of file diff --git a/scripts/build_and_run_project.sh b/scripts/build_and_run_project.sh index 96d0c67..4a2e7ed 100755 --- a/scripts/build_and_run_project.sh +++ b/scripts/build_and_run_project.sh @@ -1,6 +1,8 @@ #!/bin/bash clear # g++ src/main.cpp -o dist/jelly_rescue -L$LD_LIBRARY_PATH -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 -g++ $(find src/ -name "*.cpp") -o dist/jelly_rescue -I./include -L$LD_LIBRARY_PATH -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 +# g++ -std=c++20 $(find src/ -name "*.cpp") -o dist/jelly_rescue -I./include -L$LD_LIBRARY_PATH -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 +g++ -std=c++20 -g -O0 $(find src/ -name "*.cpp") -o dist/jelly_rescue -I./include -L$LD_LIBRARY_PATH -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 + # g++ src/main.cpp -o dist/jelly_rescue -L$LD_LIBRARY_PATH -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 -./dist/jelly_rescue +gdb dist/jelly_rescue diff --git a/scripts/build_wasm.sh b/scripts/build_wasm.sh index e921bfc..79c09b7 100755 --- a/scripts/build_wasm.sh +++ b/scripts/build_wasm.sh @@ -2,7 +2,8 @@ # emcc src/main.cpp -o dist/game.js -Wno-missing-braces -I/app/libs/raylib/build/raylib/include -L/app/libs/raylib/src /app/libs/raylib/src/libraylib.a -s USE_GLFW=3 -DPLATFORM_WEB -emcc $(find src/ -name "*.cpp") -o dist/game.js -Wno-missing-braces -I./include -I/app/libs/raylib/build/raylib/include -L/app/libs/raylib/src /app/libs/raylib/src/libraylib.a -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=268435456 -s MAXIMUM_MEMORY=4294967296 -s --preload-file assets@/assets -s USE_GLFW=3 -DPLATFORM_WEB +# emcc $(find src/ -name "*.cpp") -o dist/game.js -Wno-missing-braces -I./include -I/app/libs/raylib/build/raylib/include -L/app/libs/raylib/src /app/libs/raylib/src/libraylib.a -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=268435456 -s MAXIMUM_MEMORY=4294967296 -s --preload-file assets@/assets -s USE_GLFW=3 -DPLATFORM_WEB +emcc $(find src/ -name "*.cpp") -o dist/game.js -O3 -Wno-missing-braces -I./include -I/app/libs/raylib/build/raylib/include -L/app/libs/raylib/src /app/libs/raylib/src/libraylib.a -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=536870912 -s MAXIMUM_MEMORY=4294967296 -s --preload-file assets@/assets -s USE_GLFW=3 -s ASSERTIONS=2 -DPLATFORM_WEB # -I./include # -L$LD_LIBRARY_PATH # -I. diff --git a/src/entities/Bullet.cpp b/src/entities/Bullet.cpp new file mode 100644 index 0000000..f60b9f9 --- /dev/null +++ b/src/entities/Bullet.cpp @@ -0,0 +1,19 @@ +#include "entities/Bullet.h" +#include "services/MapService.h" + +void Bullet::envCollision() + { + if (position.x < 0 || position.y < 0 || position.y > GetScreenHeight()) + { + hasCollided = true; + return; + } + + const int tileSize = MapService::getInstance().getMap().getTileSize(); + Vector2i tilePos = Vector2i((int)this->position.x / tileSize, (int)this->position.y / tileSize); + if (MapService::getInstance().getMap().getColMap()[tilePos.y][tilePos.x] == 1) + { + hasCollided = true; + return; + } + } \ No newline at end of file diff --git a/src/entities/EBullet.cpp b/src/entities/EBullet.cpp new file mode 100644 index 0000000..cd8f468 --- /dev/null +++ b/src/entities/EBullet.cpp @@ -0,0 +1,18 @@ +#include "entities/EBullet.h" +#include "services/PlayerService.h" + +void EBullet::objCollision() + { + Player& player = PlayerService::getInstance().getPlayer(); + + /** If the player is already falling of the map, dont allow bullets to hit it anymore */ + if(player.getHealth() == 0) + return; + + if(this->position.x + this->size > player.getTexture().getPosition().x && this->position.x-this->size < player.getTexture().getPosition().x + player.getTexture().getSize().x && + this->position.y + this->size > player.getTexture().getPosition().y && this->position.y-this->size < player.getTexture().getPosition().y + player.getTexture().getSize().y) { + player.takeDamage(this->damage); + AssetService::getInstance().playSound(ESound::PLAYER_DAMAGE); + this->hasCollided = true; + } + } \ No newline at end of file diff --git a/src/entities/Enemy.cpp b/src/entities/Enemy.cpp new file mode 100644 index 0000000..7c85ccc --- /dev/null +++ b/src/entities/Enemy.cpp @@ -0,0 +1,216 @@ +#include "entities/Enemy.h" +#include "services/PlayerService.h" +#include "services/MapService.h" +#include "services/BulletService.h" +#include + +void Enemy::logic() +{ + if (health == 0) + { + fall(); + } + else + { + detectPlayer(); + if (playerSpotted) + { + shoot(); + } + else + { + walk(); + checkForCollision(); + animate(); + } + } + texture.move(velocity); +} + +void Enemy::walk() +{ + if (spriteAnimation.y == Left) + { + velocity.x = -moveSpeed; + } + else + { + velocity.x = moveSpeed; + } +} + +void Enemy::checkForCollision() +{ + std::vector corners = getCorners(); + std::vector> colMap = MapService::getInstance().getMap().getColMap(); + hasCollided = false; + + if (colMap[corners[2].y][corners[2].x] == 0 || colMap[corners[3].y][corners[3].x] == 0) // No ground below enemy + { + hasCollided = true; + } + else if (colMap[corners[0].y][corners[0].x] == 1 || colMap[corners[1].y][corners[1].x] == 1) + { + hasCollided = true; + } + + if (hasCollided) + { + if (spriteAnimation.y == Left) + spriteAnimation.y = Right; + else + spriteAnimation.y = Left; + + velocity.x *= -1; + } +} + +std::vector Enemy::getCorners() +{ + float bottom = texture.getPosition().y + texture.getSize().y + velocity.y; + float left = texture.getPosition().x + velocity.x; + float right = texture.getPosition().x + texture.getSize().x + velocity.x; + float top = texture.getPosition().y + velocity.y; + + int tileSize = MapService::getInstance().getMap().getTileSize(); + Vector2i leftCenter = Vector2i((int)(left - (spriteAnimation.y == Left ? gunLeft.getSize().x * (2.0f / 3.0f) : 0.0f)) / tileSize, (int)(bottom - texture.getSize().y / 2.0) / tileSize); + Vector2i rightCenter = Vector2i((int)(right + (spriteAnimation.y == Right ? gunRight.getSize().x * (2.0f / 3.0f) : 0.0f)) / tileSize, (int)(bottom - texture.getSize().y / 2.0) / tileSize); + + Vector2i bottomLeft(Vector2i((int)(left + 5) / tileSize, (int)bottom / tileSize)); + Vector2i bottomRight(Vector2i((int)(right - 5) / tileSize, (int)bottom / tileSize)); + + std::vector corners; + + corners.push_back(leftCenter); + corners.push_back(rightCenter); + + corners.push_back(bottomLeft); + corners.push_back(bottomRight); + return corners; +} + +void Enemy::animate() +{ + if (GetTime() - prevFrame > animationDelay || hasCollided) + { + spriteAnimation.x = std::fmod(spriteAnimation.x + 1, 3); + + Rectangle sourceRect = this->texture.getSourceRect(); + sourceRect.x = spriteAnimation.x * this->texture.getSize().x; + sourceRect.y = spriteAnimation.y * this->texture.getSize().y; + this->texture.setSourceRect(sourceRect); + prevFrame = GetTime(); + } +} + +void Enemy::drawHealthBar() +{ + float barLength = (this->texture.getSize().x / maxHealth) * health; + DrawRectangle(this->texture.getPosition().x, this->texture.getPosition().y - 12.0f, barLength, 3, RED); +} + +void Enemy::drawGun() +{ + if (this->spriteAnimation.y == Right) + { + DrawTexturePro( + this->gunRight.getTexture(), + {0, 0, this->gunRight.getSize().x, this->gunRight.getSize().y}, + {this->texture.getPosition().x + this->texture.getSize().x / 2.0f, + this->texture.getPosition().y + this->texture.getSize().y / 2.0f, + this->gunRight.getSize().x, + this->gunRight.getSize().y}, + {this->gunRight.getSize().x / 3.0f, this->gunRight.getSize().y / 2.0f}, + this->gunRight.getRotation(), + WHITE); + } + else if (this->spriteAnimation.y == Left) + { + DrawTexturePro( + this->gunLeft.getTexture(), + {0, 0, this->gunLeft.getSize().x, this->gunLeft.getSize().y}, + {this->texture.getPosition().x + this->texture.getSize().x / 2.0f, + this->texture.getPosition().y + this->texture.getSize().y / 2.0f, + this->gunLeft.getSize().x, + this->gunLeft.getSize().y}, + {this->gunLeft.getSize().x - this->gunLeft.getSize().x / 3.0f, this->gunLeft.getSize().y / 2.0f}, + this->gunLeft.getRotation(), + WHITE); + } +} + +void Enemy::fall() +{ + velocity.y += gravity; +} + +void Enemy::detectPlayer() +{ + Player &player = PlayerService::getInstance().getPlayer(); + if (this->health > 0) + { + bool spotted = false; + Vector2 playerCenter = {player.getTexture().getPosition().x + player.getTexture().getSize().x / 2.0f, player.getTexture().getPosition().y + player.getTexture().getSize().y / 2.0f}; + Vector2 enemyCenter = {texture.getPosition().x + texture.getSize().x / 2.0f, texture.getPosition().y + texture.getSize().y / 2.0f}; + if (this->spriteAnimation.y == Left && this->texture.getPosition().x - player.getTexture().getPosition().x <= spottingRange && + this->texture.getPosition().x - player.getTexture().getPosition().x > 55 && this->texture.getPosition().y + this->texture.getSize().y >= player.getTexture().getPosition().y) + { + spotted = true; + float gunRotationLeft = atan2(playerCenter.y - enemyCenter.y, + enemyCenter.x - playerCenter.x); + float target = -gunRotationLeft * 180 / M_PI; + this->gunLeft.setRotation(std::min(this->gunLeft.getRotation() + aimSpeed, target)); + } + else if (spriteAnimation.y == Right && player.getTexture().getPosition().x - this->texture.getPosition().x <= spottingRange && + player.getTexture().getPosition().x - this->texture.getPosition().x > 55 && this->texture.getPosition().y + this->texture.getSize().y >= player.getTexture().getPosition().y) + { + spotted = true; + float gunRotationRight = atan2(playerCenter.y - enemyCenter.y, + playerCenter.x - enemyCenter.x); + float target = gunRotationRight * 180 / M_PI; + this->gunRight.setRotation(std::max(this->gunRight.getRotation() - aimSpeed, target)); + } + + /** Don't set it again if the detection continues */ + if (spotted && !this->playerSpotted) + { + AssetService::getInstance().playSound(ESound::PLAYER_SPOTTED); + this->playerSpotted = true; + velocity.x = 0.0f; + } + else if (!spotted) + { + this->playerSpotted = false; + /** Slowly ease the gun back to default position */ + this->gunLeft.setRotation(std::max(this->gunLeft.getRotation() - aimSpeed / 2.0f, 0.0f)); + this->gunRight.setRotation(std::min(this->gunRight.getRotation() + aimSpeed / 2.0f, 0.0f)); + } + } +} + +void Enemy::shoot() +{ + Player &player = PlayerService::getInstance().getPlayer(); + /** Stop shooting if the player is dead (and falling out of the map) */ + if (player.getHealth() > 0 && playerSpotted && GetTime() - this->lastShot >= (1 / this->shotsPerSecond)) + { + + this->lastShot = GetTime(); + // Play shooting sound + AssetService::getInstance().playSound(ESound::SHOOT); + + float offsetDistance = this->gunLeft.getSize().x - this->gunLeft.getSize().x / 3; + Vector2 playerCenter = {player.getTexture().getPosition().x + player.getTexture().getSize().x / 2.0f, player.getTexture().getPosition().y + player.getTexture().getSize().y / 2.0f}; + Vector2 enemyCenter = {texture.getPosition().x + texture.getSize().x / 2.0f, texture.getPosition().y + texture.getSize().y / 2.0f}; + + float angleShot = atan2(playerCenter.y - enemyCenter.y, + playerCenter.x - enemyCenter.x); + + float offsetX = cos(angleShot) * offsetDistance; + float offsetY = sin(angleShot) * offsetDistance; + Vector2 bulletStartLocation = {this->texture.getPosition().x + this->texture.getSize().x / 2.0f + offsetX, + this->texture.getPosition().y + this->texture.getSize().y / 2.0f + offsetY}; + + BulletService::getInstance().addEnemyBullet(bulletStartLocation, angleShot); + } +} \ No newline at end of file diff --git a/src/entities/Map.cpp b/src/entities/Map.cpp index c1ddcc5..7e0d2b5 100644 --- a/src/entities/Map.cpp +++ b/src/entities/Map.cpp @@ -1,10 +1,8 @@ #include "entities/Map.h" +#include "services/AssetService.h" #include #include #include -#include -#include "services/AssetService.h" -#include "entities/Map.h" Map::Map() { @@ -114,19 +112,6 @@ void Map::loadTileset() this->tileset = AssetService::getInstance().loadTexture("Textures/tileset.png"); } -std::unordered_map Map::cornersToTilePos(std::unordered_map corners) -{ - // std::cout << "Starting converting" << std::endl; - std::unordered_map cornersTilePos; - for (const auto &pair : corners) - { - // std::cout << "Foreach pair" << std::endl; - Vector2 original = corners[pair.first]; - cornersTilePos[pair.first] = {floorf(original.x / this->tileSize), floorf(original.y / this->tileSize)}; - } - return cornersTilePos; -} - void Map::draw() { for (int i = 0; i < map.size(); i++) @@ -146,35 +131,3 @@ void Map::draw() } } -std::unordered_map Map::checkForCollision(TextureWrapper texture) -{ - // std::cout << "Converting.." << std::endl; - std::unordered_map cornersTilePos = cornersToTilePos(texture.getCorners()); - // std::cout << "Done with converting" << std::endl; - - std::unordered_map cornerCollisions; - - for (const auto &pair : cornersTilePos) - { - // std::cout << "foreach collision check" << std::endl; - Vector2 pos = cornersTilePos[pair.first]; - // std::cout << pos.x << " " << pos.y << std::endl; - if(colMap[pos.y][pos.x] == 1) { - cornerCollisions[pair.first] = true; - } else { - cornerCollisions[pair.first] = false; - } - } - // std::cout << "Finished collision checks" << std::endl; - - return cornerCollisions; - - // for (int i = 0; i < tiles.size(); i++) - // { - // if (colMap[tiles[i].y][tiles[i].x] == 1) - // { - // timesCollision.push_back(i); - // } - // } - -} diff --git a/src/entities/PBullet.cpp b/src/entities/PBullet.cpp new file mode 100644 index 0000000..9772e96 --- /dev/null +++ b/src/entities/PBullet.cpp @@ -0,0 +1,34 @@ +#include "entities/PBullet.h" +#include "services/CollectableService.h" +#include "services/EnemyService.h" + +void PBullet::objCollision() + { + /** Check for collisions with creates */ + for (auto &crate : CollectableService::getInstance().getCrates()) + { + if(this->position.x + this->size > crate.getObject().getPosition().x && this->position.x-this->size < crate.getObject().getPosition().x + crate.getObject().getSize().x && + this->position.y + this->size > crate.getObject().getPosition().y && this->position.y-this->size < crate.getObject().getPosition().y + crate.getObject().getSize().y) { + crate.setHit(true); + AssetService::getInstance().playSound(ESound::CRATE_BREAK); + AssetService::getInstance().playSound(ESound::JELLY_FREED); + this->hasCollided = true; + } + } + + for (auto &enemy : EnemyService::getInstance().getEnemies()) + { + /** If the enemy is already falling of the map, dont allow bullets to hit it anymore */ + if(enemy.getHealth() == 0) + continue; + + if(this->position.x + this->size > enemy.getObject().getPosition().x && this->position.x-this->size < enemy.getObject().getPosition().x + enemy.getObject().getSize().x && + this->position.y + this->size > enemy.getObject().getPosition().y && this->position.y-this->size < enemy.getObject().getPosition().y + enemy.getObject().getSize().y) { + enemy.takeDamage(); + if(enemy.getHealth() == 0) { + AssetService::getInstance().playSound(ESound::ENEMY_DEFEATED); + } + this->hasCollided = true; + } + } + } \ No newline at end of file diff --git a/src/entities/Player.cpp b/src/entities/Player.cpp new file mode 100644 index 0000000..f3c54af --- /dev/null +++ b/src/entities/Player.cpp @@ -0,0 +1,305 @@ +#include "entities/Player.h" +#include "services/BulletService.h" +#include "services/GameService.h" +#include "services/CollectableService.h" +#include "services/InputService.h" +#include "services/MapService.h" + +void Player::applyGravity() +{ + if (!groundCollision) + { + velocity.y += gravity; + } + else + { + // velocity.y = 0.0f; + } +} + +void Player::movement() +{ + if (InputService::getInstance().isKeyDown(KEY_A)) + { + spriteAnimation.y = Left; + velocity.x = -moveSpeed; + animate(); + } + else if (InputService::getInstance().isKeyDown(KEY_D)) + { + spriteAnimation.y = Right; + velocity.x = moveSpeed; + animate(); + } + else + { + velocity.x = 0.0f; + } + positionUpdates(); + handleCollision(); + + if (!groundCollision) + { + velocity.y += gravity; + } + else + { + velocity.y = 0.0f; + } + + if (InputService::getInstance().isKeyPressed(KEY_W) && groundCollision) + { + velocity.y -= jumpPower; + } + move(velocity); +} + +void Player::move(Vector2 velocity) +{ + this->texture.move(velocity); +} + +void Player::animate() +{ + if (GetTime() - prevFrame > animationDelay) + { + spriteAnimation.x = std::fmod(spriteAnimation.x + 1, 3); + + Rectangle sourceRect = this->texture.getSourceRect(); + sourceRect.x = spriteAnimation.x * this->texture.getSize().x; + sourceRect.y = spriteAnimation.y * this->texture.getSize().y; + // sourceRect.x = std::fmod(sourceRect.x, 3 * this->texture.getSize().x); + this->texture.setSourceRect(sourceRect); + prevFrame = GetTime(); + } +} + +void Player::positionUpdates() +{ + const Vector2 pos = this->texture.getPosition(); + const Vector2 size = this->texture.getSize(); + const int tileSize = MapService::getInstance().getMap().getTileSize(); + + const float bottom = pos.y + size.y + velocity.y; + const float left = pos.x + velocity.x; + const float right = pos.x + size.x + velocity.x; + const float top = pos.y + velocity.y; + + Vector2i topLeft(Vector2i((int)(left + 5) / tileSize, (int)top / tileSize)); // +5 to separate top from side + Vector2i topRight(Vector2i((int)(right - 5) / tileSize, (int)top / tileSize)); // -5 to separate top from side + + Vector2i leftTop(Vector2i((int)left / tileSize, (int)(top + 5) / tileSize)); + Vector2i leftBottom(Vector2i((int)left / tileSize, (int)(bottom - 5) / tileSize)); + Vector2i rightTop(Vector2i((int)right / tileSize, (int)(top + 5) / tileSize)); + Vector2i rightBottom(Vector2i((int)right / tileSize, (int)(bottom - 5) / tileSize)); + + Vector2i bottomLeft(Vector2i((int)(left + 5) / tileSize, (int)bottom / tileSize)); // +5 to separate bottom from side + Vector2i bottomRight(Vector2i((int)(right - 5) / tileSize, (int)bottom / tileSize)); // -5 to separate bottom from side + + tiles.clear(); + + tiles.push_back(topLeft); + tiles.push_back(topRight); + + tiles.push_back(leftTop); + tiles.push_back(leftBottom); + tiles.push_back(rightTop); + tiles.push_back(rightBottom); + + tiles.push_back(bottomLeft); + tiles.push_back(bottomRight); +} + +void Player::handleCollision() +{ + + timesCollision.clear(); + + for (int i = 0; i < tiles.size(); i++) + { + if (MapService::getInstance().getMap().getColMap()[tiles[i].y][tiles[i].x] == 1) + { + timesCollision.push_back(i); + } + } + + groundCollision = false; + + if (timesCollision.size() > 0) + { + for (int i = 0; i < timesCollision.size(); i++) + { + if (timesCollision[i] == 0 || timesCollision[i] == 1) // Topleft and Topright + { + velocity.y = 0; + } + else if (timesCollision[i] == 2 || timesCollision[i] == 3 || // Lefttop and Leftbottom + timesCollision[i] == 4 || timesCollision[i] == 5) // Righttop and Lefttop + { + velocity.x = 0; + } + else if (timesCollision[i] == 6 || timesCollision[i] == 7) // Bottomleft and Bottomright + { + groundCollision = true; + } + } + } +} + +void Player::consumableCollision() +{ + /** Check for collisions with ammo packs */ + for (auto &ammoPack : CollectableService::getInstance().getAmmoPacks()) + { + if (this->texture.getPosition().x + this->texture.getSize().x > ammoPack.getObject().getPosition().x && + this->texture.getPosition().xtexture.getPosition().y + this->texture.getSize().y> ammoPack.getObject().getPosition().y && + this->texture.getPosition().y < ammoPack.getObject().getPosition().y + ammoPack.getObject().getSize().y) + { + ammoPack.setPickedUp(true); + AssetService::getInstance().playSound(ESound::AMMO_PICKUP); + ammo += ammoPack.getAmmoAmount(); + } + } + + /** Check for collisions with health packs */ + for (auto &healthPack : CollectableService::getInstance().getHealthPacks()) + { + if (this->texture.getPosition().x + this->texture.getSize().x > healthPack.getObject().getPosition().x && + this->texture.getPosition().xtexture.getPosition().y + this->texture.getSize().y> healthPack.getObject().getPosition().y && + this->texture.getPosition().y < healthPack.getObject().getPosition().y + healthPack.getObject().getSize().y) + { + if (health < max_health) + { + healthPack.setPickedUp(true); + AssetService::getInstance().playSound(ESound::HEAL_PICKUP); + health = std::min(health + healthPack.getHealAmount(), (int)max_health); + } + } + } + + /** Checks for collisions with powerups */ + for (auto &powerup : CollectableService::getInstance().getPowerups()) + { + if (this->texture.getPosition().x + this->texture.getSize().x > powerup.getObject().getPosition().x && + this->texture.getPosition().xtexture.getPosition().y + this->texture.getSize().y> powerup.getObject().getPosition().y && + this->texture.getPosition().y < powerup.getObject().getPosition().y + powerup.getObject().getSize().y) + { + // if(health < max_health) { + powerup.setPickedUp(true); + AssetService::getInstance().playSound(ESound::POWERUP); + jumpPower += powerup.getExtraJumpPower(); + gainedPowerup = true; + } + } + + /** Checks for collisions with the finish flag */ + auto &flag = CollectableService::getInstance().getFlag(); + if (this->texture.getPosition().x + this->texture.getSize().x > flag.getObject().getPosition().x && + this->texture.getPosition().xtexture.getPosition().y + this->texture.getSize().y> flag.getObject().getPosition().y && + this->texture.getPosition().y < flag.getObject().getPosition().y + flag.getObject().getSize().y) + { + flag.setTouched(true); + finishReached = true; + } +} + +void Player::shoot() +{ + if (InputService::getInstance().isMouseButtonDown(MOUSE_BUTTON_LEFT) && GetTime() - prevShot > firingRate && groundCollision) + { + prevShot = GetTime(); + if (this->ammo == 0 || (GameService::getInstance().getMouseWorldPos().x > (this->texture.getPosition().x - this->gunLeft.getSize().x / 2.0f) && spriteAnimation.y == Left) || + (GameService::getInstance().getMouseWorldPos().x < (this->texture.getPosition().x + this->texture.getSize().x + gunLeft.getSize().x / 2.0f) && spriteAnimation.y == Right)) + { + // Play empty gun sound + AssetService::getInstance().playSound(ESound::NO_AMMO); + return; + } + // Play shooting sound + AssetService::getInstance().playSound(ESound::SHOOT); + ammo--; + + Vector2 mouseWorldPos = GameService::getInstance().getMouseWorldPos(); + + if (mouseWorldPos.x > this->texture.getPosition().x + (this->gunLeft.getSize().x) && spriteAnimation.y == Right || + mouseWorldPos.x + (this->gunLeft.getSize().x / 2) < this->texture.getPosition().x && spriteAnimation.y == Left) + { + + float angleShot = atan2(mouseWorldPos.y - (this->texture.getPosition().y + this->texture.getSize().y / 2.0), + mouseWorldPos.x - (this->texture.getPosition().x + this->texture.getSize().x / 2.0)); + + /** Making the bullet originate from the gun's mouth */ + float offsetDistance = this->gunLeft.getSize().x / 2; + float offsetX = cos(angleShot) * offsetDistance; + float offsetY = sin(angleShot) * offsetDistance; + Vector2 bulletStartLocation = {this->texture.getPosition().x + this->texture.getSize().x / 2.0f + offsetX, + this->texture.getPosition().y + this->texture.getSize().y / 2.0f + offsetY}; + + BulletService::getInstance().addPlayerBullet(bulletStartLocation, angleShot); + } + } +} + +void Player::outofboundsCheck() +{ + if (this->texture.getPosition().y > StateService::getInstance().getScreenSize().y) + { + this->health = 0; + GameService::getInstance().setGameOver(true); + } +} + +void Player::rotateGun() +{ + if (GameService::getInstance().getMouseWorldPos().x > this->texture.getPosition().x + (this->gunLeft.getSize().x) && spriteAnimation.y == Right) + { + float gunRotationRight = atan2(GameService::getInstance().getMouseWorldPos().y - (this->texture.getPosition().y + this->texture.getSize().y / 2.0), + GameService::getInstance().getMouseWorldPos().x - (this->texture.getPosition().x + this->texture.getSize().x / 2.0)); + this->gunRight.setRotation(gunRotationRight * 180 / M_PI); + } + else if (GameService::getInstance().getMouseWorldPos().x + (this->gunLeft.getSize().x / 2) < this->texture.getPosition().x && spriteAnimation.y == Left) + { + float gunRotationLeft = atan2(GameService::getInstance().getMouseWorldPos().y - (this->texture.getPosition().y + this->texture.getSize().y / 2.0), + (this->texture.getPosition().x + this->texture.getSize().x / 2.0) - GameService::getInstance().getMouseWorldPos().x); + this->gunLeft.setRotation(-gunRotationLeft * 180 / M_PI); + } +} + +void Player::drawGun() +{ + if (GameService::getInstance().getMouseWorldPos().x > this->texture.getPosition().x + this->gunLeft.getSize().x && spriteAnimation.y == Right) + { + // + this->texture.getSize().x / 2.0f + // + this->texture.getSize().y / 2.0f + DrawTexturePro( + this->gunRight.getTexture(), + {0, 0, this->gunRight.getSize().x, this->gunRight.getSize().y}, + {this->texture.getPosition().x + this->texture.getSize().x / 2.0f, + this->texture.getPosition().y + this->texture.getSize().y / 2.0f, + this->gunRight.getSize().x, + this->gunRight.getSize().y}, + {this->gunRight.getSize().x / 2.0f, this->gunRight.getSize().y / 2.0f}, + this->gunRight.getRotation(), + WHITE); + } + else if (GameService::getInstance().getMouseWorldPos().x + (this->gunLeft.getSize().x / 2) < this->texture.getPosition().x && spriteAnimation.y == Left) + { + DrawTexturePro( + this->gunLeft.getTexture(), + {0, 0, this->gunLeft.getSize().x, this->gunLeft.getSize().y}, + {this->texture.getPosition().x + this->texture.getSize().x / 2.0f, + this->texture.getPosition().y + this->texture.getSize().y / 2.0f, + this->gunLeft.getSize().x, + this->gunLeft.getSize().y}, + {this->gunLeft.getSize().x / 2.0f, this->gunLeft.getSize().y / 2.0f}, + this->gunLeft.getRotation(), + WHITE); + } +} + +void Player::fall() +{ + outofboundsCheck(); + velocity.y += gravity; + move(velocity); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e252ab4..c7b1bed 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,66 +1,9 @@ -#include "raylib.h" -// #include "screens/Screen.h" -// #include "screens/TitleScreen.h" #include "services/StateService.h" -// #include - -// #if defined(PLATFORM_WEB) -// #include -// #endif - -// void step(); -// static void gameloop(); int main() { StateService& stateService = StateService::getInstance(); stateService.setScreenSize({640,544}); stateService.startGame(); - // std::cout << "StateService instance: " << service << std::endl; - -// InitWindow(640, 544, "Jelly Rescue"); -// // SetTargetFPS(60); - -// // setScreen(EScreen::TITLE); - -// #if defined(PLATFORM_WEB) -// emscripten_set_main_loop(step, 0, 1); -// #else - -// while (!WindowShouldClose()) -// { -// step(); -// } -// #endif - -// CloseWindow(); - return 0; } - -// static void gameloop() -// { -// // InitAudioDevice(); -// InitWindow(640, 544, "Jelly Rescue"); -// // SetTargetFPS(60); - -// // setScreen(EScreen::TITLE); - -// #if defined(PLATFORM_WEB) -// emscripten_set_main_loop(step, 0, 1); -// #else - -// while (!WindowShouldClose()) -// { -// step(); -// } -// #endif - -// CloseWindow(); -// // CloseAudioDevice(); -// } - -// void step() -// { -// ClearBackground(BLACK); -// } \ No newline at end of file diff --git a/src/screens/GameScreen.cpp b/src/screens/GameScreen.cpp index d3d445a..3b01485 100644 --- a/src/screens/GameScreen.cpp +++ b/src/screens/GameScreen.cpp @@ -1,6 +1,7 @@ #include "screens/GameScreen.h" #include "entities/Map.h" #include "services/StateService.h" +#include "services/GameService.h" GameScreen::GameScreen() { @@ -12,6 +13,7 @@ GameScreen::GameScreen() // this->map = Map(); this->game = Game(); + // GameService::getInstance().setGame(this->game); // this->camera = camera; } diff --git a/src/screens/MainScreen.cpp b/src/screens/MainScreen.cpp index 77f6293..d13339e 100644 --- a/src/screens/MainScreen.cpp +++ b/src/screens/MainScreen.cpp @@ -1,14 +1,21 @@ #include "screens/MainScreen.h" +#include "raylib.h" #include "services/AudioService.h" #include "services/StateService.h" -#include "raylib.h" #include "services/InputService.h" MainScreen::MainScreen() { +#if defined(PLATFORM_WEB) + const int TOP_GAP = 100; + const int BOTTOM_GAP = 100; + const int AMNT_OF_BUTTONS = 2; +#else const int TOP_GAP = 100; const int BOTTOM_GAP = 50; const int AMNT_OF_BUTTONS = 4; +#endif + const int TOTAL_SPACE = GetScreenHeight() - (TOP_GAP + BOTTOM_GAP); const int SPACE_PER_ITEM = TOTAL_SPACE / AMNT_OF_BUTTONS; @@ -18,13 +25,22 @@ MainScreen::MainScreen() buttons.push_back(txt); } buttons[0].setColor(RED); +#if defined(PLATFORM_WEB) + buttons[1].setText("tutorial"); +#else buttons[1].setText("load game"); buttons[2].setText("tutorial"); buttons[3].setText("quit"); +#endif for (int i = 0; i < AMNT_OF_BUTTONS; i++) { +#if defined(PLATFORM_WEB) + buttons[i].setPosition({(float)(GetScreenWidth() - buttons[i].getDimensions().x) / 2.0f, (float)TOP_GAP + SPACE_PER_ITEM * i + SPACE_PER_ITEM / 2.0f}); + +#else buttons[i].setPosition({(float)(GetScreenWidth() - buttons[i].getDimensions().x) / 2.0f, (float)TOP_GAP + SPACE_PER_ITEM * i}); +#endif } /** Will be ignored if already active (like when coming from the TitleScreen) */ @@ -35,24 +51,16 @@ MainScreen::MainScreen() AudioService::getInstance().playMusic(); } - InputService::getInstance().setKeysToWatch({KEY_ENTER, KEY_UP, KEY_DOWN}, {MOUSE_BUTTON_LEFT}); + InputService::getInstance().setKeysToWatch({KEY_ENTER, KEY_W, KEY_S}, {}); } void MainScreen::logic() { - // Vector2 mouseD = GetMouseDelta(); - // std::cout << mouseD.y <= rect.x && mousePos.x <= (rect.x+rect.width) && if ((mousePos.x + textPadding) >= rect.x && mousePos.x <= (rect.x + rect.width + textPadding) && (mousePos.y + textPadding) >= rect.y && mousePos.y <= (rect.y + rect.height + textPadding)) { @@ -81,18 +89,18 @@ void MainScreen::mouseLogic() void MainScreen::keyboardLogic() { - if (InputService::getInstance().isKeyPressed(KEY_DOWN) || InputService::getInstance().isKeyPressed(KEY_UP)) + if (InputService::getInstance().isKeyPressed(KEY_S) || InputService::getInstance().isKeyPressed(KEY_W)) { buttons[hoveredButton].setColor(WHITE); /** Move up or down depending on the pressed button. +4 is used so that -1 becomes 3 (the last button) */ - hoveredButton = ((hoveredButton - (InputService::getInstance().isKeyPressed(KEY_DOWN) ? -1 : 1)) + 4) % 4; + hoveredButton = ((hoveredButton - (InputService::getInstance().isKeyPressed(KEY_S) ? -1 : 1)) + buttons.size()) % buttons.size(); buttons[hoveredButton].setColor(RED); } } void MainScreen::clickLogic() { - if (InputService::getInstance().isKeyPressed(KEY_ENTER) || InputService::getInstance().isMouseButtonPressed(MOUSE_BUTTON_LEFT)) + if (InputService::getInstance().isKeyPressed(KEY_ENTER)) { std::string selectedButton = buttons[hoveredButton].getText(); diff --git a/src/screens/TitleScreen.cpp b/src/screens/TitleScreen.cpp index 98e7f35..0dd10ed 100644 --- a/src/screens/TitleScreen.cpp +++ b/src/screens/TitleScreen.cpp @@ -3,7 +3,6 @@ #include "services/StateService.h" #include "enums/EFont.h" #include "services/AudioService.h" -#include #include "services/InputService.h" TitleScreen::TitleScreen() @@ -34,7 +33,6 @@ TitleScreen::TitleScreen() void TitleScreen::logic() { - // std::cout << "Next screen!" << std::endl; if (InputService::getInstance().isKeyPressed(KEY_ENTER) || InputService::getInstance().isMouseButtonPressed(MOUSE_BUTTON_LEFT)) { StateService::getInstance().setScreen(EScreen::MAIN); diff --git a/src/screens/TutorialScreen.cpp b/src/screens/TutorialScreen.cpp index 86b7ea5..05f5701 100644 --- a/src/screens/TutorialScreen.cpp +++ b/src/screens/TutorialScreen.cpp @@ -1,8 +1,7 @@ #include "screens/TutorialScreen.h" - -#include "services/StateService.h" -#include "enums/EFont.h" #include "enums/EScreen.h" +#include "enums/EFont.h" +#include "services/StateService.h" #include "services/InputService.h" TutorialScreen::TutorialScreen() { @@ -13,12 +12,12 @@ TutorialScreen::TutorialScreen() { this->background = Background(EBackground::TUTORIAL); - InputService::getInstance().setKeysToWatch({KEY_ENTER}, {MOUSE_BUTTON_LEFT}); + InputService::getInstance().setKeysToWatch({KEY_ENTER},{}); } void TutorialScreen::logic() { - if ( InputService::getInstance().isKeyPressed(KEY_ENTER) || InputService::getInstance().isMouseButtonPressed(MOUSE_BUTTON_LEFT)) + if ( InputService::getInstance().isKeyPressed(KEY_ENTER)) { StateService::getInstance().setScreen(EScreen::MAIN); } diff --git a/src/screens/VictoryScreen.cpp b/src/screens/VictoryScreen.cpp new file mode 100644 index 0000000..3c366df --- /dev/null +++ b/src/screens/VictoryScreen.cpp @@ -0,0 +1,119 @@ +#include "screens/VictoryScreen.h" +#include "services/InputService.h" +#include "services/AudioService.h" +#include "services/CollectableService.h" +#include +#include + +VictoryScreen::VictoryScreen() +{ + AudioService::getInstance().stopMusic(); + AssetService::getInstance().playSound(ESound::VICTORY); + + Text victory = Text("victory", EFont::GTEK, 50, 0, {255, 0, 0, 255}); + victory.setPosition({(float)(GetScreenWidth() - victory.getDimensions().x) / 2.0f, + (float)(GetScreenHeight() - victory.getDimensions().y) / 2.5f}); + + int amntOfJelliesRescued = CollectableService::getInstance().getAmountOfJelliesFreed(); + int maxAmntOfJellies = CollectableService::getInstance().getMaxAmountOfJellies(); + std::string rescuedString; + if (amntOfJelliesRescued == maxAmntOfJellies) + { + rescuedString = "You have rescued all of the jellies!"; + } + else + { + std::stringstream stream; + stream << "You have rescued "; + stream << amntOfJelliesRescued; + stream << " of the "; + stream << maxAmntOfJellies; + stream << " jellies"; + rescuedString = stream.str(); + } + + Text rescued = Text(rescuedString, EFont::SANSATION, 30, 0, WHITE); + rescued.setPosition({(float)(GetScreenWidth() - rescued.getDimensions().x) / 2.0f, + (float)(GetScreenHeight() - rescued.getDimensions().y) / 1.5f}); + + this->text.push_back(victory); + this->text.push_back(rescued); + + + const int TOP_POS = GetScreenHeight()/1.2; + const int X_GAP = 70; + const int AMNT_OF_BUTTONS = 2; + const int TOTAL_SPACE = GetScreenWidth() - X_GAP * 2; + const int SPACE_PER_ITEM = TOTAL_SPACE / AMNT_OF_BUTTONS; + + for (int i = 0; i < AMNT_OF_BUTTONS; i++) + { + Text txt = Text("Play Again", EFont::SANSATION, 30, 0, WHITE); + buttons.push_back(txt); + } + buttons[0].setColor(RED); + buttons[1].setText("Return to Main Menu"); + buttons[0].setPosition({(float)X_GAP, (float)TOP_POS}); + buttons[1].setPosition({(float)GetScreenWidth() - X_GAP - buttons[1].getDimensions().x, (float)TOP_POS}); + + const int x_gap = 50; + const int available_space = GetScreenWidth() - x_gap * 2; + const int space_per_jelly = available_space / maxAmntOfJellies; + for (int i = 0; i < amntOfJelliesRescued; i++) + { + TextureWrapper wrapper = TextureWrapper(AssetService::getInstance().getSprite(ESprite::JELLY), {32, 32}); + wrapper.setPosition({(float)x_gap + space_per_jelly * i + space_per_jelly / 2.0f - wrapper.getSize().x / 2.0f, GetScreenHeight() / 2.0f}); + textures.push_back(wrapper); + } + + this->background = Background(EBackground::VICTORY); + + InputService::getInstance().setKeysToWatch({KEY_ENTER, KEY_A, KEY_D}, {}); +} + +void VictoryScreen::logic() +{ + if (InputService::getInstance().isKeyPressed(KEY_D) || InputService::getInstance().isKeyPressed(KEY_A)) + { + buttons[hoveredButton].setColor(WHITE); + hoveredButton = ((hoveredButton - (InputService::getInstance().isKeyPressed(KEY_D) ? -1 : 1)) + buttons.size()) % buttons.size(); + buttons[hoveredButton].setColor(RED); + } + + if (InputService::getInstance().isKeyPressed(KEY_ENTER)) + { + std::string selectedButton = buttons[hoveredButton].getText(); + + if (selectedButton == "Play Again") + { + StateService::getInstance().setScreen(EScreen::GAME); + } + else if (selectedButton == "Return to Main Menu") + { + StateService::getInstance().setScreen(EScreen::MAIN); + } + } +} + +void VictoryScreen::draw() +{ + BeginDrawing(); + this->background.draw(); + + for (int t = 0; t < text.size(); t++) + { + text[t].draw(); + } + + for (int t = 0; t < buttons.size(); t++) + { + buttons[t].draw(); + } + + for (int t = 0; t < textures.size(); t++) + { + DrawTexture(textures[t].getTexture(), textures[t].getPosition().x, textures[t].getPosition().y, WHITE); + } + + EndDrawing(); +} diff --git a/src/services/AssetService.cpp b/src/services/AssetService.cpp index 06039a9..47c9c30 100644 --- a/src/services/AssetService.cpp +++ b/src/services/AssetService.cpp @@ -1,5 +1,4 @@ #include "services/AssetService.h" -#include AssetService::AssetService() { setAssetDir("assets/"); @@ -17,9 +16,22 @@ void AssetService::setAssets() music[EMusic::MAIN] = loadMusic("Audio/Music/Crash_WarpRoomTheme.ogg", true); music[EMusic::GAME] = loadMusic("Audio/Music/GameTheme_2.ogg", true); - music[EMusic::GAME_OVER] = loadMusic("Audio/Music/gameOverTheme.ogg", false); + music[EMusic::GAME_OVER] = loadMusic("Audio/Music/gameOverTheme_2.ogg", false); /** Todo: set volume */ + sounds[ESound::SHOOT] = loadSound("Audio/Sounds/gunShot.wav"); + SetSoundVolume(sounds[ESound::SHOOT], 0.4f); + sounds[ESound::NO_AMMO] = loadSound("Audio/Sounds/outOfAmmo.wav"); + sounds[ESound::CRATE_BREAK] = loadSound("Audio/Sounds/crateBreak.wav"); + sounds[ESound::JELLY_FREED] = loadSound("Audio/Sounds/jellyFreed.wav"); + sounds[ESound::HEAL_PICKUP] = loadSound("Audio/Sounds/healmag.wav"); + sounds[ESound::AMMO_PICKUP] = loadSound("Audio/Sounds/shotgunReload.wav"); + sounds[ESound::POWERUP] = loadSound("Audio/Sounds/powerUpSound.wav"); + sounds[ESound::VICTORY] = loadSound("Audio/Sounds/victorySound.wav"); + sounds[ESound::PLAYER_DAMAGE] = loadSound("Audio/Sounds/WaterBalloon.wav"); + sounds[ESound::ENEMY_DEFEATED] = loadSound("Audio/Sounds/pain.wav"); + sounds[ESound::PLAYER_SPOTTED] = loadSound("Audio/Sounds/playerSpotted.wav"); + backgrounds[EBackground::TUTORIAL] = loadTexture("Textures/TutorialBackground2.png"); backgrounds[EBackground::GAME1] = loadTexture("Textures/GameBackground_1.png"); backgrounds[EBackground::GAME2] = loadTexture("Textures/GameBackground_2.jpg"); @@ -43,6 +55,8 @@ void AssetService::setAssets() sprites[ESprite::HUD_HEART] = loadTexture("Textures/heart.png"); sprites[ESprite::HUD_HEART_EMPTY] = loadTexture("Textures/heart_empty.png"); sprites[ESprite::HUD_AMMO] = loadTexture("Textures/bullet_hud.png"); + sprites[ESprite::CROSSHAIR] = loadTexture("Textures/crosshair_3.png"); + sprites[ESprite::FLAG] = loadTexture("Textures/flag.png"); } Font AssetService::getFont(EFont font, int fontSize) diff --git a/src/services/AudioService.cpp b/src/services/AudioService.cpp index 64737c8..c93e800 100644 --- a/src/services/AudioService.cpp +++ b/src/services/AudioService.cpp @@ -13,6 +13,7 @@ void AudioService::setMusic(EMusic music) if (currentTrack == music) return; + stopMusic(); // Makes sure it doesn't continue where it left off this->currentTrack = music; this->music = AssetService::getInstance().getMusic(music); } diff --git a/src/services/CollectableService.cpp b/src/services/CollectableService.cpp new file mode 100644 index 0000000..69feae9 --- /dev/null +++ b/src/services/CollectableService.cpp @@ -0,0 +1,196 @@ +#include "services/CollectableService.h" +#include "services/MapService.h" + +void CollectableService::initCrates() +{ + crates.clear(); + int tileSize = MapService::getInstance().getMap().getTileSize(); + for (int i = 0; i < amountOfCrates; i++) + { + crates.push_back(Crate()); + } + crates[0].getObject().setPosition({38.0f * tileSize, 9.0f * tileSize}); + crates[1].getObject().setPosition({41.0f * tileSize, 5.0f * tileSize}); + crates[2].getObject().setPosition({68.0f * tileSize, 4.0f * tileSize}); + + for (int i = 0; i < crates.size(); i++) + { + Vector2i crateTilePos = Vector2i(crates[i].getObject().getPosition().x / tileSize, crates[i].getObject().getPosition().y / tileSize); + MapService::getInstance().getMap().updateColmapTile(crateTilePos, true); + } +} + +void CollectableService::initJellies() +{ + jellies.clear(); +} + +void CollectableService::initAmmoPacks() +{ + ammoPacks.clear(); + int tileSize = MapService::getInstance().getMap().getTileSize(); + for (int i = 0; i < amountOfAmmoPacks; i++) + { + ammoPacks.push_back(AmmoPack()); + } + ammoPacks[0].getObject().setPosition({32.0f * tileSize, 9.0f * tileSize}); + ammoPacks[1].getObject().setPosition({48.0f * tileSize, 5.0f * tileSize}); + ammoPacks[2].getObject().setPosition({88.0f * tileSize, 9.0f * tileSize}); + ammoPacks[3].getObject().setPosition({105.0f * tileSize, 15.0f * tileSize}); +} + +void CollectableService::initHealthPacks() +{ + healthPacks.clear(); + int tileSize = MapService::getInstance().getMap().getTileSize(); + for (int i = 0; i < amountOfHealthPacks; i++) + { + healthPacks.push_back(HealthPack()); + } + healthPacks[0].getObject().setPosition({28.0f * tileSize, 10.0f * tileSize}); + healthPacks[1].getObject().setPosition({64.0f * tileSize, 6.0f * tileSize}); + healthPacks[2].getObject().setPosition({118.0f * tileSize, 11.0f * tileSize}); + healthPacks[3].getObject().setPosition({74.0f * tileSize, 8.0f * tileSize}); +} + +void CollectableService::initPowerups() +{ + powerUps.clear(); + int tileSize = MapService::getInstance().getMap().getTileSize(); + for (int i = 0; i < amountOfPowerups; i++) + { + powerUps.push_back(Powerup()); + } + powerUps[0].getObject().setPosition({41.0f * tileSize + 8, 10.0f * tileSize - 25}); +} + +void CollectableService::initFlag() +{ + int tileSize = MapService::getInstance().getMap().getTileSize(); + this->flag = Flag({118.0f * tileSize - tileSize / 2.0f, 3.0f * tileSize - 8}); +} + +void CollectableService::crateLogic() +{ + for (auto it = crates.begin(); it != crates.end();) + { + auto &crate = *it; + /** + * If the crate has been hit: + * 1. Create a Jelly on the location of the crate + * 2. Remove the collision tile from the collision map + * 3. Remove the crate + */ + if (crate.hasBeenHit()) + { + Vector2 pos = crate.getObject().getPosition(); + + jellies.push_back(Jelly(pos)); + + Vector2i tilePos = Vector2i(pos.x / MapService::getInstance().getMap().getTileSize(), pos.y / MapService::getInstance().getMap().getTileSize()); + MapService::getInstance().getMap().updateColmapTile(tilePos, false); + it = crates.erase(it); + } + else + { + ++it; + } + } +} + +void CollectableService::jellyLogic() +{ + for (int i = 0; i < jellies.size(); i++) + { + jellies[i].logic(); + } +} + +void CollectableService::ammoPackLogic() +{ + for (auto it = ammoPacks.begin(); it != ammoPacks.end();) + { + auto &ammoPack = *it; + if (ammoPack.hasBeenPickedUp()) + { + it = ammoPacks.erase(it); + } + else + { + ++it; + } + } +} + +void CollectableService::healthPackLogic() +{ + for (auto it = healthPacks.begin(); it != healthPacks.end();) + { + auto &healthPack = *it; + if (healthPack.hasBeenPickedUp()) + { + it = healthPacks.erase(it); + } + else + { + ++it; + } + } +} + +void CollectableService::powerupLogic() +{ + for (auto it = powerUps.begin(); it != powerUps.end();) + { + auto &powerUp = *it; + if (powerUp.hasBeenPickedUp()) + { + it = powerUps.erase(it); + } + else + { + powerUp.logic(); + ++it; + } + } +} + +void CollectableService::drawCrates() +{ + for (int i = 0; i < crates.size(); i++) + { + crates[i].draw(); + } +} + +void CollectableService::drawJellies() +{ + for (int i = 0; i < jellies.size(); i++) + { + jellies[i].draw(); + } +} + +void CollectableService::drawAmmoPacks() +{ + for (int i = 0; i < ammoPacks.size(); i++) + { + ammoPacks[i].draw(); + } +} + +void CollectableService::drawHealthpacks() +{ + for (int i = 0; i < healthPacks.size(); i++) + { + healthPacks[i].draw(); + } +} + +void CollectableService::drawPowerups() +{ + for (int i = 0; i < powerUps.size(); i++) + { + powerUps[i].draw(); + } +} \ No newline at end of file diff --git a/src/services/EnemyService.cpp b/src/services/EnemyService.cpp new file mode 100644 index 0000000..646fde2 --- /dev/null +++ b/src/services/EnemyService.cpp @@ -0,0 +1,51 @@ +#include "services/EnemyService.h" +#include "services/MapService.h" + +void EnemyService::initEnemies() +{ + this->enemies.clear(); + const int AMNT_OF_ENEMIES = 8; + int tileSize = MapService::getInstance().getMap().getTileSize(); + for (int i = 0; i < AMNT_OF_ENEMIES; i++) + { + enemies.push_back(Enemy()); + } + this->enemies[0].getObject().setPosition({31.0f * tileSize, 15.0f * tileSize}); + this->enemies[1].getObject().setPosition({51.0f * tileSize, 10.0f * tileSize}); + this->enemies[2].getObject().setPosition({56.0f * tileSize, 5.0f * tileSize}); + this->enemies[3].getObject().setPosition({78.0f * tileSize, 10.0f * tileSize}); + this->enemies[4].getObject().setPosition({82.0f * tileSize, 15.0f * tileSize}); + this->enemies[5].getObject().setPosition({88.0f * tileSize, 4.0f * tileSize}); + this->enemies[6].getObject().setPosition({98.0f * tileSize, 11.0f * tileSize}); + this->enemies[7].getObject().setPosition({103.0f * tileSize, 15.0f * tileSize}); +} + +void EnemyService::enemyLogic() +{ + for (auto it = enemies.begin(); it != enemies.end();) + { + auto &enemy = *it; + if (enemy.getObject().getPosition().y < 0) + { + it = enemies.erase(it); + } + else + { + enemy.logic(); + ++it; + } + } +} + +void EnemyService::drawEnemies() +{ + for (int i = 0; i < enemies.size(); i++) + { + enemies[i].draw(); + } +} + +std::vector &EnemyService::getEnemies() +{ + return this->enemies; +} \ No newline at end of file diff --git a/src/services/StateService.cpp b/src/services/StateService.cpp index 9c56ebf..9b523af 100644 --- a/src/services/StateService.cpp +++ b/src/services/StateService.cpp @@ -1,12 +1,13 @@ -#include "services/StateService.h" #include "raylib.h" +#include "services/StateService.h" +#include "services/AudioService.h" +#include "services/InputService.h" #include "screens/TitleScreen.h" #include "screens/MainScreen.h" #include "screens/TutorialScreen.h" #include "screens/GameScreen.h" +#include "screens/VictoryScreen.h" #include -#include "services/AudioService.h" -#include "services/InputService.h" void StateService::setScreen(EScreen state) { @@ -24,6 +25,9 @@ void StateService::setScreen(EScreen state) case EScreen::GAME: currentScreen = std::make_unique(); break; + case EScreen::VICTORY: + currentScreen = std::make_unique(); + break; case EScreen::CLOSE: gameRunning = false; break; @@ -44,17 +48,10 @@ void StateService::startGame() InitAudioDevice(); // InitWindow(640,544, "Jelly Rescue"); InitWindow(screenSize.x, screenSize.y, "Jelly Rescue"); - SetTargetFPS(60); - - // #if defined(PLATFORM_WEB) - // emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_CANVAS, nullptr, 1, staticStep); - // #endif - - // #if defined(PLATFORM_WEB) - // emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_CANVAS, nullptr, 1, [](const EmscriptenFocusEvent* event) { - // std::cout << "Canvas Focused!" << std::endl; - // }); - // #endif + SetTargetFPS(45); + #if not defined(PLATFORM_WEB) + HideCursor(); + #endif setScreen(EScreen::TITLE); @@ -62,7 +59,6 @@ void StateService::startGame() emscripten_set_main_loop(StateService::staticStep, 0, 1); #else - // while (!WindowShouldClose() && instance->gameRunning) while (!WindowShouldClose() && gameRunning) { step(); @@ -85,6 +81,7 @@ void StateService::handleMusic() void StateService::step() { InputService::getInstance().checkKeys(); + InputService::getInstance().checkMousePos(); handleMusic(); currentScreen->draw();