rebase project on SDL template, added audio and fixed some bugs

This commit is contained in:
Quinn
2024-09-12 18:27:33 +02:00
parent f52c5afd1b
commit b4cd156335
8 changed files with 93 additions and 438 deletions

View File

@@ -1,9 +0,0 @@
typedef union {
unsigned packed;
struct {
unsigned char a;
unsigned char b;
unsigned char g;
unsigned char r;
};
} Colour;

View File

@@ -1,17 +0,0 @@
#pragma once
enum {
SUCCESS = 0,
FAILURE = -1,
FAILURE_STD = 1 << 0, // 1 : marks a generic C library error
FAILURE_MEMORY = FAILURE_STD | (1 << 1), // 3 : marks a memory related error
// SDL ERRORS
FAILURE_SDL = 1 << 1, // 2 : marks a generic SDL error
FAILURE_SDL_INIT = FAILURE_SDL | (1 << 2), // 6 : marks an error during SDL initialisation
FAILURE_SDL_WINDOW = FAILURE_SDL | (1 << 3), // 10 : marks an error with the window
FAILURE_SDL_RENDERER = FAILURE_SDL | (1 << 4), // 18 : marks an error with the renderer
// GAME ERRORS
FAILURE_GAME = 1 << 2, // 4 : marks a generic game error
};

View File

