绚丽的大型游戏背后,都存在着一个主循环,能根据游戏的进度控制游戏的状态。主循环管理游戏的正常运行,管理着游戏运行失败该如何处理,对某些不能运行的情况给予杀死等等。另外,在MMORPG中,主循环同样也是AI的管理者,时刻根据游戏的运行状态,调度出不同的AI运行,增强了游戏的可玩性和趣味性。同时主循环对于关卡控制,游戏画面渲染,声音管理,资源管理也有所掌控。总之,主循环对于大型游戏是不可或缺的一部分~~
以下是游戏主循环的各个组成部分:
I. 游戏初始化的相关参数:
#pragma once #include <iostream> using namespace std; //======================================================================== // Initialization.h : Defines utility functions for game initialization //======================================================================== typedef unsigned int DWORD; typedef long long DWORDLONG; typedef unsigned char TCHAR; extern bool CheckStorage(const DWORDLONG diskSpaceNeeded); extern DWORD ReadCPUSpeed(); extern bool CheckMemory(const DWORDLONG physicalRAMNeeded, const DWORDLONG virtualRAMNeeded); extern bool IsOnlyInstance(const TCHAR* gameTitle); extern const TCHAR *GetSaveGameDirectory(HWND hWnd, const TCHAR *gameAppDirectory); extern bool CheckForJoystick(HWND hWnd); struct GameOptions { // Level option 与关卡相关的选项 std::string m_Level; // Rendering options 与渲染相关的选项 std::string m_Renderer; bool m_runFullSpeed; Point m_ScreenSize; // Sound options 与音频相关 float m_soundEffectsVolume; float m_musicVolume; // Multiplayer options 多人同时在线选项 int m_expectedPlayers; int m_listenPort; std::string m_gameHost; int m_numAIs; int m_maxAIs; int m_maxPlayers; // resource cache options 资源缓存 bool m_useDevelopmentDirectories; // TiXmlElement - look at this to find other options added by the developer TiXmlDocument *m_pDoc; GameOptions(); ~GameOptions() { SAFE_DELETE(m_pDoc); } void Init(const char* xmlFilePath, LPWSTR lpCmdLine); };
//======================================================================== // Initialization.cpp : Defines utility functions for game initialization //======================================================================== #include "GameCodeStd.h" #include <shlobj.h> #include <direct.h> #include "Initialization.h" // // CheckStorage 对于端游而言的安装. // bool CheckStorage(const DWORDLONG diskSpaceNeeded) { // Check for enough free disk space on the current disk. int const drive = _getdrive(); struct _diskfree_t diskfree; _getdiskfree(drive, &diskfree); unsigned __int64 const neededClusters = diskSpaceNeeded /(diskfree.sectors_per_cluster*diskfree.bytes_per_sector); if (diskfree.avail_clusters < neededClusters) { // if you get here you donít have enough disk space! GCC_ERROR("CheckStorage Failure: Not enough physical storage."); return false; } return true; } // // CheckMemory // bool CheckMemory(const DWORDLONG physicalRAMNeeded, const DWORDLONG virtualRAMNeeded) { MEMORYSTATUSEX status; GlobalMemoryStatusEx(&status); if (status.ullTotalPhys < physicalRAMNeeded) { // you donít have enough physical memory. Tell the player to go get a real // computer and give this one to his mother. GCC_ERROR("CheckMemory Failure: Not enough physical memory."); return false; } // Check for enough free memory. if (status.ullAvailVirtual < virtualRAMNeeded) { // you donít have enough virtual memory available. // Tell the player to shut down the copy of Visual Studio running in the // background, or whatever seems to be sucking the memory dry. GCC_ERROR("CheckMemory Failure: Not enough virtual memory."); return false; } char *buff = GCC_NEW char[(unsigned int)virtualRAMNeeded]; if (buff) delete[] buff; else { // even though there is enough memory, it isnít available in one // block, which can be critical for games that manage their own memory GCC_ERROR("CheckMemory Failure: Not enough contiguous available memory."); return false; } return true; } // // ReadCPUSpeed // DWORD ReadCPUSpeed() { DWORD BufSize = sizeof(DWORD); DWORD dwMHz = 0; DWORD type = REG_DWORD; HKEY hKey; // open the key where the proc speed is hidden: long lError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &hKey); if(lError == ERROR_SUCCESS) { // query the key: RegQueryValueEx(hKey, L"~MHz", NULL, &type, (LPBYTE) &dwMHz, &BufSize); } return dwMHz; } /*** DWORD GetFreeVRAM() { // NOTE: This method is deprecated, and unfortunately not really replaced with // anything useful..... DDSCAPS2 ddsCaps; ZeroMemory(&ddsCaps, sizeof(ddsCaps)); ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY; DWORD dwUsedVRAM = 0; DWORD dwTotal=0; DWORD dwFree=0; // lp_DD points to the IDirectDraw object HRESULT hr = g_pDisplay->GetDirectDraw()->GetAvailableVidMem(&ddsCaps, &dwTotal, &dwFree); // dwUsedVRAM holds the number of bytes of VRAM used dwUsedVRAM = dwTotal-dwFree; return dwUsedVRAM; return 0; } ****/ GameOptions::GameOptions() { // set all the options to decent default valu m_Level = ""; m_Renderer = "Direct3D 9"; m_runFullSpeed = false; m_soundEffectsVolume = 1.0f; m_musicVolume = 1.0f; m_expectedPlayers = 1; m_listenPort = -1; std::string m_gameHost = "MrMike-m1710"; m_numAIs = 1; m_maxAIs = 4; m_maxPlayers = 4; m_ScreenSize = Point(1024,768); m_useDevelopmentDirectories = false; m_pDoc = NULL; } void GameOptions::Init(const char* xmlFileName, const TCHAR* lpCmdLine) { // read the XML file // if needed, override the XML file with options passed in on the command line. m_pDoc = new TiXmlDocument(xmlFileName); if (m_pDoc && m_pDoc->LoadFile()) { TiXmlElement *pRoot = m_pDoc->RootElement(); if (!pRoot) return; // Loop through each child element and load the component TiXmlElement* pNode = NULL; pNode = pRoot->FirstChildElement("Graphics"); if (pNode) { std::string attribute; attribute = pNode->Attribute("renderer"); if (attribute != "Direct3D 9" && attribute != "Direct3D 11") { GCC_ASSERT(0 && "Bad Renderer setting in Graphics options."); } else { m_Renderer = attribute; } if (pNode->Attribute("width")) { m_ScreenSize.x = atoi(pNode->Attribute("width")); if (m_ScreenSize.x < 800) m_ScreenSize.x = 800; } if (pNode->Attribute("height")) { m_ScreenSize.y = atoi(pNode->Attribute("height")); if (m_ScreenSize.y < 600) m_ScreenSize.y = 600; } if (pNode->Attribute("runfullspeed")) { attribute = pNode->Attribute("runfullspeed"); m_runFullSpeed = (attribute == "yes") ? true : false; } } pNode = pRoot->FirstChildElement("Sound"); if (pNode) { m_musicVolume = atoi(pNode->Attribute("musicVolume")) / 100.0f; m_soundEffectsVolume = atoi(pNode->Attribute("sfxVolume")) / 100.0f; } pNode = pRoot->FirstChildElement("Multiplayer"); if (pNode) { m_expectedPlayers = atoi(pNode->Attribute("expectedPlayers")); m_numAIs = atoi(pNode->Attribute("numAIs")); m_maxAIs = atoi(pNode->Attribute("maxAIs")); m_maxPlayers = atoi(pNode->Attribute("maxPlayers")); m_listenPort = atoi(pNode->Attribute("listenPort")); m_gameHost = pNode->Attribute("gameHost"); } pNode = pRoot->FirstChildElement("ResCache"); if (pNode) { std::string attribute(pNode->Attribute("useDevelopmentDirectories")); m_useDevelopmentDirectories = ((attribute == "yes") ? (true) : (false)); } } } // // IsOnlyInstance // bool IsOnlyInstance(const TCHAR* gameTitle) { // Find the window. If active, set and return false // Only one game instance may have this mutex at a time... HANDLE handle = CreateMutex(NULL, TRUE, gameTitle); // Does anyone else think 'ERROR_SUCCESS' is a bit of an oxymoron? if (GetLastError() != ERROR_SUCCESS) { HWND hWnd = FindWindow(gameTitle, NULL); if (hWnd) { // An instance of your game is already running. ShowWindow(hWnd, SW_SHOWNORMAL); SetFocus(hWnd); SetForegroundWindow(hWnd); SetActiveWindow(hWnd); return false; } } return true; } // // GetSaveGameDirectory // const TCHAR *GetSaveGameDirectory(HWND hWnd, const TCHAR *gameAppDirectory) { HRESULT hr; static TCHAR m_SaveGameDirectory[MAX_PATH]; TCHAR userDataPath[MAX_PATH]; hr = SHGetSpecialFolderPath(hWnd, userDataPath, CSIDL_APPDATA, true); _tcscpy_s(m_SaveGameDirectory, userDataPath); _tcscat_s(m_SaveGameDirectory, _T("\\")); _tcscat_s(m_SaveGameDirectory, gameAppDirectory); // Does our directory exist? if (0xffffffff == GetFileAttributes(m_SaveGameDirectory)) { if (SHCreateDirectoryEx(hWnd, m_SaveGameDirectory, NULL) != ERROR_SUCCESS) return false; } _tcscat_s(m_SaveGameDirectory, _T("\\")); return m_SaveGameDirectory; } // // bool CheckForJoystick 手柄检测 // bool CheckForJoystick(HWND hWnd) { JOYINFO joyinfo; UINT wNumDevs; BOOL bDev1Attached, bDev2Attached; if((wNumDevs = joyGetNumDevs()) == 0) return false; bDev1Attached = joyGetPos(JOYSTICKID1,&joyinfo) != JOYERR_UNPLUGGED; bDev2Attached = joyGetPos(JOYSTICKID2,&joyinfo) != JOYERR_UNPLUGGED; if(bDev1Attached) joySetCapture(hWnd, JOYSTICKID1, 1000/30, true); if (bDev2Attached) joySetCapture(hWnd, JOYSTICKID2, 1000/30, true); return true; }以上就是主循环中游戏初始化相关的东西,包括声音,资源,游戏选项等。
II. 游戏过程 > GameProcess
#pragma once //======================================================================== // Process.h : defines common game events //======================================================================== #include <memory> #include <iostream> using namespace std::shared_ptr; using namespace std::weak_ptr; class Process; typedef shared_ptr<Process> StrongProcessPtr; typedef weak_ptr<Process> WeakProcessPtr; //-------------------------------------------------------------------- // Process class // // Processes are ended by one of three methods: Success, Failure, or Aborted. // - Success means the process completed successfully. If the process has a child, it will be attached to the process mgr. // - Failure means the process started but failed in some way. If the process has a child, it will be aborted. // - Aborted processes are processes that are canceled while not submitted to the process mgr. Depending on the circumstances, they may or may not have gotten an OnInit() call. // For example, a process can // spawn another process and call AttachToParent() on itself. If the new process fails, the child will get an Abort() call on it, even though its status is RUNNING. //-------------------------------------------------------------------- class Process { friend class ProcessManager; public: enum State { // Processes that are neither dead nor alive UNINITIALIZED = 0, // created but not running REMOVED, // removed from the process list but not destroyed; this can happen when a process that is already running is parented to another process // Living processes RUNNING, // initialized and running PAUSED, // initialized but paused // Dead processes SUCCEEDED, // completed successfully FAILED, // failed to complete ABORTED, // aborted; may not have started }; private: State m_state; // the current state of the process StrongProcessPtr m_pChild; // the child process, if any public: // construction Process(void); virtual ~Process(void); protected: // interface; these functions should be overridden by the subclass as needed virtual void VOnInit(void) { m_state = RUNNING; } // called during the first update; responsible for setting the initial state (typically RUNNING) virtual void VOnUpdate(unsigned long deltaMs) = 0; // called every frame virtual void VOnSuccess(void) { } // called if the process succeeds (see below) virtual void VOnFail(void) { } // called if the process fails (see below) virtual void VOnAbort(void) { } // called if the process is aborted (see below) public: // Functions for ending the process. inline void Succeed(void); inline void Fail(void); // pause inline void Pause(void); inline void UnPause(void); // accessors State GetState(void) const { return m_state; } bool IsAlive(void) const { return (m_state == RUNNING || m_state == PAUSED); } bool IsDead(void) const { return (m_state == SUCCEEDED || m_state == FAILED || m_state == ABORTED); } bool IsRemoved(void) const { return (m_state == REMOVED); } bool IsPaused(void) const { return m_state == PAUSED; } // child functions inline void AttachChild(StrongProcessPtr pChild); StrongProcessPtr RemoveChild(void); // releases ownership of the child StrongProcessPtr PeekChild(void) { return m_pChild; } // doesn't release ownership of the child private: void SetState(State newState) { m_state = newState; } }; //-------------------------------------------------------------------------------------------- // Inline function definitions //-------------------------------------------------------------------------------------------- inline void Process::Succeed(void) { GCC_ASSERT(m_state == RUNNING || m_state == PAUSED); m_state = SUCCEEDED; } inline void Process::Fail(void) { GCC_ASSERT(m_state == RUNNING || m_state == PAUSED); m_state = FAILED; } inline void Process::AttachChild(StrongProcessPtr pChild) { if (m_pChild) m_pChild->AttachChild(pChild); else m_pChild = pChild; } inline void Process::Pause(void) { if (m_state == RUNNING) m_state = PAUSED; else GCC_WARNING("Attempting to pause a process that isn't running"); } inline void Process::UnPause(void) { if (m_state == PAUSED) m_state = RUNNING; else GCC_WARNING("Attempting to unpause a process that isn't paused"); } /* inline StrongProcessPtr Process::GetTopLevelProcess(void) { if (m_pParent) return m_pParent->GetTopLevelProcess(); else return this; } */游戏Process包括游戏初始化后的各种状态与各种处理。
III. 游戏的Process管理类
#pragma once //======================================================================== // ProcessManager.h : defines common game events //======================================================================== #include "Process.h" #include <list> #include <iostream> using namespace std::list>; class ProcessManager { typedef std::list<StrongProcessPtr> ProcessList; ProcessList m_processList; public: // construction 明显的没有virtual 因为没有子类继承 ~ProcessManager(void); // interface unsigned int UpdateProcesses(unsigned long deltaMs); // updates all attached processes WeakProcessPtr AttachProcess(StrongProcessPtr pProcess); // attaches a process to the process mgr void AbortAllProcesses(bool immediate); // accessors unsigned int GetProcessCount(void) const { return m_processList.size(); } private: void ClearAllProcesses(void); // should only be called by the destructor };
//======================================================================== // ProcessManager.cpp : defines common game events //======================================================================== #include "GameCodeStd.h" #include "ProcessManager.h" //--------------------------------------------------------------------------------------------- // Destructor //--------------------------------------------------------------------------------------------- ProcessManager::~ProcessManager(void) { ClearAllProcesses(); } //--------------------------------------------------------------------------------------------- // The process update tick. Called every logic tick. This function returns the number of process chains that // succeeded in the upper 32 bits and the number of process chains that failed or were aborted in the lower 32 bits. //--------------------------------------------------------------------------------------------- unsigned int ProcessManager::UpdateProcesses(unsigned long deltaMs) { unsigned short int successCount = 0; unsigned short int failCount = 0; ProcessList::iterator it = m_processList.begin(); while (it != m_processList.end()) { // grab the next process StrongProcessPtr pCurrProcess = (*it); // save the iterator and increment the old one in case we need to remove this process from the list ProcessList::iterator thisIt = it; ++it; // process is uninitialized, so initialize it if (pCurrProcess->GetState() == Process::UNINITIALIZED) pCurrProcess->VOnInit(); // give the process an update tick if it's running if (pCurrProcess->GetState() == Process::RUNNING) pCurrProcess->VOnUpdate(deltaMs); // check to see if the process is dead if (pCurrProcess->IsDead()) { // run the appropriate exit function switch (pCurrProcess->GetState()) { case Process::SUCCEEDED : { pCurrProcess->VOnSuccess(); StrongProcessPtr pChild = pCurrProcess->RemoveChild(); if (pChild) AttachProcess(pChild); else ++successCount; // only counts if the whole chain completed break; } case Process::FAILED : { pCurrProcess->VOnFail(); ++failCount; break; } case Process::ABORTED : { pCurrProcess->VOnAbort(); ++failCount; break; } } // remove the process and destroy it m_processList.erase(thisIt); } } return ((successCount << 16) | failCount); } //--------------------------------------------------------------------------------------------- // Attaches the process to the process list so it can be run on the next update. //--------------------------------------------------------------------------------------------- WeakProcessPtr ProcessManager::AttachProcess(StrongProcessPtr pProcess) { m_processList.push_front(pProcess); return WeakProcessPtr(pProcess); } //--------------------------------------------------------------------------------------------- // Clears all processes (and DOESN'T run any exit code) //--------------------------------------------------------------------------------------------- void ProcessManager::ClearAllProcesses(void) { m_processList.clear(); } //--------------------------------------------------------------------------------------------- // Aborts all processes. If immediate == true, it immediately calls each ones OnAbort() function and destroys all // the processes. //--------------------------------------------------------------------------------------------- void ProcessManager::AbortAllProcesses(bool immediate) { ProcessList::iterator it = m_processList.begin(); while (it != m_processList.end()) { ProcessList::iterator tempIt = it; ++it; StrongProcessPtr pProcess = *tempIt; if (pProcess->IsAlive()) { pProcess->SetState(Process::ABORTED); if (immediate) { pProcess->VOnAbort(); m_processList.erase(tempIt); } } } }以上是进度管理的相关代码,包括不断的更新进度和杀死所有进度的控制。
以上的就是游戏的主循环的相关代码了,下一篇将是游戏音效管理相关的~~