GameCode.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. //========================================================================
  2. // GameCode.cpp : Defines the Application layer for GameCode4
  3. //
  4. // Part of the GameCode4 Application
  5. //
  6. // GameCode4 is the sample application that encapsulates much of the source code
  7. // discussed in "Game Coding Complete - 4th Edition" by Mike McShaffry and David
  8. // "Rez" Graham, published by Charles River Media.
  9. // ISBN-10: 1133776574 | ISBN-13: 978-1133776574
  10. //
  11. // If this source code has found it's way to you, and you think it has helped you
  12. // in any way, do the authors a favor and buy a new copy of the book - there are
  13. // detailed explanations in it that compliment this code well. Buy a copy at Amazon.com
  14. // by clicking here:
  15. // http://www.amazon.com/gp/product/1133776574/ref=olp_product_details?ie=UTF8&me=&seller=
  16. //
  17. // There's a companion web site at http://www.mcshaffry.com/GameCode/
  18. //
  19. // The source code is managed and maintained through Google Code:
  20. // http://code.google.com/p/gamecode4/
  21. //
  22. // (c) Copyright 2012 Michael L. McShaffry and David Graham
  23. //
  24. // This program is free software; you can redistribute it and/or
  25. // modify it under the terms of the GNU Lesser GPL v3
  26. // as published by the Free Software Foundation.
  27. //
  28. // This program is distributed in the hope that it will be useful,
  29. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
  31. // http://www.gnu.org/licenses/lgpl-3.0.txt for more details.
  32. //
  33. // You should have received a copy of the GNU Lesser GPL v3
  34. // along with this program; if not, write to the Free Software
  35. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  36. //
  37. //========================================================================
  38. #include "../Unix/GameCodeStd.h"
  39. #include "../Mainloop/Initialization.h"
  40. #include "../Actors/BaseScriptComponent.h"
  41. #include "../ResourceCache/ResCache.h"
  42. #include "../Graphics3D/OpenGLRenderer.h"
  43. #include "../ResourceCache/XmlResource.h"
  44. #include "../LUAScripting/ScriptExports.h"
  45. #include "../LUAScripting/ScriptProcess.h"
  46. #include "../LUAScripting/LuaStateManager.h"
  47. #include "../EventManager/EventManagerImpl.h"
  48. #include "../Debugging/Logger.h"
  49. #include "../Utilities/Dialogs.h"
  50. #include <imgui.h>
  51. #include <imgui_impl_sdl3.h>
  52. #include <imgui_impl_opengl3.h>
  53. #include <GL/glew.h>
  54. GameCodeApp *g_pApp = NULL;
  55. const char* SCRIPT_PREINIT_FILE = "Scripts/PreInit.lua";
  56. const unsigned int SCREEN_REFRESH_RATE(1000/60);
  57. //========================================================================
  58. //
  59. // GameCodeApp implementation
  60. //
  61. //========================================================================
  62. GameCodeApp::GameCodeApp()
  63. {
  64. g_pApp = this;
  65. m_pGame = NULL;
  66. m_bIsRunning = false;
  67. m_bQuitRequested = false;
  68. m_bQuitting = false;
  69. }
  70. //===================================================================
  71. // Win32 Specific Stuff
  72. //
  73. // InitInstance - this checks system resources, creates your window, and launches the game
  74. //
  75. // preprocessor macro setting in the C++ options of the project provides the base macro
  76. // C preprocessor string concatenation takes care of the rest.
  77. //
  78. // GameCodeApp::InitInstance - Chapter 5, page 136
  79. //
  80. //===================================================================
  81. bool GameCodeApp::InitInstance(int screenWidth, int screenHeight)
  82. {
  83. // Check for adequate machine resources.
  84. bool resourceCheck = false;
  85. while (!resourceCheck)
  86. {
  87. const size_t physicalRAM = 512 * MEGABYTE;
  88. const size_t virtualRAM = 1024 * MEGABYTE;
  89. const size_t diskSpace = 10 * MEGABYTE;
  90. if (!CheckStorage(diskSpace))
  91. return false;
  92. const unsigned int minCpuSpeed = 1300; // 1.3Ghz
  93. unsigned int thisCPU = ReadCPUSpeed();
  94. if (thisCPU < minCpuSpeed)
  95. {
  96. GCC_ERROR("GetCPUSpeed reports CPU is too slow for this game.");
  97. return false;
  98. }
  99. resourceCheck = true;
  100. }
  101. IResourceFile *zipFile = (m_bIsEditorRunning || m_Options.m_useDevelopmentDirectories) ?
  102. GCC_NEW DevelopmentResourceZipFile(L"Assets.zip", DevelopmentResourceZipFile::Editor) :
  103. GCC_NEW ResourceZipFile(L"Assets.zip");
  104. m_ResCache = GCC_NEW ResCache(50, zipFile);
  105. if (!m_ResCache->Init())
  106. {
  107. GCC_ERROR("Failed to initialize resource cache! Are your paths set up correctly?");
  108. return false;
  109. }
  110. // extern std::shared_ptr<IResourceLoader> CreateWAVResourceLoader();
  111. // extern std::shared_ptr<IResourceLoader> CreateOGGResourceLoader();
  112. extern std::shared_ptr<IResourceLoader> CreateDDSResourceLoader();
  113. extern std::shared_ptr<IResourceLoader> CreateJPGResourceLoader();
  114. extern std::shared_ptr<IResourceLoader> CreateXmlResourceLoader();
  115. extern std::shared_ptr<IResourceLoader> CreateGlbMeshResourceLoader();
  116. extern std::shared_ptr<IResourceLoader> CreateScriptResourceLoader();
  117. // Note - register these in order from least specific to most specific! They get pushed onto a list.
  118. // RegisterLoader is discussed in Chapter 5, page 142
  119. // m_ResCache->RegisterLoader(CreateWAVResourceLoader());
  120. // m_ResCache->RegisterLoader(CreateOGGResourceLoader());
  121. // m_ResCache->RegisterLoader(CreateDDSResourceLoader());
  122. m_ResCache->RegisterLoader(CreateJPGResourceLoader());
  123. m_ResCache->RegisterLoader(CreateXmlResourceLoader());
  124. m_ResCache->RegisterLoader(CreateGlbMeshResourceLoader());
  125. m_ResCache->RegisterLoader(CreateScriptResourceLoader());
  126. if(!LoadStrings("English"))
  127. {
  128. GCC_ERROR("Failed to load strings");
  129. return false;
  130. }
  131. // Rez up the Lua State manager now, and run the initial script - discussed in Chapter 5, page 144.
  132. if (!LuaStateManager::Create())
  133. {
  134. GCC_ERROR("Failed to initialize Lua");
  135. return false;
  136. }
  137. // Load the preinit file. This is within braces to create a scope and destroy the resource once it's loaded. We
  138. // don't need to do anything with it, we just need to load it.
  139. {
  140. Resource resource(SCRIPT_PREINIT_FILE);
  141. std::shared_ptr<ResHandle> pResourceHandle = m_ResCache->GetHandle(&resource); // this actually loads the script from the zip file
  142. }
  143. // // Register function exported from C++
  144. ScriptExports::Register();
  145. ScriptProcess::RegisterScriptClass();
  146. BaseScriptComponent::RegisterScriptFunctions();
  147. // The event manager should be created next so that subsystems can hook in as desired.
  148. // Discussed in Chapter 5, page 144
  149. m_pEventManager = GCC_NEW EventManager("GameCodeApp Event Mgr", true);
  150. if (!m_pEventManager)
  151. {
  152. GCC_ERROR("Failed to create EventManager.");
  153. return false;
  154. }
  155. SDL_InitFlags initSystems = SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMEPAD | SDL_INIT_EVENTS;
  156. if (!SDL_Init(initSystems)) {
  157. GCC_ERROR(SDL_GetError());
  158. return false;
  159. }
  160. static int const OPENGL_MAJOR_VERSION = 4;
  161. static int const OPENGL_MINOR_VERSION = 6;
  162. if (!SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, OPENGL_MAJOR_VERSION)) {
  163. GCC_ERROR(SDL_GetError());
  164. }
  165. if (!SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, OPENGL_MINOR_VERSION)) {
  166. GCC_ERROR(SDL_GetError());
  167. }
  168. if (!SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)) {
  169. GCC_ERROR(SDL_GetError());
  170. }
  171. if (!SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE)) {
  172. GCC_ERROR(SDL_GetError());
  173. }
  174. if (!SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG)) {
  175. GCC_ERROR(SDL_GetError());
  176. }
  177. m_Window = SDL_CreateWindow(
  178. VGetGameTitle().c_str(),
  179. screenWidth,
  180. screenHeight,
  181. /*settings.window_flags |*/ SDL_WINDOW_OPENGL
  182. );
  183. if (!m_Window) {
  184. GCC_ERROR(SDL_GetError());
  185. return false;
  186. }
  187. // TODO(munnellg) - Set an icon
  188. // SDL_SetWindowIcon(m_Window, VGetIcon());
  189. m_GlContext = SDL_GL_CreateContext(m_Window);
  190. if (!m_GlContext) {
  191. GCC_ERROR(SDL_GetError());
  192. return false;
  193. }
  194. GLuint err = glewInit();
  195. if (err != GLEW_OK) {
  196. GCC_ERROR((char*) glewGetString(err));
  197. return false;
  198. }
  199. ImGui::CreateContext();
  200. ImGuiIO& io = ImGui::GetIO(); (void)io;
  201. io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
  202. // Setup Dear ImGui style
  203. ImGui::StyleColorsDark();
  204. //ImGui::StyleColorsLight();
  205. // Setup scaling
  206. float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
  207. ImGuiStyle& style = ImGui::GetStyle();
  208. 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)
  209. 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)
  210. // Setup Platform/Renderer backends
  211. ImGui_ImplSDL3_InitForOpenGL(m_Window, m_GlContext);
  212. ImGui_ImplOpenGL3_Init(nullptr);
  213. // TODO(munnellg) - implement this
  214. // initialize the directory location you can store save game files
  215. // _tcscpy_s(m_saveGameDirectory, GetSaveGameDirectory(GetHwnd(), VGetGameAppDirectory()));
  216. if (GetRendererImpl() == Renderer_OpenGL46)
  217. {
  218. m_Renderer = std::shared_ptr<IRenderer>(GCC_NEW OpenGLRenderer46());
  219. }
  220. else
  221. {
  222. GCC_FATAL("Unknown renderer");
  223. }
  224. glEnable(GL_DEPTH_TEST);
  225. glDepthFunc(GL_LESS);
  226. glEnable(GL_BLEND);
  227. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  228. m_Renderer->VSetBackgroundColor(255, 20, 20, 200);
  229. m_Renderer->VOnRestore();
  230. // You usually must have an HWND to initialize your game views...
  231. // VCreateGameAndView - Chapter 5, page 145
  232. m_pGame = VCreateGameAndView();
  233. if (!m_pGame)
  234. return false;
  235. // now that all the major systems are initialized, preload resources
  236. // Preload calls are discussed in Chapter 5, page 148
  237. m_ResCache->Preload("*.ogg", NULL);
  238. m_ResCache->Preload("*.dds", NULL);
  239. m_ResCache->Preload("*.jpg", NULL);
  240. m_screenSize = Point(screenWidth, screenHeight);
  241. m_bIsRunning = true;
  242. return true;
  243. }
  244. bool GameCodeApp::VLoadGame(void)
  245. {
  246. // Read the game options and see what the current game
  247. // needs to be - all of the game graphics are initialized by now, too...
  248. return m_pGame->VLoadGame(m_Options.m_Level.c_str());
  249. }
  250. int GameCodeApp::OnSysCommand(SDL_Event const &e)
  251. {
  252. switch (e.type)
  253. {
  254. case SDL_EVENT_QUIT:
  255. {
  256. // The quit dialog confirmation would appear once for
  257. // every SC_CLOSE we get - which happens multiple times
  258. // if modal dialogs are up. This now uses the g_QuitNoPrompt
  259. // flag to only prompt when receiving a SC_CLOSE that isn't
  260. // generated by us (identified by g_QuitNoPrompt).
  261. // ET - 05/21/01 - Bug #1916 - Begin
  262. // We were receiving multiple close dialogs
  263. // when closing again ALT-F4 while the close
  264. // confirmation dialog was up.
  265. // Eat if already servicing a close
  266. if (m_bQuitRequested)
  267. return true;
  268. // Wait for the application to be restored
  269. // before going any further with the new
  270. // screen. Flash until the person selects
  271. // that they want to restore the game, then
  272. // reinit the display if fullscreen.
  273. // The reinit is necessary otherwise the game
  274. // will switch to windowed mode.
  275. // Quit requested
  276. m_bQuitRequested = true;
  277. // Prompt
  278. // TODO(munnellg): The book uses its own UI class for this. We should do the same
  279. // int result = MessageBox(m_Window, "Are you sure you want to quit?", "Quit?", MB_YESNO);
  280. // if (result == IDNO)
  281. // {
  282. // // Bail - quit aborted
  283. // // Reset quit requested flag
  284. // m_bQuitRequested = false;
  285. // return true;
  286. // }
  287. m_bQuitting = true;
  288. // Reset the quit after any other dialogs have popped up from this close
  289. m_bQuitRequested = false;
  290. }
  291. }
  292. return 0;
  293. }
  294. bool GameCodeApp::LoadStrings(std::string language)
  295. {
  296. std::string languageFile = "Strings/";
  297. languageFile += language;
  298. languageFile += ".xml";
  299. TiXmlElement* pRoot = XmlResourceLoader::LoadAndReturnRootXmlElement(languageFile.c_str());
  300. if (!pRoot)
  301. {
  302. GCC_ERROR("Strings are missing.");
  303. return false;
  304. }
  305. // Loop through each child element and load the component
  306. for (TiXmlElement* pElem = pRoot->FirstChildElement(); pElem; pElem = pElem->NextSiblingElement())
  307. {
  308. const char *pKey=pElem->Attribute("id");
  309. const char *pText=pElem->Attribute("value");
  310. const char *pHotkey=pElem->Attribute("hotkey");
  311. if (pKey && pText)
  312. {
  313. m_textResource[std::string(pKey)] = std::string(pText);
  314. // TODO(munnellg) - maybe implement? Don't see what it's doing, but can look when SDL onboarded
  315. // if (pHotKey)
  316. // {
  317. // m_hotkeys[std::string(wideKey)] = MapCharToKeycode(*pHotkey);
  318. // }
  319. }
  320. }
  321. return true;
  322. }
  323. //----------------------------------------------------------
  324. // GameCodeApp::GetString - Chapter 5, page 144
  325. //
  326. // creates a string from a string resource ID in the string table
  327. // stored in a special DLL, LANG.DLL, so game text strings
  328. // can be language independant
  329. //
  330. std::string GameCodeApp::GetString(std::string sID)
  331. {
  332. auto localizedString = m_textResource.find(sID);
  333. if (localizedString == m_textResource.end())
  334. {
  335. GCC_ASSERT(0 && "String not found!");
  336. return "";
  337. }
  338. return localizedString->second;
  339. }
  340. //----------------------------------------------------------
  341. // Win32 Specific Message Handlers
  342. //
  343. // WndProc - the main message handler for the window class
  344. //
  345. // OnNcCreate - this is where you can set window data before it is created
  346. // OnMove - called whenever the window moves; used to update members of g_App
  347. // OnDeviceChange - called whenever you eject the CD-ROM.
  348. // OnDisplayChange - called whenever the user changes the desktop settings
  349. // OnPowerBroadcast - called whenever a power message forces a shutdown
  350. // OnActivate - called whenever windows on the desktop change focus.
  351. //
  352. // Note: pUserContext added to comply with DirectX 9c - June 2005 Update
  353. //
  354. int GameCodeApp::MsgProc(SDL_Event &e)
  355. {
  356. int result = ImGui_ImplSDL3_ProcessEvent(&e);
  357. ImGuiIO& io = ImGui::GetIO();
  358. switch (e.type)
  359. {
  360. case SDL_EVENT_QUIT:
  361. // case SDL_APP_TERMINATING:
  362. // case SDL_APP_LOWMEMORY:
  363. // case SDL_APP_WILLENTERBACKGROUND:
  364. // case SDL_APP_DIDENTERBACKGROUND:
  365. // case SDL_APP_WILLENTERFOREGROUND:
  366. // case SDL_APP_DIDENTERFOREGROUND:
  367. // case SDL_DISPLAYEVENT:
  368. // case SDL_WINDOWEVENT:
  369. // case SDL_SYSWMEVENT:
  370. result = g_pApp->OnSysCommand(e);
  371. break;
  372. case SDL_EVENT_KEY_DOWN:
  373. case SDL_EVENT_KEY_UP:
  374. if (io.WantCaptureKeyboard)
  375. {
  376. break;
  377. }
  378. case SDL_EVENT_MOUSE_MOTION:
  379. case SDL_EVENT_MOUSE_BUTTON_DOWN:
  380. case SDL_EVENT_MOUSE_BUTTON_UP:
  381. if (io.WantCaptureMouse)
  382. {
  383. break;
  384. }
  385. case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
  386. case SDL_EVENT_JOYSTICK_BUTTON_UP:
  387. case SDL_EVENT_JOYSTICK_AXIS_MOTION:
  388. case SDL_EVENT_JOYSTICK_HAT_MOTION:
  389. {
  390. //
  391. // See Chapter 10, page 278 for more explanation of this code.
  392. //
  393. if (g_pApp->m_pGame)
  394. {
  395. BaseGameLogic *pGame = g_pApp->m_pGame;
  396. // Note the reverse order! User input is grabbed first from the view that is on top,
  397. // which is the last one in the list.
  398. for(GameViewList::reverse_iterator i=pGame->m_gameViews.rbegin(); i!=pGame->m_gameViews.rend(); ++i)
  399. {
  400. if ( (*i)->VOnMsgProc( e ) )
  401. {
  402. result = true;
  403. break; // WARNING! This breaks out of the for loop.
  404. }
  405. }
  406. }
  407. break;
  408. }
  409. }
  410. return 0;
  411. }
  412. GameCodeApp::Renderer GameCodeApp::GetRendererImpl()
  413. {
  414. return Renderer_OpenGL46;
  415. }
  416. void GameCodeApp::RunMainLoop()
  417. {
  418. Uint64 lastUpdate = SDL_GetTicks();
  419. Uint64 lastDraw = SDL_GetTicks();
  420. while (!m_bQuitting)
  421. {
  422. // process any inputs
  423. PumpSDLEvents();
  424. OnUpdateGame((float) SDL_GetTicks(), ((float) (SDL_GetTicks() - lastUpdate)) / 1000.0f);
  425. lastUpdate = SDL_GetTicks();
  426. // render the frame
  427. OnOpenGLFrameRender((float) SDL_GetTicks(), ((float) (SDL_GetTicks() - lastDraw)) / 1000.0f);
  428. lastDraw = SDL_GetTicks();
  429. }
  430. }
  431. void GameCodeApp::Shutdown()
  432. {
  433. // TODO(munnellg): release assets and shut down gracefully
  434. }
  435. void GameCodeApp::PumpSDLEvents()
  436. {
  437. SDL_Event e;
  438. while (SDL_PollEvent(&e))
  439. {
  440. MsgProc(e);
  441. }
  442. }
  443. //--------------------------------------------------------------------------------------
  444. // This callback function will be called once at the beginning of every frame. This is the
  445. // best location for your application to handle updates to the scene, but is not
  446. // intended to contain actual rendering calls, which should instead be placed in the
  447. // OnFrameRender callback.
  448. //
  449. // See Game Coding Complete - 4th Edition - Chapter X, page Y
  450. //--------------------------------------------------------------------------------------
  451. void GameCodeApp::OnUpdateGame(double fTime, float fElapsedTime)
  452. {
  453. // if (g_pApp->HasModalDialog())
  454. // {
  455. // // don't update the game if a modal dialog is up.
  456. // return;
  457. // }
  458. // if (g_pApp->m_bQuitting)
  459. // {
  460. // PostMessage(g_pApp->GetHwnd(), WM_CLOSE, 0, 0);
  461. // }
  462. if (g_pApp->m_pGame)
  463. {
  464. IEventManager::Get()->VUpdate(20); // allow event queue to process for up to 20 ms
  465. // if (g_pApp->m_pBaseSocketManager)
  466. // g_pApp->m_pBaseSocketManager->DoSelect(0); // pause 0 microseconds
  467. g_pApp->m_pGame->VOnUpdate(float(fTime), fElapsedTime);
  468. }
  469. }
  470. void GameCodeApp::OnOpenGLFrameRender(double fTime, float fElapsedTime)
  471. {
  472. static uint64_t lastDraw = 0;
  473. uint64_t currTick = SDL_GetTicks();
  474. if (currTick == lastDraw)
  475. return;
  476. // if (currTick - lastDraw > SCREEN_REFRESH_RATE)
  477. // {
  478. g_pApp->m_Renderer->VPreRender();
  479. BaseGameLogic *pGame = g_pApp->m_pGame;
  480. for (GameViewList::iterator i = pGame->m_gameViews.begin(),
  481. end = pGame->m_gameViews.end(); i!=end; ++i)
  482. {
  483. (*i)->VOnRender(fTime, fElapsedTime);
  484. }
  485. g_pApp->m_pGame->VRenderDiagnostics();
  486. g_pApp->m_Renderer->VPostRender();
  487. // record the last successful paint
  488. lastDraw = currTick;
  489. // }
  490. }