Recursivity для DragonECS
November 12, 2024 · View on GitHub
Recursivity для DragonECS
| Readme Languages: | ||
|
Русский |
English(WIP) |
|
Упрощает обработку событий симулируя поведение рекурсии, но в рамках стандартной структуры ECS. Гарантирует для событий которые должны быть обработаны в рамках одного кадра, что они будут обработаны всеми системами ровно один раз, в независимости от места появления.
Warning
Проект в стадии разработки. API может меняться.
Оглавление
Установка
Семантика версионирования - Открыть
Окружение
Обязательные требования:
- Зависимость: DragonECS
- Минимальная версия C# 7.3;
Опционально:
- Игровые движки с C#: Unity, Godot, MonoGame и т.д.
Протестировано:
- Unity: Минимальная версия 2020.1.0;
Установка для Unity
Поддерживается установка в виде Unity-модуля в при помощи добавления git-URL в PackageManager или ручного добавления в Packages/manifest.json:
https://github.com/DCFApixels/DragonECS-Recursivity.git
-
В виде исходников
Пакет так же может быть добавлен в проект в виде исходников.
Инициализация
Для обработки событий используется процесс IOn<T>.ToRun() где T это тип компонента-события. Процесс IOn<T>.ToRun() контролирует специальная система, которую необходимо инициализировать в пайплайне.
_world = new EcsDefaultWorld();
_pipeline = EcsPipeline.New()
// ...
// Инициализация системы контролирующей процесс IOn<DamageEvent>.ToRun().
// Аргумент maxLoops устанавливает лимит на количество выполнений за один кадр.
.AddOn<DamageEvent>(maxLoops: 100)
// Добавление систем обрабатывающих этот процесс
.Add(new SomeDamageSystem())
.Add(new SomeReturnDamageAbilitySystem())
// ...
.Inject(_world)
.BuildAndInit();
За запуск всех систем которые выполняются рекурсивно, включая управляющих процессом
IOn<T>.ToRun(), ответственна система рекурсии. По умолчанию система рекурсии добавляется на слойEcsRecursivityConsts.RECURSIVE_LAYER, а слойEcsRecursivityConsts.RECURSIVE_LAYERвставляется послеEcsConsts.BASIC_LAYER.
Обработка событий
Системы с IOn<T>.ToRun() будут выполняться до тех пор пока в мире остается хотя бы один компонент-событие T.
С точки зрения замедления производительности влияние не высоко по нескольким причинам:
- Системы с
IOn<T>.ToRun()не выполняются вовсе если в мире нет ни одного компонентаT. - В инициализации
.AddOn<DamageEvent>(maxLoops)Можно выставить лимит(maxLoops) вызоваIOn<T>.ToRun(), тогда при достижении лимита, оставшиеся события будут обработаны в следующем кадре.
Имеется защита от бесконечного зацикливания в виде глобального лимита в
100_000повторений.
Ниже приведен пример системы обрабатывающей события. В примере реализована система применения урона к здоровью и система способности возврата урона атакующему, что-то вроде шипов.
Используемые в примере компоненты
using DCFApixels.DragonECS;
public struct Health : IEcsComponent
{
public float points;
}
public struct DamageEvent : IEcsComponent
{
public entlong source;
public entlong target;
public float points;
}
public struct ReturnDamageAbility : IEcsComponent
{
public float multiplier;
}
Этот пример имеет некоторые проблемы, но как пример достаточно нагляден для понимания работы.
// Система которая применяет полученный урон к здоровью.
public class SomeApplyDamageSystem : IOn<DamageEvent>, IEcsInject<EcsDefaultWorld>
{
class EventAspect : EcsAspect
{
public EcsPool<DamageEvent> damageEvents = Inc;
}
class Aspect : EcsAspect
{
public EcsPool<Health> healths = Inc;
}
EcsDefaultWorld _world;
// targetEntities содержит все сущности с компонентом DamageEvent.
// Сущности из этого же списка в конце цикла будут автоматически отчищены от компонента DamageEvent.
void IOn<DamageEvent>.ToRun(EcsSpan targetEntities)
{
var a = _world.GetAspect<Aspect>();
// Итерироваться нужно по targetEntities,
// так можно гарантировать что системы будут обрабатывать каждое событие один раз.
foreach (var ee in targetEntities.Where(out EventAspect ea))
{
ref var damageEvent = ref ea.damageEvents.Get(ee);
// Извлечение ID сущности с проверкой что она не была удалена.
// И проверка на соответствие аспекту Aspect.
if (damageEvent.target.TryGetID(out int e) &&
_world.IsMatchesMask(a, e))
{
ref var health = ref a.healths.Get(e);
health.points -= damageEvent.points;
}
}
}
public void Inject(EcsDefaultWorld obj) => _world = obj;
}
// Система которая делает возвратный урон.
public class SomeReturnDamageAbilitySystem : IOn<DamageEvent>, IEcsInject<EcsDefaultWorld>
{
class EventAspect : EcsAspect
{
public EcsPool<DamageEvent> damageEvents = Inc;
}
class Aspect : EcsAspect
{
public EcsPool<ReturnDamageAbility> returnDamageAbilities = Inc;
}
EcsDefaultWorld _world;
void IOn<DamageEvent>.ToRun(EcsSpan targetEntities)
{
var a = _world.GetAspect<Aspect>();
// Итерируемся по targetEntities.
foreach (var ee in targetEntities.Where(out EventAspect ea))
{
ref var damageEvent = ref ea.damageEvents.Get(ee);
if (damageEvent.target.TryGetID(out int targetE) &&
damageEvent.source.TryGetID(out int sourceE) &&
_world.IsMatchesMask(a, targetE))
{
ref var returnDamageAbility = ref a.returnDamageAbilities.Get(targetE);
// Создание события возвратного урона,
// которое будет обработано в следующем цикле.
int newEE = _world.NewEntity(ea);
ref var newDamageEvent = ref ea.damageEvents.Get(newEE);
newDamageEvent.target = damageEvent.source;
newDamageEvent.source = damageEvent.target;
newDamageEvent.points = damageEvent.points * returnDamageAbility.multiplier;
}
}
}
public void Inject(EcsDefaultWorld obj) => _world = obj;
}