Multiple tables for multiple root types, then share variables
If two types have little overlap in variables and don't need to be in the same list, you can have different roots and store in separate collections, but there's a lot of room for variation within those root types using settings. The only drawback is redundancy in a few unused variables, but practice can make you find more generic names for the variables so that multiple types can reuse them. 80% variable use is good enough and the rest can have N/A values to make debugging easier. All weapons in Doom were created using only different images, sounds and variables. A shotgun just increased the number of projectiles per shot, made reloads slower, increased the spread varaible...
Why not try procedural C++ without classes? It has overloading, namespaces, references, array-based lists and lambdas for a clean and simple functional style. This will still give you fast and robust compilation by not exposing an entangled mess of classes in headers.
If you don't like std::vector, I have a wrapper that makes it faster and easier to use for beginners by pre-allocating a larger buffer by default, accessing elements by signed indices (to allow looping backwards without the dangers) and hiding the error-prone iterators.
https://github.com/Dawoodoz/DFPSR...er/Source/DFPSR/collection/List.h
Unless you absolutely must place unrelated types in the same list of entity classes (1990s OOP style), all you need is overloading for individual types. Overloading of global methods get the same perks as multiple inheritance, but without the entangled mess of having dependencies across unrelated classes. Define one type for static (standing still) items containing an index or pointer to the type. Define another type for dynamic (moving) items containing the type, team, health, et cetera. Then define interactions for pairs of dynamic items so that ghosts can eat the player and such. Another set of global functions define dynamic to static interactions so that the player can eat the items and receive perks.
Static-dynamic separation is much faster than having a single list of all types, because there's no point in handling (S+D)² possible interactions (all to all, including the impossible static to static interactions) instead of D + D². You can have a simpler broad-phase and still get much faster results than with polymorphism. Any special differences between the types of dynamic items can be expressed using variables in the type table or even lambda expressions for custom methods.
Pseudo C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | struct StaticItemType {
// Physics properties
CollisionEnum collisionType;
// Lambdas defining what happens when interacting with it to be called from interact
EffectFunction playerEffect;
EffectFunction enemyEffect;
}
...
// Table describing different pick-ups and obstacles
List<StaticItemType> staticItemTypes;
// Table describing players and enemies using properties and lambda functions
List<DynamicItemType> dynamicItemTypes;
// All items that won't move
List<StaticItem> staticItems;
// All players, emenies, particles...
List<DynamicItem> dynamicItems;
void interact(DynamicItem &a, StaticItem &b) {
...
}
void interact(DynamicItem &a, DynamicItem &b) {
...
}
for (int item = 0; item < dynamicItems.length(); item++) {
// Interact with any static items nearby using a fixed broad-phase (grid / quad-tree)
...
// Interact with any dynamic items nearby using a dynamic broad-phase (grid / sorting)
}
|