From 75c6d5fa97342d8453c2f1787389505a04c7d100 Mon Sep 17 00:00:00 2001 From: Joey de Vries Date: Mon, 27 Apr 2020 14:26:24 +0200 Subject: [PATCH] Update Breakout progress code for PowerUps chapter. Also few missed items on prev. progress code. --- .../3.2d_game/0.full_source/power_up.h | 2 +- .../3.2d_game/0.full_source/progress/5.game.h | 4 + .../0.full_source/progress/7.game.cpp | 5 + .../0.full_source/progress/8.game.cpp | 465 ++++++++++++++++++ .../3.2d_game/0.full_source/progress/8.game.h | 76 +++ 5 files changed, 551 insertions(+), 1 deletion(-) create mode 100644 src/7.in_practice/3.2d_game/0.full_source/progress/8.game.cpp create mode 100644 src/7.in_practice/3.2d_game/0.full_source/progress/8.game.h diff --git a/src/7.in_practice/3.2d_game/0.full_source/power_up.h b/src/7.in_practice/3.2d_game/0.full_source/power_up.h index 7bec521..822be68 100644 --- a/src/7.in_practice/3.2d_game/0.full_source/power_up.h +++ b/src/7.in_practice/3.2d_game/0.full_source/power_up.h @@ -29,7 +29,7 @@ const glm::vec2 VELOCITY(0.0f, 150.0f); class PowerUp : public GameObject { public: - // power-up state + // powerup state std::string Type; float Duration; bool Activated; diff --git a/src/7.in_practice/3.2d_game/0.full_source/progress/5.game.h b/src/7.in_practice/3.2d_game/0.full_source/progress/5.game.h index 3055d3e..d46d230 100644 --- a/src/7.in_practice/3.2d_game/0.full_source/progress/5.game.h +++ b/src/7.in_practice/3.2d_game/0.full_source/progress/5.game.h @@ -12,6 +12,8 @@ #include #include +#include "game_level.h" + // Represents the current state of the game enum GameState { GAME_ACTIVE, @@ -49,6 +51,8 @@ public: bool Keys[1024]; bool KeysProcessed[1024]; unsigned int Width, Height; + std::vector Levels; + unsigned int Level; // constructor/destructor Game(unsigned int width, unsigned int height); ~Game(); diff --git a/src/7.in_practice/3.2d_game/0.full_source/progress/7.game.cpp b/src/7.in_practice/3.2d_game/0.full_source/progress/7.game.cpp index b79f789..3c09f37 100644 --- a/src/7.in_practice/3.2d_game/0.full_source/progress/7.game.cpp +++ b/src/7.in_practice/3.2d_game/0.full_source/progress/7.game.cpp @@ -192,6 +192,11 @@ void Game::DoCollisions() // destroy block if not solid if (!box.IsSolid) box.Destroyed = true; + else + { // if block is solid, enable shake effect + ShakeTime = 0.05f; + Effects->Shake = true; + } // collision resolution Direction dir = std::get<1>(collision); glm::vec2 diff_vector = std::get<2>(collision); diff --git a/src/7.in_practice/3.2d_game/0.full_source/progress/8.game.cpp b/src/7.in_practice/3.2d_game/0.full_source/progress/8.game.cpp new file mode 100644 index 0000000..a6f29a9 --- /dev/null +++ b/src/7.in_practice/3.2d_game/0.full_source/progress/8.game.cpp @@ -0,0 +1,465 @@ +/******************************************************************* +** This code is part of Breakout. +** +** Breakout is free software: you can redistribute it and/or modify +** it under the terms of the CC BY 4.0 license as published by +** Creative Commons, either version 4 of the License, or (at your +** option) any later version. +******************************************************************/ +#include "game.h" +#include "resource_manager.h" +#include "sprite_renderer.h" +#include "game_object.h" +#include "ball_object.h" +#include "particle_generator.h" +#include "post_processor.h" + +// Game-related State data +SpriteRenderer *Renderer; +GameObject *Player; +BallObject *Ball; +ParticleGenerator *Particles; +PostProcessor *Effects; + +float ShakeTime = 0.0f; + +Game::Game(unsigned int width, unsigned int height) + : State(GAME_MENU), Keys(), KeysProcessed(), Width(width), Height(height) +{ + +} + +Game::~Game() +{ + delete Renderer; + delete Player; + delete Ball; + delete Particles; + delete Effects; +} + +void Game::Init() +{ + // load shaders + ResourceManager::LoadShader("shaders/sprite.vs", "shaders/sprite.frag", nullptr, "sprite"); + ResourceManager::LoadShader("shaders/particle.vs", "shaders/particle.frag", nullptr, "particle"); + ResourceManager::LoadShader("shaders/post_processing.vs", "shaders/post_processing.frag", nullptr, "postprocessing"); + // configure shaders + glm::mat4 projection = glm::ortho(0.0f, static_cast(this->Width), + static_cast(this->Height), 0.0f, -1.0f, 1.0f); + ResourceManager::GetShader("sprite").Use().SetInteger("image", 0); + ResourceManager::GetShader("sprite").SetMatrix4("projection", projection); + ResourceManager::GetShader("particle").Use().SetInteger("sprite", 0); + ResourceManager::GetShader("particle").SetMatrix4("projection", projection); + // load textures + ResourceManager::LoadTexture("textures/background.jpg", false, "background"); + ResourceManager::LoadTexture("textures/awesomeface.png", true, "face"); + ResourceManager::LoadTexture("textures/block.png", false, "block"); + ResourceManager::LoadTexture("textures/block_solid.png", false, "block_solid"); + ResourceManager::LoadTexture("textures/paddle.png", true, "paddle"); + ResourceManager::LoadTexture("textures/particle.png", true, "particle"); + ResourceManager::LoadTexture("textures/powerup_speed.png", true, "powerup_speed"); + ResourceManager::LoadTexture("textures/powerup_sticky.png", true, "powerup_sticky"); + ResourceManager::LoadTexture("textures/powerup_increase.png", true, "powerup_increase"); + ResourceManager::LoadTexture("textures/powerup_confuse.png", true, "powerup_confuse"); + ResourceManager::LoadTexture("textures/powerup_chaos.png", true, "powerup_chaos"); + ResourceManager::LoadTexture("textures/powerup_passthrough.png", true, "powerup_passthrough"); + // set render-specific controls + Renderer = new SpriteRenderer(ResourceManager::GetShader("sprite")); + Particles = new ParticleGenerator(ResourceManager::GetShader("particle"), ResourceManager::GetTexture("particle"), 500); + Effects = new PostProcessor(ResourceManager::GetShader("postprocessing"), this->Width, this->Height); + // load levels + GameLevel one; one.Load("levels/one.lvl", this->Width, this->Height / 2); + GameLevel two; two.Load("levels/two.lvl", this->Width, this->Height / 2); + GameLevel three; three.Load("levels/three.lvl", this->Width, this->Height / 2); + GameLevel four; four.Load("levels/four.lvl", this->Width, this->Height / 2); + this->Levels.push_back(one); + this->Levels.push_back(two); + this->Levels.push_back(three); + this->Levels.push_back(four); + this->Level = 0; + // configure game objects + glm::vec2 playerPos = glm::vec2(this->Width / 2.0f - PLAYER_SIZE.x / 2.0f, this->Height - PLAYER_SIZE.y); + Player = new GameObject(playerPos, PLAYER_SIZE, ResourceManager::GetTexture("paddle")); + glm::vec2 ballPos = playerPos + glm::vec2(PLAYER_SIZE.x / 2.0f - BALL_RADIUS, -BALL_RADIUS * 2.0f); + Ball = new BallObject(ballPos, BALL_RADIUS, INITIAL_BALL_VELOCITY, ResourceManager::GetTexture("face")); +} + +void Game::Update(float dt) +{ + // update objects + Ball->Move(dt, this->Width); + // check for collisions + this->DoCollisions(); + // update particles + Particles->Update(dt, *Ball, 2, glm::vec2(Ball->Radius / 2.0f)); + // reduce shake time + if (ShakeTime > 0.0f) + { + ShakeTime -= dt; + if (ShakeTime <= 0.0f) + Effects->Shake = false; + } + // check loss condition + if (Ball->Position.y >= this->Height) // did ball reach bottom edge? + { + this->ResetLevel(); + this->ResetPlayer(); + } +} + +void Game::ProcessInput(float dt) +{ + if (this->State == GAME_ACTIVE) + { + float velocity = PLAYER_VELOCITY * dt; + // move playerboard + if (this->Keys[GLFW_KEY_A]) + { + if (Player->Position.x >= 0.0f) + { + Player->Position.x -= velocity; + if (Ball->Stuck) + Ball->Position.x -= velocity; + } + } + if (this->Keys[GLFW_KEY_D]) + { + if (Player->Position.x <= this->Width - Player->Size.x) + { + Player->Position.x += velocity; + if (Ball->Stuck) + Ball->Position.x += velocity; + } + } + if (this->Keys[GLFW_KEY_SPACE]) + Ball->Stuck = false; + } +} + +void Game::Render() +{ + if(this->State == GAME_ACTIVE) + { + // begin rendering to postprocessing framebuffer + Effects->BeginRender(); + // draw background + Renderer->DrawSprite(ResourceManager::GetTexture("background"), glm::vec2(0.0f, 0.0f), glm::vec2(this->Width, this->Height), 0.0f); + // draw level + this->Levels[this->Level].Draw(*Renderer); + // draw player + Player->Draw(*Renderer); + // draw PowerUps + for (PowerUp &powerUp : this->PowerUps) + if (!powerUp.Destroyed) + powerUp.Draw(*Renderer); + // draw particles + Particles->Draw(); + // draw ball + Ball->Draw(*Renderer); + // end rendering to postprocessing framebuffer + Effects->EndRender(); + // render postprocessing quad + Effects->Render(glfwGetTime()); + } +} + + +void Game::ResetLevel() +{ + if (this->Level == 0) + this->Levels[0].Load("levels/one.lvl", this->Width, this->Height / 2); + else if (this->Level == 1) + this->Levels[1].Load("levels/two.lvl", this->Width, this->Height / 2); + else if (this->Level == 2) + this->Levels[2].Load("levels/three.lvl", this->Width, this->Height / 2); + else if (this->Level == 3) + this->Levels[3].Load("levels/four.lvl", this->Width, this->Height / 2); +} + +void Game::ResetPlayer() +{ + // reset player/ball stats + Player->Size = PLAYER_SIZE; + Player->Position = glm::vec2(this->Width / 2.0f - PLAYER_SIZE.x / 2.0f, this->Height - PLAYER_SIZE.y); + Ball->Reset(Player->Position + glm::vec2(PLAYER_SIZE.x / 2.0f - BALL_RADIUS, -(BALL_RADIUS * 2.0f)), INITIAL_BALL_VELOCITY); + // also disable all active powerups + Effects->Chaos = Effects->Confuse = false; + Ball->PassThrough = Ball->Sticky = false; + Player->Color = glm::vec3(1.0f); + Ball->Color = glm::vec3(1.0f); +} + +// powerups +bool IsOtherPowerUpActive(std::vector &powerUps, std::string type); + +void Game::UpdatePowerUps(float dt) +{ + for (PowerUp &powerUp : this->PowerUps) + { + powerUp.Position += powerUp.Velocity * dt; + if (powerUp.Activated) + { + powerUp.Duration -= dt; + + if (powerUp.Duration <= 0.0f) + { + // remove powerup from list (will later be removed) + powerUp.Activated = false; + // deactivate effects + if (powerUp.Type == "sticky") + { + if (!IsOtherPowerUpActive(this->PowerUps, "sticky")) + { // only reset if no other PowerUp of type sticky is active + Ball->Sticky = false; + Player->Color = glm::vec3(1.0f); + } + } + else if (powerUp.Type == "pass-through") + { + if (!IsOtherPowerUpActive(this->PowerUps, "pass-through")) + { // only reset if no other PowerUp of type pass-through is active + Ball->PassThrough = false; + Ball->Color = glm::vec3(1.0f); + } + } + else if (powerUp.Type == "confuse") + { + if (!IsOtherPowerUpActive(this->PowerUps, "confuse")) + { // only reset if no other PowerUp of type confuse is active + Effects->Confuse = false; + } + } + else if (powerUp.Type == "chaos") + { + if (!IsOtherPowerUpActive(this->PowerUps, "chaos")) + { // only reset if no other PowerUp of type chaos is active + Effects->Chaos = false; + } + } + } + } + } + // Remove all PowerUps from vector that are destroyed AND !activated (thus either off the map or finished) + // Note we use a lambda expression to remove each PowerUp which is destroyed and not activated + this->PowerUps.erase(std::remove_if(this->PowerUps.begin(), this->PowerUps.end(), + [](const PowerUp &powerUp) { return powerUp.Destroyed && !powerUp.Activated; } + ), this->PowerUps.end()); +} + +bool ShouldSpawn(unsigned int chance) +{ + unsigned int random = rand() % chance; + return random == 0; +} +void Game::SpawnPowerUps(GameObject &block) +{ + if (ShouldSpawn(75)) // 1 in 75 chance + this->PowerUps.push_back(PowerUp("speed", glm::vec3(0.5f, 0.5f, 1.0f), 0.0f, block.Position, ResourceManager::GetTexture("powerup_speed"))); + if (ShouldSpawn(75)) + this->PowerUps.push_back(PowerUp("sticky", glm::vec3(1.0f, 0.5f, 1.0f), 20.0f, block.Position, ResourceManager::GetTexture("powerup_sticky"))); + if (ShouldSpawn(75)) + this->PowerUps.push_back(PowerUp("pass-through", glm::vec3(0.5f, 1.0f, 0.5f), 10.0f, block.Position, ResourceManager::GetTexture("powerup_passthrough"))); + if (ShouldSpawn(75)) + this->PowerUps.push_back(PowerUp("pad-size-increase", glm::vec3(1.0f, 0.6f, 0.4), 0.0f, block.Position, ResourceManager::GetTexture("powerup_increase"))); + if (ShouldSpawn(15)) // Negative powerups should spawn more often + this->PowerUps.push_back(PowerUp("confuse", glm::vec3(1.0f, 0.3f, 0.3f), 15.0f, block.Position, ResourceManager::GetTexture("powerup_confuse"))); + if (ShouldSpawn(15)) + this->PowerUps.push_back(PowerUp("chaos", glm::vec3(0.9f, 0.25f, 0.25f), 15.0f, block.Position, ResourceManager::GetTexture("powerup_chaos"))); +} + +void ActivatePowerUp(PowerUp &powerUp) +{ + if (powerUp.Type == "speed") + { + Ball->Velocity *= 1.2; + } + else if (powerUp.Type == "sticky") + { + Ball->Sticky = true; + Player->Color = glm::vec3(1.0f, 0.5f, 1.0f); + } + else if (powerUp.Type == "pass-through") + { + Ball->PassThrough = true; + Ball->Color = glm::vec3(1.0f, 0.5f, 0.5f); + } + else if (powerUp.Type == "pad-size-increase") + { + Player->Size.x += 50; + } + else if (powerUp.Type == "confuse") + { + if (!Effects->Chaos) + Effects->Confuse = true; // only activate if chaos wasn't already active + } + else if (powerUp.Type == "chaos") + { + if (!Effects->Confuse) + Effects->Chaos = true; + } +} + +bool IsOtherPowerUpActive(std::vector &powerUps, std::string type) +{ + // Check if another PowerUp of the same type is still active + // in which case we don't disable its effect (yet) + for (const PowerUp &powerUp : powerUps) + { + if (powerUp.Activated) + if (powerUp.Type == type) + return true; + } + return false; +} + +// collision detection +bool CheckCollision(GameObject &one, GameObject &two); +Collision CheckCollision(BallObject &one, GameObject &two); +Direction VectorDirection(glm::vec2 closest); + +void Game::DoCollisions() +{ + for (GameObject &box : this->Levels[this->Level].Bricks) + { + if (!box.Destroyed) + { + Collision collision = CheckCollision(*Ball, box); + if (std::get<0>(collision)) // if collision is true + { + // destroy block if not solid + if (!box.IsSolid) + { + box.Destroyed = true; + this->SpawnPowerUps(box); + } + else + { // if block is solid, enable shake effect + ShakeTime = 0.05f; + Effects->Shake = true; + } + // collision resolution + Direction dir = std::get<1>(collision); + glm::vec2 diff_vector = std::get<2>(collision); + if (!(Ball->PassThrough && !box.IsSolid)) // don't do collision resolution on non-solid bricks if pass-through is activated + { + if (dir == LEFT || dir == RIGHT) // horizontal collision + { + Ball->Velocity.x = -Ball->Velocity.x; // reverse horizontal velocity + // relocate + float penetration = Ball->Radius - std::abs(diff_vector.x); + if (dir == LEFT) + Ball->Position.x += penetration; // move ball to right + else + Ball->Position.x -= penetration; // move ball to left; + } + else // vertical collision + { + Ball->Velocity.y = -Ball->Velocity.y; // reverse vertical velocity + // relocate + float penetration = Ball->Radius - std::abs(diff_vector.y); + if (dir == UP) + Ball->Position.y -= penetration; // move ball bback up + else + Ball->Position.y += penetration; // move ball back down + } + } + } + } + } + + // also check collisions on PowerUps and if so, activate them + for (PowerUp &powerUp : this->PowerUps) + { + if (!powerUp.Destroyed) + { + // first check if powerup passed bottom edge, if so: keep as inactive and destroy + if (powerUp.Position.y >= this->Height) + powerUp.Destroyed = true; + + if (CheckCollision(*Player, powerUp)) + { // collided with player, now activate powerup + ActivatePowerUp(powerUp); + powerUp.Destroyed = true; + powerUp.Activated = true; + } + } + } + + // and finally check collisions for player pad (unless stuck) + Collision result = CheckCollision(*Ball, *Player); + if (!Ball->Stuck && std::get<0>(result)) + { + // check where it hit the board, and change velocity based on where it hit the board + float centerBoard = Player->Position.x + Player->Size.x / 2.0f; + float distance = (Ball->Position.x + Ball->Radius) - centerBoard; + float percentage = distance / (Player->Size.x / 2.0f); + // then move accordingly + float strength = 2.0f; + glm::vec2 oldVelocity = Ball->Velocity; + Ball->Velocity.x = INITIAL_BALL_VELOCITY.x * percentage * strength; + //Ball->Velocity.y = -Ball->Velocity.y; + Ball->Velocity = glm::normalize(Ball->Velocity) * glm::length(oldVelocity); // keep speed consistent over both axes (multiply by length of old velocity, so total strength is not changed) + // fix sticky paddle + Ball->Velocity.y = -1.0f * abs(Ball->Velocity.y); + + // if Sticky powerup is activated, also stick ball to paddle once new velocity vectors were calculated + Ball->Stuck = Ball->Sticky; + } +} + +bool CheckCollision(GameObject &one, GameObject &two) // AABB - AABB collision +{ + // collision x-axis? + bool collisionX = one.Position.x + one.Size.x >= two.Position.x && + two.Position.x + two.Size.x >= one.Position.x; + // collision y-axis? + bool collisionY = one.Position.y + one.Size.y >= two.Position.y && + two.Position.y + two.Size.y >= one.Position.y; + // collision only if on both axes + return collisionX && collisionY; +} + +Collision CheckCollision(BallObject &one, GameObject &two) // AABB - Circle collision +{ + // get center point circle first + glm::vec2 center(one.Position + one.Radius); + // calculate AABB info (center, half-extents) + glm::vec2 aabb_half_extents(two.Size.x / 2.0f, two.Size.y / 2.0f); + glm::vec2 aabb_center(two.Position.x + aabb_half_extents.x, two.Position.y + aabb_half_extents.y); + // get difference vector between both centers + glm::vec2 difference = center - aabb_center; + glm::vec2 clamped = glm::clamp(difference, -aabb_half_extents, aabb_half_extents); + // now that we know the the clamped values, add this to AABB_center and we get the value of box closest to circle + glm::vec2 closest = aabb_center + clamped; + // now retrieve vector between center circle and closest point AABB and check if length < radius + difference = closest - center; + + if (glm::length(difference) < one.Radius) // not <= since in that case a collision also occurs when object one exactly touches object two, which they are at the end of each collision resolution stage. + return std::make_tuple(true, VectorDirection(difference), difference); + else + return std::make_tuple(false, UP, glm::vec2(0.0f, 0.0f)); +} + +// calculates which direction a vector is facing (N,E,S or W) +Direction VectorDirection(glm::vec2 target) +{ + glm::vec2 compass[] = { + glm::vec2(0.0f, 1.0f), // up + glm::vec2(1.0f, 0.0f), // right + glm::vec2(0.0f, -1.0f), // down + glm::vec2(-1.0f, 0.0f) // left + }; + float max = 0.0f; + unsigned int best_match = -1; + for (unsigned int i = 0; i < 4; i++) + { + float dot_product = glm::dot(glm::normalize(target), compass[i]); + if (dot_product > max) + { + max = dot_product; + best_match = i; + } + } + return (Direction)best_match; +} \ No newline at end of file diff --git a/src/7.in_practice/3.2d_game/0.full_source/progress/8.game.h b/src/7.in_practice/3.2d_game/0.full_source/progress/8.game.h new file mode 100644 index 0000000..8c5c880 --- /dev/null +++ b/src/7.in_practice/3.2d_game/0.full_source/progress/8.game.h @@ -0,0 +1,76 @@ +/******************************************************************* +** This code is part of Breakout. +** +** Breakout is free software: you can redistribute it and/or modify +** it under the terms of the CC BY 4.0 license as published by +** Creative Commons, either version 4 of the License, or (at your +** option) any later version. +******************************************************************/ +#ifndef GAME_H +#define GAME_H + +#include +#include + +#include "game_level.h" +#include "power_up.h" + +// Represents the current state of the game +enum GameState { + GAME_ACTIVE, + GAME_MENU, + GAME_WIN +}; + +// Represents the four possible (collision) directions +enum Direction { + UP, + RIGHT, + DOWN, + LEFT +}; +// Defines a Collision typedef that represents collision data +typedef std::tuple Collision; // + +// Initial size of the player paddle +const glm::vec2 PLAYER_SIZE(100.0f, 20.0f); +// Initial velocity of the player paddle +const float PLAYER_VELOCITY(500.0f); +// Initial velocity of the Ball +const glm::vec2 INITIAL_BALL_VELOCITY(100.0f, -350.0f); +// Radius of the ball object +const float BALL_RADIUS = 12.5f; + +// Game holds all game-related state and functionality. +// Combines all game-related data into a single class for +// easy access to each of the components and manageability. +class Game +{ +public: + // game state + GameState State; + bool Keys[1024]; + bool KeysProcessed[1024]; + unsigned int Width, Height; + std::vector Levels; + std::vector PowerUps; + unsigned int Level; + // constructor/destructor + Game(unsigned int width, unsigned int height); + ~Game(); + // initialize game state (load all shaders/textures/levels) + void Init(); + // game loop + void ProcessInput(float dt); + void Update(float dt); + void Render(); + void DoCollisions(); + // reset + void ResetLevel(); + void ResetPlayer(); + // powerups + void SpawnPowerUps(GameObject &block); + void UpdatePowerUps(float dt); +}; + +#endif \ No newline at end of file