Приведи пример интересной функции, которую реализовывал
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример интересной функции: Динамическая система квестов с нелинейными условиями и визуальным редактором
Одной из самых интересных функций, которые я реализовывал в 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.
Эта функция не только оживила игровой мир, предоставив игроку настоящую свободу, но и значительно ускорила работу команды дизайнеров, дав им мощный и наглядный инструмент для прототипирования и реализации нарратива.