5 changed files with 582 additions and 0 deletions
@ -0,0 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.14) |
||||
project(pong) |
||||
|
||||
set(CMAKE_CXX_STANDARD 17) |
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) |
||||
set(CMAKE_CXX_EXTENSIONS OFF) |
||||
|
||||
find_package(SDL2 REQUIRED) |
||||
|
||||
add_executable(pong Main.cpp) |
||||
|
||||
target_link_libraries(pong PRIVATE SDL2::SDL2 SDL2_ttf SDL2_mixer) |
Binary file not shown.
@ -0,0 +1,570 @@
|
||||
#include <chrono> |
||||
#include <SDL2/SDL.h> |
||||
#include <SDL2/SDL_mixer.h> |
||||
#include <SDL2/SDL_ttf.h> |
||||
#include <string> |
||||
|
||||
|
||||
const int WINDOW_WIDTH = 1280; |
||||
const int WINDOW_HEIGHT = 720; |
||||
const float PADDLE_SPEED = 1.0f; |
||||
const int PADDLE_WIDTH = 10; |
||||
const int PADDLE_HEIGHT = 100; |
||||
const float BALL_SPEED = 1.0f; |
||||
const int BALL_WIDTH = 15; |
||||
const int BALL_HEIGHT = 15; |
||||
|
||||
|
||||
enum Buttons |
||||
{ |
||||
PaddleOneUp = 0, |
||||
PaddleOneDown, |
||||
PaddleTwoUp, |
||||
PaddleTwoDown, |
||||
}; |
||||
|
||||
|
||||
enum class CollisionType |
||||
{ |
||||
None, |
||||
Top, |
||||
Middle, |
||||
Bottom, |
||||
Left, |
||||
Right |
||||
}; |
||||
|
||||
|
||||
struct Contact |
||||
{ |
||||
CollisionType type; |
||||
float penetration; |
||||
}; |
||||
|
||||
|
||||
class Vec2 |
||||
{ |
||||
public: |
||||
Vec2() |
||||
: x(0.0f), y(0.0f) |
||||
{} |
||||
|
||||
Vec2(float x, float y) |
||||
: x(x), y(y) |
||||
{} |
||||
|
||||
Vec2 operator+(Vec2 const& rhs) |
||||
{ |
||||
return Vec2(x + rhs.x, y + rhs.y); |
||||
} |
||||
|
||||
Vec2& operator+=(Vec2 const& rhs) |
||||
{ |
||||
x += rhs.x; |
||||
y += rhs.y; |
||||
|
||||
return *this; |
||||
} |
||||
|
||||
Vec2 operator*(float rhs) |
||||
{ |
||||
return Vec2(x * rhs, y * rhs); |
||||
} |
||||
|
||||
float x, y; |
||||
}; |
||||
|
||||
class Paddle |
||||
{ |
||||
public: |
||||
Paddle(Vec2 position, Vec2 velocity) |
||||
: position(position), velocity(velocity) |
||||
{ |
||||
rect.x = static_cast<int>(position.x); |
||||
rect.y = static_cast<int>(position.y); |
||||
rect.w = PADDLE_WIDTH; |
||||
rect.h = PADDLE_HEIGHT; |
||||
} |
||||
|
||||
void Update(float dt) |
||||
{ |
||||
position += velocity * dt; |
||||
|
||||
if (position.y < 0) |
||||
{ |
||||
// Restrict to top of the screen
|
||||
position.y = 0; |
||||
} |
||||
else if (position.y > (WINDOW_HEIGHT - PADDLE_HEIGHT)) |
||||
{ |
||||
// Restrict to bottom of the screen
|
||||
position.y = WINDOW_HEIGHT - PADDLE_HEIGHT; |
||||
} |
||||
} |
||||
|
||||
void Draw(SDL_Renderer* renderer) |
||||
{ |
||||
rect.y = static_cast<int>(position.y); |
||||
|
||||
SDL_RenderFillRect(renderer, &rect); |
||||
} |
||||
|
||||
Vec2 position; |
||||
Vec2 velocity; |
||||
SDL_Rect rect{}; |
||||
}; |
||||
|
||||
|
||||
class Ball |
||||
{ |
||||
public: |
||||
Ball(Vec2 position, Vec2 velocity) |
||||
: position(position), velocity(velocity) |
||||
{ |
||||
rect.x = static_cast<int>(position.x); |
||||
rect.y = static_cast<int>(position.y); |
||||
rect.w = BALL_WIDTH; |
||||
rect.h = BALL_HEIGHT; |
||||
} |
||||
|
||||
void Update(float dt) |
||||
{ |
||||
position += velocity * dt; |
||||
} |
||||
|
||||
void Draw(SDL_Renderer* renderer) |
||||
{ |
||||
rect.x = static_cast<int>(position.x); |
||||
rect.y = static_cast<int>(position.y); |
||||
|
||||
SDL_RenderFillRect(renderer, &rect); |
||||
} |
||||
|
||||
void CollideWithPaddle(Contact const& contact) |
||||
{ |
||||
position.x += contact.penetration; |
||||
velocity.x = -velocity.x; |
||||
|
||||
if (contact.type == CollisionType::Top) |
||||
{ |
||||
velocity.y = -.75f * BALL_SPEED; |
||||
} |
||||
else if (contact.type == CollisionType::Bottom) |
||||
{ |
||||
velocity.y = 0.75f * BALL_SPEED; |
||||
} |
||||
} |
||||
|
||||
void CollideWithWall(Contact const& contact) |
||||
{ |
||||
if ((contact.type == CollisionType::Top) |
||||
|| (contact.type == CollisionType::Bottom)) |
||||
{ |
||||
position.y += contact.penetration; |
||||
velocity.y = -velocity.y; |
||||
} |
||||
else if (contact.type == CollisionType::Left) |
||||
{ |
||||
position.x = WINDOW_WIDTH / 2.0f; |
||||
position.y = WINDOW_HEIGHT / 2.0f; |
||||
velocity.x = BALL_SPEED; |
||||
velocity.y = 0.75f * BALL_SPEED; |
||||
} |
||||
else if (contact.type == CollisionType::Right) |
||||
{ |
||||
position.x = WINDOW_WIDTH / 2.0f; |
||||
position.y = WINDOW_HEIGHT / 2.0f; |
||||
velocity.x = -BALL_SPEED; |
||||
velocity.y = 0.75f * BALL_SPEED; |
||||
} |
||||
} |
||||
|
||||
Vec2 position; |
||||
Vec2 velocity; |
||||
SDL_Rect rect{}; |
||||
}; |
||||
|
||||
|
||||
class PlayerScore |
||||
{ |
||||
public: |
||||
PlayerScore(Vec2 position, SDL_Renderer* renderer, TTF_Font* font) |
||||
: renderer(renderer), font(font) |
||||
{ |
||||
surface = TTF_RenderText_Solid(font, "0", {0xFF, 0xFF, 0xFF, 0xFF}); |
||||
texture = SDL_CreateTextureFromSurface(renderer, surface); |
||||
|
||||
int width, height; |
||||
SDL_QueryTexture(texture, nullptr, nullptr, &width, &height); |
||||
|
||||
rect.x = static_cast<int>(position.x); |
||||
rect.y = static_cast<int>(position.y); |
||||
rect.w = width; |
||||
rect.h = height; |
||||
} |
||||
|
||||
~PlayerScore() |
||||
{ |
||||
SDL_FreeSurface(surface); |
||||
SDL_DestroyTexture(texture); |
||||
} |
||||
|
||||
void SetScore(int score) |
||||
{ |
||||
SDL_FreeSurface(surface); |
||||
SDL_DestroyTexture(texture); |
||||
|
||||
surface = TTF_RenderText_Solid(font, std::to_string(score).c_str(), {0xFF, 0xFF, 0xFF, 0xFF}); |
||||
texture = SDL_CreateTextureFromSurface(renderer, surface); |
||||
|
||||
int width, height; |
||||
SDL_QueryTexture(texture, nullptr, nullptr, &width, &height); |
||||
rect.w = width; |
||||
rect.h = height; |
||||
} |
||||
|
||||
void Draw() |
||||
{ |
||||
SDL_RenderCopy(renderer, texture, nullptr, &rect); |
||||
} |
||||
|
||||
SDL_Renderer* renderer; |
||||
TTF_Font* font; |
||||
SDL_Surface* surface{}; |
||||
SDL_Texture* texture{}; |
||||
SDL_Rect rect{}; |
||||
}; |
||||
|
||||
Contact CheckPaddleCollision(Ball const& ball, Paddle const& paddle) |
||||
{ |
||||
float ballLeft = ball.position.x; |
||||
float ballRight = ball.position.x + BALL_WIDTH; |
||||
float ballTop = ball.position.y; |
||||
float ballBottom = ball.position.y + BALL_HEIGHT; |
||||
|
||||
float paddleLeft = paddle.position.x; |
||||
float paddleRight = paddle.position.x + PADDLE_WIDTH; |
||||
float paddleTop = paddle.position.y; |
||||
float paddleBottom = paddle.position.y + PADDLE_HEIGHT; |
||||
|
||||
Contact contact{}; |
||||
|
||||
if (ballLeft >= paddleRight) |
||||
{ |
||||
return contact; |
||||
} |
||||
|
||||
if (ballRight <= paddleLeft) |
||||
{ |
||||
return contact; |
||||
} |
||||
|
||||
if (ballTop >= paddleBottom) |
||||
{ |
||||
return contact; |
||||
} |
||||
|
||||
if (ballBottom <= paddleTop) |
||||
{ |
||||
return contact; |
||||
} |
||||
|
||||
float paddleRangeUpper = paddleBottom - (2.0f * PADDLE_HEIGHT / 3.0f); |
||||
float paddleRangeMiddle = paddleBottom - (PADDLE_HEIGHT / 3.0f); |
||||
|
||||
if (ball.velocity.x < 0) |
||||
{ |
||||
// Left paddle
|
||||
contact.penetration = paddleRight - ballLeft; |
||||
} |
||||
else if (ball.velocity.x > 0) |
||||
{ |
||||
// Right paddle
|
||||
contact.penetration = paddleLeft - ballRight; |
||||
} |
||||
|
||||
if ((ballBottom > paddleTop) |
||||
&& (ballBottom < paddleRangeUpper)) |
||||
{ |
||||
contact.type = CollisionType::Top; |
||||
} |
||||
else if ((ballBottom > paddleRangeUpper) |
||||
&& (ballBottom < paddleRangeMiddle)) |
||||
{ |
||||
contact.type = CollisionType::Middle; |
||||
} |
||||
else |
||||
{ |
||||
contact.type = CollisionType::Bottom; |
||||
} |
||||
|
||||
return contact; |
||||
} |
||||
|
||||
|
||||
Contact CheckWallCollision(Ball const& ball) |
||||
{ |
||||
float ballLeft = ball.position.x; |
||||
float ballRight = ball.position.x + BALL_WIDTH; |
||||
float ballTop = ball.position.y; |
||||
float ballBottom = ball.position.y + BALL_HEIGHT; |
||||
|
||||
Contact contact{}; |
||||
|
||||
if (ballLeft < 0.0f) |
||||
{ |
||||
contact.type = CollisionType::Left; |
||||
} |
||||
else if (ballRight > WINDOW_WIDTH) |
||||
{ |
||||
contact.type = CollisionType::Right; |
||||
} |
||||
else if (ballTop < 0.0f) |
||||
{ |
||||
contact.type = CollisionType::Top; |
||||
contact.penetration = -ballTop; |
||||
} |
||||
else if (ballBottom > WINDOW_HEIGHT) |
||||
{ |
||||
contact.type = CollisionType::Bottom; |
||||
contact.penetration = WINDOW_HEIGHT - ballBottom; |
||||
} |
||||
|
||||
return contact; |
||||
} |
||||
|
||||
|
||||
int main() |
||||
{ |
||||
// Initialize SDL components
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); |
||||
TTF_Init(); |
||||
Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048); |
||||
|
||||
SDL_Window* window = SDL_CreateWindow("Pong", 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN); |
||||
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0); |
||||
|
||||
|
||||
// Initialize the font
|
||||
TTF_Font* scoreFont = TTF_OpenFont("DejaVuSansMono.ttf", 40); |
||||
|
||||
|
||||
// Initialize sound effects
|
||||
Mix_Chunk* wallHitSound = Mix_LoadWAV("WallHit.wav"); |
||||
Mix_Chunk* paddleHitSound = Mix_LoadWAV("PaddleHit.wav"); |
||||
|
||||
// Game logic
|
||||
{ |
||||
// Create the player score text fields
|
||||
PlayerScore playerOneScoreText(Vec2(WINDOW_WIDTH / 4, 20), renderer, scoreFont); |
||||
|
||||
PlayerScore playerTwoScoreText(Vec2(3 * WINDOW_WIDTH / 4, 20), renderer, scoreFont); |
||||
|
||||
|
||||
// Create the ball
|
||||
Ball ball( |
||||
Vec2(WINDOW_WIDTH / 2.0f, WINDOW_HEIGHT / 2.0f), |
||||
Vec2(BALL_SPEED, 0.0f)); |
||||
|
||||
|
||||
// Create the paddles
|
||||
Paddle paddleOne( |
||||
Vec2(50.0f, WINDOW_HEIGHT / 2.0f), |
||||
Vec2(0.0f, 0.0f)); |
||||
|
||||
Paddle paddleTwo( |
||||
Vec2(WINDOW_WIDTH - 50.0f, WINDOW_HEIGHT / 2.0f), |
||||
Vec2(0.0f, 0.0f)); |
||||
|
||||
|
||||
int playerOneScore = 0; |
||||
int playerTwoScore = 0; |
||||
|
||||
bool running = true; |
||||
bool buttons[4] = {}; |
||||
|
||||
float dt = 0.0f; |
||||
|
||||
while (running) |
||||
{ |
||||
auto startTime = std::chrono::high_resolution_clock::now(); |
||||
|
||||
SDL_Event event; |
||||
while (SDL_PollEvent(&event)) |
||||
{ |
||||
if (event.type == SDL_QUIT) |
||||
{ |
||||
running = false; |
||||
} |
||||
else if (event.type == SDL_KEYDOWN) |
||||
{ |
||||
if (event.key.keysym.sym == SDLK_ESCAPE) |
||||
{ |
||||
running = false; |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_w) |
||||
{ |
||||
buttons[Buttons::PaddleOneUp] = true; |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_s) |
||||
{ |
||||
buttons[Buttons::PaddleOneDown] = true; |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_UP) |
||||
{ |
||||
buttons[Buttons::PaddleTwoUp] = true; |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_DOWN) |
||||
{ |
||||
buttons[Buttons::PaddleTwoDown] = true; |
||||
} |
||||
} |
||||
else if (event.type == SDL_KEYUP) |
||||
{ |
||||
if (event.key.keysym.sym == SDLK_w) |
||||
{ |
||||
buttons[Buttons::PaddleOneUp] = false; |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_s) |
||||
{ |
||||
buttons[Buttons::PaddleOneDown] = false; |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_UP) |
||||
{ |
||||
buttons[Buttons::PaddleTwoUp] = false; |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_DOWN) |
||||
{ |
||||
buttons[Buttons::PaddleTwoDown] = false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
if (buttons[Buttons::PaddleOneUp]) |
||||
{ |
||||
paddleOne.velocity.y = -PADDLE_SPEED; |
||||
} |
||||
else if (buttons[Buttons::PaddleOneDown]) |
||||
{ |
||||
paddleOne.velocity.y = PADDLE_SPEED; |
||||
} |
||||
else |
||||
{ |
||||
paddleOne.velocity.y = 0.0f; |
||||
} |
||||
|
||||
if (buttons[Buttons::PaddleTwoUp]) |
||||
{ |
||||
paddleTwo.velocity.y = -PADDLE_SPEED; |
||||
} |
||||
else if (buttons[Buttons::PaddleTwoDown]) |
||||
{ |
||||
paddleTwo.velocity.y = PADDLE_SPEED; |
||||
} |
||||
else |
||||
{ |
||||
paddleTwo.velocity.y = 0.0f; |
||||
} |
||||
|
||||
|
||||
// Update the paddle positions
|
||||
paddleOne.Update(dt); |
||||
paddleTwo.Update(dt); |
||||
|
||||
|
||||
// Update the ball position
|
||||
ball.Update(dt); |
||||
|
||||
|
||||
// Check collisions
|
||||
if (Contact contact = CheckPaddleCollision(ball, paddleOne); |
||||
contact.type != CollisionType::None) |
||||
{ |
||||
ball.CollideWithPaddle(contact); |
||||
|
||||
Mix_PlayChannel(-1, paddleHitSound, 0); |
||||
} |
||||
else if (contact = CheckPaddleCollision(ball, paddleTwo); |
||||
contact.type != CollisionType::None) |
||||
{ |
||||
ball.CollideWithPaddle(contact); |
||||
|
||||
Mix_PlayChannel(-1, paddleHitSound, 0); |
||||
} |
||||
else if (contact = CheckWallCollision(ball); |
||||
contact.type != CollisionType::None) |
||||
{ |
||||
ball.CollideWithWall(contact); |
||||
|
||||
if (contact.type == CollisionType::Left) |
||||
{ |
||||
++playerTwoScore; |
||||
|
||||
playerTwoScoreText.SetScore(playerTwoScore); |
||||
} |
||||
else if (contact.type == CollisionType::Right) |
||||
{ |
||||
++playerOneScore; |
||||
|
||||
playerOneScoreText.SetScore(playerOneScore); |
||||
} |
||||
else |
||||
{ |
||||
Mix_PlayChannel(-1, wallHitSound, 0); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Clear the window to black
|
||||
SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0xFF); |
||||
SDL_RenderClear(renderer); |
||||
|
||||
// Set the draw color to be white
|
||||
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); |
||||
|
||||
|
||||
// Draw the net
|
||||
for (int y = 0; y < WINDOW_HEIGHT; ++y) |
||||
{ |
||||
if (y % 5) |
||||
{ |
||||
SDL_RenderDrawPoint(renderer, WINDOW_WIDTH / 2, y); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Draw the ball
|
||||
ball.Draw(renderer); |
||||
|
||||
// Draw the paddles
|
||||
paddleOne.Draw(renderer); |
||||
paddleTwo.Draw(renderer); |
||||
|
||||
// Display the scores
|
||||
playerOneScoreText.Draw(); |
||||
playerTwoScoreText.Draw(); |
||||
|
||||
|
||||
// Present the backbuffer
|
||||
SDL_RenderPresent(renderer); |
||||
|
||||
|
||||
// Calculate frame time
|
||||
auto stopTime = std::chrono::high_resolution_clock::now(); |
||||
dt = std::chrono::duration<float, std::chrono::milliseconds::period>(stopTime - startTime).count(); |
||||
} |
||||
} |
||||
|
||||
Mix_FreeChunk(wallHitSound); |
||||
Mix_FreeChunk(paddleHitSound); |
||||
SDL_DestroyRenderer(renderer); |
||||
SDL_DestroyWindow(window); |
||||
TTF_CloseFont(scoreFont); |
||||
Mix_Quit(); |
||||
TTF_Quit(); |
||||
SDL_Quit(); |
||||
|
||||
return 0; |
||||
} |
||||
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue