---
# System prepended metadata

title: Введение

---

Это руководство является ставит перед собой цель дать как можно большие знания об кодбазе СС14 как таковой, так и об аспектах ECS и в целом нетворкинга.
Тут не содержится никакая информация об с-шарпе как таковом, так как документация майков вполне неплохо справляется с целью обучения этому языку, его принципам, ООП. Можно считать, что это руководство способно дополнить его и дать знания, которые помогут помимо банального знания синтаксиса и парадигмы ООП, будут содержать в себе информацию реально полезную для разработки сски.

# Введение

Стоит начать с самых основ; СС14 разработана на самописном движке робаст тулбокс (далее РТ), использует си-шарп и парадигму ECS. ECS это достаточно простая концепция, разбивающая весь проект на три составные части:

1. E - entities, сущности
2. C - components, компоненты
3. S - systems, системы

Позже мы разберём их отдельно подробнее, а пока хочу обратить внимание на то, как именно разделён РТ и сама игра (в дальнейшем вместо "самой игры" я буду использовать слово контент, так как помимо удобства в написании, этот вариант ещё и единственно верное определение этой самой "игры" как таковой). Движок разработан учитывая необходимость в нетворкинге (разберём его отдельно позже), являющимся основной причиной разделения движка, а следовательно, и самой игры на две составные части - клиент и сервер. Помимо этого существует также шаред, это часть кода, которая может проигрываться как на клиенте, так и на сервере.
РТ использует сериализацию при помощи yaml, да-да, те самые прототипы это лишь набор сериализованных сущностей и компонентов, не правда-ли чудесно?

# ECS

Давайте разберём ранее упомянутую ECS подробнее;

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

Приведу пример; существует сущность. Она не содержит в себе никаких компонентов, кроме пары технических. Она не видна глазу, она не способна двигаться, она не способна разрушаться. Но стоит нам добавить `TransformComponent` ответственный за местоположение сущности в мире и у неё теперь есть местоположение! Всё это благодоря тому, что `TransformSystem` просчитывает логику перемещения объекта в мире, а сам `TransformComponent` хранит в себе всю необходимую систему для работы системы, локальные координаты, координаты карты, грида, самой сущности.
Карту с этой сущностью можно сериализовать (сохранить), сохранив информацию об компонентах и самой сущности.

---

## Сущности

Сущности в РТ представлены цифровым значением (uid).

---

## Компоненты

Компоненты являются классами, и имею более сложную структуру, чем сущности. Во первых, компонент должен наследоваться от класса компонента:

``` c#

public sealed partial class ExampleComponent : Component

```

Во вторых, для того, чтобы он действительно считался компонентом ему требуется соответствующий атрибьют - `RegisterComponent`:

``` c#

[RegisterComponent]
public sealed partial class ExampleComponent : Component

```

### Тело компонента

Стоит дать несколько уточнений по атрибьютам и самим полям.
Для начала, добавим вышепреведённому компоненту поле, например, с float.

``` c#

[RegisterComponent]
public sealed partial class ExampleComponent : Component
{
    public float ExampleField = 5;
}

```

Мы - молодцы; компонент получил поле, которое может меняться системами.
Добавим к нему атрибьют ViewVariables. Он позволит просматривать значения в панели этих самых ViewVariables прямо в игре.

``` c#

[RegisterComponent]
public sealed partial class ExampleComponent : Component
{
    [ViewVariables]
    public float ExampleField = 5;
}

```

Но это только начало; важнейший эллемент в компонентах - поля с атрибьютом **`DataField`**.
Что они делают? Они позволяют сериализовывать поля. Как и было сказано ранее - сериализация происходит в yaml. Это значит, что поля, которые не содержат `DataField` сохраняться в yaml не могут, следовательно в прототипах (которые фактически являются уже сохранёнными сущностями-заготовками) мы изменять значения таких полей не сможем, ровно также как и не сможем сохранять значения этих полей вместе с картой.
Т.е. если у вас, например, есть прототип холодильника, и у него есть компонент, отвечающий за его температуру, а поля в этом компоненте не сериализуются, то при попытке сохранения этого холодильника вместе с картой, значения в этом поле компонента будут сбрасываться к обычным.
Тоже самое и в самих прототипах - сколько бы вы не пытались менять значения в самом yaml - в игре изменений не будет.
Важно также уточнить неверное использование аргументов, предоставляемых атрибьютуту, например:

``` c#

[RegisterComponent]
public sealed partial class ExampleComponent : Component
{
    [DataField(exampleField)]
    public float ExampleField = 5;
}

```

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

``` c#

[RegisterComponent]
public sealed partial class ExampleComponent : Component
{
    [DataField]
    public float ExampleField = 5;
}

```

``` yaml

- type: Example
  exampleField: 10

```

Стоит также уточнить важную детать - датафилд содержит в себе атрибьют `ViewVariables`, поэтому его указание, например: 

``` c#

[RegisterComponent]
public sealed partial class ExampleComponent : Component
{
    [DataField, ViewVariables]
    public float ExampleField = 5;
}

```

Является ошибкой.

---

Я так-же хочу поговорить об самих полях. Несмотря на то, что я говорил о том, что в этом руководстве не будет советов по си-шарп, я просто обязан указать несколько деталей, без которых этот блок будет неполным, так как некоторые путаются в этом или не могут сформулировать запрос, чтобы найти ответ.
Речь пойдёт об значениях полей по умолчанию; в отличии от переменных внутри методов поле класса, может быть объявлено без присваивания, и тогда оно примет значение по-умолчанию. Так например в

``` c#

[RegisterComponent]
public sealed partial class ExampleComponent : Component
{
    [DataField]
    public float ExampleField;
}

```

