Framework: Entity & EntityManager

Немного отвлечёмся от игровых экранов и перейдём ко второй основе моейго фреймворка — к сущьностям (Entity) и их менеджеру (EntityManager).

Entity

elmortem.game.entities.Entity
Сущность — это основа всех игровых объектов. Будь-то юнит, платформа или триггер — все они так или иначе унаследованы от класса Entity. Класс этот наследуется от EventDispatcher и реализует несколько базовых методов.
package elmortem.game.entities {
  import elmortem.game.Simulation;
  import flash.events.EventDispatcher;
  
  public class Entity extends EventDispatcher {
    static private var _ids:uint = 0;
    private var pId:uint;
    private var pName:String;
    private var pAlive:Boolean;
    private var pSim:Simulation = null;
    public var attr:Object;
    
    public function Entity() {
    }
    public function setAttr(attr:Object):Entity {
      this.attr = attr;
      pId = _ids++;
      pName = (attr.name != null)?attr.name:"entity" + pId;
      pAlive = true;
      
      return this;
    }
    public function init(sim:Simulation):void {
      pSim = sim;
    }
    public function free():void {
      attr = null;
      pName = null;
    }
    public function die():void {
      if(!pAlive) return;
      pAlive = false;
      sim.eventer.add(this, new EntityEvent(this, EntityEvent.DEAD));
    }
    public function update(delta:Number):void {
    }
    public function render():void {
    }
    
    public function get id():uint { return pId; }
    public function get name():String { return pName; }
    public function get alive():Boolean { return pAlive; }
    public function get sim():Simulation { return pSim; }
  }
}

Про класс Simulation я расскажу позже, как и про его параметр eventer, пока просто считайте, что sim.eventer.add — это тоже самое, что dispatchEvent — генерация события.
У каждой сущности есть уникальный id, который, правда, мне пока ещё не разу не понадобился.
Параметр alive показывает, жива сущность или уже сдохла и трогать её не надо.
Метод setAttr принимает Object, в который мы можем поместить любые входные параметры. Про это я уже говорил. Разные сущноси могут прнимать разные параметры, поэтому в данным случае универсальность — это хорошё. Так же мы оставили конструктор без параметров, что позволит реализовать кеширование любых Объектов, отнаследованных от Entity — вместо удаления и создания новых объектов можно будет просто вызвать метод setAttr уже созданых и помещённых в специальном кеше.
Про кеширование подробно рассказывали на форуме http://flashgamedev.ru, но я позже напишу про это подробней применительно к моему фреймворку.

EntityManager

elmortem.game.entities.EntityManager
Теперь нам нужен некий класс для того, чтобы управлять всем этим добром. Для этого используется класс EntityManager, который реализует добавление, удаление и обработку сущностей.
package elmortem.game.entities {
  import elmortem.game.Simulation;
  import flash.events.EventDispatcher;

  public class EntityManager extends EventDispatcher {
    private var sim:Simulation; // об этом позже!
    private var list:/*Entity*/Array;
    private var dead_list:/*Entity*/Array;
    
    public function EntityManager(sim:Simulation) {
      super();
      
      this.sim = sim;
      list = [];
      dead_list = [];
    }
    public function free():void {
      clear();
      list = null;
      dead_list = null;
    }
    public function clear():void {
      for (var i:int = 0; i < list.length; i++) {
        list[i].free();
      }
      list = [];
      dead_list = [];
    }
    public function add(entity:Entity):Entity {
      if (entity == null || list == null) {
        trace("Entity or List is empty.");
        return null;
      }
      list.push(entity);
      entity.init(sim);
      entity.addEventListener(EntityEvent.DEAD, onDead);
      sim.eventer.add(new EntityEvent(entity, EntityEvent.ADD));
      return entity;
    }
    public function remove(entity:Entity):void {
      if (entity == null || list == null) {
        trace("Entity or List is empty.");
        return;
      }
      var index:int = list.indexOf(entity);
      if(index >= 0) {
        list.splice(index, 1);
        sim.eventer.add(new EntityEvent(entity, EntityEvent.REMOVE));
        entity.free();
      }
    }
    public function findEntitiesByClass(cls:Class):/*Entity*/Array {
      var arr:/*Entity*/Array = [];
      if (list == null) return arr;
      for(var i:int = 0; i < list.length; i++) {
        if(list[i] is cls) {
          arr.push(list[i]);
        }
      }
      return arr;
    }
    public function findEntityByName(name:String, cls:Class = null):Entity {
      if (list == null) return null;
      if(cls == null) cls = Entity;
      for(var i:int = 0; i < list.length; i++) {
        if(list[i] is cls && list[i].name == name) {
          return list[i];
        }
      }
      return null;
    }
    
    public function update(delta:Number):void {
      var i:int;
      for(i = 0; i < list.length; i++) {
        if(list[i].alive) {
          list[i].update(delta);
        }
      }
      
      // dead list
      if (dead_list.length > 0) {
        for(i = 0; i < dead_list.length; i++) {
          remove(dead_list[i]);
        }
        dead_list = [];
      }
    }
    public function render():void {
      for(var i:int = 0; i < list.length; i++) {
        list[i].render();
      }
    }
    
    public function onDead(e:EntityEvent):void {
      if (dead_list == null) return;
      dead_list.push(e.entity);
    }
  }
}

