El framework Artemis, versión C++

El framework Artemis, versión C++
Sin comentarios Facebook Twitter Flipboard E-mail

Ya vimos en el artículo anterior las ventajas del modelo Entidad - Componente frente al modelo tradicional Orientado a Objetos de la programación de videojuegos, en este artículo veremos el Framework Artemis y un ejemplo de uso del mismo, para ello no usaremos la versión oficial escrita en Java sino un port de C++ del mismo.

En este pequeño ejemplo lo que vamos a hacer es crear una Entidad Player que va a tener dos componente posición y velocidad, también vamos a crear un sistema llamado MovementSystem que lo que va a hacer es actualizar la posición de las entidades que tengan un componente de posición y otro de velocidad.

Para crear tal sistema lo único que debemos definir son los componentes que no son más que sacos de datos y los sistemas que trabajan con los componentes.

Empezaremos definiendo los dos componente, ambos son muy parecidos ya que tanto la posición como la velocidad se pueden representar con un vector con un eje X y un eje Y. Así que vamos allá, el componente posición sería así.

#pragma once
#include "artemis/Component.h"
class PositionComponent : public artemis::Component
{
public:
    float positionX;
    float positionY;
    PositionComponent();
    PositionComponent(float, float);
}; // class PositionComponent
#include "PositionComponent.h"
PositionComponent::PositionComponent()
{
    this->positionX = 0.0f;
    this->positionY = 0.0f;
}
PositionComponent::PositionComponent(float x, float y)
{
    this->positionX = x;
    this->positionY = y;
}

Como podemos ver solo tenemos una clase con dos atributos publicos y sus constructores. Se podrían haber hecho los atributos privados y utilizar métodos get y set para acceder a ellos, pero para el ejemplo creo que es mejor dejarlo lo más simple posible.

El componente velocidad es muy parecido ya que la velocidad y la posición tienen los mismos atributos.

#pragma once
#include "artemis/Component.h"
class VelocityComponent : public artemis::Component
{
public:
    float velocityX;
    float velocityY;
    VelocityComponent();
    VelocityComponent(float, float);
}; // class VelocityComponent
#include "VelocityComponent.h"
VelocityComponent::VelocityComponent()
{
    this->velocityX = 0.0f;
    this->velocityY = 0.0f;
}
VelocityComponent::VelocityComponent(float x, float y)
{
    this->velocityX = x;
    this->velocityY = y;
}

Comentar que ambos heredan de la clase Component del namespace artemis. Esto es lo que le da la funcionalidad de componente.

Una vez definidos nuestros componentes podemos elaborar un sistema que actúe sobre ellos, vamos a crear un sistema llamado MovementSystem que actuará sobre todas las entidades que tengan los componentes de Posición y Velocidad que se encargará de cambiar la posición de las entidades con respecto al tiempo y a la velocidad.

#pragma once
#include "artemis/Entity.h"
#include "artemis/ComponentMapper.h"
#include "artemis/EntityProcessingSystem.h"
#include "PositionComponent.h"
#include "VelocityComponent.h"
class MovementSystem : public artemis::EntityProcessingSystem
{
public:
    MovementSystem();
    virtual void initialize();
    virtual void processEntity(artemis::Entity&);
private:
    artemis::ComponentMapper velocityMapper;
    artemis::ComponentMapper positionMapper;
}; // class MovementSystem
#include "MovementSystem.h"
MovementSystem::MovementSystem()
{
    addComponentType();
    addComponentType();
}
void MovementSystem::initialize()
{
    velocityMapper.init(*world);
    positionMapper.init(*world);
}
void MovementSystem::processEntity(artemis::Entity& e)
{
    positionMapper.get(e)->positionX += velocityMapper.get(e)->velocityX * world->getDelta();
    positionMapper.get(e)->positionY += velocityMapper.get(e)->velocityY * world->getDelta();
}

En el sistema lo primero que hacemos es sobrecargar los métodos initialize y processEntity, el primero se encarga de inicializar el sistema y el segundo es el método encargado de procesar todas las entidades que cumplan los requisitos, en nuestro caso que tengan un componente de velocidad y otro de posición.

