An open API service indexing awesome lists of open source software.

https://github.com/teoadal/hexecs

Это маленький движок логики, имплементирующий ECS (Entity-Component-System), который весьма удобен для создания игровой логики на C#
https://github.com/teoadal/hexecs

c-sharp ecs entity-component-system game game-engine gamedev

Last synced: 5 months ago
JSON representation

Это маленький движок логики, имплементирующий ECS (Entity-Component-System), который весьма удобен для создания игровой логики на C#

Awesome Lists containing this project

README

          

[![.NET Core](https://github.com/teoadal/Hexecs/actions/workflows/dotnet.yaml/badge.svg?branch=master)](https://github.com/teoadal/Hexecs/actions/workflows/dotnet.yaml)
[![NuGet](https://img.shields.io/nuget/v/Hexecs.svg)](https://www.nuget.org/packages/Hexecs)
[![NuGet](https://img.shields.io/nuget/dt/Hexecs.svg)](https://www.nuget.org/packages/Hexecs)
[![codecov](https://codecov.io/gh/teoadal/Hexecs/branch/master/graph/badge.svg?token=8L4HN9FAIV)](https://codecov.io/gh/teoadal/Hexecs)
[![CodeFactor](https://www.codefactor.io/repository/github/teoadal/Hexecs/badge)](https://www.codefactor.io/repository/github/teoadal/Hexecs)

# HexECS

Привет! Это движок, имплементирующий ECS. Тут ты можешь делать многое, к чему и так привык.
При этом, ты можешь чуть больше, чем обычные ECS, так как помимо ECS в библиотеке есть встроенный CQRS и DI.
CQRS в связке с ECS представляется мне максимально подходящей штукой для управления состоянием сущностей через системы.
DI необходим для того, чтобы всё это работало удобно.

## Базовые возможности

Так как всё в этом мире завязано на "мир" (`World`), то мы начнём с его создания.
```csharp
var world = new WorldBuilder()
.DefaultParallelWorker(4) // чтобы обрабатывать сущности в 4 потока
.DebugWorld() // чтобы получать больше информации в Debug-режиме
.CreateLogger(logger => logger.UseConsoleSink()) // чтобы логи выводились в консоль
.Build();
```
Этот код создаёт сконфигурированный объект World. Так как библиотека AOT-friendly, тут нет сканеров assembly для регистрации отдельных компонентов. Всё нужно регистрировать вручную.

### Создание компонента

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

```csharp
public struct FlyComponent(int current, int max) : IActorComponent { // необходимо указать маркерный интерфейс
public int CurrentSpeed = current;
public int MaxSpeed = max;
}
```
Компоненты должны быть `struct`, чтобы всё в вашем мире могло работать эффективно и быстро.
Например, обрабатывать в системах или обработчиках шины сообщений.

### Создание сущности

В этом мире есть две сущности - Actor'ы и Asset'ы.
- **Актёры** - это динамичные изменяемые сущности, которые могут создаваться и удаляться, и которым можно добавлять и удалять компоненты.
- **Ассеты** - это сущности, которые нельзя изменить, представляющие из себя настройки мира, но которые также имеют компоненты.
Единственное отличие - их нельзя создавать в процессе работы, так как они, по задумке, представляют из себя ресурсы, на основе которых будут создаваться актёры.

Создать актёра можно следующим образом:
```csharp
Actor actor = world.Actors.CreateActor();
actor.Add(new FlyComponent(10, 20));
Actor typedActor = actor.As();
```

В последней строчку был создан т.н. "типизированный актёр" или актёр, в котором точно есть летающий компонент.
Эта щепотка типизации очень важна в случаях, когда вам хотелось бы знать заранее - действительно ли актёр содержит нужный компонент. Например, в шине сообщений.

Структура `Actor` и `Actor` предоставляют удобную обёртку для взаимодействия с актёром. Актёр всегда знает к какому `ActorContext` он принадлежит и просто проксирует методы к контексту, чтобы программисту было проще.

Более привычным типом entity для классических ECS будет являться содержимое поля `Actor.Id`, которое содержит в себе идентификатор актёра.
Если вам удобнее работать с ними, можно сделать так:

```csharp
ActorId actorId = typedActor;
```

### Добавление компонентов

Созданный актёр может быть создан с использованием метода `world.Actors.Create()`.
В этом случае он не будет обладать компонентом `FlyComponent`. Если мы не добавили его ранее, то добавим его:
```csharp
actor.Add(new FlyComponent { CurrentSpeed = 5, MaxSpeed = 7 });
```
То, что компонент теперь принадлежит актёру можно легко проверить с помощью метода:
```csharp
actor.Has(); // it's true!
```

Поздравляю, мы создали компонент и теперь можем его редактировать.
Для этого достаточно получить ссылку на компонент и отредактировать его.
```csharp
ref var component = ref actor.Get();
component.Current = 15;
```

Однако, в данном случае никто никогда не узнает о том, что было что-то изменено.
Иногда это важно, например, чтобы держать кэш позиций некоторых актёров.

Чтобы все точно узнали, что были изменения, нужно использовать специальный метод:

```csharp
var component = actor.Get(); // копируем компонент
component.Current = 15; // изменяем значение копии
actor.Update(component); // в этот момент все узнают, что что-то изменилось
```

### Удаление компонентов

Тут всё тоже предельно просто. Представим, что у нас есть компонент `Pilot` (на самом деле структура `PilotComponent : IActorComponent`), который мы уже добавили к актёру-самолёту.
После того как пилот захотел спать, он покинет самолёт следующим способом:
```csharp
actor.Remove();
```
Если пилота там не было, метод вернёт `false` и мы сможем обработать это вопиющее нарушение правил полётов.
Если компонент был, то метод вернёт `true`, а значит мы можем быть уверенными, что пассажиры в безопасности.

## Дочерние элементы

Пассажиры! Их тоже можно создать примерно так:

```csharp
Asset passengerAsset = world.GetAsset();
Actor passenger = world.Actor.BuildActor(passengerAsset, Arg.Rent("plane", planeActor))
```

Мы находим ассет пассажира (вспомним, что у него есть компоненты).
Допустим, что он всего один.
На второй строке, с помощью зарегистрированных `IActorBuilder` (имплементации "строителей" актёров по компонентам ассета), буквательно создаём актёра.
Кстати, если нам будет нужно, мы сможем проверить, по какому ассету был построен актёр, запросив `actor.TryGetAsset`.

После создания пассажиров, мы можем в прямом смысле посадить их на самолёт.
Для этого нужно воспользоваться следующим методом:

```csharp
Actor plane = ...;
Actor passenger = ...;
plane.AddChild(passenger);
```

Если повторить это много раз, то в самолёте будут сидеть множество пассажиров (в зависимости от типа самолёта, конечно).

## Системы

У нас же ecS, а значит у нас есть системы.
Системы - это классы, которые обрабатывают компоненты актёров.
Они могут быть зарегистрированы в мире и запущены для обработки компонентов.