逻辑部分定义游戏引擎的核心原则和算法,数据部分则提供其内容和行为的具体细节。 游戏数据应该从文件载入,而不应该内嵌在代码中。
你的游戏应该有能力应对规则、角色、种族、武器、关卡、控制方案以及对象的改变进行处理 没有这种灵活性,改变的代价很大,并且每一个改变都需要有程序员参与——这完全是资源浪费
私有整型变量可以使用m_iSomeVar等| 类型 | 描述 |
|---|---|
| I | Interger |
| F | Float |
| D | Double(float) |
| L | Long(nteger) |
| C | Character |
| B | Boolean |
| Dw | Double wond |
| W | Wond |
| by 或 byte | Byte |
| Sz | C-style(null-terminated) string |
| 常用扩展名 | 描述 |
|---|---|
| Str | C++字符串对象 |
| H | 句柄(自定义类型) |
| V | 向量(自定义类) |
| Rgb | RGB三元组(自定义结构或类型) |
| P | 指针 |
| R | 索引 |
| U | 非符号 |
| a或ary | 数组 |
| 范围 | 描述 |
|---|---|
| m_ | 成员变量 |
| g_ | 全局变量 |
| s_ | 静态变量 |
常用的标注类型(对象通常不加任何前缀) 不要再代码规范上做的太过,如果写代码要查规范严重影响效率 类名的设计也应该易于阅读和维护,用功能作为类名前缀也是很有用的
class Sample{
public:
Sample() {Clear();}
~Sample() {Destroy();}
void Clear(); // 清空所有内部成员变量
bool Create(); // 动态创建对象,返回true/false表示创建成功或失败
void Update();
void Destroy(); // 销毁对象
}
动态分配内存的开销很大,所以只要可能都尽量避免
无论表示什么对象,Create()和Destroy()成员函数真正完成创建和销毁对象的工作
Create()方法也可以返回错误代码类型,通常为符号整形Destroy()方法我们希望既有自动清理的便利,又有按需创建和删除的灵活性,所以Destroy()函数能安全地多次调用,或者没有调用Create()的情况下也能安全调用
通过继承来复用类,是面向对象编程的一大要素 扩展类之间的合作只要是两种方法:继承和分层
分层:从一个类派生出另一个类 分层:一个对象作为成员包含于另一个对象,也称组合
#include <iostream>
using namespace std;
template< unsigned N >
struct Fib{
enum{
// 递归定义 不常见但合法
Val = Fib<N-1>::Val + Fib<N-2>::Val
};
};
// 基于特殊情况的模板特化
template<> struct Fib< 0 > {enum{Val = 0};};
template<> struct Fib< 1 > {enum{Val = 1};};
#define FibT( n ) Fib< n >::Val
int main()
{
std::cout << FibT(4) << std::endl;
return 0;
}
模板函数并不是真正的函数——他是叫做Val的枚举整形,在编译期递归生成 模板参数N用于指定函数的输入,以简化编辑。在默认情况下结构数据是公用的 要终止递归,需要正确地处理结束条件。对于斐波那契数来说,终止条件就是0和1
template< unsigned N >
struct Fact{
enum {
Val = N * Fact< N - 1 >::Val
};
}
template<> struct Fact<1>{
enum {Val = 1};
};
#define FactT( n ) Fact< n >::Val
通过泰勒公式可以直接用上面类似的方法展开
并非所有编译程序都能完全与C++标准兼容,尤其是在它们处理模板的时候更是如此 模板是如此灵活而且强大,以至于为了让他们能够正确运行,编译程序的开发者需要付出大量辛苦的劳动
游戏通常有多种类型的数据库,每种数据库常常将各种不同的情况硬编码,以保证速度。 游戏数据库的例子有:文件系统、纹理管理器、字体管理器、游戏角色管理器等。在此之上有大量基于不同游戏风格和内容的专用数据库
建立在所有游戏中的一个资源数据库就是基本对象内存管理器。程序员调用new创建对象、不用时delete将资源返还给系统,但这种做法不适用共享资源
游戏中的不同系统:开发控制台和GUI中的文本控制都要使用字体对象,但不能让每个系统都创建自己的本地字体对象拷贝,因为这样做的运行速度很慢并且会消耗大量内存。这个时候使用一个单例的字体管理器FontManager对象,使用它获得字体指针、在系统空闲时加载、将其高速缓存直到不再需要位置、全局范围有效,负责系统中的所有字体对象。但对于何时创建、如何释放等问题单纯的单例模式并不能给出完美的解决方案
资源管理器的工作是按需创建资源,将它提供给需要使用的对象,并最终将其删除
因此用指针的方式提供资源,对安全、灵活的资源管理器来说并不是个好主意
可以不必编写高智能、过于复杂的智能指针,而是加一层抽象,使用句柄,把重任抓交给管理器类
Win32文件系统中
CreateFile()调用返回的HANDLE类型就是句柄 句柄很适合单CPU寄存器,因为它能以集合方式搞笑存储,并且能作为参数传递给函数,易于检查有效性,而且不是直接指向资源所以改变底层的数据组织方式而不会产生无效句柄,句柄还可以直接存储
#include <vector>
#include <map>
#include <casset>
//...[平台相关的句柄类型]
typedef LPDIRECTDRAWSURFACR7 OsHandle;
struct tagTexture {};
typedef Handle <tagTexture> HTexture;
class TextureMgr
{
//材质对象数据
struct Texture
{
typedef std::vector <OsHandle> HandleVec;
std::string m_Name; //重构造时使用
unsigned int m_Width; //宽度
unsigned int m_Height; //高度
HandleVec m_Handles; //surface句柄
OsHandle GetOsHandle(unsigned int mip) const
{
assert(mip < m_Handles.size());
return { m_Handles[mip]};
}
bool Load (const std::string& name);
void Unload(void);
};
typedef HandleMgr <Texture, HTexture> HTextureMgr;
//数据库以名字为索引
//大小写不敏感的字符串比较谓词
struct istring_less
{
bool operator() (const std::string& l, const std::string& r) const {
return ( ::stricmp(1.c_str(), r.c_str()) < 0 );
}
};
typedef std::map <std::string, HTexture, istring_less> NameIndex;
typedef std::pair <NameIndex::iterator, bool> NameIndexInsertRc;
//私有数据
HTextureMgr m_Texture;
NameIndex m_NameIndex;
public:
//生命期
TextureMgr(void) { /*...*/ }
~TextureMgr(void);
//材质管理
HTexture GetTexture(const char* name);
void DeleteTexture(HTexture htex);
//材质查询
const std::string& GetName(HTexture htex) const{
return m_Texture.Dereference(htex)->m_Name;
}
int GetWidth(HTexture htex) const{
return m_Texture.Dereference(htex)->m_Width;
}
int GetHeight(HTexture htex) const{
return m_Texture.Dereference(htex)->m_Height;
}
OsHandle GetTexture(HTexture htex, unsigned int mip = 0) const{
return m_Texture.Dereference(htex)->GetOsHandle(mip);
}
};
TextureMgr::~TextureMgr(void)
{
//在析构前释放索引剩余材质
NameIndex::iterator i, begin = m_NameIndex.begin(), end = m_NameIndex.end();
for(i = begin; i != end; ++i)
{
m_Textures.Dereference(i->second)->Unload();
}
}
HTexture TextureMgr::GetTexture(const char* name)
{
//插入和查找
NameIndexInsertRc rc = m_NameIndex.insert(std::make_pair(name, HTexture()));
if(rc.second)
{
//这是一个新的插入操作
Texture* tex = m_Textures.Acquire(rc.first->second);
if(!tex->Load(rc.first->first))
{
DeleteTexture(rc.first->second);
rc.first->second=HTexture();
}
}
return rc.first->second;
}
void TextureMgr::DeleteTexture(HTexture htex)
{
Texture* tex = m_Textures.Dereference(htex);
if(tex != 0)
{
//使用索引删除
m_NameIndex.erase(m_NameIndex.find(tex->m_Name));
//从数据库删除
tex->Unload();
m_Texture.Release(htex);
}
}
bool TextureMgr::Texture::Load(const std::string& name)
{
m_Name = name;
//...[从文件系统载入材质,失败时返回false]
return true;
}
void TextureMgr::Texture::UnLoad(void)
{
m_Name.erase();
//...[释放surfaces]
m_Handles.clear();
}