No momento, estou criando um pequeno projeto de hobby para voltar ao desenvolvimento de jogos e decidi estruturar minhas entidades usando um ECS (Entity Component System). Esta implementação de um ECS está estruturada da seguinte forma:
- Entidade : no meu caso, é um
intidentificador exclusivo usado como chave para uma lista de componentes. - Componente : Armazena apenas dados, por exemplo, o
Positioncomponente possui umxeycoordenar, e oMovementcomponente contém umspeededirectionvariável. - Sistema : componentes canetas, por exemplo, que leva os
PositioneMovementcomponentes e adiciona ospeededirectionpara a posição dexeycoordenadas.
Isso funciona bem, mas agora desejo implementar scripts nos meus jogos, na forma de uma linguagem de script. Em projetos anteriores, usei uma implementação OOP de objetos de jogo, o que significava que o script era bastante direto. Por exemplo, um script simples pode ser algo como isto:
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
No entanto, ao usar um ECS, a própria entidade não possui funções como , moveToou seja getInventory, o script acima, escrito no estilo ECS, seria algo como isto:
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
Isso é muito mais detalhado se comparado à versão OOP, o que não é desejável quando o script é voltado principalmente para não programadores (jogadores do jogo).
Uma solução seria ter algum tipo de objeto wrapper que encapsule Entitye forneça funções como moveTodiretamente e lide com o resto internamente, embora essa solução pareça subótima, pois é preciso muito trabalho para cobrir todos os componentes e todos os Quando um novo componente é adicionado, você precisa alterar o objeto wrapper com novas funções.
Para todos os desenvolvedores de jogos que já implementaram scripts em um ECS antes - como você fez isso? O foco principal aqui é a usabilidade do usuário final, com o menor custo possível de "manutenção" (de preferência, você não precisa alterá-lo toda vez que adicionar um componente).
moveTométodo como parte do sistema subjacente no seu caso de uso, por exemplo, MovementSystem? Dessa forma, não somente então você poderá usá-lo nos scripts que escrever, mas também poderá usá-lo como parte do código C ++, sempre que precisar. Portanto, sim, você terá que expor novos métodos à medida que novos sistemas são adicionados, mas isso é de se esperar, pois seu comportamento totalmente novo é introduzido de qualquer maneira.
System(s) classe (s) para permitir que os componentes permaneçam estruturas de dados.