@@ -1,162 +0,0 @@
#include "level.h"
#include <stdbool.h>
#include <SDL2/SDL.h>
#include "vector2.h"
#include "constants.h"
#include "renderer.h"
//prepares the level to be in a playable state
void level_init(Level* level) {
level->stop = false;
// initialize bouncer
level->bouncer.pos.x = (SCREEN_WIDTH / 2) - (BOUNCER_WIDTH_DEFAULT / 2);
level->bouncer.pos.y = SCREEN_HEIGHT - (BOUNCER_HEIGHT * 2);
level->bouncer.width = BOUNCER_WIDTH_DEFAULT;
// initialize ball
level->ball.pos.x = (SCREEN_WIDTH / 2) - (BALL_SIZE_DEFAULT / 2);
level->ball.pos.y = level->bouncer.pos.y - BOUNCER_HEIGHT - BALL_SIZE_DEFAULT;
level->ball.size = BALL_SIZE_DEFAULT;
level->ball.direction = (Vector2){ 0, -BALL_SPEED };
// initialize bricks
// define the colours of the brick rows
const Colour colours[BRICK_COLUMNS] = {
{0x5BCEFAFF},
{0xF5A9B8FF},
{0xFFFFFFFF},
{0xF5A9B8FF},
{0x5BCEFAFF}
};
int brick_width = BRICK_WIDTH + BRICK_PADDING;
int brick_height = BRICK_HEIGHT + (BRICK_PADDING / 2);
float level_padding_left = ((float)SCREEN_WIDTH - ((SCREEN_WIDTH / brick_width) * brick_width)) / 2.0F; //for centering
// store bricks in the level
for (int x = 0; x < BRICK_COLUMNS; x++) {
for (int y = 0; y < BRICK_ROWS; y++) {
Brick* brick = &level->bricks[x][y];
brick->colour = colours[y];
brick->pos.x = (x * brick_width) + level_padding_left;
brick->pos.y = (y * brick_height) + BRICK_PADDING_TOP;
brick->destroyed = false;
}
}
}
// updates the player's "bouncer"
static void update_player(Bouncer* bouncer, Ball* ball, const Uint8* keys) {
// if move bouncer LEFT
if (keys[SDL_SCANCODE_A] || keys[SDL_SCANCODE_LEFT]) {
if (((bouncer->pos.x) < 0) == false) {
bouncer->pos.x -= BOUNCER_SPEED;
if (ball->moving == false)
ball->pos.x -= BOUNCER_SPEED;
}
}
// if move bouncer RIGHT
if (keys[SDL_SCANCODE_D] || keys[SDL_SCANCODE_RIGHT]) {
if (((bouncer->pos.x + bouncer->width) > SCREEN_WIDTH) == false) {
bouncer->pos.x += BOUNCER_SPEED; // increase the bouncer pos
if (ball->moving == false)
ball->pos.x += BOUNCER_SPEED;
}
}
// ball launching
if (ball->moving == false && keys[SDL_SCANCODE_SPACE]) {
ball->moving = true;
}
}
// updates the player's ball
static void update_ball(Level* level, Ball* ball, Bouncer* bouncer) {
// update ball position
ball->pos.x += ball->direction.x;
ball->pos.y += ball->direction.y;
// check X axis out of bounds collisions
if ((ball->pos.x + ball->size) > SCREEN_WIDTH || ball->pos.x < 0) {
ball->direction.x *= -1;
}
// check Y axis out of bounds collisions
if (ball->pos.y < 0) {
ball->direction.y *= -1;
}
// check bouncer collisions
if ((ball->pos.x + ball->size) > bouncer->pos.x && ball->pos.x < (bouncer->pos.x + bouncer->width) &&
(ball->pos.y + ball->size) > bouncer->pos.y && ball->pos.y < (bouncer->pos.y + BOUNCER_HEIGHT)) {
float x = ball->pos.x - bouncer->pos.x + (ball->size / 2) + 2; // get the X axis relative to the bouncer (add 2, see below)
unsigned max = bouncer->width + 4; // get the maxiumum of this X axis (add 4 to make it feel more accurate)
float angle = (x - (max / 2.0F)) / max * (PI / 1.5F); // calculate the angle in radians where the bouncer X axis falls on -60° to 60°
// change the ball direction
ball->direction.x = SDL_sinf(angle) * BALL_SPEED;
ball->direction.y = -SDL_cosf(angle) * BALL_SPEED;
}
// check brick collisions
bool collided = false;
for (int x = 0; x < BRICK_COLUMNS; x++) {
for (int y = 0; y < BRICK_ROWS; y++) {
const Brick* brick = &level->bricks[x][y];
if (brick->destroyed == true) {
continue;
}
const float max_brick_x = brick->pos.x + BRICK_WIDTH;
const float brick_max_y = brick->pos.y + BRICK_HEIGHT;
if (ball->pos.x < max_brick_x && (ball->pos.x + ball->size) > brick->pos.x &&
ball->pos.y < brick_max_y && (ball->pos.y + ball->size) > brick->pos.y) {
level->bricks[x][y].destroyed = true;
// skip changing direction of we already did
if (collided == true) {
continue;
}
//float ball_centre_x = ball->pos.x + (ball->size / 2.0F);
float ball_centre_y = ball->pos.y + (ball->size / 2.0F);
// manage ball bounce direction; only bounce along the X axis if the ball's Y centre is in between dthe top and bottom of the block
if (brick->pos.y < ball_centre_y && ball_centre_y < brick_max_y) {
ball->direction.x *= -1;
}
else {
ball->direction.y *= -1;
}
collided = true;
}
}
}
}
//updates the level
void level_update(Level* level, const Uint8* keys) {
Bouncer* bouncer = &level->bouncer;
Ball* ball = &level->ball;
update_player(bouncer, ball, keys);
if (ball->moving == true)
update_ball(level, ball, bouncer);
// check lose condition
if ((ball->pos.y + ball->size) > SCREEN_HEIGHT) {
level->stop = true;
ball->moving = false;
}
}

View File

@@ -1,35 +0,0 @@
#pragma once
#include <stdbool.h>
#include <SDL2/SDL.h>
#include "vector2.h"
#include "constants.h"
#include "colour.h"
typedef struct {
Vector2 pos;
Vector2 direction;
unsigned size;
bool moving;
} Ball;
typedef struct {
Vector2 pos;
unsigned width;
} Bouncer;
typedef struct {
Vector2 pos;
Colour colour;
bool destroyed;
} Brick;
typedef struct {
bool stop;
Ball ball;
Bouncer bouncer;
Brick bricks[BRICK_COLUMNS][BRICK_ROWS];
} Level;
void level_init(Level* level);
void level_update(Level* level, const Uint8* keys);

View File

