#include #include #include #include #include #include #include #include #include "breakout.h" #include "vector.h" #include "font.h" extern float XScale, YScale; #define BALL_TexturePath "assets/images/ball.png" #define PADDLE_TexturePath "assets/images/paddle.png" #define BLOCK_TexturePath "assets/images/spritesheet.png" #ifndef __nullptr__ #define Nullptr(type) (type *)0 #endif // __nullptr__ int BLOCK_TextureCount = 24; int BREAKOUT_BoxWidth, BREAKOUT_BoxHeight; SDL_Texture * BALL_Texture; SDL_Texture * PADDLE_Texture; SDL_Texture * BLOCK_Texture; SDL_Rect * BALL_SourceRects; SDL_Rect * PADDLE_SourceRects; SDL_Rect * BLOCK_SourceRects; Uint8 * PADDLE_MoveLeftKeys, * PADDLE_MoveRightKeys; bool BREAKOUT_IsInit = false; bool BALL_IsInit = false; bool PADDLE_IsInit = false; bool BLOCK_IsInit = false; void BREAKOUT_INITIALIZE(SDL_Renderer * renderer, int width, int height){ if (!BREAKOUT_IsInit) { printf("Initializing Game...\n"); srand(time(NULL)); BREAKOUT_BoxWidth = width; BREAKOUT_BoxHeight = height; BALL_Initialize(renderer); PADDLE_Initialize(renderer); BLOCK_Initialize(renderer); printf("Game initialized!\n"); BREAKOUT_IsInit = true; } else printf("Game is already initialized!\n"); } /* BREAKOUT_INITIALIZE */ Scenery BREAKOUT_CreateDefault(){ Scenery scenery; scenery.StartCountdown = 240; scenery.IsGameOver = false; scenery.BlockCount = 135; scenery.ball = BALL_CreateDefault(); scenery.paddle = PADDLE_CreateDefault(); scenery.blocks = malloc(scenery.BlockCount * sizeof(Block)); if (!(scenery.blocks)) printf("FATAL! Memory allocation failed!\n"); scenery.IsPaused = false; scenery.Lives = 3; int index; for (int y = 0; y < 9; y++) { index = 15 * y; for (int x = 0; x < 15; x++) { scenery.blocks[x + index] = BLOCK_CreateDefault(); scenery.blocks[x + index].TargetRect = (SDL_Rect) {.x = ((128 * x) + 2), .y = ((64 * y) + 1), .w = 124, .h = 62 }; scenery.blocks[x + index].TextureIndex = y + x; } } return scenery; } /* BREAKOUT_CreateDefault */ // This Function is obsolete! Do not use it! void BREAKOUT_ChangeSize(int width, int height){ BREAKOUT_BoxWidth = width; BREAKOUT_BoxHeight = height; } void BREAKOUT_Update(Scenery * scenery, const Uint8 * keystate){ if (scenery->IsPaused) return; // Currently paused if ((scenery->StartCountdown)-- < 0) (scenery->StartCountdown) = 0; else return; // Currently Counting down if (scenery->IsGameOver) { BALL_ResetPosition(&(scenery->ball)); PADDLE_ResetPosition(&(scenery->paddle)); scenery->StartCountdown = 240; scenery->IsGameOver = false; if (--(scenery->Lives) <= 0) printf("Game over, no lives left!\n"); else printf("Oh oh, only %d lives left!\n", scenery->Lives); return; } PADDLE_Update(&(scenery->paddle), keystate); // Update paddle before ball because paddle is not static! BALL_Update(&(scenery->ball), scenery); for (int i = 0; i < (scenery->BlockCount); i++) { BLOCK_Update((scenery->blocks) + i); } } /* BREAKOUT_Update */ void BREAKOUT_Draw(Scenery * scenery, SDL_Renderer * renderer){ for (int i = 0; i < (scenery->BlockCount); i++) { BLOCK_Draw(renderer, &((scenery->blocks)[i])); } BALL_Draw(renderer, &(scenery->ball)); PADDLE_Draw(renderer, &(scenery->paddle)); if (scenery->IsPaused) FONT_RenderTextCentered(renderer, "PAUSED", 3.0f, 1); else if ((scenery->StartCountdown) > 0) { if ((scenery->StartCountdown) <= 60) { FONT_RenderTextCentered(renderer, "GO", 6.0f, 3); } else if ((scenery->StartCountdown) <= 120) { FONT_RenderTextCentered(renderer, "1", 6.0f, 1); } else if ((scenery->StartCountdown) <= 180) { FONT_RenderTextCentered(renderer, "2", 6.0f, 1); } else { FONT_RenderTextCentered(renderer, "3", 6.0f, 1); } } } void BREAKOUT_DEINITIALIZE(){ if (BREAKOUT_IsInit) { printf("De-initializing Game...\n"); free(PADDLE_MoveLeftKeys); free(PADDLE_MoveRightKeys); free(BALL_SourceRects); free(PADDLE_SourceRects); free(BLOCK_SourceRects); BALL_Deinitialize(); PADDLE_Deinitialize(); BLOCK_Deinitialize(); printf("Game de-initialized!\n"); BREAKOUT_IsInit = false; } else printf("Game is already de-initialized!\n"); } void BREAKOUT_DestroyObject(Scenery * scenery){ for (size_t i = 0; i < (scenery->BlockCount); i++) { BLOCK_DestroyObject((scenery->blocks) + i); } BALL_DestroyObject(&(scenery->ball)); PADDLE_DestroyObject(&(scenery->paddle)); free((scenery->blocks)); } void BALL_Initialize(SDL_Renderer * renderer){ if (!BALL_IsInit) { printf("Initializing Ball...\n"); BALL_Texture = IMG_LoadTexture(renderer, BALL_TexturePath); if (!BALL_Texture) printf("Ball texture failed to load!\n"); BALL_SourceRects = (SDL_Rect *)malloc(1 * sizeof(SDL_Rect)); if (!BALL_SourceRects) printf("FATAL! Memory allocation failed!\n"); BALL_SourceRects[0] = (SDL_Rect) {.x = 0, .y = 0, .w = 512, .h = 512 }; printf("Ball initialized!\n"); BALL_IsInit = true; } else printf("Ball is already initialized!\n"); } Ball BALL_CreateDefault(){ double rotation = (double)(rand() % 360); return (Ball) { .Location = (Vector) {.x = BREAKOUT_BoxWidth / 2 - 15, .y = BREAKOUT_BoxHeight - 130 }, .Momentum = (Vector) {.x = 0.0f, .y = 15.0f }, .TargetRect = (SDL_Rect) {.x = BREAKOUT_BoxWidth / 2 - 15, .y = BREAKOUT_BoxHeight - 130, .w = 30, .h = 30 }, .Size = 15.0f, .Rotation = rotation, .RotationValue = 2, .TextureIndex = 0, .Speed = 15.0f }; // Objekt für die Eigenschaften des Balls } void BALL_ResetPosition(Ball * obj){ (obj->Location).x = BREAKOUT_BoxWidth / 2 - 15; (obj->Location).y = BREAKOUT_BoxHeight - 130; RECT_SetTargetPos(&(obj->TargetRect), &(obj->Location)); } void BALL_Draw(SDL_Renderer * renderer, Ball * obj){ // printf("Ball drawn at (%d|%d)!\n", (obj->TargetRect).x, (obj->TargetRect).x); SDL_RenderCopyEx(renderer, BALL_Texture, BALL_SourceRects + (obj->TextureIndex), &(obj->TargetRect), obj->Rotation, NULL, SDL_FLIP_NONE); } bool BALL_CollideWithRect(Ball * obj, SDL_Rect * rect){ if (!RECT_Collide(&(obj->TargetRect), rect)) return false; // Already returned with false if square ball hitbox didnt collide with rect SDL_Point ballCenter = BALL_GetCenter(obj); Vector center = (Vector) {.x = ballCenter.x, .y = ballCenter.y }; Vector corner; bool left, right, top, bottom, yMid = false, xMid = false; left = (ballCenter.x) < (rect->x); right = (ballCenter.x) > (rect->x) + (rect->w); top = (ballCenter.y) < (rect->y); bottom = (ballCenter.y) > (rect->y) + (rect->h); yMid = !(top || bottom); xMid = !(left || right); if (yMid) // Hit left or right (obj->Momentum).x = -(obj->Momentum).x; if (xMid) // Hit bottom or top (obj->Momentum).y = -(obj->Momentum).y; if (xMid || yMid) return true; // double oldAngle = fmod((double)(VECTOR_GetRotation(obj->Momentum)), 360.0f); if (left) { corner.x = (rect->x); } else if (right) { corner.x = ((rect->x) + (rect->w)); } // Other case will not appear since returned above if (top) { corner.y = (rect->y); } else if (bottom) { corner.y = ((rect->y) + (rect->h)); } // printf("Hit corner at %.0f|%.0f!\n", corner.x, corner.y); // printf("Ball center at %.0f|%.0f!\n", center.x, center.y); // TODO: Reflection at lot double lot = VECTOR_GetRotation(VECTOR_GetVectorFromTo(corner, center)); double inRotation = fmod(VECTOR_GetRotation(obj->Momentum) + 180.0f, 360.0f); double outAngle = fmod(lot + (0.5 * (lot - inRotation)), 360.0f); printf("In: %.2f | Lot: %.2f | Out: %.2f\n", inRotation, lot, outAngle); (obj->Momentum) = VECTOR_GetScaledDirectionalUnitVector(outAngle, (obj->Speed)); // printf("New Magnitude = %.2f\n", VECTOR_GetMagnitude((obj->Momentum))); return true; } /* BALL_CollideWithRect */ bool RECT_Collide(SDL_Rect * rect1, SDL_Rect * rect2){ if ((rect1->x) + (rect1->w) < (rect2->x)) { return false; } if ((rect1->x) > (rect2->x) + (rect2->w)) { return false; } if ((rect1->y) + (rect1->w) < (rect2->y)) { return false; } if ((rect1->y) > (rect2->y) + (rect2->h)) { return false; } return true; } void BALL_SteerMomentum(Ball * obj, Paddle * paddle){ double paddleHalfLen = ((double)((paddle->TargetRect).w) / 2.0f); double offset = (((obj->TargetRect).x) + (obj->Size)) - ((paddle->TargetRect).x + paddleHalfLen); offset /= paddleHalfLen; offset *= (paddle->SteeringAngle); DOUBLE_Constrain(&offset, -(paddle->SteeringAngle), (paddle->SteeringAngle)); (obj->Momentum) = VECTOR_GetDirectionalUnitVector(offset); } void RECT_SetTargetPos(SDL_Rect * rect, Vector * Location){ rect->x = (int)round(Location->x); rect->y = (int)round(Location->y); } SDL_Point BALL_GetCenter(Ball * obj){ return (SDL_Point) {.x = ((obj->TargetRect).x) + (obj->Size), .y = ((obj->TargetRect).y) + (obj->Size) }; } void BALL_CollideWithBorders(Ball * obj){ if ((obj->Location).y < 0.0f) (obj->Momentum).y = -(obj->Momentum).y; if ((obj->Location).x < 0.0f || (obj->Location).x > BREAKOUT_BoxWidth - (2 * (obj->Size))) (obj->Momentum).x = -(obj->Momentum).x; } void BALL_MoveAwayFromBoundaries(Ball * obj){ while (((obj->Location).y) < 0) ((obj->Location).y)++; while (((obj->Location).x) < 0) ((obj->Location).x)++; while ((((obj->Location).x) + ((obj->Size) * 2)) > BREAKOUT_BoxWidth) ((obj->Location).x)--; } bool BALL_CollideWithPaddle(Ball * obj, Paddle * paddle){ if (RECT_Collide(&(obj->TargetRect), &(paddle->TargetRect))) { SDL_Point ballCenter = BALL_GetCenter(obj); if (ballCenter.y > (paddle->TargetRect).y) // if the ball hits the paddle from the sides (or the bottom (?)) BALL_CollideWithRect(obj, &(paddle->TargetRect)); else BALL_SteerMomentum(obj, paddle); // Sets it to unit vector! // Following assumes that the paddle position was udated before the ball was updated while (RECT_Collide(&(obj->TargetRect), &(paddle->TargetRect))) { // Move away from rect in small steps (obj->Location) = VECTOR_Add((obj->Location), (obj->Momentum)); BALL_MoveAwayFromBoundaries(obj); RECT_SetTargetPos(&(obj->TargetRect), &(obj->Location)); } (obj->Momentum) = VECTOR_ChangeScaleTo((obj->Momentum), (obj->Speed)); return true; } return false; } /* BALL_CollideWithPaddle */ void BALL_Update(Ball * obj, Scenery * scenery){ Block * blocks = (scenery->blocks); Paddle * paddle = &(scenery->paddle); int BlockCount = scenery->BlockCount; Vector oldMomentum = obj->Momentum; Vector oldLocation = obj->Location; (obj->Rotation) += (obj->RotationValue); // No effect on physics (obj->Location) = VECTOR_Add((obj->Location), oldMomentum); RECT_SetTargetPos(&(obj->TargetRect), &(obj->Location)); if (!BALL_CollideWithPaddle(obj, paddle)) // Collide with Paddle for (int i = 0; i < BlockCount; i++) { // Check Collide with each block if (blocks[i].HP <= 0) continue; oldMomentum = obj->Momentum; oldLocation = obj->Location; if (BALL_CollideWithRect(obj, &(blocks[i].TargetRect))) { BLOCK_DealDamage(blocks + i, 1); (obj->Location) = VECTOR_Add(oldLocation, (obj->Momentum)); BALL_MoveAwayFromBoundaries(obj); RECT_SetTargetPos(&(obj->TargetRect), &(obj->Location)); } } if ((obj->Location).y > BREAKOUT_BoxHeight) // Collide with box boundaries scenery->IsGameOver = true; else BALL_CollideWithBorders(obj); RECT_SetTargetPos(&(obj->TargetRect), &(obj->Location)); } /* BALL_Update */ void BALL_DestroyObject(Ball * obj){ } void BALL_Deinitialize(){ if (BALL_IsInit) { printf("De-initializing Ball...\n"); SDL_DestroyTexture(BALL_Texture); printf("Ball de-initialized!\n"); BALL_IsInit = false; } else printf("Ball is already de-initialized!\n"); } void PADDLE_Initialize(SDL_Renderer * renderer){ if (!PADDLE_IsInit) { printf("Initializing Paddle...\n"); PADDLE_Texture = IMG_LoadTexture(renderer, PADDLE_TexturePath); if (!PADDLE_Texture) printf("Paddle texture failed to load!\n"); PADDLE_SourceRects = (SDL_Rect *)malloc(1 * sizeof(SDL_Rect)); if (!PADDLE_SourceRects) printf("FATAL! Memory allocation failed!\n"); PADDLE_SourceRects[0] = (SDL_Rect) {.x = 0, .y = 0, .w = 1000, .h = 100 }; PADDLE_MoveLeftKeys = (Uint8 *)malloc(2 * sizeof(Uint8)); if (!PADDLE_MoveLeftKeys) printf("FATAL! Memory allocation failed!\n"); PADDLE_MoveRightKeys = (Uint8 *)malloc(2 * sizeof(Uint8)); if (!PADDLE_MoveRightKeys) printf("FATAL! Memory allocation failed!\n"); PADDLE_MoveLeftKeys[0] = 2; // Erster wert gibt größe des arrays an PADDLE_MoveLeftKeys[1] = SDL_SCANCODE_LEFT; PADDLE_MoveLeftKeys[2] = SDL_SCANCODE_A; PADDLE_MoveRightKeys[0] = 2; PADDLE_MoveRightKeys[1] = SDL_SCANCODE_RIGHT; PADDLE_MoveRightKeys[2] = SDL_SCANCODE_D; printf("Paddle initialized!\n"); PADDLE_IsInit = true; } else printf("Paddle is already initialized!\n"); } /* PADDLE_Initialize */ Paddle PADDLE_CreateDefault(){ int defaultpaddlewidth = 300; return (Paddle) { .TargetRect = (SDL_Rect) {.x = (BREAKOUT_BoxWidth - defaultpaddlewidth) / 2, .y = BREAKOUT_BoxHeight - 100, .w = defaultpaddlewidth, .h = 30 }, .TextureIndex = 0, .Speed = 10, .SteeringAngle = 40.0f, .Mode = KeyboardControl }; // Objekt für die Eigenschaften des Balls } void PADDLE_ResetPosition(Paddle * obj){ (obj->TargetRect).x = (BREAKOUT_BoxWidth - ((obj->TargetRect).w)) / 2; (obj->TargetRect).y = BREAKOUT_BoxHeight - 100; } void PADDLE_Draw(SDL_Renderer * renderer, Paddle * obj){ // printf("Paddle drawn at (%d|%d)!\n", (obj->TargetRect).x, (obj->TargetRect).x); SDL_RenderCopy(renderer, PADDLE_Texture, PADDLE_SourceRects + (obj->TextureIndex), &(obj->TargetRect)); // SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); // SDL_RenderDrawRect(renderer, &(obj->TargetRect)); } bool KeyPressed(const Uint8 * keystate, Uint8 * keyArray){ for (int i = 0; i < (*keyArray); i++) { if (keystate[keyArray[(i + 1)]]) return true; } return false; } void INT_Constrain(int * variable, int min, int max){ if (*variable > max) *variable = max; else if (*variable < min) *variable = min; } void DOUBLE_Constrain(double * variable, double min, double max){ if (*variable > max) *variable = max; else if (*variable < min) *variable = min; } void PADDLE_Update(Paddle * obj, const Uint8 * keystate){ bool leftKeyPressed = false, rightKeyPressed = false; int paddleXMid = (obj->TargetRect).x + ((obj->TargetRect).w / 2); int mouseX; switch (obj->Mode) { case MouseControl: SDL_GetMouseState(&mouseX, NULL); mouseX = (int)roundf((float)mouseX / XScale); if (abs(mouseX - paddleXMid) > (obj->Speed)) { if (mouseX > paddleXMid) rightKeyPressed = true; else leftKeyPressed = true; } break; case KeyboardControl: leftKeyPressed = KeyPressed(keystate, PADDLE_MoveLeftKeys); rightKeyPressed = KeyPressed(keystate, PADDLE_MoveRightKeys); break; default: printf("Unknown Paddle Control Mode: %d!\n", obj->Mode); break; } if (leftKeyPressed && (!rightKeyPressed)) { ((obj->TargetRect).x) -= (obj->Speed); } else if ((!leftKeyPressed) && rightKeyPressed) { ((obj->TargetRect).x) += (obj->Speed); } INT_Constrain(&((obj->TargetRect).x), 0, (BREAKOUT_BoxWidth - ((obj->TargetRect).w))); } /* PADDLE_Update */ void PADDLE_DestroyObject(Paddle * obj){ } void PADDLE_Deinitialize(){ if (PADDLE_IsInit) { printf("De-initializing Paddle...\n"); SDL_DestroyTexture(PADDLE_Texture); printf("Paddle de-initialized!\n"); PADDLE_IsInit = false; } else printf("Paddle is already de-initialized!\n"); } void BLOCK_Initialize(SDL_Renderer * renderer){ if (!BLOCK_IsInit) { printf("Initializing Block...\n"); BLOCK_Texture = IMG_LoadTexture(renderer, BLOCK_TexturePath); if (!BLOCK_Texture) printf("Block texture failed to load!\n"); BLOCK_SourceRects = (SDL_Rect *)malloc(BLOCK_TextureCount * sizeof(SDL_Rect)); if (!BLOCK_SourceRects) printf("FATAL! Memory allocation failed!\n"); BLOCK_SourceRects[0] = (SDL_Rect) {.x = 2000, .y = 1500, .w = 1000, .h = 500 }; BLOCK_SourceRects[1] = (SDL_Rect) {.x = 2000, .y = 2000, .w = 1000, .h = 500 }; BLOCK_SourceRects[2] = (SDL_Rect) {.x = 2000, .y = 2500, .w = 1000, .h = 500 }; BLOCK_SourceRects[3] = (SDL_Rect) {.x = 0, .y = 3000, .w = 1000, .h = 500 }; BLOCK_SourceRects[4] = (SDL_Rect) {.x = 1000, .y = 3000, .w = 1000, .h = 500 }; BLOCK_SourceRects[5] = (SDL_Rect) {.x = 2000, .y = 3000, .w = 1000, .h = 500 }; BLOCK_SourceRects[6] = (SDL_Rect) {.x = 0, .y = 3500, .w = 1000, .h = 500 }; BLOCK_SourceRects[7] = (SDL_Rect) {.x = 1000, .y = 3500, .w = 1000, .h = 500 }; BLOCK_SourceRects[8] = (SDL_Rect) {.x = 2000, .y = 3500, .w = 1000, .h = 500 }; BLOCK_SourceRects[9] = (SDL_Rect) {.x = 0, .y = 500, .w = 1000, .h = 500 }; BLOCK_SourceRects[10] = (SDL_Rect) {.x = 2000, .y = 0, .w = 1000, .h = 500 }; BLOCK_SourceRects[11] = (SDL_Rect) {.x = 0, .y = 1000, .w = 1000, .h = 500 }; BLOCK_SourceRects[12] = (SDL_Rect) {.x = 0, .y = 1500, .w = 1000, .h = 500 }; BLOCK_SourceRects[13] = (SDL_Rect) {.x = 1000, .y = 0, .w = 1000, .h = 500 }; BLOCK_SourceRects[14] = (SDL_Rect) {.x = 1000, .y = 500, .w = 1000, .h = 500 }; BLOCK_SourceRects[15] = (SDL_Rect) {.x = 1000, .y = 1000, .w = 1000, .h = 500 }; BLOCK_SourceRects[16] = (SDL_Rect) {.x = 1000, .y = 1500, .w = 1000, .h = 500 }; BLOCK_SourceRects[17] = (SDL_Rect) {.x = 0, .y = 2000, .w = 1000, .h = 500 }; BLOCK_SourceRects[18] = (SDL_Rect) {.x = 1000, .y = 2000, .w = 1000, .h = 500 }; BLOCK_SourceRects[19] = (SDL_Rect) {.x = 0, .y = 2500, .w = 1000, .h = 500 }; BLOCK_SourceRects[20] = (SDL_Rect) {.x = 1000, .y = 2500, .w = 1000, .h = 500 }; BLOCK_SourceRects[21] = (SDL_Rect) {.x = 0, .y = 0, .w = 1000, .h = 500 }; BLOCK_SourceRects[22] = (SDL_Rect) {.x = 2000, .y = 500, .w = 1000, .h = 500 }; BLOCK_SourceRects[23] = (SDL_Rect) {.x = 2000, .y = 1000, .w = 1000, .h = 500 }; printf("Block initialized!\n"); BLOCK_IsInit = true; } else printf("Block is already initialized!\n"); } /* PADDLE_Initialize */ Block BLOCK_CreateDefault() { return (Block) { .TargetRect = (SDL_Rect) {.x = 0, .y = 0, .w = 100, .h = 50 }, .TextureIndex = (rand() % BLOCK_TextureCount), .HP = 1 }; // Objekt für die Eigenschaften des Balls } void BLOCK_Draw(SDL_Renderer * renderer, Block * obj){ if ((obj->HP) > 0) { // printf("Block drawn at (%d|%d)!\n", (obj->TargetRect).x, (obj->TargetRect).y); SDL_RenderCopy(renderer, BLOCK_Texture, (BLOCK_SourceRects + (obj->TextureIndex)), &(obj->TargetRect)); } } void BLOCK_DealDamage(Block * obj, int dmg){ if (((obj->HP) -= dmg) <= 0) printf("Block was destroyed!\n"); } void BLOCK_Update(Block * obj){ // Do nothing currently } void BLOCK_DestroyObject(Block * obj){ } void BLOCK_Deinitialize(){ if (BLOCK_IsInit) { printf("De-initializing Block...\n"); SDL_DestroyTexture(BLOCK_Texture); printf("Block de-initialized!\n"); BLOCK_IsInit = false; } else printf("Block is already de-initialized!\n"); }