quinta-feira, 9 de julho de 2015

Entidades, Componentes e Game Engine própria

Conforme dito no último post, desenvolver uma game engine surgiu da necessidade de rodar a mesma lógica no cliente e servidor. Um amigo assumiu a tarefa, mas todos ajudaram um pouco, então falarei de todo o esquema de maneira geral.


Todas as entidades estão dentro de GameEngine, que controla inclusive os updates
Para desenvolver os padrões do jogo, seguimos o modelo de Unity, que trabalha com game objects. Definimos, então, entidades, equivalente a game objects.

Entidades

Cada entidade tem um id, uma chave (pela qual pode ser acessada depois), um transform (posição e ângulo) e EventHandlers. O design utilizado é o de componentes: cada entidade possui vários componentes, que são acoplados.

As entidades podem ainda estar ligadas a outras entidades, e nesse caso, sua posição no mundo é setada em relação a ela. Por isso, foram definidas também subentidades. O player, por exemplo, possui uma barra de vida e vários canhões, e que, como são diferentes do player em si, são subentidades. Os eventos die (morte) e de revive (reviver) também estão relacionados.

Uma entidade genérica, prototipada, segue este modelo:
function Entity(id, key) {
    this.key = key;
    this.id = id;
    this.transform = new Transform(this);
    this.components = new ComponentManager(this);
    this.subentityManager = new SubentityManager(this);
    this._eventHandlers = {};
    this.baseEntity;
},
Entity.prototype.sync = function(transform) {
    //Trigger of sync event here
},
Entity.prototype.destroy = function() {},
Entity.prototype.update = function() {},
Entity.prototype.die = function() {},
// Other functions that trigger events
Componentes

Cada entidade possui vários componentes. Um exemplo é o Player no cliente:

Componentes do Player no cliente
Essa estrutura de componentes é vantajosa em relação a herança, já que reduz o acoplamento das classes (modificar uma classe não causa impacto direto nas demais, já que uma não depende de outra). Cada componente cuida do seu próprio comportamento. Um exemplo é a componente respawn, que controla o tempo de respawn de um player no servidor:
function RespawnComponent(team) {
    this.key = "respawn";
    this._currentRespawnTime = null;
    this._currentTime = null;
    this._teamName = team.name;
};

// Inherit from a generic GameComponent class
RespawnComponent.prototype = Object.create(GameComponent.prototype);
RespawnComponent.prototype.constructor = RespawnComponent;

RespawnComponent.prototype.init = function() {
    // Add events
    this.owner.on('entity.die', this.onEntityDie.bind(this));
    this.owner.on('entity.revive', this.onEntityRevive.bind(this));
};

RespawnComponent.prototype.update = function() {
    // If the player is currently dead, count time to respawn
};

RespawnComponent.prototype.onEntityDie = function() {
    // Start respawn counter
};

RespawnComponent.prototype.onEntityRevive = function() {
    // Ends respawn counter
};
Game Engine

Todas as entidades devem estar inseridas em um contexto. A GameEngine é quem controla tudo. Como é única para cada partida e não deve ser instanciada mais de uma vez, é uma classe Singleton, ou seja, só possui uma instância.

A Game Engine, que é a mesma tanto para o cliente como servidor, trata da física e é o mais genérico possível, podendo ser utilizada inclusive para outro jogo totalmente diferente.

Uma versão "resumida" está representada aqui.
// Singleton class
var GameEngine = (function () {
    var instance;
    function init() {
        // Private properties, functions and physics stuff
        var _world = //receive world from p2
        var _entities = {};
        var _deleteEntities = function() {
            // Delete entities
        };
 /* Physics stuff skipped here */
        return {
            // Public properties and functions
            entities: _entities,
            world: _world,
            gameStep: function() {
                // Runs game step
                // Calls entities update
            },
            addEntity: function(entity, id) {},
            deleteEntity: function(entity) {}
        };
    };
    return {
        getInstance: function () {
            if (!instance) {
                instance = init();
            }
            return instance;
        }
    };
})();
Todas as classes aqui representadas são protótipos, claro, mas é fácil ver que podemos extender a lógica para componentes e para o jogo.

Nenhum comentário:

Postar um comentário