I may have a way around the 20x20 solution though, at least a way of narrowing down the possible combinations needed.
I have a list of Entity types:
Code: Select all
class HeroMale;
class HeroFemale;
class StreetThugMale;
class StreetThugFemale;
class Level1Boss;
class Level2Boss;
class Level3Boss;
class Level4Boss;
class Crate;
class Wall;
class Bullet;
class Shell;
class Grenade;
class PlasmaBall;
class KnifeEntity;
class BatEntity;
class PipeEntity;
So my entity variant would be:
Code: Select all
using entity_t =
std::variant<
HeroMale,
HeroFemale,
StreetThugMale,
StreetThugFemale,
Level1Boss,
Level2Boss,
Level3Boss,
Level4Boss,
Wall,
Crate,
Bullet,
Shell,
Grenade,
PlasmaBall,
KnifeEntity,
BatEntity,
PipeEntity
>;
There are 17 types. Instead of trying to make 17x17=289 combinations of functions, we can narrow it down depending on the situation.
For the collision checking, we get the bounding object, this way we only need a few functions for them.
But let's say we only want to check collision between specific types, like HeroMale and Wall, but not Bullet and Shell.
If we leverage the type system, we can use if constexpr and a few type traits.
Remember our code from earlier:
Code: Select all
void CheckCollisions( std::vector<std::variant<Player, Enemy>>& _variants )
{
for( size_t i = 0; i < _variants.size(); ++i )
{
for( size_t j = i + 1; j < _variants.size(); ++i )
{
if( std::visit( Collidable::IsColliding, _variants[ i ], _variants[ j ] ) )
{
_obj1.TakeDamage( _obj2.DealDamage() );
_obj2.TakeDamage( _obj1.DealDamage() );
}
}
}
}
Let's modify it:
Code: Select all
// We can setup a type trait to help us
template<typename T> struct is_ammo : std::false_type{};
template<> struct is_ammo<Bullet> : std::true_type{};
template<> struct is_ammo<Shell> : std::true_type{};
template<> struct is_ammo<Grenade> : std::true_type{};
template<> struct is_ammo<PlasmaBall> : std::true_type{};
template<typename T> constexpr bool is_ammo_v = is_ammo<T>::value;
template<typename T> struct is_input_controlled
: std::conditional_t<std::disjunction<std::is_same_v<T,HeroMale>,std::is_same_v<T,HeroFemale>>,
std::true_type : // if T is HeroMale or HeroFemale
std::false_type>{}; // If T is not
template<typename T> struct is_input_constrolled_v = is_input_controlled<T>::value;
template<typename T> struct is_movable
{
static constexpr bool value = std::is_same_v<T::move_type, dynamic_tag>;
};
template<typename T> constexpr bool is_movable_v = is_movable<T>::value;
template<typename T, typename U> struct is_collidable_with
{
static constexpr bool value = !std::conjunction_v<is_ammo_v<T>, is_ammo_v<U>>;
};
template<typename T, typename U> constexpr bool is_collidable_with_v = is_collidable_with<T,U>::value;
void CollisionSystem::CheckCollisions()
{
for( size_t i = 0; i < _variants.size(); ++i )
{
for( size_t j = i + 1; j < _variants.size(); ++i )
{
auto check = [&]( auto&& obj1, auto&& obj2 )
{
// First, decltype gets the actual type
// decay_t removes referenc from the type, so if auto&& obj1 turns out to be HeroMale&
// then decay_t will return HeroMale
// This is important in determing if two types are the same, because HeroMale and HeroMale& aren't
using type1 = std::decay_t<decltype(obj1)>;
using type2 = std::decay_t<decltype(obj2)>;
if constexpr( is_collidable_with_v<type1, type2> )
{
// There, now if both are listed as ammo types, this function will be empty and the loops will continue
// We eliminated the need for 16 of the 289 functions lol
// Now we call the GetBounding functions on the members to eliminate all but the four IsColliding functions
if( Collidable::IsColliding( obj1.GetBounding(), obj2.GetBounding() )
{
// Handle collisions
// ...
// Send notification that there was a collision.
message_system.send<Collide>( obj1, obj2 );
// This too can be broken up, since something like a wall or crate won't have velocity, you can overload
// the MessageDispatch::send function so that one accepts a movable and non-movable object or two
// movable objects.
if constexpr( is_movable_v<type1> )
{
message_system.send<Collide>( obj1, obj2 );
}
else if constexpr( is_movable_v<type2> && !is_movable<type1> )
{
message_system.send<Collide>( obj2, obj1 );
}
}
}
};
std::visit( check, variants[i], variants[j] );
}
}
}
Alternatively, each type can have traits built in that can help. As shown above, is_movable expected T to have a tag of move_type. Just make sure the alias is public.
Code: Select all
struct dynamic_tag;
struct static_tag;
class Wall
{
public:
using move_type = static_tag;
};
class Bullet
{
public:
using move_type = dynamic_tag;
};
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com