game-engine / devlog / fall-damage

Урон от падения - простая и настраиваемая реализация

В версии v0.24 я сделал урон от падения. Мне показалось не лишним написать маленькую заметку о том, как эта механика работает в моей игре.

Начнём с формулы:

t = (velocity - safe) * (fatal - safe)
damage = lerp(min_damage, max_damage, t)

Идея в том, что safe - это минимальная скорость падения, перейдя которую урон будет наноситься, а fatal - это летальная. Всё что между - там урон высчитывается по коэффициенту.

Если попытаться визуализировать такую систему, то это будет шкала, которая как бы конвертирует velocity в фактор урона от 0.0 до 1.0.

safe fatal 0.0 0.5 1.0 velocity

Потом при помощи 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-проекты используют именно его.

Спасибо за прочтение и удачи в ваших проектах!

разработка игр механики урон от падения