我正在使用C++制作一个简单的实时战略游戏。
我想知道如何处理在游戏中创建新的单位(例如通过兵营创建机枪手)。我该如何存储这些单位呢?
我考虑过创建一个名为“unit”的类,然后让特定类型的单位继承它(例如机枪手、火蝠等),但如果我为这些单位创建数组(例如Marines myMarines[20]),那么这将对单位数目设立硬性上限。
我应该如何创建这样一个可以随意扩展的数组呢? 谢谢!
我正在使用C++制作一个简单的实时战略游戏。
我想知道如何处理在游戏中创建新的单位(例如通过兵营创建机枪手)。我该如何存储这些单位呢?
我考虑过创建一个名为“unit”的类,然后让特定类型的单位继承它(例如机枪手、火蝠等),但如果我为这些单位创建数组(例如Marines myMarines[20]),那么这将对单位数目设立硬性上限。
我应该如何创建这样一个可以随意扩展的数组呢? 谢谢!
std::vector
模板用于动态调整大小的数组。一个 std::vector<Marine>
将是 Marines myMarines[20]
最直接的替代品。std::vector<Unit>
似乎是显而易见的解决方案,但实际上并不是。问题在于 std::vector
通过值存储对象。下面的代码将无法正常工作:std::vector<Unit> v;
v.push_back(Marine("John Doe"));
Marine
对象将被复制到Unit
对象中,这是向量存储的内容。这种复制会导致所谓的slicing:所有特定于Marine的成员都将丢失,只有存在于Unit
中的成员才会被存储。std::unique_ptr
。std::vector<std::unique_ptr<Unit>> v;
v.emplace_back(new Marine("John Doe"));
std::vector
的std::unique_ptr
:boost::ptr_vector
。那将是另一种选择。vector<Unit*>
比一个 vector<unique_ptr<Unit>>
更难正确处理。为什么初学者要从难的开始呢?学习 char *
先于 std::string
的时代早已过去。 - fredoverflow在这里使用std::vector
可能会对您有所帮助。这将允许您随意添加和删除项目,并且在内部处理动态内存分配(无需关心琐碎的细节!)。
假设您想要存储一份海军名单(用以下示例中的虚构类CMarine表示):
std::vector<CMarine> marinesList;
marinesList.push_back( CMarine( <whatever-its-constructor-takes> ) );
CMarine& marine = marinesList.at( 0 );
marine.someVar = 33;
marine.doMethod();
我使用引用是因为 CMarine 可能太过笨重,无法高效地通过值传递。
你也可以使用迭代器遍历所有的海军士兵,像这样:
for ( std::vector<CMarine>::iterator _it = marinesList.begin();
_it != marinesList.end(); ++_it );
{
CMarine& marine = *_it;
// Now you can do something with this marine reference
}
更新:
如果CMarine
是多态的,也就是说,它继承自一个超类(可能类似于你的情况中的CUnit
),并且你有一个“全局”的向量存储所有单位 - Georg Fritzsche正确地指出,如果我们按值存储,则可能会发生对象切片。相反,你最好使用像这样的CUnit
(智能)指针向量:
std::vector<std::unique_ptr<CUnit>> unitsList;
// To add a marine:
unitsList.push_back( new CMarine( <whatever-its-constructor-takes> ) );
在这里阅读更多关于向量的信息。
很有可能你不想为每个单位类型单独创建一个容器。因此,你需要进行一些泛化,使用类似于组件化设计的东西。一旦你完成了这个步骤,你就需要在第一种情况下使用std::vector<GameUnit*>
或std::list<GameUnit*>
,在第二种情况下使用std::vector<GameUnit>
或std::list<GameUnit>
。无论哪种方式,你都应该使用标准库容器来存储东西。
你可以在http://cppreference.com上找到更多关于std::vector
和std::list
的信息,尽管你的书本应该已经涵盖了它们。另外,请参见
std::list< Unit * > unitList
列表可以让你追加任意数量的对象,虽然它不允许快速访问列表的任意成员,但是你可以轻松地迭代它而不必担心当你从其中删除某个元素时会尝试移动大量内存。
我喜欢做的一件事是让一个单元在其构造函数内自动向单位列表注册自己。所以假设Marine是Unit的子类,我需要做的只是这样说:
new Marine(x_pos, y_pos);
此时,一个新的Marine将自动创建并添加到列表中。
在此时,每一帧,您可以遍历unitList中的每个单位,并运行该单位的更新函数(这是一个虚函数,对于每个子类都不同)。
在更新循环之后,运行清理循环再次遍历unitsList,找到所有被摧毁的单位,并将它们从列表中移除并删除它们。
我说 std::vector
!通常我会创建一个基类 unit(或者我喜欢称之为 GameObject
)这是我会做的:
class GameObject {} // Maybe has virtual methods for the Size, Location and Image?
class Barrack
{
std::vector< GameObject > gameUnits;
public:
// code
void AddUnit() { gameUnits.push_back( GameObject() ); }
void DestroyUnit(int index);
// etc. etc.
}
然而,如果你不想过度依赖继承,即你有不同种类的单位,它们并不都继承自一个基类,那么你可以尝试一下我几天前实现的vector_any
类来保存我RPG游戏中的精灵:
struct element
{
element( void* data, const std::type_info& info ) : value(data), type( &info ) {}
void* value;
const std::type_info* type;
};
class type_conversion_exception : exception {};
class linked_vector
{
vector< element > stack;
public:
linked_vector() {}
template< typename T > void add_item( T& item )
{
stack.push_back( element( static_cast< void* >( &item ), typeid(item) ) );
}
template< typename T > T& get_item( int index )
{
if ( *( stack[index].type ) == typeid( T ) )
{
return *( static_cast< T* >( stack[index].value ) );
}
else throw type_conversion_exception();
}
};
你可以像这样在游戏单位中使用它。
linked_vector gameUnits;
MilitaryUnit mUnit;
AirUnit aUnit;
gameUnits.add_item( mUnit );
gameUnits.add_item( aUnit );
try{ draw( gameUnits.get_item< MilitaryUnit >(0) ); }
catch( type_conversion_exception e ) { /* error handling */ }
// etc. etc.