Здоровье, урон и инвентарь

Почти любая игра считает здоровье и хранит предметы. Эти системы — чистая логика, не зависящая от движка, поэтому их легко понять и протестировать.

Суть: система здоровья хранит текущее и максимальное HP, уменьшает его при уроне (не ниже нуля), увеличивает при лечении (не выше максимума) и сообщает о смерти при HP=0. Инвентарь хранит предметы, позволяет добавлять и использовать их. Обе системы — почти чистая логика.

Здоровье и инвентарь — отличный пример того, что в игре много логики, не зависящей от Unity. Урон, лечение, лимиты HP, добавление предмета в сумку — это обычные операции над числами и списками. Их можно (и нужно) писать как чистые методы, а компонент MonoBehaviour лишь связывает их с игрой.

Как работает под капотом

Система здоровья:

  TakeDamage(20):  hp = max(0, hp - 20)
  Heal(15):        hp = min(maxHp, hp + 15)
  if hp == 0  ->  вызвать событие Die()

  hp: [#########.] 90/100  -> урон 30 -> [######....] 60/100
using UnityEngine;
using System;

public class Health : MonoBehaviour
{
    [SerializeField] private int maxHp = 100;
    private int hp;
    public event Action OnDied;

    void Awake() { hp = maxHp; }

    public void TakeDamage(int amount)
    {
        hp = Mathf.Max(0, hp - amount);
        if (hp == 0) OnDied?.Invoke();
    }

    public void Heal(int amount)
    {
        hp = Mathf.Min(maxHp, hp + amount);
    }
}

Реализуем здоровье и инвентарь целиком на Python — это та же логика, что в C#, только без обёртки MonoBehaviour:

# Здоровье с лимитами и инвентарь
class Health:
    def __init__(self, max_hp):
        self.max_hp = max_hp
        self.hp = max_hp
    def take_damage(self, dmg):
        self.hp = max(0, self.hp - dmg)
        return self.hp == 0          # True если погиб
    def heal(self, amount):
        self.hp = min(self.max_hp, self.hp + amount)

h = Health(100)
print("Старт hp:", h.hp)
died = h.take_damage(30); print("После урона 30:", h.hp, "погиб?", died)
h.heal(50); print("После лечения 50:", h.hp)   # не выше 100
died = h.take_damage(200); print("После урона 200:", h.hp, "погиб?", died)

# Простой инвентарь
inventory = []
def add_item(item):
    inventory.append(item)
def use_item(item):
    if item in inventory:
        inventory.remove(item)
        return f"Использовали {item}"
    return f"Нет предмета {item}"

add_item("зелье"); add_item("ключ")
print("Сумка:", inventory)
print(use_item("зелье"))
print("Сумка после:", inventory)

Та же логика на Python ▶ — обрати внимание на лимиты: урон не уводит hp ниже 0, лечение не поднимает выше максимума. Это типичные защиты, без которых появляются баги вроде «отрицательного здоровья» или «лечения сверх лимита».

Частые ошибки

  • Не ограничивать HP. Без Max(0, ...) и Min(maxHp, ...) здоровье уходит в минус или зашкаливает.
  • Логика урона размазана по врагам. Лучше единый компонент Health на любом объекте, который может получать урон — переиспользуется и героем, и врагами.
  • Уведомлять о смерти проверкой каждый кадр. Лучше событие OnDied в момент достижения нуля, чем опрос hp в Update.
  • Инвентарь как набор отдельных булевых полей. hasKey, hasPotion, hasSword... быстро разрастается. Список или словарь гибче.

Best practices

  • Выноси игровую логику (здоровье, урон, инвентарь) в чистые методы — их легко тестировать и переиспользовать.
  • Один компонент Health для всех, кто может получать урон.
  • Сообщай об изменениях через события (OnDied, OnHealthChanged), чтобы UI и звук реагировали без опроса в Update.

Итоги: здоровье и инвентарь — почти чистая логика поверх чисел и списков. Здоровье ограничивают снизу нулём и сверху максимумом, о смерти сообщают событием. Инвентарь хранит предметы в списке/словаре. Держи логику отдельно от MonoBehaviour — так её проще понять, тестировать и переиспользовать.

Почему чистую логику стоит тестировать

Главная ценность вынесения здоровья и инвентаря в чистые методы — их можно проверить без запуска игры. Тебе не нужно ставить врага на сцену, бить его и смотреть глазами: достаточно вызвать take_damage с разными числами и сравнить результат с ожидаемым. Урон 200 при 100 HP должен дать ровно 0, а не минус 100; лечение сверх максимума не должно поднять HP выше предела; использование предмета, которого нет в сумке, не должно ломать игру. Такие проверки называются модульными тестами, и они ловят баги за секунды, тогда как ручная проверка через геймплей съедает минуты на каждый случай. Чем больше игровой логики ты отделяешь от MonoBehaviour и движка, тем большую её часть можно покрыть быстрыми автоматическими тестами — и тем спокойнее ты спишь перед релизом.

Проверьте себя
1. Зачем при уроне писать hp = max(0, hp - amount)?
AДля красоты
BЧтобы здоровье не ушло в отрицательное значение
CЧтобы ускорить игру
DЭто требование Unity
2. Почему систему здоровья лучше делать единым компонентом Health?
AТак быстрее рендер
BЕго можно переиспользовать и героем, и врагами, не дублируя логику
CЭто обязательно по правилам C#
DЧтобы было больше скриптов