You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
570 lines
11 KiB
570 lines
11 KiB
#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; |
|
} |
|
|
|
|