@@ -1,105 +1,93 @@
#include <SDL2/SDL.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
#include "error_codes.h"
#include "renderer.h"
#include "level.h"
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
#define UPDATES_PER_SEC 50
#define CLOCKS_PER_UPDATE (CLOCKS_PER_SEC / UPDATES_PER_SEC)
#define CLOCKS_PER_UPDATE_F (((float)CLOCKS_PER_SEC) / UPDATES_PER_SEC)
Level level = { 0 }; // stores the game's state
SDL_Window* window = NULL; // the window that is given to the OS
SDL_Renderer* renderer = NULL; // the renderer used to draw to the window
// initializes the game
static int init(void) {
level_init(&level);
return renderer_init(&window, &renderer);
}
// called on each game update
static bool update(void) {
const clock_t clock_start = clock();
//update the event keys
{
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_QUIT:
return false;
break;
}
}
}
RenderData render_data = { window, renderer, &level }; //contains the data which is used to render the game
const Uint8* keys = SDL_GetKeyboardState(NULL);
// preform updates
{
level_update(&level, keys); //update the level
renderer_update(&render_data); //update the render
}
const clock_t clock_end = clock();
// provide a delay if needed, otherwise log a warning
const clock_t clock_diff = clock_end - clock_start; // difference / how long the operations took
const int remaining_ms = (int)((CLOCKS_PER_UPDATE_F - clock_diff) / CLOCKS_PER_SEC) * 1000;
if (remaining_ms < 0) {
(void)printf("delay between updates was %dms too long.", -remaining_ms);
}
else {
SDL_Delay(remaining_ms); // wait the time in ms
}
return true;
}
#ifdef __EMSCRIPTEN__
static void update_game(void) {
// stop updating if update returns false
if (update() == false) {
emscripten_cancel_main_loop();
}
}
#endif
// cleans up resources
static void close(SDL_Window* window, SDL_Renderer* renderer) {
renderer_destroy(window, renderer);
}
// entry-point of the application
int main(int argc, char *argv[]) {
// initialize
{
const int code = init();
if (code != SUCCESS)
exit(code);
}
// TEMP: tell me that the program has been initialized
(void)printf("initialized!\n");
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(update_game, 0, 1); // don't utilise setting the FPS, as we already do this
#else
while (update() && (level.stop == false));
#endif
// frees media and shuts down SDL
close(window, renderer);
(void)printf("finished!\n");
return 0;
}
#include "main.h"
#include <SDL2/SDL.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "errors.h"
#include "game/level.h"
#include "window/audio.h"
#include "window/renderer.h"
#ifdef __EMSCRIPTEN__ // for web builds
#include <emscripten.h>
#endif
bool playing = true;
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
Level* level = NULL;
// handles game application initialisation
static void init(void) {
// initialize SDL
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
error(FAILURE_SDL_INIT, "SDL could not initialize! SDL Error: %s", SDL_GetError());
return;
}
// initialize the renderer
if (renderer_init(&window, &renderer) < 0) {
error(FAILURE_SDL_RENDERER, SDL_GetError());
return;
}
// initialize level
level = malloc(sizeof(Level));
level_init(level);
// initialize audio
level->audio_device = audio_device_init(32000, AUDIO_S16, 1, 4096);
level->bounce_sfx = audio_load_wav(level->audio_device, "./assets/bounce.wav");
}
// handles game application updating
static void update(void) {
// update the input
{
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_QUIT:
exit(SUCCESS);
break;
}
}
}
// preform updates
level_update(level, SDL_GetKeyboardState(NULL));
renderer_update(&(RenderData){window, renderer, level});
}
// handles game application quitting
void stop(void) {
playing = false;
#if __EMSCRIPTEN__
emscripten_cancel_main_loop();
#endif
}
// entry point of the application
int main(int argc, char** argv) {
init();
#if __EMSCRIPTEN__
emscripten_set_main_loop(update, 0, 1);
#else
while (playing)
update();
#endif
// cleanup of resources
SDL_FreeWAV(level->bounce_sfx.buffer);
SDL_CloseAudioDevice(level->audio_device->id);
free(level);
SDL_Quit();
return 0;
}

