我目前正在用c ++制作简单的RTS风格游戏。
我想知道的是如何处理游戏中新单位的创建(即从军营中建造海军陆战队员)。我如何存储这些单位?
我想有一个类'单位',然后由特定的单位类型(即海军陆战队,火焰等)继承,但如果我为这些创建一个数组(即。海军陆战队myMarines [20])将在这些单位上创造一个硬帽。
如何创建可以随意扩展的数组? 谢谢!
答案 0 :(得分:9)
标准库为动态可调整大小的数组提供了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:所有海洋特定成员都将丢失,只有Unit
中存在的成员才会被存储。
此问题的一个解决方案是将指针存储在向量中,因为复制指针不会更改它们指向的对象。但这带来了其他问题。要存储指针,这意味着您需要动态分配对象。这意味着现在您负责手动销毁这些对象。这是一项令人厌烦且容易出错的任务。
解决方案是存储在自动销毁动态分配对象的向量对象中,而不是指针。这些对象称为智能指针。标准库中最简单的一个是std::unique_ptr
。
std::vector<std::unique_ptr<Unit>> v;
v.emplace_back(new Marine("John Doe"));
这是C ++ 11的一项功能。如果您的编译器不支持它,您可以找到alternatives in the Boost libraries。 Boost甚至包含一个容器,其行为与std::vector
std::unique_ptr
s boost::ptr_vector
非常相似。那将是另一种选择。
答案 1 :(得分:4)
您可能会在此处使用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正确地指出object slicing可能发生(如果我们按值存储)。相反,你可能会更好地使用这样的CUnit
(智能)指针向量:
std::vector<std::unique_ptr<CUnit>> unitsList;
// To add a marine:
unitsList.push_back( new CMarine( <whatever-its-constructor-takes> ) );
详细了解向量here.
答案 2 :(得分:1)
您可能不希望为每种单位类型设置单独的容器。因此,您必须稍微概括一下,并使用类似component based design之类的内容。完成后,您需要在第一种情况下使用std::vector<GameUnit*>
或std::list<GameUnit*>
,在第二种情况下使用std::vector<GameUnit>
或std::list<GameUnit>
。无论哪种方式,您都应该使用标准库容器来存储东西。
您可以在std::vector
上找到有关std::list
和http://cppreference.com的更多信息,但您的图书应该已经涵盖了它们。另外,请参阅
答案 3 :(得分:1)
首先,我创建一个Unit类,然后从中继承你的单元,这样你就不必处理一堆单独的列表了。然后,我将指向单位的指针存储在:
std::list< Unit * > unitList
列表允许您附加您喜欢的许多对象,并且虽然它不允许快速访问列表中的随机成员,但您可以轻松地迭代它而不必担心它试图移动大量当你从中间删除某些内容时的内存。
我喜欢做的一件事是从单元的构造函数内部自动使用单元寄存器和单元列表。所以假设Marine是Unit的子类,我需要做的就是说:
new Marine(x_pos, y_pos);
...将创建一个新的海军陆战队并自动附加到列表中。
此时,每个帧,你可以迭代unitList中的每个Unit并运行unit的更新函数(这是一个对每个子类都不同的虚函数)。
在更新循环之后,运行一个清理循环,再次遍历unitsList,找到所有已销毁的单元,并从列表中删除它们并删除它们。
答案 4 :(得分:0)
我说std::vector
!我通常创建一个单位(或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.
答案 5 :(得分:0)
矢量和列表之间的选择是一个棘手的问题。每次在向量上push_back()时,都会重新分配和复制整个向量。列表没有此问题。无论如何,你可以预先分配矢量并设置一个单位上限 - 除非你想在地图上有效地使用'无限'单位,否则你可能没有。但是你可能没有。
对于查找,向量对任何索引都有恒定的时间查找,但是你想多久跳转到一个特定的索引?当涉及迭代整个列表或向量时,我认为没有任何性能差异。
此外,当你想从一个向量中删除一个单位(当它被杀死时)你会有更多的重新分配和随机问题,列表可以更有效地删除任何项目。
我个人倾向于列表。
如前所述,您选择的容器应该包含指向单元基类的指针。