Pong!
|
The end of the second week of Game Programming I and our lecturer guided the class in putting together basic, two-player Pong during a two-day intensive workshop. In this project we’ve gone beyond the scope of what we’ve covered so far in the course, which has no programming prerequisites, so it’s right into the deep end! Collision in this version is rough: Start/Pause: Spacebar If the exe isn’t working, grab the following .NET frameworks: ASP.NET Web Frameworks and Tools 2012.2 4.1.21001.0 Here’s the project code:
// main.cpp
// includes
#include <sdl.h> //header file, includes function prototypes (explain that a function exists and how it looks roughly, but not the function itself)
#include <cmath>
// Pro+: disable warning(s) that are annoying
#pragma warning(disable:4098)
// pragma directives (can also be linked through project settings)
#pragma comment(lib, "SDL2.lib") //.lib är kompilerade filer med funktionalitet som beskriver hur dll filer fungerar
#pragma comment(lib, "SDL2main.lib")
// structs
struct Paddle
{
float x, y; //vi måste ha en position på paddeln, float då vi kommer använda decimaltal för deltatime
bool input[2]; //vi definerar en boolean array för rörelse
};
struct Ball
{
float x, y; //position på bollen
float dirx, diry; //bollens riktning
};
enum EGameState
{
GAME_STATE_PAUSE, //vi har två states, när allt på skärmen pauses och när vi spelar
GAME_STATE_PLAY, //enum variables seperated by commas not semicolons
};
struct Game //struct är sätt att gruppera variabler, ex så vi kan ha en 2d position i två grupperade variabler som utför en struct
{
SDL_Window* window; //en pekare till fönstret som sdl hjälper oss skapa
SDL_Renderer* renderer;
int width, height;
unsigned int tick; //för senare användning, när vi vill veta hur mycket tid gått sedan tidigare uppdatering, inte sedan datorn startades
EGameState state;
bool start; //kommer använda detta för att kontrolla byten mellan game states pause och play
unsigned int score0, score1; //variabel för att ha koll på score:en
Paddle left;
Paddle right;
Ball ball;
};
void initialize_ball(Ball* ball, float x, float y)
{
ball->x = x; //the ball is 20px wide, but positioning happens from the upper righthand corner, not the center of objects
ball->y = y; //we're going to calc x and y elsewhere
ball->dirx = 0.0f;
ball->diry = 0.0f;
}
void initialize_paddle(Paddle* paddle, float x, float y)
{
paddle->input[0] = false;
paddle->input[1] = false,
paddle->x = x;
paddle->y = y;
}
// functions
bool initialize(Game* game, int width, int height) { //pekare till struct:en Game
SDL_Log("initialize()");
SDL_Init(SDL_INIT_EVERYTHING); //SDL_Init initierar sdl, här initierar vi allting
game->window = nullptr; //för att accessa något i struct:en game måste vi använda punkt . om det inte är en pekare
game->renderer = nullptr;
game->width = width;
game->height = height;
game->tick = SDL_GetTicks(); //för användning när vi vill veta hur mycket tid gått sedan tidigare uppdatering, inte sedan datorn startades
game->state = GAME_STATE_PAUSE;
game->start = false;
game->score0 = 0;
game->score1 = 0;
initialize_ball(&game->ball, //vi sätter bollen i mitten, vi kommer behöva göra det flera ggr (efter varje mål)
game->width / 2 - 20.0f,
game->height / 2 - 20.0f);
//initialization of game variables:
initialize_paddle(&game->left, //positioning of the paddles using a function, since we want to reset the screen after every goal
50,
game->height / 2 - 50);
initialize_paddle(&game->right,
game->width - 70,
game->height / 2 - 50);
//game.right.input[0] = false;
//game.right.input[1] = false;
//game.right.x = 954; //1024-70
//game.right.y = game.height / 2 - 50;
//game.left.input[0] = false;
//game.left.input[1] = false;
//game.left.x = 50; //koordinat-variabler för vänstra paddelns position (600/2 -50, då paddeln är satt till 100pix totallt i draw och skärmen 600)
//game.left.y = game.height / 2 - 50;
// creating a window
game->window = SDL_CreateWindow("Pong", //vi använder en pil för att accessa struct medlemmens variabel
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
game->width,
game->height,
SDL_WINDOW_OPENGL); //vi vill använda hårdvaruaccelerarad utritning
// error checking window
if (game->window == nullptr) {
return false;
}
// creating a renderer that we use to draw stuff with
game->renderer = SDL_CreateRenderer(game->window,
-1, SDL_RENDERER_ACCELERATED);
// error checking renderer
if (game->renderer == nullptr) {
return false;
}
// everything went ok!
return true;
}
void shutdown(Game* game) {
// clean up resources
SDL_Log("shutdown()");
// check if we have a valid renderer //renderer är beroende av window, så vi tar bort renderer först eftersom vi satt igång den först ovan
if (game->renderer != nullptr) {
SDL_DestroyRenderer(game->renderer);
}
game->renderer = nullptr;
// check if we have a valid window
if (game->window != nullptr) {
SDL_DestroyWindow(game->window);
}
game->window = nullptr;
// shut down SDL
SDL_Quit();
}
bool update_input(Game* game) {
// check events for user input
SDL_Event event;
while (SDL_PollEvent(&event)) { //sdl är en abstraktion över själva os, typ platformsoberoende, behöver bara kompileras om
switch (event.type) { //switch för 'om något händer'
case SDL_KEYDOWN: //om en knapp blir nertryckt
switch (event.key.keysym.sym) { //ytterliggare en switch
// player 1 keys
case SDLK_w:
game->left.input[0] = true; //pil för att pekaren kan komma åt en struct:s variabel, när pekaren väl pekare på left, punkt för att komma åt left:s variabel.
break;
case SDLK_s:
game->left.input[1] = true; //
break;
// player 2 keys
case SDLK_o:
game->right.input[0] = true;
break;
case SDLK_l:
game->right.input[1] = true;
break;
// space-key
case SDLK_SPACE:
game->start = true; //vi sätter start till true när spacebar är nedtryckt för att byta till game state pause
break;
}
break;
case SDL_KEYUP: //vi tar reda på om ni slutar trycka ner en knapp
switch (event.key.keysym.sym) {
// player 1 keys
case SDLK_w:
game->left.input[0] = false; //en array för att skilja på upp och ner, position 0 i arrayen är upp, position 1 i arrayen är ner TROR JAG
break;
case SDLK_s:
game->left.input[1] = false; //
break;
// player 2 keys
case SDLK_o:
game->right.input[0] = false;
break;
case SDLK_l:
game->right.input[1] = false;
break;
// space-key
case SDLK_SPACE:
game->start = false; //start till false när spacebar inte är nedtryckt så game state är play
break;
// quit-key
case SDLK_ESCAPE:
return false;
break;
}
break;
case SDL_QUIT:
return false;
break;
}
}
return true;
}
bool check_collision(Paddle* paddle, Ball* ball) //titta kollision mellan paddel och bollen
{
if (paddle->x + 20.0f < ball->x || paddle->x > ball->x + 20.0f)
return false;
if (paddle->y + 100.0f < ball->y || paddle->y > ball->y + 20.0f)
return false;
return true;
}
bool update(Game* game) {
if (!update_input(game)) { //om vi har tryckt på en specifik knapp, stänger vi ner spelet. Annars körs det
SDL_Log("Quit.");
return false;
}
unsigned int now = SDL_GetTicks(); //vi vill ha TIME DEPENDENT movement, inte FRAME DEPENDENT movement. Representerar en tid i millisekunder. Unsigned då tiden ska vara positiva tal
unsigned int delta = now - game->tick; //formel för att kunna räkna ut förändringen delta, dvs hur länge det gått sedan sista uppdateringen
game->tick = now;
float deltatime = static_cast<float>(delta) * 0.001f; //vi vill omvandla millisekunder till sekunder, och vi vill veta hur mycket tid gått sedan tidigare uppdatering, inte sedan datorn startades
float paddleSpeed = 500.0f; //definerar en paddle speed variable
// för att gå från ms till s: 0.001f * 1000.0f == 1
// att dela på 1000 är likt med att multiplicera på 0.001
switch (game->state) //vi har två game states, det här är sättet att hantera dem
{
case GAME_STATE_PAUSE:
if (game->start)
{
game->state = GAME_STATE_PLAY;
if (now % 2 == 0) //vi vill slumpa fram en viss rörelse på bollen när spelet startar
{
game->ball.dirx = -1.0f;
game->ball.diry = 1.0f;
}
else
{
game->ball.dirx = 1.0f;
game->ball.diry = -1.0f;
}
float len = sqrtf( //we want the hypoteneuse, since we want a vector from the x and y values, so pyth theorum: c = sqrtf(a*a+b*b);
game->ball.dirx * game->ball.dirx +
game->ball.diry * game->ball.diry);
game->ball.dirx /= len;
game->ball.diry /= len;
}
break;
case GAME_STATE_PLAY:
if (game->left.input[0]) //OM tangenten w är nedtryckt, flyttar man vänstra paddeln uppåt
{
game->left.y -= paddleSpeed * deltatime; //likt med sig själv -1, paddeln flyttas ned i en takt per uppdateringsperiod, dvs time dependent movement
if (game->left.y < 0)
game->left.y = 0; //korrigerar och stoppar paddeln vid y=0 eller toppen av fönstret
}
if (game->left.input[1]) //OM tangenten s är nedtryckt, flyttar man vänstra paddeln neråt
{
game->left.y += paddleSpeed * deltatime; //likt med sig själv +paddlespeed, paddeln flyttas ned i en viss hastighet beroende på tiden sedan sista uppdateringen
if (game->left.y > game->height - 100)
game->left.y = game->height - 100; //korrigerar och stoppar paddeln vid fönsterns höjd - paddelns höjd
}
if (game->right.input[0])
{
game->right.y -= paddleSpeed * deltatime;
if (game->right.y < 0)
game->right.y = 0;
}
if (game->right.input[1])
{
game->right.y += paddleSpeed * deltatime;
if (game->right.y > game->height - 100)
game->right.y = game->height - 100;
}
//update ball
game->ball.x += game->ball.dirx * deltatime * paddleSpeed;
game->ball.y += game->ball.diry * deltatime * paddleSpeed;
if (game->ball.y < 0.0f)
{
game->ball.y = 0.0f;
game->ball.diry = -game->ball.diry;
}
if (game->ball.y > game->height - 20.0f)
{
game->ball.y = game->height - 20.0f;
game->ball.diry = -game->ball.diry;
}
if (game->ball.x < 0.0f)
{
game->score1++;
initialize_paddle(&game->left, 50.0f, game->height / 2 - 50.0f); //poäng till högerspelaren om bollen når vänstra kanten x = 0.0f
initialize_paddle(&game->right, game->width - 70.0f, game->height / 2 - 50.0f);
initialize_ball(&game->ball, game->width / 2 - 10.0f, game->height / 2 - 10.0f);
game->state = GAME_STATE_PAUSE;
SDL_Log("Player1: %d Player2: %d", game->score0, game->score1);
}
if (game->ball.x > game->width - 20.0f)
{
game->score0++;
initialize_paddle(&game->left, 50.0f, game->height / 2 - 50.0f); //poäng till högerspelaren om bollen når vänstra kanten x = 0.0f
initialize_paddle(&game->right, game->width - 70.0f, game->height / 2 - 50.0f);
initialize_ball(&game->ball, game->width / 2 - 10.0f, game->height / 2 - 10.0f);
game->state = GAME_STATE_PAUSE;
SDL_Log("Player1: %d Player2: %d", game->score0, game->score1);
}
if (check_collision(&game->left, &game->ball))
{
game->ball.dirx = -game->ball.dirx;
}
else if (check_collision(&game->right, &game->ball))
{
game->ball.dirx = -game->ball.dirx;
}
break;
}
SDL_Delay(10); //sist i update-function:en, säger vi gör inget i minst 10s.
return true;
}
void draw(Game* game) {
SDL_SetRenderDrawColor(game->renderer,
0x11, 0x22, 0x33, 0xff); //hexadec definitioner av färger
SDL_RenderClear(game->renderer); //vi rensar skärmen
// here will be drawing of game
SDL_SetRenderDrawColor(game->renderer,
0xff, 0xff, 0xff, 0xff);
// below an example of drawing left paddle
{
SDL_Rect rect = { game->left.x, game->left.y, 20, 100 }; //vi ritar paddelns rörelse
//SDL_Rect rect = { 50, (game->height / 2) - 50, 20, 100 };
SDL_RenderFillRect(game->renderer, &rect); //pekare till struct:en renderer och height
}
// below an example of drawing right paddle
{
SDL_Rect rect = { game->right.x, game->right.y, 20, 100 };
//SDL_Rect rect = { game->width - 60, (game->height / 2) - 50, 20, 100 };
SDL_RenderFillRect(game->renderer, &rect);
}
// below an example of drawing the ball
{
SDL_Rect rect = { game->ball.x, game->ball.y, 20, 20 };
SDL_RenderFillRect(game->renderer, &rect);
}
SDL_RenderPresent(game->renderer); //man har två stycken skärmar, den som man visar till användaren, och den som man jobbar på (back buffer)
}
// ,--------------.
// | main function |
// `--------------´
int main(int argc, char* argv[])
{
// game instance
Game game;
// initialize game
if (initialize(&game, 1024, 600)) { //om minnesaddressen existerar (vi använder ju en pekare i början)
// game loop
while (update(&game)) { //hämtar input från spelaren, man uppdaterar logiken för spelet
draw(&game);
}
}
shutdown(&game);
return 0;
}
Ideally we don’t want to write all of the code in one file. For a small game such as this it was manageable but we definitely will be using different files for different elements going forward. |