Осваиваем ECS evolved в Defold | Часть 1 | Сущности, фрагменты, компоненты

Я также записал видео на эту тему:
[YouTube]
[VK Видео]

Что такое ECS?
ECS или же (Entity-Component-System) — это архитектурный паттерн проектирования программы, овладев которым, вы сможете создавать масштабируемые игровые системы.

Меня как начинающего игрового разработчика пугает документация и особенно высокие отдалённые абстракции, мне подавай наглядные примеры, чтобы я из них мог извлечь крупицу понимания того, как работать с той или иной технологией.

Поэтому цель этого урока — прийти к понимания того, как пользоваться ECS Evolved в Defold. И как научиться мыслить этим шаблоном. И если после этого урока вы сможете что-то понять из темы ECS, я буду только рад. А если нет, дайте знать, где возникли у вас вопросы.


Как это всё будет происходить в ECS

Для начала скачаем модуль evolved с GitHub и добавим в какую-нибудь папку:
image

Создадим игровой скрипт, например, example_1.script.
Затем импортируем модуль evolved в ваш скрипт:

local evolved = require('modules.evolved')

Давайте рассмотрим три схожих примера:

Пример 1
local evolved = require("modules.evolved")

function init(self)
	-- Создаём фрагменты
	self.position = evolved.id()
	self.velocity = evolved.id() 
	self.go_url = evolved.id() 
	self.player_tag = evolved.id()

	-- Создаём сущность с помощью builder
	self.player_e = evolved.builder()
	:set(self.position, vmath.vector3(150, 300, 0))
	:set(self.velocity, vmath.vector3(150, 0, 0))
	:set(self.go_url, go.get_id())
	:set(self.player_tag, true)
	:spawn()

	print("-------------------")
	print("Компоненты сущности")
	print("-------------------")
	print("URL сущности: " .. evolved.get(self.player_e, self.go_url))
	print("Позиция сущности: " .. evolved.get(self.player_e, self.position))
	print("Скорость сущности: " .. evolved.get(self.player_e, self.velocity))
	print("Сущность Игрок? " .. tostring(evolved.get(self.player_e, self.player_tag)))
end
Пример 2
local evolved = require("modules.evolved")

function init(self)
	-- Создаём фрагменты
	self.position = evolved.id()
	self.velocity = evolved.id() 
	self.go_url = evolved.id() 
	self.player_tag = evolved.id()

	-- Создаём сущность с помощью builder
	self.player_e = evolved.builder()
	:set(self.position, vmath.vector3(150, 300, 0))
	:set(self.velocity, vmath.vector3(150, 0, 0))
	:set(self.go_url, go.get_id("/player"))
	:set(self.player_tag, true)
	:spawn()

	print("-------------------")
	print("Компоненты сущности")
	print("-------------------")
	print("URL сущности: " .. evolved.get(self.player_e, self.go_url))
	print("Позиция сущности: " .. evolved.get(self.player_e, self.position))
	print("Скорость сущности: " .. evolved.get(self.player_e, self.velocity))
	print("Сущность Игрок? " .. tostring(evolved.get(self.player_e, self.player_tag)))

end
Пример 3
local evolved = require("modules.evolved")

function init(self)

	-- Создаём фрагменты
	self.position = evolved.id()
	self.velocity = evolved.id() 
	self.go_url = evolved.id() 
	self.player_tag = evolved.id()

	-- Создаём сущность с помощью builder
	self.player_e = evolved.builder()
	:set(self.position, vmath.vector3(150, 300, 0))
	:set(self.velocity, vmath.vector3(150, 0, 0))
	:set(self.go_url, go.get_id("/player"))
	:set(self.player_tag, true)
	:spawn()

	print("-------------------")
	print("Компоненты сущности")
	print("-------------------")
	print("URL сущности: " .. evolved.get(self.player_e, self.go_url))
	print("Позиция сущности: " .. evolved.get(self.player_e, self.position))
	print("Скорость сущности: " .. evolved.get(self.player_e, self.velocity))
	print("Сущность Игрок? " .. tostring(evolved.get(self.player_e, self.player_tag)))


	self.enemy_e = evolved.builder()
	:set(self.position, vmath.vector3(550, 600, 0))
	:set(self.velocity, vmath.vector3(50, 0, 0))
	:set(self.go_url, go.get_id("/enemy"))
	:set(self.player_tag, false)
	:spawn()
	
	print("-------------------")
	print("Компоненты сущности")
	print("-------------------")
	print("URL сущности: " .. evolved.get(self.enemy_e, self.go_url))
	print("Позиция сущности: " .. evolved.get(self.enemy_e, self.position))
	print("Скорость сущности: " .. evolved.get(self.enemy_e, self.velocity))
	print("Сущность Игрок? " .. tostring(evolved.get(self.enemy_e, self.player_tag)))
end

Как вы можете заметить, во всех трёх примерах сущности состоят из фрагментов, которые описывают тип компонентов, а компоненты в свою очередь имеет определённые значения:

СУЩНОСТЬ (Entity)  
  ↓ содержит  
ФРАГМЕНТЫ (Fragments) ← ID/типы компонентов  
  ↓ имеют значения  
КОМПОНЕНТЫ (Components) ← данные (числа, векторы, boolean...)

Это похоже на человека, который является сущностью.
В свою очередь, этот человек (сущность) имеет фрагменты (компоненты): рост, вес, цвет кожи и т.д.
Компоненты в свою очередь имеют значения: рост = 180, вес = 67, цвет кожи = чёрный.

evolved.id() создаёт универсальные идентификаторы, которые используются и для фрагментов, и для сущностей.

Создание фрагментов (типов компонентов):

self.position = evolved.id()     -- ID для хранения координат
self.velocity = evolved.id()     -- ID для хранения скорости  
self.go_url = evolved.id()       -- ID для хранения ссылки на GO
self.player_tag = evolved.id()   -- ID для маркера "игрок"

Каждый evolved.id() = уникальный ключ (40-битное число) для типа данных. Один ID position используется для всех объектов с позицией в игре.

Создание сущности через Builder

self.player_e = evolved.builder()
    :set(self.position, vmath.vector3(150, 300, 0))  -- координаты
    :set(self.velocity, vmath.vector3(150, 0, 0))    -- скорость вправо
    :set(self.go_url, go.get_id())                   -- текущий GameObject
    :set(self.player_tag, true)                      -- это игрок
    :spawn()                                         -- создать сущность!

Builder — цепочка методов :set(фрагмент, значение) . :spawn() фиксирует сущность в ECS-движке и возвращает её ID (self.player_e )

Отладочный вывод (проверка компонентов)

print("URL сущности: " .. evolved.get(self.player_e, self.go_url))

evolved.get(entity, fragment) читает значение конкретного фрагмента у конкретной сущности.

Вывод в консоли (один из примеров):