В настоящее время я создаю небольшой хобби-проект, чтобы вернуться к разработке игр, и я решил структурировать свои сущности, используя ECS (Entity Component System). Эта реализация ECS имеет следующую структуру:
- Entity : В моем случае это уникальный
int
идентификатор, который используется в качестве ключа к списку компонентов. - Компонент : содержит только данные, например,
Position
компонент содержит координаты andx
иy
, аMovement
компонент содержит переменные aspeed
иdirection
. - Система : Рукоятки компоненты, например , он принимает
Position
иMovement
компоненты и добавляетspeed
иdirection
к позиции наx
иy
координаты.
Это прекрасно работает, но теперь я хочу внедрить сценарии в свои игры в форме языка сценариев. В предыдущих проектах я использовал ООП-реализацию игровых объектов, что означало, что сценарии были довольно просты. Например, простой скрипт может выглядеть примерно так:
function start()
local future = entity:moveTo(pos1)
wait(future)
local response = entity:showDialog(dialog1)
if wait(response) == 1 then
local itemStack = entity:getInventory():removeItemByName("apple", 1)
world:getPlayer():getInventory():addItemStack(itemStack)
else
entity:setBehavior(world:getPlayer(), BEHAVIOR_HOSTILE)
end
end
Однако при использовании ECS сам объект не имеет никаких функций, таких как moveTo
или getInventory
, вместо этого приведенный выше скрипт, написанный в стиле ECS, будет выглядеть примерно так:
function start()
local movement = world:getComponent(MOVEMENT, entity)
movement:moveTo(pos1)
local position = world:getComponent(POSITION, entity)
local future = Future:untilEquals(position.pos, pos1)
wait(future)
local dialogComp = world:getComponent(DIALOG, entity)
local response = dialogComp:showDialog(dialog1)
if wait(response) == 1 then
local entityInventory = world:getComponent(INVENTORY, entity)
local playerInventory = world:getComponent(INVENTORY, world:getPlayer())
local itemStack = entityInventory:removeItemByName("apple", 1)
playerInventory:addItemStack(itemStack)
else
local entityBehavior = world:getComponent(BEHAVIOR, entity)
local playerBehavior = world:getComponent(BEHAVIOR, world:getPlayer())
entityBehavior:set(playerBehavior, BEHAVIOR_HOSTILE)
end
end
Это намного более многословно по сравнению с версией ООП, что нежелательно, когда сценарии ориентированы в основном на непрограммистов (игроков в игре).
Одним из решений было бы иметь некоторый объект-обертку, который инкапсулирует Entity
и предоставляет функции, такие как moveTo
непосредственно, и обрабатывает все остальное внутри, хотя такое решение кажется неоптимальным, поскольку для охвата всех компонентов и каждого требуется много работы. При добавлении нового компонента вам потребуется заменить объект-оболочку новыми функциями.
Всем разработчикам игр, которые ранее использовали скрипты в ECS - как вы это сделали? Основное внимание здесь уделяется удобству использования для конечного пользователя с минимальными затратами на «обслуживание» (желательно, чтобы вам не приходилось менять его каждый раз, когда вы добавляете компонент).
moveTo
метод как часть базовой системы в вашем случае использования, например MovementSystem? Таким образом, вы можете не только использовать его в сценариях, которые вы пишете, но вы также можете использовать его как часть кода C ++ там, где вам это нужно. Так что да, вам придется раскрывать новые методы по мере добавления новых систем, но этого следует ожидать, так как эти системы в любом случае вводят совершенно новое поведение.
System
классов / классов, чтобы компоненты оставались структурами данных.