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#
- Host: GitHub
- URL: https://github.com/teoadal/hexecs
- Owner: teoadal
- License: mit
- Created: 2025-06-04T11:46:10.000Z (about 1 year ago)
- Default Branch: master
- Last Pushed: 2025-06-05T08:15:39.000Z (about 1 year ago)
- Last Synced: 2025-07-05T16:22:44.845Z (11 months ago)
- Topics: c-sharp, ecs, entity-component-system, game, game-engine, gamedev
- Language: C#
- Homepage:
- Size: 251 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/teoadal/Hexecs/actions/workflows/dotnet.yaml)
[](https://www.nuget.org/packages/Hexecs)
[](https://www.nuget.org/packages/Hexecs)
[](https://codecov.io/gh/teoadal/Hexecs)
[](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, а значит у нас есть системы.
Системы - это классы, которые обрабатывают компоненты актёров.
Они могут быть зарегистрированы в мире и запущены для обработки компонентов.