Здоровье, урон и инвентарь
Почти любая игра считает здоровье и хранит предметы. Эти системы — чистая логика, не зависящая от движка, поэтому их легко понять и протестировать.
Суть: система здоровья хранит текущее и максимальное 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 и движка, тем большую её часть можно покрыть быстрыми автоматическими тестами — и тем спокойнее ты спишь перед релизом.