← Назад к вопросам

Приведи пример интересной функции, которую реализовывал

1.3 Junior🔥 131 комментариев
#Другое

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пример интересной функции: Динамическая система квестов с нелинейными условиями и визуальным редактором

Одной из самых интересных функций, которые я реализовывал в Unity, была динамическая система квестов для RPG-проекта. Основная задача — уйти от статичных "поговори с NPC А, принеси 5 предметов Б" к живым цепочкам задач, где условия выполнения могут меняться в зависимости от игрового контекста, а игрок имеет свободу выбора в методах достижения цели.

Ключевые возможности системы:

  • Нелинейные условия выполнения: Квест можно завершить разными путями (например, украсть ключ, найти обходной путь, убедить стражника или просто сломать дверь).
  • Динамический контекст: Условия могут зависеть от времени суток, репутации игрока, выполненных ранее квестов или даже погоды в игровом мире.
  • Визуальный редактор на основе Node Graph (с использованием xNode или аналогичного): Это позволяло дизайнерам и нарративным дизайнерам создавать и редактировать сложные цепочки квестов без вмешательства программиста.
  • Система слушателей событий (Event-driven): Квестовые цели активировались не только по скриптовым триггерам, но и подписывались на глобальные игровые события.

Архитектура и реализация

Основой стал абстрактный класс QuestObjective, от которого наследовались конкретные типы задач: сбор предметов, разговор с NPC, достижение локации, убийство определенного типа врагов и кастомные скриптовые цели.

public abstract class QuestObjective : ScriptableObject
{
    public string objectiveID;
    public string description;
    public bool isOptional;
    public bool isCompleted { get; protected set; }

    // Событие для уведомления о прогрессе или завершении
    public event Action<QuestObjective> OnObjectiveUpdated;

    public virtual void Initialize(QuestContext context)
    {
        // Базовая инициализация, подписка на события
        isCompleted = false;
    }

    protected virtual void CompleteObjective()
    {
        isCompleted = true;
        OnObjectiveUpdated?.Invoke(this);
    }

    public abstract string GetProgressText();
}

Конкретная реализация для цели "Убить существ" демонстрировала подписку на глобальную систему событий:

public class KillObjective : QuestObjective
{
    public EnemyType targetEnemyType;
    public int requiredAmount;
    private int currentAmount;

    public override void Initialize(QuestContext context)
    {
        base.Initialize(context);
        currentAmount = 0;
        // Подписываемся на глобальное событие "Враг убит"
        GameEvents.OnEnemyDefeated += HandleEnemyDefeated;
    }

    private void HandleEnemyDefeated(EnemyData enemyData)
    {
        if (!isCompleted && enemyData.Type == targetEnemyType)
        {
            currentAmount++;
            OnObjectiveUpdated?.Invoke(this);
            if (currentAmount >= requiredAmount)
            {
                GameEvents.OnEnemyDefeated -= HandleEnemyDefeated; // Отписываемся
                CompleteObjective();
            }
        }
    }

    public override string GetProgressText()
    {
        return $"Убито {currentAmount}/{requiredAmount} {targetEnemyType.DisplayName}";
    }
}

Сердцем системы был QuestManager — синглтон, отвечавший за хранение активных квестов, загрузку данных из ScriptableObject, инициализацию целей и оповещение UI о изменениях.

Визуальный редактор

Для редактора использовался ассет xNode. Узлы (Nodes) представляли собой этапы квеста (начало, диалог, цель, ветвление, завершение), а связи между ними — возможные переходы. В каждом узле через [Input] и [Output] атрибуты определялись порты для соединения. Это позволяло наглядно строить логику, включая циклы и параллельное выполнение задач.

[NodeTint("#FFCC66")] // Цвет узла в редакторе
public class ObjectiveNode : QuestBaseNode
{
    [Input(connectionType = ConnectionType.Override)] public QuestFlow Input;
    public QuestObjective objective;
    [Output(connectionType = ConnectionType.Override)] public QuestFlow OutputSuccess;
    [Output(connectionType = ConnectionType.Override)] public QuestFlow OutputFail;

    public override void Trigger(QuestRunner runner)
    {
        // Менеджер квестов регистрирует цель и начинает её отслеживать
        QuestManager.Instance.ActivateObjective(objective, runner);
    }
}

Вызовы и решения

  • Производительность: Подписка множества целей на глобальные события требовала аккуратного управления — обязательная отписка при завершении квеста или его отмене во избежание утечек памяти.
  • Сериализация состояния: Для сохранения прогресса сложного нелинейного квеста (какие цели выполнены, какой путь выбран) пришлось разработать гибкую систему сериализации на основе уникальных ID узлов графа и словарей с состоянием.
  • Интеграция с UI: Создан отдельный компонент QuestTrackerUI, который динамически обновлял список целей, подписываясь на события OnObjectiveUpdated у QuestManager.

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

Приведи пример интересной функции, которую реализовывал | PrepBro