437 lines
12 KiB
C
437 lines
12 KiB
C
#ifdef _WIN32
|
|
#include <Windows.h>
|
|
#include <SDL.h>
|
|
#include <SDL_ttf.h>
|
|
#include <SDL_image.h>
|
|
#else
|
|
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_ttf.h>
|
|
#include <SDL2/SDL_image.h>
|
|
#endif
|
|
#include <GL/gl.h>
|
|
#include <GL/glu.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#include "include/structs.h"
|
|
#include "include/utilityFunctions.h"
|
|
#include "include/bricks.h"
|
|
#include "include/paddleAndBall.h"
|
|
|
|
int loadTextures(GLuint *texture, const char *imageLoc);
|
|
void surfaceToTexture(GLuint *texture, SDL_Surface *surface);
|
|
|
|
void updateScore(int *playerScore, brick *bricks, int *brickCount, int *level, ball *theBall);
|
|
|
|
int main(void) {
|
|
|
|
/*
|
|
* RETURN CODES
|
|
* 0: Success
|
|
* 1: SDL init fail
|
|
* 2: Error creating Window
|
|
* 3: Error creating context
|
|
* 4: SDL_TTF init fail
|
|
* 5: Error loading font(s)
|
|
* 6: Error loading image asset(s)
|
|
*
|
|
*/
|
|
|
|
/* ----------------
|
|
* SDL Init
|
|
* ----------------
|
|
*/
|
|
if (SDL_Init(SDL_INIT_EVERYTHING)!=0) {
|
|
perror("[FAILED] Initialising SDL: ");
|
|
return 1;
|
|
}
|
|
|
|
if (TTF_Init() < 0) {
|
|
perror("[FAILED] Initialising SDL_TTF: ");
|
|
}
|
|
|
|
int go; //Var for loop control
|
|
int step = 0; //Var for step counter
|
|
|
|
/* ----------------
|
|
* Main Window
|
|
* ----------------
|
|
* Create the main output Window for SDL
|
|
*/
|
|
|
|
// Window vars
|
|
int winPosX = 100; int winPosY = 100;
|
|
int winWidth = 480; int winHeight = 720;
|
|
char windowTitle[128] = "Breakout";
|
|
|
|
Uint32 timer;
|
|
|
|
SDL_Window *window1 = SDL_CreateWindow(
|
|
windowTitle,
|
|
winPosX, winPosY,
|
|
winWidth, winHeight,
|
|
SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL
|
|
);
|
|
if (window1==NULL) {
|
|
fprintf(stderr, "[FAILED] Creating Window window1");
|
|
return 2; //Error creating Window
|
|
}
|
|
|
|
/*
|
|
* ----------------
|
|
* Renderer
|
|
* ----------------
|
|
* Creates the main renderer (tied to main window)
|
|
*/
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); // Specifies the version of the OpenGL context
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5); // (here, 1.5) -- hello, 2003!
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); // Allows deprecated functions to be used
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); // Set depth buffer to 16 bits, if OpenGL agrees (see below)
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // Enable double buffering
|
|
|
|
SDL_GLContext context1 = SDL_GL_CreateContext(window1); // Use SDL_GL_MakeCurrent function to switch contexts
|
|
|
|
if (context1 == NULL) {
|
|
fprintf(stderr, "[FAILED] Creating render context context1 for window1");
|
|
return 3;
|
|
}
|
|
|
|
glFrontFace(GL_CCW); // Counter-clockwise winding
|
|
glEnable(GL_NORMALIZE); // Vectors normalized after transforming to keep units consistent for lighting
|
|
glShadeModel(GL_SMOOTH); // GL_FLAT for flat shading instead
|
|
glEnable(GL_DEPTH_TEST); // Enables depth comparisons
|
|
glEnable(GL_TEXTURE_2D); // Enables use of textures
|
|
glAlphaFunc(GL_GREATER, 0); // How we plan on treating aplha
|
|
glEnable(GL_ALPHA_TEST); // Enables alpha testing (needed for transparent textures)
|
|
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
glMatrixMode(GL_PROJECTION); // Sets our matrix mode (initially) to work with the projection stack
|
|
glLoadIdentity(); // Projection matrix is now the identity matrix, so we can work in 2D space
|
|
|
|
gluOrtho2D(
|
|
-1.0*(GLdouble)(winWidth / 2), // Left
|
|
(GLdouble)(winWidth / 2), // Right
|
|
-1.0*(GLdouble)(winHeight / 2), // Bottom
|
|
(GLdouble)(winHeight / 2) // Top
|
|
);
|
|
|
|
glViewport(0, 0, winWidth, winHeight); // Set the viewport to fill the window
|
|
|
|
timer = SDL_GetTicks();
|
|
|
|
/*
|
|
* ----------------
|
|
* Game variables
|
|
* ----------------
|
|
*/
|
|
|
|
char subSteps = 1; // Draw every N+1 frames, thereby calculating movement more precisely
|
|
int targetFPS = 90; // Limit FPS to this value, to stop unneccessary calculations
|
|
|
|
char moveX=0; // Toggled between -1, 0 and 1 by keypresses later
|
|
|
|
int playerScore = 0;
|
|
int level = 1;
|
|
int playerLives = 3;
|
|
|
|
brick bricks[2048]; // Sets up array for the bricks to be stored in, max no. bricks is 2048
|
|
int brickCount;
|
|
colour brickColours[7]; //brickColours[n] corresponds to the colour of a brick with type n+1
|
|
brickColours[6] = (colour) { .r = 0.9000 /*255*/, .g = 0.9000 /*64 */, .b = 0.9000 /*167*/, .a = 1 };
|
|
brickColours[5] = (colour) { .r = 0.8000 /*255*/, .g = 0.8000 /*64 */, .b = 0.8000 /*167*/, .a = 1 };
|
|
brickColours[4] = (colour) { .r = 0.7000 /*255*/, .g = 0.7000 /*64 */, .b = 0.7000 /*167*/, .a = 1 };
|
|
brickColours[3] = (colour) { .r = 1.0000 /*255*/, .g = 0.2510 /*64 */, .b = 0.6549 /*167*/, .a = 1 };
|
|
brickColours[2] = (colour) { .r = 0.4588 /*117*/, .g = 0.5490 /*140*/, .b = 1.0000 /*255*/, .a = 1 };
|
|
brickColours[1] = (colour) { .r = 0.3490 /*89 */, .g = 0.8706 /*222*/, .b = 1.0000 /*255*/, .a = 1 };
|
|
brickColours[0] = (colour) { .r = 1.0000 /*255*/, .g = 0.7569 /*193*/, .b = 0.1216 /*31 */, .a = 1 };
|
|
initialiseBricks(&bricks, &brickCount, level); // Sets up the bricks for level one
|
|
|
|
colour paddleCol;
|
|
paddleCol.r = 1.0; paddleCol.g = 1.0; paddleCol.b = 1.0; paddleCol.a = 1.0; // RGBA of paddle and ball
|
|
|
|
colour bgcol1; colour bgcol2;
|
|
bgcol1.r = 0.7216; /*184*/ bgcol2.r = 0.0824; /*21 */ // RGBA values for the gradient, bgcol1 refers to the bottom colour and bgcol2 to the top
|
|
bgcol1.g = 0.2471; /*63 */ bgcol2.g = 0.0901; /*23 */
|
|
bgcol1.b = 0.6078; /*155*/ bgcol2.b = 0.4431; /*113*/
|
|
bgcol1.a = 1.0000; /*255*/ bgcol2.a = 1.0000; /*255*/
|
|
|
|
ball theBall = {
|
|
.x = 0,
|
|
.y = 0,
|
|
.vX = 1.5f, // Change to desired X / Y speeds
|
|
.vY = -5.0f,
|
|
.initVX = theBall.vX,
|
|
.initVY = theBall.vY,
|
|
.radius = 4,
|
|
.isColliding = 0,
|
|
.stickToPaddle = 1
|
|
}; // Initialising the ball
|
|
|
|
paddle thePaddle = {
|
|
.width = 50,
|
|
.height = 10,
|
|
.x = (winWidth/2)-(thePaddle.width/2)
|
|
}; // Initialising the paddle
|
|
|
|
char txtL[4] = "000"; // Placeholder text
|
|
char txtR[4] = "000";
|
|
|
|
/*
|
|
* ----------------
|
|
* Font stuff
|
|
* ----------------
|
|
*/
|
|
|
|
TTF_Font* font_main = TTF_OpenFont("assets/font_main.ttf", 56);
|
|
if (font_main == NULL) {
|
|
printf("[FAILED] Unable to load font\n");
|
|
return 5;
|
|
}
|
|
SDL_Color textColour = {
|
|
.a = 255,
|
|
.r = 255,
|
|
.g = 255,
|
|
.b = 255
|
|
};
|
|
|
|
/*
|
|
* --------------------------------
|
|
* Loading required textures
|
|
* --------------------------------
|
|
*/
|
|
|
|
GLuint heartFullTex;
|
|
GLuint heartEmptyTex;
|
|
if (
|
|
loadTextures(&heartEmptyTex, "assets/heartUsed.png") ||
|
|
loadTextures(&heartFullTex, "assets/heartFull.png") > 1) {
|
|
return 6;
|
|
}
|
|
|
|
/*
|
|
* ----------------
|
|
* Let's go!
|
|
* ----------------
|
|
*/
|
|
|
|
|
|
go=1;
|
|
while(go) {
|
|
/* ----------------
|
|
* Timing control
|
|
* ----------------
|
|
*
|
|
*/
|
|
|
|
|
|
Uint32 tickrate=SDL_GetTicks()-timer;
|
|
timer=SDL_GetTicks();
|
|
int fps=1000/(tickrate+1);
|
|
|
|
/*
|
|
* ----------------
|
|
* Event handling
|
|
* ----------------
|
|
*/
|
|
SDL_Event incomingEvent;
|
|
while (SDL_PollEvent(&incomingEvent)) {
|
|
switch (incomingEvent.type) {
|
|
case SDL_QUIT:
|
|
// User requested program quit - e.g. window close
|
|
go=0;
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
switch(incomingEvent.key.keysym.sym) {
|
|
case SDLK_ESCAPE:
|
|
go = 0;
|
|
break;
|
|
case SDLK_LEFT:
|
|
moveX=-1;
|
|
break;
|
|
case SDLK_RIGHT:
|
|
moveX=1;
|
|
break;
|
|
case SDLK_SPACE:
|
|
if (playerLives>0) theBall.stickToPaddle = 0;
|
|
else {
|
|
playerLives = 3;
|
|
level = 1;
|
|
theBall.stickToPaddle = 1;
|
|
initialiseBricks(&bricks, &brickCount, level);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case SDL_KEYUP:
|
|
switch(incomingEvent.key.keysym.sym) {
|
|
case SDLK_LEFT:
|
|
moveX=0;
|
|
break;
|
|
case SDLK_RIGHT:
|
|
moveX=0;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ----------------
|
|
* Update elements
|
|
* ----------------
|
|
*/
|
|
|
|
if (playerLives > 0) {
|
|
sprintf(txtL, "%03d", playerScore);
|
|
sprintf(txtR, "%03d", level);
|
|
updatePaddle(tickrate, &thePaddle, moveX, winWidth, winHeight);
|
|
updateBall(tickrate, &theBall, &thePaddle, winWidth, winHeight, &bricks, brickCount, &playerLives);
|
|
updateScore(&playerScore, &bricks, &brickCount, &level, &theBall);
|
|
}
|
|
|
|
if (level > 3) {
|
|
printf("Congratulations! You won!\n");
|
|
go = 0;
|
|
}
|
|
|
|
if (step % (subSteps+1) == 0) {
|
|
//step = 0;
|
|
/*
|
|
* ----------------
|
|
* Draw to buffer
|
|
* ----------------
|
|
*/
|
|
|
|
SDL_GL_MakeCurrent(window1, context1);
|
|
|
|
sprintf(windowTitle, "Breakout: Score (%d), lives remaining (%d)", playerScore, playerLives);
|
|
SDL_SetWindowTitle(window1, windowTitle);
|
|
|
|
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
|
|
if (playerLives==0) {
|
|
drawText("Game Over", font_main, textColour, (coord){ .x=0, .y=-128});
|
|
}
|
|
|
|
drawText(txtL, font_main, textColour, (coord){ .x=-128, .y=(winHeight/2)-32 });
|
|
drawText(txtR, font_main, textColour, (coord){ .x=128, .y=(winHeight/2)-32 });
|
|
drawHearts(heartFullTex, heartEmptyTex, playerLives, winWidth, winHeight);
|
|
drawPaddle(&thePaddle, winWidth, winHeight, &paddleCol);
|
|
drawBall(&theBall, &paddleCol);
|
|
drawBricks(&bricks, brickCount, winWidth, winHeight, &brickColours);
|
|
drawBg(winWidth, winHeight, &bgcol1, &bgcol2);
|
|
|
|
glFlush();
|
|
|
|
/*
|
|
* --------------------------------
|
|
* Output to Window
|
|
* --------------------------------
|
|
*/
|
|
SDL_GL_SwapWindow(window1);
|
|
}
|
|
if (SDL_GetTicks() - timer < 1000 / (targetFPS * (subSteps+1))) SDL_Delay((1000 / (targetFPS * (subSteps+1))) - (SDL_GetTicks() - timer));
|
|
step++;
|
|
}
|
|
|
|
/*
|
|
* --------------------------------
|
|
* Clean up after ourselves
|
|
* --------------------------------
|
|
*/
|
|
SDL_GL_DeleteContext(context1);
|
|
SDL_DestroyWindow(window1);
|
|
SDL_Quit();
|
|
|
|
printf("Your score was: %d, with %d lives remaining\n", playerScore, playerLives);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int loadTextures(GLuint *texture, const char *imageLoc) {
|
|
char errorMsg[512];
|
|
sprintf(errorMsg, "[FAILED] Opening %s", imageLoc);
|
|
SDL_Surface *surface = IMG_Load(imageLoc);
|
|
if (surface == NULL) {
|
|
fprintf(stderr, errorMsg);
|
|
return 1;
|
|
}
|
|
*texture = 0;
|
|
|
|
// Start of code from SDLTutorials.com
|
|
|
|
int Mode = GL_RGB;
|
|
|
|
glGenTextures(1, texture);
|
|
glBindTexture(GL_TEXTURE_2D, *texture);
|
|
|
|
if (surface->format->BytesPerPixel == 4) {
|
|
Mode = GL_RGBA;
|
|
}
|
|
glTexImage2D(GL_TEXTURE_2D, 0, Mode, surface->w, surface->h, 0, Mode, GL_UNSIGNED_BYTE, surface->pixels);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
// End of code from SDLTutorials.com
|
|
// Tim Jones (2011) SDL Surface to OpenGL Texture, Available at: http://www.sdltutorials.com/sdl-tip-sdl-surface-to-opengl-texture (Accessed: 10/01/2018)
|
|
|
|
return 0;
|
|
}
|
|
|
|
void surfaceToTexture(GLuint *texture, SDL_Surface *surface) {
|
|
*texture = 0;
|
|
|
|
int Mode = GL_RGB;
|
|
|
|
glGenTextures(1, texture);
|
|
glBindTexture(GL_TEXTURE_2D, *texture);
|
|
|
|
if (surface->format->BytesPerPixel == 4) {
|
|
Mode = GL_RGBA;
|
|
}
|
|
glTexImage2D(GL_TEXTURE_2D, 0, Mode, surface->w, surface->h, 0, Mode, GL_UNSIGNED_BYTE, surface->pixels);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
}
|
|
|
|
void updateScore(int *playerScore, brick *bricks, int *brickCount, int *level, ball *theBall) {
|
|
*playerScore = 0;
|
|
char allBricksDestroyed = 1;
|
|
for (int i = 0; i < *brickCount; i++) {
|
|
if (bricks[i].destroyed == 1) {
|
|
switch (bricks[i].brickType) {
|
|
case 1:
|
|
*playerScore += 1;
|
|
break;
|
|
case 2:
|
|
*playerScore += 3;
|
|
break;
|
|
case 3:
|
|
*playerScore += 5;
|
|
break;
|
|
case 4:
|
|
*playerScore += 7;
|
|
break;
|
|
}
|
|
} else {
|
|
if (bricks[i].brickType<5) allBricksDestroyed = 0; // Excludes 'power-up bricks' from having to be destroyed before moving on to the next level
|
|
}
|
|
}
|
|
if (allBricksDestroyed==1) {
|
|
*level += 1;
|
|
initialiseBricks(bricks, brickCount, *level);
|
|
theBall->stickToPaddle = 1;
|
|
}
|
|
} |