поле `ExampleField` будет не null, а значением float по-умолчанию, т.е. будет равно 0.
А вот в

``` c#

[RegisterComponent]
public sealed partial class ExampleComponent : Component
{
    [DataField]
    public float? ExampleField;
}

```

Оно уже будет null.

---

## Системы

Если компоненты и сущности это просто контейнеры для хранения информации, то системы это вещь хранящая логику для их работы. 
Первое, и самое важное - все системы должны наслодоваться от базового класса - `EntitySystem`

``` c#
public sealed class ExampleSystem : EntitySystem
```

Он содержит полезные методы для перегрузки, например `Update()` и методы `EntityManager`, вот несколько из них:

``` c#

// Создаёт сущность с определённым прототипом в нуллспейсе
Spawn("прототип сущности")

// Удаляет сущность с определённой uid.
Del("uid сущности")

// Проверяет, есть-ли у сущности компонент.
HasComp<"тип компонента">("uid сущности")

// Проверяет, есть-ли у сущности компонент, и возвращет его с out.
TryComp<"тип компонента">("uid сущности", out "компонент (если он есть)")

// Проверяет, есть-ли у сущности компонент
// Использование этого метода предпочтительнее, если у нас уже есть переменная с типом необходимого нам компонента.
Resolve("uid сущности", ref "переменная компонента, существование которого должно быть доказано")

```

Это далеко не все прокси методы для работы в `EntitySystem`. В качестве примера приведу написание простейшего метода:

``` c#
private void ExampleMethod(EntityUid uid, ExampleComponent? comp)
{
    if (!Resolve(uid, ref comp))
        return;

    if (HasComp<ItemComponent>(uid) 
        && TryComp<ArmorComponent>(uid, out var armor))
    {
        Spawn("прототип для спавна", Transform(uid).Coordinates);
        RemComp(uid, armor);
    }
}
```

Это метод, принимающий в качестве аргумента `uid` сущности и компонент (опционально).
Он заресолвит компонент, который является нулл на нашей сущности и присвоит ему значение компонента с неё, затем проверит, есть-ли у сущности компонент предмета, и брони (при этом компонент брони он так-же ещё и вернёт, что может быть полезно), а затем создаст новую сущность с нужным нам прототипом на месте нашей.
Стоит уточнить одну деталь: аргументы метода - использование `EntityUid` и `Component` по-отдельности является плохой практикой, так как есть более современный способ, способный сократить это в одну переменную - тип `Entity<Component>`. В нашем случае, с его использованием метод будет выглядеть как:

``` c#
private void ExampleMethod(Entity<ExampleComponent?> ent)
{
    if (!Resolve(ent, ref ent.Comp))
        return;

    if (HasComp<ItemComponent>(ent) 
        && TryComp<ArmorComponent>(ent, out var armor))
    {
        Spawn("прототип для спавна", Transform(ent).Coordinates);
        RemComp(ent, armor);
    }
}
```

Как понятно из метода - разделяется он обратно на `EntityUid` и `Component` при помощи расширений `ent.Owner` и `ent.Comp` где `Owner` как-бы указывает на владельца компонентов. Но в большинстве случаев получение конкретно `uid` - не требуется, так как сам `Entity<T>` прекрасно справляется с этой ролью, только если не будет требоваться предоставление uid с другими аргументами, как например в этом случае:

``` c#
private void ExampleMethod(Entity<ExampleComponent?> ent)
{
    if (!Resolve(ent, ref ent.Comp))
        return;

    if (HasComp<ItemComponent>(ent) 
        && TryComp<ArmorComponent>(ent, out var armor))
    {
        Spawn("прототип для спавна", Transform(ent).Coordinates);
        RemComp(ent, armor);
        ExampleVoid(ent.Owner); // Мы предостовляем ent.Owner вместо ent, потому-что они используют разные компоненты. Такая-же ситуация с null и не null компонентами.
    }
}

private void ExampleVoid(Entity<ItemComponent?> ent)
{
    if (!Resolve(ent, ref ent.Comp))
        return;
}
```

Достаточно частой ошибкой является использование методов из `EntityManager` напрямую, вместо использования их прокси-версий, любезно предоставленных в `EntitySystem` по-умолчанию. Например `EntityManager.HasComponent` вместо `HasComp`.

## Не-ECS
Несмотря на то, что игра использует парадигму ECS, далеко не всё является сущностями, компонентами или системами для работы с сущностями.
Достаточно просто зайти в папку с прототипами и увидеть то, насколько-же много разнообразных прототипов. Прототип это **уже** сериализиванные (сохранённые) в yml данные, например, в случае с сущностями их прототипы хранят их айди, компоненты, их имя, родительские сущности и т.д. В случае с технологиями, то, какие предметы можно получить, открыв их, айди технологии, иконку, локализируемое название. Но не смотря на всё это, работа с любыми сохранёнными в прототипе данными в `EntitySystem` более чем возможна.
Также со структурой ECS обладают крайне малой связью интерфейсы. Их необходимо разбирать отдельно, так как это сам по-себе один из самых затруднительных аспектов в кодбазе.

# Предугадывание на стороне клиента
Поговорим о достаточно непопулярной в ру-сегменте теме - предугадывании на стороне клиента. Что-же это такое?
Стоит вернуться к самому началу и вспомнить, то, что игра и движок разделяются на три части:
**Server** (Серверная) - основной костяк игры, именно на сервере проигрывается основной пласт контента
**Client** (Клиентская) - отвечает в основном за визуал и логику интерфейсов. Именно дублирование кода на сторону клиента позволяет предугадыванию существовать.
**Shared** (Шаред) - код, дублирующийся и на сервер, и на клиент.
Огромная час