Тут стоит обратить внимание на следующие вещи.
1. При добавлении и удалении сущности генерятся события об этом, которые в последствии можно будет поймать и совершить с сущностью необходимые действия. Например при удалении сущности игрока нужно показать экран проигрыша.
2. Менеджер подписывается на событие EntityEvent.DEAD, которое генерится при вызове метода die у сущности.
3. Непосредственное удаление сущностей при их смерти (onDead) происходит только после их полной обработки, а до того они помещаются в специальный массив dead_list. Делается это для того, чтобы не было ошибок обращения к параметрам и методам удалённой сущности. Например нельзя удалять тела Box2D, пока программа находится внутри метода Step. И если сущность содержит в себе физическое тело, а мы его попытаемся удалить сразу, то произойдёт ошибка.

Комментарии (17)

0
Вот бы этот блог да пару лет назад :)
А так свой фреймворк пришлось создавать и оттачивать со временем…
А новичкам думаю очень полезно будет, особенно если расписать поподробнее, хотя бы комменты к функциям добавить.
0
По сути на них и расчёт, поэтому последую совету и добавлю комментарии к функциям, как только время будет. (:
0
Эх. Смотрится вкусно и логично. Но увы, своё сделано и вроде даже работает =). Спасибо за статью.
0
Всегда можно подсматривать соседу в код и допиливать свой двиг. (:
Расскажи про свой, мне интересно, обожаю чужой код.
0
Вот кому-кому, а тебе точно ничего интересного в моём двиге не будет ))))
0
Не скажи, знаешь какие алмазы порой находишь в чужом коде. Так что ты это зря. (:
0
Я подумаю =) Мне бы прибраться там для начала.
0
Полезно.
Руки дойдут — поделюсь своими наработками.
0
Спасибо за посты! Очень полезно.

Маленькое замечание:

public function remove(entity:Entity):void {

// лучше не id, а itemIndex, targetIndex или index… а то я не сразу понял, что речь не про id в Entity
var id:int = list.indexOf(entity);

}

Может есть смысл в fashwiki? Возможна коллективная доработка.
0
index — это ж так длинно, id коротко и понятно, видно же, что тут у нас indexOf — т.е. относится именно к list. Но вообще это скорее привычка. (:
0
id все же принято сокращать identifier, индекс — это не всегда идентификатор. Если в этом случае рядом indexOf, то в другом куске кода пониже уже может и не будет ясно что это индекс. Правильное именование в различных конвеншнах для того и придумано, чтобы читаемость кода для любого другого девелопера в любом месте была наилучшая.

Удобное и частое сокращение для индекса — idx
0
Да вы оба правы по сути. Сейчас поменяю.
0
Я бы очень рекомендовал избавится от *.length в циклах. Хотя бы в функции update().
0
Что, значительный прирост?
0
На удивление, да. Посмотри мой пост (Пункт «Цикл FOR» flashgameblogs.ru/blog/actionscript/198.html). Сам сейчас работаю над оптимизацией своего кода.
0
Что, сильно ощутимый прирост производительности? Вообще я думал отойти от массивов к векторам внутри самого движка, но некогда пока этим занятся, всё жду нагрузочной игры, чтобы повод появился…
0
Вектор — это вообще хорошо.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.