#include "gamedirector.h" //Calcula el cuadrado de la distancia entre dos puntos double distanceSquared(int x1, int y1, int x2, int y2) { int deltaX = x2 - x1; int deltaY = y2 - y1; return deltaX * deltaX + deltaY * deltaY; } //Detector de colisiones entre dos circulos bool checkCollision(Circle &a, Circle &b) { //Calcula el radio total al cuadrado int totalRadiusSquared = a.r + b.r; totalRadiusSquared = totalRadiusSquared * totalRadiusSquared; //Si la distancia entre el centro de los circulos es inferior a la suma de sus radios if (distanceSquared(a.x, a.y, b.x, b.y) < (totalRadiusSquared)) { //Los circulos han colisionado return true; } //En caso contrario return false; } //Constructor GameDirector::GameDirector() { init(); } //Iniciador void GameDirector::init() { //Variables mGameStatus = GAME_STATE_TITLE; mOldTicks = 0; mMaxBalloons = 50; mMaxBullets = 50; mGameSpeed = 15; mMenaceLevel = 0; mMenaceLevelThreshold = 7; mScore = 0; mHiScore = 0; mScoreText = std::to_string(mScore); mHiScoreText = std::to_string(mHiScore); mGetReady = true; //Objeto jugador player.init(); //Establece a cero todos los valores del vector de objetos globo resetBalloons(); //Crea dos objetos globo y los centra en el area de juego //balloon[0].init(0, BLOCK, BALLOON_4, BALLON_VELX_POSITIVE, 0); //balloon[0].allignTo(PLAY_AREA_WIDTH / 2); //balloon[1].init(0, BLOCK, BALLOON_4, BALLON_VELX_NEGATIVE, 0); //balloon[1].allignTo(PLAY_AREA_WIDTH / 2); //Con los globos creados, calcula el nivel de amenaza calculateMenaceLevel(); //Establece a cero todos los valores del vector de objetos bala resetBullets(); #ifdef TEST balloonTest.init(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, BALLOON_4, 0); balloonTest.stop(); bulletTest.init(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 2, BULLET_UP); #endif //Los fondos gameBackground.init(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - (0 * BLOCK), &gGameBackgroundTexture); titleBackground.init(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, &gTitleBackgroundTexture); //Objetos texto, uno de cada color whiteText.init(&gWhiteFontTexture); blackText.init(&gBlackFontTexture); //Inicializa el objeto con el menu del titulo menuTitle.init(0, 16 * BLOCK, MENU_SELECTOR_WHITE, MENU_BACKGROUND_TRANSPARENT); menuTitle.addItem("START"); menuTitle.addItem("EXIT"); menuTitle.setBackgroundColor(0, 0, 0, 255); menuTitle.centerMenuOnScreen(); //Inicializa el objeto con el menu de pausa menuPause.init(0, 12 * BLOCK, MENU_SELECTOR_WHITE, MENU_BACKGROUND_SOLID); menuPause.addItem("CONTINUE"); menuPause.addItem("EXIT TO TITLE"); menuPause.setBackgroundColor(0x73, 0x27, 0x5c, 255); menuPause.centerMenuOnScreen(); } //Hace una pausa de milisegundos void GameDirector::sleep(Uint16 time) { Uint32 ticks = SDL_GetTicks(); while (SDL_GetTicks() - ticks < time) { /* code */ } } //Establece el valor de la variable void GameDirector::setScore(Uint32 score) { mScore = score; } //Establece el valor de la variable void GameDirector::setHiScore(Uint32 score) { mHiScore = score; } //Actualiza el valor de HiScore en caso necesario void GameDirector::updateHiScore() { if (mScore > mHiScore) { mHiScore = mScore; } } //Transforma un valor numérico en una cadena de 6 cifras std::string GameDirector::updateScoreText(Uint32 num) { switch (num) { case 0 ... 9: return ("00000" + std::to_string(num)); break; case 10 ... 99: return ("0000" + std::to_string(num)); break; case 100 ... 999: return ("000" + std::to_string(num)); break; case 1000 ... 9999: return ("00" + std::to_string(num)); break; case 10000 ... 99999: return ("0" + std::to_string(num)); break; case 100000 ... 999999: return (std::to_string(num)); break; default: return (std::to_string(num)); break; } } //Pinta el marcador en pantalla usando un objeto texto void GameDirector::renderScoreBoard(Text &text) { mScoreText = updateScoreText(mScore); mHiScoreText = updateScoreText(mHiScore); text.write(SCORE_WORD_X, SCORE_WORD_Y, "SCORE"); text.write(SCORE_NUMBER_X, SCORE_NUMBER_Y, mScoreText); text.write(HISCORE_WORD_X, HISCORE_WORD_Y, "HI-SCORE"); text.write(HISCORE_NUMBER_X, HISCORE_NUMBER_Y, mHiScoreText); } //Mueve todos los globos activos void GameDirector::moveBalloons() { for (Uint8 i = 0; i < mMaxBalloons; i++) { if (balloon[i].isActive()) { balloon[i].move(); } } } //Pinta en pantalla todos los globos activos void GameDirector::renderBalloons() { for (Uint8 i = 0; i < mMaxBalloons; i++) { if (balloon[i].isActive()) { balloon[i].render(); } } } //Devuelve el primer indice no activo del vector de globos Uint8 GameDirector::getBallonFreeIndex() { int index = 0; for (Uint8 i = 0; i < mMaxBalloons; i++) { if (balloon[i].isActive() == false) { index = i; break; } } return index; } //Crea un globo nuevo en el vector de globos Uint8 GameDirector::createNewBalloon(int x, int y, Uint8 kind, float velx, Uint16 creationtimer) { Uint8 index = getBallonFreeIndex(); balloon[index].init(x, y, kind, velx, creationtimer); return index; } //Establece a cero todos los valores del vector de objetos globo void GameDirector::resetBalloons() { for (Uint8 i = 0; i < mMaxBalloons; i++) { balloon[i].erase(); } } //Explosiona un globo. Lo destruye y crea otros dos si es el caso void GameDirector::popBalloon(Uint8 index) { if (balloon[index].isActive()) { Uint8 kind = balloon[index].getKind(); Uint8 freeIndex = 0; switch (kind) { //Si es del tipo más pequeño, simplemente elimina el globo case BALLOON_1: balloon[index].erase(); break; //En cualquier otro caso, crea dos globos de un tipo inferior default: freeIndex = getBallonFreeIndex(); balloon[freeIndex].init(0, balloon[index].getPosY(), balloon[index].getKind() - 1, BALLON_VELX_NEGATIVE, 0); balloon[freeIndex].allignTo(balloon[index].getPosX() + (balloon[index].getWidth() / 2)); balloon[freeIndex].setVelY(-2.5); freeIndex = getBallonFreeIndex(); balloon[freeIndex].init(0, balloon[index].getPosY(), balloon[index].getKind() - 1, BALLON_VELX_POSITIVE, 0); balloon[freeIndex].allignTo(balloon[index].getPosX() + (balloon[index].getWidth() / 2)); balloon[freeIndex].setVelY(-2.5); //Elimina el globo balloon[index].erase(); break; } } } //Detiene todos los globos void GameDirector::stopAllBalloons() { for (Uint8 i = 0; i < mMaxBalloons; i++) { if (balloon[i].isActive()) { balloon[i].setStop(true); } } } //Pone en marcha todos los globos void GameDirector::startAllBalloons() { for (Uint8 i = 0; i < mMaxBalloons; i++) { if (balloon[i].isActive()) { balloon[i].setStop(false); } } } //Obtiene el numero de globos activos Uint8 GameDirector::countBalloons() { Uint8 num = 0; for (Uint8 i = 0; i < mMaxBalloons; i++) { if (balloon[i].isActive()) { ++num; } } return num; } //Comprueba la colisión entre el jugador y los globos activos bool GameDirector::checkPlayerBallonCollision() { bool result = false; for (Uint8 i = 0; i < mMaxBalloons; i++) { if (balloon[i].isActive()) { if (checkCollision(player.getCollider(), balloon[i].getCollider())) { result = true; break; } } } return result; } //Comprueba y procesa la colisión entre las balas y los globos void GameDirector::processBulletBallonCollision() { for (Uint8 i = 0; i < mMaxBalloons; i++) { for (Uint8 j = 0; j < mMaxBullets; j++) { if (balloon[i].isActive() && !(balloon[i].isInvulnerable()) && bullet[j].isActive()) { if (checkCollision(balloon[i].getCollider(), bullet[j].getCollider())) { player.addScore(balloon[i].getScore()); setScore(player.getScore()); updateHiScore(); popBalloon(i); Mix_PlayChannel(-1, gPopBalloonFX, 0); bullet[j].erase(); calculateMenaceLevel(); break; } } } } } //Mueve las balas activas void GameDirector::moveBullets() { for (Uint8 i = 0; i < mMaxBullets; i++) { if (bullet[i].isActive()) { bullet[i].move(); } } } //Pinta las balas activas void GameDirector::renderBullets() { for (Uint8 i = 0; i < mMaxBullets; i++) { if (bullet[i].isActive()) { bullet[i].render(); } } } //Devuelve el primer indice no activo del vector de balas Uint8 GameDirector::getBulletFreeIndex() { Uint8 index = 0; for (int i = 0; i < mMaxBullets; i++) { if (bullet[i].isActive() == false) { index = i; break; } } return index; } //Establece a cero todos los valores del vector de objetos bala void GameDirector::resetBullets() { for (Uint8 i = 0; i < mMaxBullets; i++) { bullet[i].init(0, 0, NO_KIND); } } //Crea un objeto bala void GameDirector::createBullet(int x, int y, Uint8 kind) { bullet[getBulletFreeIndex()].init(x, y, kind); } //Calcula y establece el valor de amenaza en funcion de los globos activos void GameDirector::calculateMenaceLevel() { mMenaceLevel = 0; for (Uint8 i = 0; i < mMaxBalloons; i++) { switch (balloon[i].getKind()) { case BALLOON_1: mMenaceLevel += 1; break; case BALLOON_2: mMenaceLevel += 2; break; case BALLOON_3: mMenaceLevel += 4; break; case BALLOON_4: mMenaceLevel += 8; break; default: mMenaceLevel += 0; break; } } } //Obtiene el valor de la variable Uint8 GameDirector::getMenaceLevel() { return mMenaceLevel; } //Gestiona el nivel de amenaza void GameDirector::checkMenaceLevel() { //Aumenta el nivel de amenaza en función de la puntuación mMenaceLevelThreshold = 7 + (4 * (mScore / 10000)); //Si el nivel de amenza es inferior al umbral if (mMenaceLevel < mMenaceLevelThreshold) { Uint8 index = 0; //Obtiene el centro del jugador en el eje X int x = player.getPosX() + (player.getWidth() / 2); //Crea un globo sobre el jugador en dirección hacia el centro if (x < (PLAY_AREA_WIDTH / 2)) { index = createNewBalloon(0, PLAY_AREA_TOP + BLOCK - 37, BALLOON_4, BALLON_VELX_POSITIVE, 400); } else { index = createNewBalloon(0, PLAY_AREA_TOP + BLOCK - 37, BALLOON_4, BALLON_VELX_NEGATIVE, 400); } balloon[index].allignTo(x); //Recalcula el nivel de amenaza con el nuevo globo calculateMenaceLevel(); } } //Gestiona la entrada de teclado y mando durante el juego void GameDirector::checkGameInput() { //Obtiene el estado de las teclas pulsadas del teclado const Uint8 *keystates = SDL_GetKeyboardState(NULL); //Si está pulsada la tecla izquierda o el mando hacia la izquierda if ((keystates[SDL_SCANCODE_LEFT] != 0) || (SDL_JoystickGetAxis(gGameController, 0) < -JOYSTICK_DEAD_ZONE)) { player.checkInput(INPUT_LEFT); } //Si está pulsada la tecla derecha o el mando hacia la derecha else if ((keystates[SDL_SCANCODE_RIGHT] != 0) || (SDL_JoystickGetAxis(gGameController, 0) > JOYSTICK_DEAD_ZONE)) { player.checkInput(INPUT_RIGHT); } //Ninguna de las dos direcciones pulsadas else { player.checkInput(NO_INPUT); } //Comprobamos la tecla o el botón de disparo central if ((SDL_JoystickGetButton(gGameController, BUTTON_X)) || (keystates[SDL_SCANCODE_W] != 0)) { if (player.canFire()) { createBullet(player.getPosX() + (player.getWidth() / 2) - 4, player.getPosY(), BULLET_UP); player.setFireCooldown(10); //Reproduce el sonido de disparo Mix_PlayChannel(-1, gBulletFX, 0); } } //Comprobamos la tecla o el botón de disparo izquierdo if ((SDL_JoystickGetButton(gGameController, BUTTON_Y)) || (keystates[SDL_SCANCODE_Q] != 0)) { if (player.canFire()) { createBullet(player.getPosX() + (player.getWidth() / 2) - 4, player.getPosY(), BULLET_LEFT); player.setFireCooldown(10); //Reproduce el sonido de disparo Mix_PlayChannel(-1, gBulletFX, 0); } } //Comprobamos la tecla o el botón de disparo derecho if ((SDL_JoystickGetButton(gGameController, BUTTON_A)) || (keystates[SDL_SCANCODE_E] != 0)) { if (player.canFire()) { createBullet(player.getPosX() + (player.getWidth() / 2) - 4, player.getPosY(), BULLET_RIGHT); player.setFireCooldown(10); //Reproduce el sonido de disparo Mix_PlayChannel(-1, gBulletFX, 0); } } //Comprobamos la tecla o el botón de pausa/menu if ((SDL_JoystickGetButton(gGameController, BUTTON_START)) || (keystates[SDL_SCANCODE_ESCAPE] != 0)) { setGameStatus(GAME_STATE_PAUSED); //Detiene la música Mix_HaltMusic(); } } //Gestiona la entrada de teclado y mando durante el menu void GameDirector::checkMenuInput(Menu *menu) { //Obtiene el estado de las teclas pulsadas del teclado const Uint8 *keystates = SDL_GetKeyboardState(NULL); //Si está pulsada la tecla izquierda o el mando hacia la izquierda if ((keystates[SDL_SCANCODE_UP] != 0) || (SDL_JoystickGetAxis(gGameController, 1) < -JOYSTICK_DEAD_ZONE)) { menu->checkInput(INPUT_UP); } //Si está pulsada la tecla derecha o el mando hacia la derecha else if ((keystates[SDL_SCANCODE_DOWN] != 0) || (SDL_JoystickGetAxis(gGameController, 1) > JOYSTICK_DEAD_ZONE)) { menu->checkInput(INPUT_DOWN); } //Comprobamos la tecla o el botón de menu/pausa else if (keystates[SDL_SCANCODE_RETURN] != 0 || (SDL_JoystickGetButton(gGameController, BUTTON_A))) { menu->checkInput(INPUT_FIRE); } #ifdef TEST if (SDL_JoystickGetButton(gGameController, 1)) { std::cout << "button1\n"; } if (SDL_JoystickGetButton(gGameController, 1)) { std::cout << "button1\n"; } if (SDL_JoystickGetButton(gGameController, 2)) { std::cout << "button2\n"; } if (SDL_JoystickGetButton(gGameController, 3)) { std::cout << "button3\n"; } if (SDL_JoystickGetButton(gGameController, 4)) { std::cout << "button4\n"; } if (SDL_JoystickGetButton(gGameController, 5)) { std::cout << "button5\n"; } if (SDL_JoystickGetButton(gGameController, 6)) { std::cout << "button6\n"; } if (SDL_JoystickGetButton(gGameController, 7)) { std::cout << "button7\n"; } if (SDL_JoystickGetButton(gGameController, 8)) { std::cout << "button8\n"; } if (SDL_JoystickGetButton(gGameController, 9)) { std::cout << "button9\n"; } if (SDL_JoystickGetButton(gGameController, 10)) { std::cout << "button10\n"; } if (SDL_JoystickGetButton(gGameController, 11)) { std::cout << "button11\n"; } if (SDL_JoystickGetButton(gGameController, 12)) { std::cout << "button12\n"; } if (SDL_JoystickGetButton(gGameController, 13)) { std::cout << "button13\n"; } if (SDL_JoystickGetButton(gGameController, 14)) { std::cout << "button14\n"; } if (SDL_JoystickGetButton(gGameController, 15)) { std::cout << "button15\n"; } #endif } //Obtiene el valor de la variable Uint8 GameDirector::getGameStatus() { return mGameStatus; } //Establece el valor de la variable void GameDirector::setGameStatus(Uint8 status) { mGameStatus = status; } //Pinta una transición en pantalla void GameDirector::renderTransition(Uint8 index) { switch (index) { case 0: SDL_Rect rect; rect.x = 0; rect.y = 0; rect.w = SCREEN_WIDTH; rect.h = 0; SDL_RenderPresent(gRenderer); SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 0); for (Uint16 i = 0; i < SCREEN_HEIGHT; i = i + 2) { rect.h = i; SDL_RenderFillRect(gRenderer, &rect); SDL_RenderPresent(gRenderer); } break; case 1: SDL_Rect rect1; rect1.x = 0; rect1.y = 0; rect1.w = SCREEN_WIDTH; rect1.h = 0; SDL_Rect rect2; rect2.x = 0; rect2.y = 0; rect2.w = SCREEN_WIDTH; rect2.h = 0; SDL_RenderPresent(gRenderer); SDL_SetRenderDrawBlendMode(gRenderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 64); for (Uint16 i = 0; i < (SCREEN_HEIGHT / 2); i = i + 4) { rect1.h = i; SDL_RenderFillRect(gRenderer, &rect1); rect2.h = i; rect2.y = SCREEN_HEIGHT - (i); SDL_RenderFillRect(gRenderer, &rect2); SDL_RenderPresent(gRenderer); } rect1.x = 0; rect1.y = 0; rect1.w = SCREEN_WIDTH; rect1.h = SCREEN_HEIGHT; for (Uint16 i = 0; i < (SCREEN_HEIGHT / 2); i = i + 4) { SDL_RenderFillRect(gRenderer, &rect1); SDL_RenderPresent(gRenderer); } break; default: break; } } //Pinta el texto GetReady en pantalla void GameDirector::renderGetReady() { if (mGetReady) { Sprite sprite; sprite.setTexture(gMiscTexture); sprite.setWidth(53); sprite.setHeight(10); sprite.setPosX((PLAY_AREA_WIDTH / 2) - (sprite.getWidth() / 2)); sprite.setPosY((PLAY_AREA_HEIGHT / 2) - (sprite.getHeight() / 2)); sprite.setSpriteClip(0, 0, sprite.getWidth(), sprite.getHeight()); for (Uint8 i = 0; i < 1; i++) { sprite.render(); SDL_RenderPresent(gRenderer); SDL_Delay(1500); } mGetReady = false; } } //Bucle para el titulo del juego void GameDirector::runTitle() { //Si la música no está sonando if (Mix_PlayingMusic() == 0) { //Reproduce la música Mix_PlayMusic(gTitleMusic, -1); } //Comprueba los eventos que hay en la cola while (SDL_PollEvent(&eventHandler) != 0) { //Evento de salida de la aplicación if (eventHandler.type == SDL_QUIT) { setGameStatus(GAME_STATE_QUIT); } } //Limpia la pantalla SDL_SetRenderDrawColor(gRenderer, 0x00, 0x00, 0x00, 0xFF); SDL_RenderClear(gRenderer); //Dibuja los objetos titleBackground.render(); menuTitle.render(whiteText); //Actualiza la pantalla SDL_RenderPresent(gRenderer); //Comprueba las entradas para el menu checkMenuInput(&menuTitle); //Comprueba si se ha seleccionado algún item del menú switch (menuTitle.getItemSelected()) { case 0: setGameStatus(GAME_STATE_PLAYING); menuTitle.resetMenu(); renderTransition(1); Mix_HaltMusic(); SDL_Delay(1200); break; case 1: setGameStatus(GAME_STATE_QUIT); menuTitle.resetMenu(); renderTransition(1); Mix_HaltMusic(); break; default: break; } } //Bucle para el juego void GameDirector::runGame() { //Si la música no está sonando if (Mix_PlayingMusic() == 0) { //Reproduce la música Mix_PlayMusic(gPlayingMusic, -1); } //Lógica del juego //Comprueba que la diferencia de ticks sea mayor a la velocidad del juego if (SDL_GetTicks() - mOldTicks > mGameSpeed) { //Actualiza el contador de ticks mOldTicks = SDL_GetTicks(); //Comprueba el teclado/mando checkGameInput(); //Comprueba los eventos que hay en la cola while (SDL_PollEvent(&eventHandler) != 0) { //Evento de salida de la aplicación if (eventHandler.type == SDL_QUIT) { setGameStatus(GAME_STATE_QUIT); } //Tecla T pulsada if (eventHandler.key.keysym.sym == SDLK_t) { //startAllBalloons(); popBalloon(0); break; } #ifdef TEST //W key pressed if (eventHandler.key.keysym.sym == SDLK_w) { bulletTest.setPosY(bulletTest.getPosY() - 1); bulletTest.testMove(); break; } //S key pressed if (eventHandler.key.keysym.sym == SDLK_s) { bulletTest.setPosY(bulletTest.getPosY() + 1); bulletTest.testMove(); break; } //A key pressed if (eventHandler.key.keysym.sym == SDLK_a) { bulletTest.setPosX(bulletTest.getPosX() - 1); bulletTest.testMove(); break; } //D key pressed if (eventHandler.key.keysym.sym == SDLK_d) { bulletTest.setPosX(bulletTest.getPosX() + 1); bulletTest.testMove(); break; } #endif } //Actualiza el jugador player.update(); //Mueve los globos moveBalloons(); #ifdef TEST balloonTest.move(); #endif //Mueve las balas moveBullets(); //Procesa las colisiones entre globos y balas processBulletBallonCollision(); //Comprueba el nivel de amenaza checkMenaceLevel(); //Comprueba la colisión entre el jugador y los globos if (checkPlayerBallonCollision()) { //stopAllBalloons(); } } //Limpia la pantalla SDL_SetRenderDrawColor(gRenderer, 0x00, 0x00, 0x00, 0xFF); SDL_RenderClear(gRenderer); //Dibuja los objetos gameBackground.render(); renderBalloons(); #ifdef TEST balloonTest.render(); bulletTest.render(); if (checkCollision(balloonTest.getCollider(), bulletTest.getCollider())) { whiteText.write(0, 0, "X"); } #endif //whiteText.write(0, 0, std::to_string(mMenaceLevelThreshold)); //whiteText.write(0, BLOCK, std::to_string(player.getPosX() + player.getWidth())); renderBullets(); player.render(); renderScoreBoard(whiteText); renderGetReady(); //Actualiza la pantalla SDL_RenderPresent(gRenderer); } //Bucle para el menu de pausa del juego void GameDirector::runPausedGame() { //Comprueba los eventos que hay en la cola while (SDL_PollEvent(&eventHandler) != 0) { //Evento de salida de la aplicación if (eventHandler.type == SDL_QUIT) { setGameStatus(GAME_STATE_QUIT); } } //Dibuja los objetos gameBackground.render(); renderBalloons(); renderBullets(); player.render(); renderScoreBoard(whiteText); menuPause.render(whiteText); //Limpia la pantalla SDL_RenderPresent(gRenderer); //Comprueba las entradas para el menu checkMenuInput(&menuPause); //Comprueba si se ha seleccionado algún item del menú switch (menuPause.getItemSelected()) { case 0: setGameStatus(GAME_STATE_PLAYING); menuPause.resetMenu(); break; case 1: setGameStatus(GAME_STATE_TITLE); menuPause.resetMenu(); renderTransition(1); init(); break; default: break; } }