En el archivo de implementación podemos ver como accedemos a los atributos de los componentes y los modificamos en este caso obteniendo el delta pasado que se encuentra dentro del objeto world que es un objeto global donde viven todas las entidades y nuestros sistemas.

Por último vamos a ver como usar estos componentes y sistemas que hemos creado en una aplicación. Lo que vamos a hacer en nuestro ejemplo es ir mostrando la posición del jugador en cada instante de tiempo después de asignarle una posición inicial y una velocidad.

#include 
#include "artemis/World.h"
#include "artemis/SystemManager.h"
#include "MovementSystem.h"
int main(int argc, char **argv) {
    artemis::World world;
    artemis::SystemManager * sm = world.getSystemManager();
    MovementSystem * movementsys = (MovementSystem*)sm->setSystem(new MovementSystem());
    artemis::EntityManager * em = world.getEntityManager();
    sm->initializeAll();
    artemis::Entity & player = em->create();
    player.addComponent(new VelocityComponent(2,4));
    player.addComponent(new PositionComponent(0,0));
    player.refresh();
    PositionComponent * comp = (PositionComponent*)player.getComponent();
    while(true){
        world.loopStart();
        world.setDelta(0.0016f);
        movementsys->process();
        std::cout << "X:"<< comp->positionX << std::endl;
        std::cout << "Y:"<< comp->positionY << std::endl;
        Sleep(160);
    }
    return 0;
}

Lo primero que hacemos es crear un objeto mundo a continuación obtenemos el SystemManager que es un objeto encargado de controlar y manejar todos nuestros sistemas.

La siguiente línea puede ser algo liosa, pero básicamente lo que hace es añadir nuestro MovementSystem al gestor de sistemas y obtener un puntero a él.

A continuación obtenemos un puntero al EntityManager que como podemos deducir gestiona las entidades del mundo.

Luego llamamos al método initializeAll del SystemManager que lo único que hace es llamar a los métodos initialize de todos los sistemas adscritos a nuestro mundo.

Después lo que hacemos es mediante el EntityManager crear una entidad y una referencia a la misma llamada Player. Esta será la entidad que representará a nuestro jugador que como podemos ver hasta ahora no había aparecido en escena.

Las siguientes tres líneas son obvias y lo que hacen es enchufarle los dos componentes de posición y velocidad dándole unos valores iniciales por último llamamos al método refresh necesario para que la entidad sepa que componentes contiene.

La siguiente línea solo creamos un puntero al componente posición de la entidad jugador para poder acceder a los datos del mismo y mostrarlos en pantalla. En realidad si quisiéramos mostrar estos datos en pantalla correctamente lo que deberíamos hacer es crear un sistema que se encargara de dicho cometido, pero se sale de lo que intenta mostrar este ejemplo.

Por último tenemos nuestro GameLoop típico en todos los juegos que se encarga de controlar el tiempo y en él procesamos todos los sistemas que a su vez procesan todas las entidades, para luego gracias al puntero auxiliar que hemos creado podemos acceder y mostrar los valores de la posición.

Mucho más

El framework Artemis abarca mucho más, podemos añadir las entidades a grupos o añadirle etiquetas. Esto sirve para que los sistemas puendan elegir procesar solo las entidades que pertenezcan a un determinado grupo como puede ser el grupo "enemigos", etc. Las posibilidades son infinitas y todo es ponerle algo de imaginación y podremos crear sistemas verdaderamente complejos.

Repositorio de código

Si quieres probar este ejemplo he creado un repositorio de código en la plataforma GitHub, de momento solo esta creada la solución para Visual Studio 2012, pero sacaremos solución para otros entornos y sistemas, como siempre será bienvenido por parte de las usuarios hacer un fork y aportar nuevas soluciones.

Sitio oficial | Artemis Entity System Framework Port C++ | Artemis C++ Repositorio de ejemplo | https://github.com/genbetadev/artemis-example

Comentarios cerrados
Inicio