绚丽的大型游戏背后,都存在着一个主循环,能根据游戏的进度控制游戏的状态。主循环管理游戏的正常运行,管理着游戏运行失败该如何处理,对某些不能运行的情况给予杀死等等。另外,在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);
}
}
}
}
以上是进度管理的相关代码,包括不断的更新进度和杀死所有进度的控制。
以上的就是游戏的主循环的相关代码了,下一篇将是游戏音效管理相关的~~