[Flash 11]

Octobre 2012

Data Driven Programming ou programmation basé sur les entités

On en entend pas mal parlé, et il y a beaucoup de moteurs de jeux basés dessus comme la citrus engine, ash ou smashIO, très bon article de Iann Lobb


J’ai construis un moteur très simple capable de gérer des entités possédant jusqu’à 5 composants

  1. positionnement
  2. physique
  3. armes
  4. santé
  5. visualisation

Ci dessous la classe qui s’occupe des vaisseaux (joueur et EvilTwin )

package asteroids
{
	import asteroids.stateShip.*;
	import engine.Body;
	import engine.E;
	import engine.Entity;
	import engine.events.*;
	import engine.fsm.StateMachine;
	import engine.Health;
	import engine.Keypad;
	import engine.Physics;
	import engine.View;
	import flash.display.GraphicsPathWinding;
	import flash.display.Sprite;
	import flash.events.KeyboardEvent;
	import flash.ui.Keyboard;
	import engine.EventBroker;
	
	public class Ship extends Entity
	{
		// FSM
		public static const NORMAL:String = "normal_state";
		public static const SHIELD:String = "shield_state";
		public static const POWER_UP:String = "power_up_state";
		public static const BLINKING:String = "blinking_state";
		private var _fsm:StateMachine
		
		// UserInput
		protected var keypad:Keypad;
		
		public function Ship()
		{
			//
			body = new Body(this);
			body.x = E.WIDTH >> 1;
			body.y = E.HEIGHT >> 1;
			body.radius = 20;
			//body.angle = E.degreesToRadians(270);
			
			//
			physics = new Physics(this);
			physics.vX = 0;
			physics.vY = 0;
			physics.drag = 0.95;
			
			//
			health = new AHealth(this);
			health.hp = 5;
			
			//
			weapon = new PlasmaGun(this);
			weapon.ammo = 50;
			
			//
			view = new View(this);
			view.sprite.x = E.WIDTH >> 1;
			view.sprite.y = E.HEIGHT >> 1;
			view.sprite.graphics.lineStyle(1.5, 0xCCCCCC);
			view.sprite.graphics.drawPath(	Vector.<int>([1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]), 
											Vector.<Number>(
											[ -7.3, 10.3, -5.5, 10.3, -7, 0.6, -0.5, -2.8, 6.2, 0.3, 4.5, 10.3, 
											6.3, 10.3, 11.1, -1.4, -0.2, -9.6, -11.9, -1.3, -7.3, 10.3]), 
											GraphicsPathWinding.NON_ZERO);			

			
			_fsm = new StateMachine;			
			_fsm.addState( NORMAL, new StateNormal(this), [SHIELD, POWER_UP, BLINKING] );
			_fsm.addState( POWER_UP, new StatePowerUp(this), [NORMAL/*, SHIELD*/] );
			_fsm.addState( SHIELD, new StateShield(this), [NORMAL/*, POWER_UP*/] );
			_fsm.addState( BLINKING, new StateBlink(this), [NORMAL, SHIELD, POWER_UP] );
			
			_fsm.state = NORMAL;
			
			keypad = new Keypad(E.stage);
		}		
		
		public function get state():String
		{
			return _fsm.state;
		}
		
		public function set state( state:String ):void
		{
			_fsm.state = state;
		}			
		
		override public function onHit():void 
		{
			this.state = BLINKING;
		}
		
		override public function onDie():void 
		{
			destroy();			
		}				
		
		override public function destroy():void 
		{
			// destruction totale
			keypad.destroy();
			keypad = null;
			
			super.destroy();
			
			group.length = 0;
			targets.length = 0;
			this.body = null;
			this.physics = null;
			this.weapon = null;
			this.health = null;
			this.view = null;
		}
		
		override public function update():void
		{
			super.update();
			
			// --
			if (keypad.isDown(Keyboard.UP))
			{
				physics.impulse( .6 );
			}			
			
			if (keypad.isDown(Keyboard.LEFT))
			{
				body.angle -= .1;
			}
			
			if (keypad.isDown(Keyboard.RIGHT))
			{
				body.angle += .1;
			}			
		}
		
	}

}

Cette classe est la plus complète car elle est composée des 5 composants tandis que d’autres n’ont ni arme, ni physique (les bonus par exemple).

Le bon côté de cette approche je trouve, c’est que l’on code de manière ‘horizontale’ par opposition à une approche basé uniquement sur de l’héritage où l’on qui fonctionne plus de manière ‘verticale’, ce qui amène souvent à créer beaucoup de classes avec des comportements spécifiques.
( pour ce jeu la profiondeur d’héritage ne dépasse jamais 2 niveaux : Entity -> Ship -> EnemmyShip )

Autre exemple avec la classe/composant ‘Physic’

package engine
{
public class Physics
{
public var entity:Entity;
public var drag:Number = 1.; // frottement null
public var vX:Number = .0;
public var vY:Number = .0;

public function Physics(entity:Entity)
{
this.entity = entity;
}

public function update():void
{
entity.body.x += vX;
entity.body.y += vY;

vX *= drag;
vY *= drag;

// monde torique, toutes les entités y sont soumises
if (entity.body.x > 850) entity.body.x -= 900;
if (entity.body.x < -50) entity.body.x += 900; if (entity.body.y > 650) entity.body.y -= 700;
if (entity.body.y < -50) entity.body.y += 700; } public function impulse( pow:Number ):void { vX += Math.sin(-entity.body.angle) * pow; vY += Math.cos(-entity.body.angle) * pow; } } }

Ainsi toutes les entités qui seront décorées avec le composant 'Physic' seront soumissent aux contraintes d'un monde torique ^^

Le piège principal de cette approche c'est la conso de mémoire.
Pour chaque entité on crée des objets pour les comportements, par exemple 25 objets Ship donneront au total 125 objets ( 25 * 5 ) d'où la nécessité d'utiliser du Pooling d'objets de recycler un maximum.
Dans ce jeu par exemple j'ai fait 2 pool, une pour les tirs et une pour les astéroïdes.

Les musiques viennent de Thunder force 3, les bruitages ont été fait avec bfxr


Vote in HexoSearch