From abbcac57437b58e79aeb2e1c4b9d72c64ca9dcea Mon Sep 17 00:00:00 2001 From: Joey de Vries Date: Fri, 24 Apr 2020 15:51:32 +0200 Subject: [PATCH] Add game progress code for Breakout collision chapters. Add forgotten destructor logic on previous progress files. --- .../0.full_source/progress/3.game.cpp | 2 +- .../0.full_source/progress/4.game.cpp | 3 +- .../0.full_source/progress/5.game.cpp | 262 ++++++++++++++++++ .../3.2d_game/0.full_source/progress/5.game.h | 67 +++++ 4 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 src/7.in_practice/3.2d_game/0.full_source/progress/5.game.cpp create mode 100644 src/7.in_practice/3.2d_game/0.full_source/progress/5.game.h diff --git a/src/7.in_practice/3.2d_game/0.full_source/progress/3.game.cpp b/src/7.in_practice/3.2d_game/0.full_source/progress/3.game.cpp index 377ed91..6d78d49 100644 --- a/src/7.in_practice/3.2d_game/0.full_source/progress/3.game.cpp +++ b/src/7.in_practice/3.2d_game/0.full_source/progress/3.game.cpp @@ -23,7 +23,7 @@ Game::Game(unsigned int width, unsigned int height) Game::~Game() { - + delete Renderer; } void Game::Init() diff --git a/src/7.in_practice/3.2d_game/0.full_source/progress/4.game.cpp b/src/7.in_practice/3.2d_game/0.full_source/progress/4.game.cpp index 9aafcd9..4d11bbb 100644 --- a/src/7.in_practice/3.2d_game/0.full_source/progress/4.game.cpp +++ b/src/7.in_practice/3.2d_game/0.full_source/progress/4.game.cpp @@ -23,7 +23,8 @@ Game::Game(unsigned int width, unsigned int height) Game::~Game() { - + delete Renderer; + delete Player; } void Game::Init() diff --git a/src/7.in_practice/3.2d_game/0.full_source/progress/5.game.cpp b/src/7.in_practice/3.2d_game/0.full_source/progress/5.game.cpp new file mode 100644 index 0000000..4009f5b --- /dev/null +++ b/src/7.in_practice/3.2d_game/0.full_source/progress/5.game.cpp @@ -0,0 +1,262 @@ +/******************************************************************* +** 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" + +// Game-related State data +SpriteRenderer *Renderer; +GameObject *Player; +BallObject *Ball; + +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; +} + +void Game::Init() +{ + // load shaders + ResourceManager::LoadShader("shaders/sprite.vs", "shaders/sprite.frag", nullptr, "sprite"); + // 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); + // set render-specific controls + Renderer = new SpriteRenderer(ResourceManager::GetShader("sprite")); + // 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"); + // 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(); + // 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) + { + // 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 ball + Ball->Draw(*Renderer); + } +} + + +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); +} + +// 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; + // collision resolution + Direction dir = std::get<1>(collision); + glm::vec2 diff_vector = std::get<2>(collision); + 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 + } + } + } + } + // 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); + } +} + +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/5.game.h b/src/7.in_practice/3.2d_game/0.full_source/progress/5.game.h new file mode 100644 index 0000000..3055d3e --- /dev/null +++ b/src/7.in_practice/3.2d_game/0.full_source/progress/5.game.h @@ -0,0 +1,67 @@ +/******************************************************************* +** 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 + +// 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; + // 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(); +}; + +#endif \ No newline at end of file