View File

@@ -1,78 +0,0 @@
#include "renderer.h"
#include <SDL2/SDL.h>
#include <stdio.h>
#include "error_codes.h"
#include "level.h"
#include "constants.h"
// initializes the window and renderer
int renderer_init(SDL_Window** window, SDL_Renderer** renderer) {
// initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
(void)printf("SDL Failed to initialize! SDL_Error: %s\n", SDL_GetError());
return FAILURE_SDL_INIT;
}
// init the window
*window = SDL_CreateWindow("Quinn's Breakout Clone", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (*window == NULL) {
(void)printf("Window failed to be created! SDL_Error: %s\n", SDL_GetError());
return FAILURE_SDL_WINDOW;
}
// create a renderer
*renderer = SDL_CreateRenderer(*window, -1, SDL_RENDERER_PRESENTVSYNC);
if (*renderer == NULL) {
(void)printf("Renderer failed to be created! SDL_Error: %s\n", SDL_GetError());
return FAILURE_SDL_RENDERER;
}
return SUCCESS;
}
// renders the screen
void renderer_update(RenderData* render_data) {
SDL_Renderer* renderer = render_data->renderer;
Level* level = render_data->level;
int success = 0; // if an error occurs, this value is <0
// render background
success |= SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
success |= SDL_RenderClear(renderer);
// draw player components
success |= SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
success |= SDL_RenderFillRectF(renderer, &(SDL_FRect) {level->bouncer.pos.x, level->bouncer.pos.y, level->bouncer.width, 5}); // draw bouncer
success |= SDL_RenderFillRectF(renderer, &(SDL_FRect) {level->ball.pos.x, level->ball.pos.y, level->ball.size, level->ball.size}); // draw ball
// draw bricks
for (int x = 0; x < BRICK_COLUMNS; x++) {
for (int y = 0; y < BRICK_ROWS; y++) {
Brick* brick = &level->bricks[x][y];
if (brick->destroyed == true) {
continue;
}
success |= SDL_SetRenderDrawColor(renderer, brick->colour.r, brick->colour.g, brick->colour.b, brick->colour.a);
success |= SDL_RenderFillRectF(renderer, &(SDL_FRect) {brick->pos.x, brick->pos.y, BRICK_WIDTH, BRICK_HEIGHT}); // draw brick
}
}
if (success < 0) {
printf("something went wrong whilst rendering: %s\n", SDL_GetError());
exit(FAILURE_SDL_RENDERER);
}
// present the result to the renderer
SDL_RenderPresent(renderer);
}
void renderer_destroy(SDL_Window* window, SDL_Renderer* renderer) {
SDL_DestroyWindow(window);
SDL_DestroyRenderer(renderer);
SDL_Quit();
}

View File

@@ -1,14 +0,0 @@
#pragma once
#include <SDL2/SDL.h>
#include "level.h"
typedef struct {
SDL_Window* window;
SDL_Renderer* renderer;
Level* level;
} RenderData;
int renderer_init(SDL_Window** window, SDL_Renderer** renderer);
void renderer_update(RenderData* render_data);
void renderer_destroy(SDL_Window* window, SDL_Renderer* renderer);

View File

@@ -1,18 +0,0 @@
#pragma once
#define VECTOR2_UP ((Vector2){ 0, 1 })
#define VECTOR2_DOWN ((Vector2){ 0, -1 })
#define VECTOR2_LEFT ((Vector2){ -1, 0 })
#define VECTOR2_RIGHT ((Vector2){ 1, 0 })
#define VECTOR2_ZERO ((Vector2){ 0, 0 })
#define VECTOR2_ONE ((Vector2){ 1, 1 })
#define vec2_add(v1, v2) ((Vector2){v1.x + v2.x, v1.y + v2.y})
#define vec2_subt(v1, v2) ((Vector2){v1.x - v2.x, v1.y - v2.y})
#define vec2_mult(v, a) ((Vector2){v.x * a, v.y * a})
#define vec2_div(v, a) ((Vector2){v.x / a, v.y / a})
typedef struct {
float x;
float y;
} Vector2;