// ---------------------------------------------------------------- // From Game Programming in C++ by Sanjay Madhav // Copyright (C) 2017 Sanjay Madhav. All rights reserved. // // Released under the BSD License // See LICENSE in root directory for full details. // ---------------------------------------------------------------- #include "Game.h" #include "SDL/SDL_image.h" #include #include "Actor.h" #include "SpriteComponent.h" #include "Random.h" Game::Game() :mWindow(nullptr) ,mRenderer(nullptr) ,mIsRunning(true) ,mUpdatingActors(false) { } bool Game::Initialize() { if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) { SDL_Log("Unable to initialize SDL: %s", SDL_GetError()); return false; } mWindow = SDL_CreateWindow("Game Programming in C++ (Chapter 4)", 100, 100, 1024, 768, 0); if (!mWindow) { SDL_Log("Failed to create window: %s", SDL_GetError()); return false; } mRenderer = SDL_CreateRenderer(mWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!mRenderer) { SDL_Log("Failed to create renderer: %s", SDL_GetError()); return false; } if (IMG_Init(IMG_INIT_PNG) == 0) { SDL_Log("Unable to initialize SDL_image: %s", SDL_GetError()); return false; } Random::Init(); LoadData(); mTicksCount = SDL_GetTicks(); return true; } void Game::RunLoop() { while (mIsRunning) { ProcessInput(); UpdateGame(); GenerateOutput(); } } void Game::ProcessInput() { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: mIsRunning = false; break; // Process mouse button event case SDL_MOUSEBUTTONDOWN: if (event.button.button == SDL_BUTTON_LEFT && !mBoardState.IsTerminal()) { // Convert x into column int col = event.button.x - 64; if (col >= 0) { col /= 128; if (col <= 6) { bool playerMoved = TryPlayerMove(&mBoardState, col); if (playerMoved && !mBoardState.IsTerminal()) { CPUMove(&mBoardState); } } } } break; } } const Uint8* keyState = SDL_GetKeyboardState(NULL); if (keyState[SDL_SCANCODE_ESCAPE]) { mIsRunning = false; } mUpdatingActors = true; for (auto actor : mActors) { actor->ProcessInput(keyState); } mUpdatingActors = false; } void Game::UpdateGame() { // Compute delta time // Wait until 16ms has elapsed since last frame while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount + 16)) ; float deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f; if (deltaTime > 0.05f) { deltaTime = 0.05f; } mTicksCount = SDL_GetTicks(); // Update all actors mUpdatingActors = true; for (auto actor : mActors) { actor->Update(deltaTime); } mUpdatingActors = false; // Move any pending actors to mActors for (auto pending : mPendingActors) { mActors.emplace_back(pending); } mPendingActors.clear(); // Add any dead actors to a temp vector std::vector deadActors; for (auto actor : mActors) { if (actor->GetState() == Actor::EDead) { deadActors.emplace_back(actor); } } // Delete dead actors (which removes them from mActors) for (auto actor : deadActors) { delete actor; } } void Game::GenerateOutput() { SDL_SetRenderDrawColor(mRenderer, 255, 255, 255, 255); SDL_RenderClear(mRenderer); // Draw all sprite components for (auto sprite : mSprites) { sprite->Draw(mRenderer); } // For Exercise 4.2 // Draw background DrawTexture(GetTexture("Assets/Board.png"), Vector2(512.0f, 384.0f), Vector2(896.0f, 768.0f)); // Draw pieces for (int i = 0; i < 6; i++) { for (int j = 0; j < 7; j++) { Vector2 pos(j * 128.0f + 128.0f, i * 128.0f + 64.0f); if (mBoardState.mBoard[i][j] == BoardState::Yellow) { DrawTexture(GetTexture("Assets/YellowPiece.png"), pos, Vector2(128.0f, 128.0f)); } else if (mBoardState.mBoard[i][j] == BoardState::Red) { DrawTexture(GetTexture("Assets/RedPiece.png"), pos, Vector2(128.0f, 128.0f)); } } } SDL_RenderPresent(mRenderer); } void Game::LoadData() { } void Game::UnloadData() { // Delete actors // Because ~Actor calls RemoveActor, have to use a different style loop while (!mActors.empty()) { delete mActors.back(); } // Destroy textures for (auto i : mTextures) { SDL_DestroyTexture(i.second); } mTextures.clear(); } SDL_Texture* Game::GetTexture(const std::string& fileName) { SDL_Texture* tex = nullptr; // Is the texture already in the map? auto iter = mTextures.find(fileName); if (iter != mTextures.end()) { tex = iter->second; } else { // Load from file SDL_Surface* surf = IMG_Load(fileName.c_str()); if (!surf) { SDL_Log("Failed to load texture file %s", fileName.c_str()); return nullptr; } // Create texture from surface tex = SDL_CreateTextureFromSurface(mRenderer, surf); SDL_FreeSurface(surf); if (!tex) { SDL_Log("Failed to convert surface to texture for %s", fileName.c_str()); return nullptr; } mTextures.emplace(fileName.c_str(), tex); } return tex; } void Game::DrawTexture(SDL_Texture* texture, const Vector2& pos, const Vector2& size) { SDL_Rect r; // Scale the width/height by owner's scale r.w = static_cast(size.x); r.h = static_cast(size.y); // Center the rectangle around the position of the owner r.x = static_cast(pos.x) - r.w / 2; r.y = static_cast(pos.y) - r.h / 2; // Draw (have to convert angle from radians to degrees, and clockwise to counter) SDL_RenderCopy(mRenderer, texture, nullptr, &r); } void Game::Shutdown() { UnloadData(); IMG_Quit(); SDL_DestroyRenderer(mRenderer); SDL_DestroyWindow(mWindow); SDL_Quit(); } void Game::AddActor(Actor* actor) { // If we're updating actors, need to add to pending if (mUpdatingActors) { mPendingActors.emplace_back(actor); } else { mActors.emplace_back(actor); } } void Game::RemoveActor(Actor* actor) { // Is it in pending actors? auto iter = std::find(mPendingActors.begin(), mPendingActors.end(), actor); if (iter != mPendingActors.end()) { // Swap to end of vector and pop off (avoid erase copies) std::iter_swap(iter, mPendingActors.end() - 1); mPendingActors.pop_back(); } // Is it in actors? iter = std::find(mActors.begin(), mActors.end(), actor); if (iter != mActors.end()) { // Swap to end of vector and pop off (avoid erase copies) std::iter_swap(iter, mActors.end() - 1); mActors.pop_back(); } } void Game::AddSprite(SpriteComponent* sprite) { // Find the insertion point in the sorted vector // (The first element with a higher draw order than me) int myDrawOrder = sprite->GetDrawOrder(); auto iter = mSprites.begin(); for ( ; iter != mSprites.end(); ++iter) { if (myDrawOrder < (*iter)->GetDrawOrder()) { break; } } // Inserts element before position of iterator mSprites.insert(iter, sprite); } void Game::RemoveSprite(SpriteComponent* sprite) { // (We can't swap because it ruins ordering) auto iter = std::find(mSprites.begin(), mSprites.end(), sprite); mSprites.erase(iter); }