Handling "units" in an RTS game - C ++

I'm in the process of creating a simple RTS style game in C ++.

I am wondering how to deal with the creation of new units in the game (i.e. make marines from a hut). How can I store these units?

I thought that I have a class subdivision that will then be inherited by specific types of units (for example, marines, firebats, etc.), but if I create an array for them (for example, Marines myMarines [20]), which will create a hard drive on these devices.

How to create an array that can be expanded as you wish? Thanks!

+4
source share
6 answers

The standard library provides them with the std::vector template for dynamically resizing arrays. A std::vector<Marine> will be the easiest alternative to Marines myMarines[20] .

However, you probably do not need a separate list for each type of device. It is very likely that you will want to keep all units in one list, regardless of their type. std::vector<Unit> will sound like an obvious solution, but it is not. The problem is that std::vector stores objects by value. The following will not work correctly:

 std::vector<Unit> v; v.push_back(Marine("John Doe")); 

The problem is that the Marine object will be copied to the Unit object, which is stored in the vector. Such a copy leads to what is called slicing : all special Marine members will be lost and only those existing in Unit will be saved.

One solution to this problem is to keep the pointers in the vector, because copy pointers do not change the objects they point to. But this creates other problems. To save pointers, this means that you will need to dynamically highlight objects. And this means that now you are responsible for the destruction of these objects manually. This is a tedious and error prone task.

The solution is to save in vector objects that automatically destroy dynamically allocated objects, not pointers. These objects are known as smart pointers. The simplest one that exists in the standard library is std::unique_ptr .

 std::vector<std::unique_ptr<Unit>> v; v.emplace_back(new Marine("John Doe")); 

This is a C ++ 11 function. If your compiler does not support it, you can find alternatives in the Boost libraries . Boost even includes a container that acts something like std::vector of std::unique_ptr s: boost::ptr_vector . That would be another alternative.

+9
source

You will likely benefit from using std::vector here. This will allow you to add and remove elements as you wish and handle dynamic memory allocation inside (without any details about the details).

Suppose you want to keep a list of marines (indicated by the imaginary class CMarine in the following example):

 std::vector<CMarine> marinesList; 

Now add a marine just:

 marinesList.push_back( CMarine( <whatever-its-constructor-takes> ) ); 

To access this marine organism, you can do something like this:

 CMarine& marine = marinesList.at( 0 ); marine.someVar = 33; marine.doMethod(); 

(I use the link since CMarine can be too bulky to effectively bypass the value)

You can also scroll through all marines using an iterator:

 for ( std::vector<CMarine>::iterator _it = marinesList.begin(); _it != marinesList.end(); ++_it ); { CMarine& marine = *_it; // Now you can do something with this marine reference } 

UPDATE:

If CMarine is polymorphic, that is, it inherits from the superclass (maybe something like CUnit in your case), and you have a "global" vector of all units. Georg Fritsche rightly noted that the fragment of the object (if we keep the value). Instead, you might be better off with a CUnit vector of (smart) pointers like this:

 std::vector<std::unique_ptr<CUnit>> unitsList; // To add a marine: unitsList.push_back( new CMarine( <whatever-its-constructor-takes> ) ); 

More about vectors here.

+4
source

Most likely, you do not need a separate container for each type of your device. Therefore, you will have to generalize a bit and use something like component design . After you do this, you will need std::vector<GameUnit*> or std::list<GameUnit*> in the first case, and std::vector<GameUnit> or std::list<GameUnit> in the second case . In any case, you should use a standard library container for storing things.

You can find more information about std::vector and std::list at http://cppreference.com , although your book should already cover them. Also see

+1
source

First, I would create a Unit class, and then subclass its units, so you don't need to process a bunch of separate lists. Then I would save the pointers to units in:

 std::list< Unit * > unitList 

The list allows you to add many objects that you like, and although it does not allow you to quickly access random members of the list, you can easily skip it and not worry about it, trying to move large amounts of memory when you delete something from the middle .

One thing that I like to do is to automatically register a unit with a list of units from within the block constructor. Therefore, assuming Marine is a subclass of Unit, all I would have to do is say:

 new Marine(x_pos, y_pos); 

... and a new marine will be automatically created and added to the list.

At this point, every frame, you can iterate through each module in unitList and run the device update function (which is a virtual function that is different for each subclass).

After the update cycle, start the cleaning cycle, which is repeated again through unitList, finds all the destroyed units and removes them from the list and removes them.

+1
source

I say std::vector ! I usually create the base class unit (or GameObject , as I like to call them). What would I do:

 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. } 

However, if you do not want to rely too much on inheritance, i.e. you have different types of units, and they all do not inherit from the same base class, you can try this vector_any class, which I implemented a few days ago to keep sprites of my RPG game:

 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(); } }; 

You can use it for your game modules like this.

 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. 
0
source

The choice between a vector and a list is difficult. Each time you press push_back () on a vector, the entire vector is redistributed and copied. The list does not have this problem. However, you could pre-distribute the vector and have a unit cap - this is great if you do not want effectively โ€œunlimitedโ€ units on the map, but you probably do not.

As for search, a vector has a constant time search for any index, but how often do you want to go to a specific index? When it comes to iterating over an entire list or vector, I donโ€™t think there is a difference in performance.

In addition, if you want to remove a block (when it was killed) from the vector, you will have problems with redistribution and shuffling, the list can remove any element much more efficiently.

I am personally inclined to the list.

And as already indicated, the selected container must contain pointers to the base class of the device.

0
source

Source: https://habr.com/ru/post/1399318/


All Articles