//======================================================================== // GameCode.cpp : Defines the Application layer for GameCode4 // // Part of the GameCode4 Application // // GameCode4 is the sample application that encapsulates much of the source code // discussed in "Game Coding Complete - 4th Edition" by Mike McShaffry and David // "Rez" Graham, published by Charles River Media. // ISBN-10: 1133776574 | ISBN-13: 978-1133776574 // // If this source code has found it's way to you, and you think it has helped you // in any way, do the authors a favor and buy a new copy of the book - there are // detailed explanations in it that compliment this code well. Buy a copy at Amazon.com // by clicking here: // http://www.amazon.com/gp/product/1133776574/ref=olp_product_details?ie=UTF8&me=&seller= // // There's a companion web site at http://www.mcshaffry.com/GameCode/ // // The source code is managed and maintained through Google Code: // http://code.google.com/p/gamecode4/ // // (c) Copyright 2012 Michael L. McShaffry and David Graham // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser GPL v3 // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See // http://www.gnu.org/licenses/lgpl-3.0.txt for more details. // // You should have received a copy of the GNU Lesser GPL v3 // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //======================================================================== #include "../Unix/GameCodeStd.h" #include "../Mainloop/Initialization.h" #include "../Actors/BaseScriptComponent.h" #include "../ResourceCache/ResCache.h" #include "../Graphics3D/OpenGLRenderer.h" #include "../ResourceCache/XmlResource.h" #include "../LUAScripting/ScriptExports.h" #include "../LUAScripting/ScriptProcess.h" #include "../LUAScripting/LuaStateManager.h" #include "../EventManager/EventManagerImpl.h" #include "../Debugging/Logger.h" #include "../Utilities/Dialogs.h" #include #include #include #include GameCodeApp *g_pApp = NULL; const char* SCRIPT_PREINIT_FILE = "Scripts/PreInit.lua"; const unsigned int SCREEN_REFRESH_RATE(1000/60); //======================================================================== // // GameCodeApp implementation // //======================================================================== GameCodeApp::GameCodeApp() { g_pApp = this; m_pGame = NULL; m_bIsRunning = false; m_bQuitRequested = false; m_bQuitting = false; } //=================================================================== // Win32 Specific Stuff // // InitInstance - this checks system resources, creates your window, and launches the game // // preprocessor macro setting in the C++ options of the project provides the base macro // C preprocessor string concatenation takes care of the rest. // // GameCodeApp::InitInstance - Chapter 5, page 136 // //=================================================================== bool GameCodeApp::InitInstance(int screenWidth, int screenHeight) { // Check for adequate machine resources. bool resourceCheck = false; while (!resourceCheck) { const size_t physicalRAM = 512 * MEGABYTE; const size_t virtualRAM = 1024 * MEGABYTE; const size_t diskSpace = 10 * MEGABYTE; if (!CheckStorage(diskSpace)) return false; const unsigned int minCpuSpeed = 1300; // 1.3Ghz unsigned int thisCPU = ReadCPUSpeed(); if (thisCPU < minCpuSpeed) { GCC_ERROR("GetCPUSpeed reports CPU is too slow for this game."); return false; } resourceCheck = true; } IResourceFile *zipFile = (m_bIsEditorRunning || m_Options.m_useDevelopmentDirectories) ? GCC_NEW DevelopmentResourceZipFile(L"Assets.zip", DevelopmentResourceZipFile::Editor) : GCC_NEW ResourceZipFile(L"Assets.zip"); m_ResCache = GCC_NEW ResCache(50, zipFile); if (!m_ResCache->Init()) { GCC_ERROR("Failed to initialize resource cache! Are your paths set up correctly?"); return false; } // extern std::shared_ptr CreateWAVResourceLoader(); // extern std::shared_ptr CreateOGGResourceLoader(); extern std::shared_ptr CreateDDSResourceLoader(); extern std::shared_ptr CreateJPGResourceLoader(); extern std::shared_ptr CreateXmlResourceLoader(); extern std::shared_ptr CreateGlbMeshResourceLoader(); extern std::shared_ptr CreateScriptResourceLoader(); // Note - register these in order from least specific to most specific! They get pushed onto a list. // RegisterLoader is discussed in Chapter 5, page 142 // m_ResCache->RegisterLoader(CreateWAVResourceLoader()); // m_ResCache->RegisterLoader(CreateOGGResourceLoader()); // m_ResCache->RegisterLoader(CreateDDSResourceLoader()); m_ResCache->RegisterLoader(CreateJPGResourceLoader()); m_ResCache->RegisterLoader(CreateXmlResourceLoader()); m_ResCache->RegisterLoader(CreateGlbMeshResourceLoader()); m_ResCache->RegisterLoader(CreateScriptResourceLoader()); if(!LoadStrings("English")) { GCC_ERROR("Failed to load strings"); return false; } // Rez up the Lua State manager now, and run the initial script - discussed in Chapter 5, page 144. if (!LuaStateManager::Create()) { GCC_ERROR("Failed to initialize Lua"); return false; } // Load the preinit file. This is within braces to create a scope and destroy the resource once it's loaded. We // don't need to do anything with it, we just need to load it. { Resource resource(SCRIPT_PREINIT_FILE); std::shared_ptr pResourceHandle = m_ResCache->GetHandle(&resource); // this actually loads the script from the zip file } // // Register function exported from C++ ScriptExports::Register(); ScriptProcess::RegisterScriptClass(); BaseScriptComponent::RegisterScriptFunctions(); // The event manager should be created next so that subsystems can hook in as desired. // Discussed in Chapter 5, page 144 m_pEventManager = GCC_NEW EventManager("GameCodeApp Event Mgr", true); if (!m_pEventManager) { GCC_ERROR("Failed to create EventManager."); return false; } SDL_InitFlags initSystems = SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMEPAD | SDL_INIT_EVENTS; if (!SDL_Init(initSystems)) { GCC_ERROR(SDL_GetError()); return false; } static int const OPENGL_MAJOR_VERSION = 4; static int const OPENGL_MINOR_VERSION = 6; if (!SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, OPENGL_MAJOR_VERSION)) { GCC_ERROR(SDL_GetError()); } if (!SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, OPENGL_MINOR_VERSION)) { GCC_ERROR(SDL_GetError()); } if (!SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)) { GCC_ERROR(SDL_GetError()); } if (!SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE)) { GCC_ERROR(SDL_GetError()); } if (!SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG)) { GCC_ERROR(SDL_GetError()); } m_Window = SDL_CreateWindow( VGetGameTitle().c_str(), screenWidth, screenHeight, /*settings.window_flags |*/ SDL_WINDOW_OPENGL ); if (!m_Window) { GCC_ERROR(SDL_GetError()); return false; } // TODO(munnellg) - Set an icon // SDL_SetWindowIcon(m_Window, VGetIcon()); m_GlContext = SDL_GL_CreateContext(m_Window); if (!m_GlContext) { GCC_ERROR(SDL_GetError()); return false; } GLuint err = glewInit(); if (err != GLEW_OK) { GCC_ERROR((char*) glewGetString(err)); return false; } ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); // Setup scaling float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); ImGuiStyle& style = ImGui::GetStyle(); style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) style.FontScaleDpi = main_scale; // Set initial font scale. (in docking branch: using io.ConfigDpiScaleFonts=true automatically overrides this for every window depending on the current monitor) // Setup Platform/Renderer backends ImGui_ImplSDL3_InitForOpenGL(m_Window, m_GlContext); ImGui_ImplOpenGL3_Init(nullptr); // TODO(munnellg) - implement this // initialize the directory location you can store save game files // _tcscpy_s(m_saveGameDirectory, GetSaveGameDirectory(GetHwnd(), VGetGameAppDirectory())); if (GetRendererImpl() == Renderer_OpenGL46) { m_Renderer = std::shared_ptr(GCC_NEW OpenGLRenderer46()); } else { GCC_FATAL("Unknown renderer"); } glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); m_Renderer->VSetBackgroundColor(255, 20, 20, 200); m_Renderer->VOnRestore(); // You usually must have an HWND to initialize your game views... // VCreateGameAndView - Chapter 5, page 145 m_pGame = VCreateGameAndView(); if (!m_pGame) return false; // now that all the major systems are initialized, preload resources // Preload calls are discussed in Chapter 5, page 148 m_ResCache->Preload("*.ogg", NULL); m_ResCache->Preload("*.dds", NULL); m_ResCache->Preload("*.jpg", NULL); m_screenSize = Point(screenWidth, screenHeight); m_bIsRunning = true; return true; } bool GameCodeApp::VLoadGame(void) { // Read the game options and see what the current game // needs to be - all of the game graphics are initialized by now, too... return m_pGame->VLoadGame(m_Options.m_Level.c_str()); } int GameCodeApp::OnSysCommand(SDL_Event const &e) { switch (e.type) { case SDL_EVENT_QUIT: { // The quit dialog confirmation would appear once for // every SC_CLOSE we get - which happens multiple times // if modal dialogs are up. This now uses the g_QuitNoPrompt // flag to only prompt when receiving a SC_CLOSE that isn't // generated by us (identified by g_QuitNoPrompt). // ET - 05/21/01 - Bug #1916 - Begin // We were receiving multiple close dialogs // when closing again ALT-F4 while the close // confirmation dialog was up. // Eat if already servicing a close if (m_bQuitRequested) return true; // Wait for the application to be restored // before going any further with the new // screen. Flash until the person selects // that they want to restore the game, then // reinit the display if fullscreen. // The reinit is necessary otherwise the game // will switch to windowed mode. // Quit requested m_bQuitRequested = true; // Prompt // TODO(munnellg): The book uses its own UI class for this. We should do the same // int result = MessageBox(m_Window, "Are you sure you want to quit?", "Quit?", MB_YESNO); // if (result == IDNO) // { // // Bail - quit aborted // // Reset quit requested flag // m_bQuitRequested = false; // return true; // } m_bQuitting = true; // Reset the quit after any other dialogs have popped up from this close m_bQuitRequested = false; } } return 0; } bool GameCodeApp::LoadStrings(std::string language) { std::string languageFile = "Strings/"; languageFile += language; languageFile += ".xml"; TiXmlElement* pRoot = XmlResourceLoader::LoadAndReturnRootXmlElement(languageFile.c_str()); if (!pRoot) { GCC_ERROR("Strings are missing."); return false; } // Loop through each child element and load the component for (TiXmlElement* pElem = pRoot->FirstChildElement(); pElem; pElem = pElem->NextSiblingElement()) { const char *pKey=pElem->Attribute("id"); const char *pText=pElem->Attribute("value"); const char *pHotkey=pElem->Attribute("hotkey"); if (pKey && pText) { m_textResource[std::string(pKey)] = std::string(pText); // TODO(munnellg) - maybe implement? Don't see what it's doing, but can look when SDL onboarded // if (pHotKey) // { // m_hotkeys[std::string(wideKey)] = MapCharToKeycode(*pHotkey); // } } } return true; } //---------------------------------------------------------- // GameCodeApp::GetString - Chapter 5, page 144 // // creates a string from a string resource ID in the string table // stored in a special DLL, LANG.DLL, so game text strings // can be language independant // std::string GameCodeApp::GetString(std::string sID) { auto localizedString = m_textResource.find(sID); if (localizedString == m_textResource.end()) { GCC_ASSERT(0 && "String not found!"); return ""; } return localizedString->second; } //---------------------------------------------------------- // Win32 Specific Message Handlers // // WndProc - the main message handler for the window class // // OnNcCreate - this is where you can set window data before it is created // OnMove - called whenever the window moves; used to update members of g_App // OnDeviceChange - called whenever you eject the CD-ROM. // OnDisplayChange - called whenever the user changes the desktop settings // OnPowerBroadcast - called whenever a power message forces a shutdown // OnActivate - called whenever windows on the desktop change focus. // // Note: pUserContext added to comply with DirectX 9c - June 2005 Update // int GameCodeApp::MsgProc(SDL_Event &e) { int result = ImGui_ImplSDL3_ProcessEvent(&e); ImGuiIO& io = ImGui::GetIO(); switch (e.type) { case SDL_EVENT_QUIT: // case SDL_APP_TERMINATING: // case SDL_APP_LOWMEMORY: // case SDL_APP_WILLENTERBACKGROUND: // case SDL_APP_DIDENTERBACKGROUND: // case SDL_APP_WILLENTERFOREGROUND: // case SDL_APP_DIDENTERFOREGROUND: // case SDL_DISPLAYEVENT: // case SDL_WINDOWEVENT: // case SDL_SYSWMEVENT: result = g_pApp->OnSysCommand(e); break; case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: if (io.WantCaptureKeyboard) { break; } case SDL_EVENT_MOUSE_MOTION: case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: if (io.WantCaptureMouse) { break; } case SDL_EVENT_JOYSTICK_BUTTON_DOWN: case SDL_EVENT_JOYSTICK_BUTTON_UP: case SDL_EVENT_JOYSTICK_AXIS_MOTION: case SDL_EVENT_JOYSTICK_HAT_MOTION: { // // See Chapter 10, page 278 for more explanation of this code. // if (g_pApp->m_pGame) { BaseGameLogic *pGame = g_pApp->m_pGame; // Note the reverse order! User input is grabbed first from the view that is on top, // which is the last one in the list. for(GameViewList::reverse_iterator i=pGame->m_gameViews.rbegin(); i!=pGame->m_gameViews.rend(); ++i) { if ( (*i)->VOnMsgProc( e ) ) { result = true; break; // WARNING! This breaks out of the for loop. } } } break; } } return 0; } GameCodeApp::Renderer GameCodeApp::GetRendererImpl() { return Renderer_OpenGL46; } void GameCodeApp::RunMainLoop() { Uint64 lastUpdate = SDL_GetTicks(); Uint64 lastDraw = SDL_GetTicks(); while (!m_bQuitting) { // process any inputs PumpSDLEvents(); OnUpdateGame((float) SDL_GetTicks(), ((float) (SDL_GetTicks() - lastUpdate)) / 1000.0f); lastUpdate = SDL_GetTicks(); // render the frame OnOpenGLFrameRender((float) SDL_GetTicks(), ((float) (SDL_GetTicks() - lastDraw)) / 1000.0f); lastDraw = SDL_GetTicks(); } } void GameCodeApp::Shutdown() { // TODO(munnellg): release assets and shut down gracefully } void GameCodeApp::PumpSDLEvents() { SDL_Event e; while (SDL_PollEvent(&e)) { MsgProc(e); } } //-------------------------------------------------------------------------------------- // This callback function will be called once at the beginning of every frame. This is the // best location for your application to handle updates to the scene, but is not // intended to contain actual rendering calls, which should instead be placed in the // OnFrameRender callback. // // See Game Coding Complete - 4th Edition - Chapter X, page Y //-------------------------------------------------------------------------------------- void GameCodeApp::OnUpdateGame(double fTime, float fElapsedTime) { // if (g_pApp->HasModalDialog()) // { // // don't update the game if a modal dialog is up. // return; // } // if (g_pApp->m_bQuitting) // { // PostMessage(g_pApp->GetHwnd(), WM_CLOSE, 0, 0); // } if (g_pApp->m_pGame) { IEventManager::Get()->VUpdate(20); // allow event queue to process for up to 20 ms // if (g_pApp->m_pBaseSocketManager) // g_pApp->m_pBaseSocketManager->DoSelect(0); // pause 0 microseconds g_pApp->m_pGame->VOnUpdate(float(fTime), fElapsedTime); } } void GameCodeApp::OnOpenGLFrameRender(double fTime, float fElapsedTime) { static uint64_t lastDraw = 0; uint64_t currTick = SDL_GetTicks(); if (currTick == lastDraw) return; // if (currTick - lastDraw > SCREEN_REFRESH_RATE) // { g_pApp->m_Renderer->VPreRender(); BaseGameLogic *pGame = g_pApp->m_pGame; for (GameViewList::iterator i = pGame->m_gameViews.begin(), end = pGame->m_gameViews.end(); i!=end; ++i) { (*i)->VOnRender(fTime, fElapsedTime); } g_pApp->m_pGame->VRenderDiagnostics(); g_pApp->m_Renderer->VPostRender(); // record the last successful paint lastDraw = currTick; // } }