Урон от падения - простая и настраиваемая реализация
В версии v0.24 я сделал урон от падения. Мне показалось не лишним написать маленькую заметку о том, как эта механика работает в моей игре.
Начнём с формулы:
t = (velocity - safe) * (fatal - safe)
damage = lerp(min_damage, max_damage, t)
Идея в том, что safe - это минимальная скорость падения, перейдя которую урон будет наноситься, а fatal - это летальная. Всё что между - там урон высчитывается по коэффициенту.
Если попытаться визуализировать такую систему, то это будет шкала, которая как бы конвертирует velocity в фактор урона от 0.0 до 1.0.
Потом при помощи lerp этот фактор (t) конвертируется в урон от min_damage до max_damage.
Удобство в том, что такую формулу легко настраивать и расширять. Мы можем контролировать минимальную и максимальную скорость падения, можем контролировать минимальный и максимальный урон от падения, а ещё мы можем играться с самим фактором t, например использовать easing-функции или сделать так, чтобы разные типы поверхностей смягчали урон:
#define PLAYER_SAFE_FALL_VELOCITY 12.0f // 12 units/s before fall damage starts
#define PLAYER_FATAL_FALL_VELOCITY 30.0f // 30 units/s for fatal damage
static float GetSurfaceFallDamageFactor(int surface_type)
{
switch (surface_type) {
case SURFACE_TYPE_STONE: return 1.0f;
case SURFACE_TYPE_GRASS: return 0.5f;
case SURFACE_TYPE_WOOD: return 0.7f;
case SURFACE_TYPE_METAL: return 1.0f;
case SURFACE_TYPE_CARPET: return 0.3f;
case SURFACE_TYPE_CERAMIC: return 1.0f;
default: return 1.0f;
}
}
static float CalcFallDamageFactor(float velocity, float safe, float fatal)
{
return Clamp((velocity - safe) / (fatal - safe), 0.0f, 1.0f);
}
void Player_ApplyFallDamage(Player *player)
{
// Calculate fall damage based on vertical velocity
float t = CalcFallDamageFactor(player.vertical_velocity, PLAYER_SAFE_FALL_VELOCITY, PLAYER_FATAL_FALL_VELOCITY);
t *= EaseInOut(t);
t *= GetSurfaceFallDamageFactor(player.ground_surface); // adjust damage based on surface
// Apply damage
int damage = (int)Lerp(0.0f, (float)player->max_health, t);
if (damage > 0) {
Player_TakeDamage(player, damage);
}
}
Вот и всё, о чём мне хотелось написать. Я думаю, что такой подход - это самый очевидный, гибкий и контролируемый способ реализации этой несложной механики. Наверняка многие AAA-проекты используют именно его.
Спасибо за прочтение и удачи в ваших проектах!