Страницы

четверг, 27 декабря 2012 г.

Система обратных вызовов (Callbacks) в Nape

      Система обратных вызовов в Nape позволяет получать события, когда происходят определенные действия, такие как засыпание Тела или разрушается Ограничение, или два участника Взаимодействия (Фигуры, Тела, Объединения) начинают взаимодействовать.

      Типы обратных вызовов (CbTypes) - можно рассматривать как теги, которые присваиваются объектам. На один объект можно назначить несколько CbTypes.
      Например, представим что вы пишите игру в которой есть объекты - Шипы (Spikes), и мы назначаем им CbType SPIKE, а любому объекту, который может быть убит этими шипами назначаем CbType SPIKEABLE. Или например есть в игре объекты, которые можно собирать, назначаем им CbType COLLECABLE, а объектам, которые могут их собирать - CbType COLLECTOR. Различным объектам можно назначать несколько типов CbType.


var SPIKE       = new CbType();
var SPIKEABLE   = new CbType();
var COLLECTABLE = new CbType();
var COLLECTOR   = new CbType();

for (obj in spikeyObjects) {
    obj.cbTypes.add(SPIKE);
}
for (obj in collectables) {
    obj.cbTypes.add(COLLECTABLE);
}
// etc.

      Слушатели (Listeners), такие как BodyListeners, ConstraintsListeners, InteractionListeners оперируют через CbTypes, или в частности через наборы CbTypes используя API исключения/включения. Задается посредством OptionType. Типы обратных функций
CbTypes назначаются объектам, когда добавляются в список включения, и их нет в списке исключения.
      Для конструкторов слушателей можно передать набор CbTypes по разному: CbTypes, OptionTypes, CbTypeList, Array<CbType>, flash.Vector<CbType>:


// Соответствует любому объекту, у которого CbType SPIKE.
options = SPIKE

// Соответствует любому объекту, у которого CbType SPIKE или/и COLLECTABLE
options = [SPIKE, COLLECTABLE]

// Соответствует любому объекту, у которого CbType SPIKE, но у которого нет COLLECTABLE
options = SPIKE.excluding(COLLECTABLE)

// Соответствует любому объекту, у которого CbType SPIKE или/и COLLECTABLE,
// но которые не относятся к типам COLLECTOR или SPIKEABLE.
options = new OptionType([SPIKE, COLLECTABLE]).excluding([COLLECTOR, SPIKEABLE])
// можно так
options = SPIKE.including(COLLECTABLE).excluding(COLLECTOR).excluding(SPIKEABLE)
// и т.д.


      Для типов обратных вызовов CbTypes есть предопределенные свойства:

  • ANY_BODY - для любого тела
  • ANY_SHAPE - для любой фигуры
  • ANY_COMPOUND - для любого объединения
  • ANY_CONSTRAINT - для любого ограничения
      Например:
// Назначается любому Телу, исключая тип - SPIKE
options = CbType.ANY_BODY.excluding(SPIKE)

// Назначается любой Фигуре или Телу
options = [CbType.ANY_BODY, CbType.ANY_SHAPE]


      Слушатели Тел (BodyListeners) обрабатывают события, связанные с Телом, обратный вызов происходит в конце вызова шага расчета пространства (space.step()). Поддерживается 2 типа обратных вызова для Тел:

  • CbEvent.SLEEP
  • CbEvent.WAKE
      При создании BodyListener необходимо использовать конструктор следующего формата:

var listener:Listener = new BodyListener(event, options, handler, precedence = 0);


      Обработчик:


function bodyWakeHandler(cb:BodyCallback):Void {
    var wokenBody:Body = cb.body;
}


      Слушатели Ограничений (ConstraintListeners) обрабатывают события, связанные с Ограничениями, обратный вызов происходит в конце вызова шага расчета пространства (space.step()). Поддерживается 3 типа обратных вызовов для Ограничений:

  • CbEvent.SLEEP
  • CbEvent.WAKE
  • CbEvent.BREAK
      Конструктор:

var listener:Listener = new ConstraintListener(event, options, handler, precedence = 0);


      Слушатели Взаимодействия (InteractionListeners) обрабатывают события, связанные с Взаимодействиями объектов (Фигур, Тел, Объединений).  Обратный вызов также происходит в конце space.step(). Поддерживается 3 типа обратных вызовов для Взаимодействий:

  • CbEvent.BEGIN
  • CbEvent.ONGOING
  • CbEvent.END
      Событие BEGIN наступает на первом шаге, когда произошло взаимодействие. Событие END возникает на первом шаге, когда взаимодействие пропадает. Событие ONGOING продолжается все время пока происходит взаимодействие, сразу после BEGIN.

      Конструктор:

var listener = new InteractionListener(event, interactionType, options1, options2, handler, precedence = 0);


      Для определенных событий в Nape есть специальный тип InteractionListeners - это PreListeners. Это событие происходит, когда два объекта взаимодействия находят пересечение, но физика еще не обработана. Обработчик может принять решение игнорировать взаимодействие или выполнить действие, например изменение значения Арбитра (Arbiter) трения.
      Обработчик для этих событий происходит прям о в середине шага расчета пространства, поэтому тут мы имеем некоторые ограничения, например мы не можем добавлять/удалять тела и т.п. Это лучше сделать в BEGIN обработчике InteractionListener.
      Конструктор:

var listener = new PreListener(interactionType, options1, options2, handler, precedence = 0, pure = false);


      Обработчик этого события возвращает тип PreFlag либо null. PreFlag может иметь следующие значения:

  • PreFlag.ACCEPT - разрешает взаимодействие для этого шага и для последующих шагов. Если это финальное состояние взаимодействия, то PreListener больше не будет влиять на объекты, пока те не отделятся и не начнут взаимодействовать снова.
  • PreFlag.IGNORE - игнорирует взаимодействие для этого шага и для всех последующих шагов. Если это финальное состояние взаимодействия, то PreListener больше не будет влиять на объекты, пока те не отделятся и не начнут взаимодействовать снова.
  • PreFlag.ACCEPT_ONCE - разрешает взаимодействие для этого шага. Если это финальное состояние взаимодействия, то PreListener будет вызван в следующем шаге, чтобы спросить снова.
  • PreFlag.IGNORE_ONCE - игнорирует взаимодействие на этом шаге. Если это финальное состояние взаимодействия, то PreListener будет вызван в следующем шаге, чтобы спросить снова.
      Если обработчик события возвращает null, то он никак не повлияет на взаимодействие. Значение по умолчанию для PreFlag - PreFlag.ACCEPT_ONCE.
      Параметр - pure важен, когда дело доходит до использования ACCEPT_ONCE, IGNORE_ONCE состояний. Если ваш обработчик не детерминирован (non-deterministic), то для Nape нет возможности разрешить объектам заснуть, поскольку ваш обработчик может внезапно передумать, что делать с взаимодействием. Если ваш обработчик детерминирован - то вы должны установить параметр pure - в true и больше нет необходимости останавливать засыпание объектов.

// Non-deterministic (pure=false) handler
function impureHandler(cb:PreCallback):PreFlag {
    if (Math.random() < 0.5) return PreFlag.IGNORE_ONCE;
    else return null;
}

// Non-deterministic (pure=false) handler
function impureHandler2(cb:PreCallback):PreFlag {
    if (getTimer() > 2000) return PreFlag.ACCEPT_ONCE;
    else return PreFlag.IGNORE_ONCE;
}

// Deterministic (pure=true) handler.
function pureHandler(cb:PreCallback):PreFlag {
    // cb.arbiter is an input to the function, so we can make decisions with it.
    if (cb.arbiter.normal.y > 0) return PreFlag.IGNORE_ONCE;
    else return PreFlag.ACCEPT_ONCE;
}


      Возвращаемся к нашему примеру с машинкой. Сделал обработку обратного вызова - засыпание машины (а точнее правого колеса):

package
{ 
    import flash.display.Sprite;
    import flash.events.Event;
 import nape.callbacks.BodyCallback;
 import nape.constraint.DistanceJoint;
 import nape.phys.Compound;
 import nape.phys.Material;
 import nape.callbacks.CbType; // типы обратных вызовов
 import nape.callbacks.CbEvent; // события обратных вызовов
 import nape.callbacks.BodyListener; //слушатель для колеса
 
    import nape.geom.Vec2;
    import nape.phys.Body;
    import nape.phys.BodyType;
    import nape.shape.Circle;
    import nape.shape.Polygon;
    import nape.space.Space;
    import nape.util.BitmapDebug;
    import nape.util.Debug;
 import nape.shape.Shape;
 
    public class Main extends Sprite
    {
        private var space:Space;
        private var debug:Debug;
  
  private var wheel_front:Body;
  private var wheel_rear:Body;
  private var cabin:Body;
  
  //Ограничение для колес
  private var wheels_joint:DistanceJoint; //Держит расстояние между колес
  private var cabin_front_joint:DistanceJoint; //Амортизатор спереди машины
  private var cabin_rear_joint:DistanceJoint; //Амортизатор сзади машины

  // Объединения
  private var car:Compound;
 
  // Обратные вызовы
  private var cbCarType:CbType;
  
        public function Main():void
  {
            super();
 
            if (stage != null) {
                initialise(null);
            }
            else {
                addEventListener(Event.ADDED_TO_STAGE, initialise);
            }
        }
 
        private function initialise(ev:Event):void
     {
            if (ev != null) {
                removeEventListener(Event.ADDED_TO_STAGE, initialise);
            }
 
            var gravity:Vec2 = Vec2.weak(0, 900);
            space = new Space(gravity);
 
            debug = new BitmapDebug(stage.stageWidth, stage.stageHeight,
       stage.color);
   
   //Рисуем Ограничения
   debug.drawConstraints = true;
            addChild(debug.display);
   
            setUp();
 
            stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
        }
 
        private function setUp():void
     {
            var w:int = stage.stageWidth;
            var h:int = stage.stageHeight;
   
            // Пол
            var floor:Body = new Body(BodyType.STATIC);
            floor.shapes.add(new Polygon(Polygon.regular(320, 100, 6, 0, false),
       Material.ice()));
            
   floor.position.setxy(340, 300);
   floor.space = space;  
   
   // Объединение - машина
   car = new Compound();
   //car.cbTypes.add(carType);
      
   // Колесо переднее
   wheel_front = new Body(BodyType.DYNAMIC);
   wheel_front.position.setxy(290, 106);
   var wheel_frontShape:Shape = new Circle(30, null, Material.rubber());
   wheel_frontShape.body = wheel_front; 
   //wheel_front.angularVel = 20;
   wheel_front.compound = car;
   
   // Колесо заднее
   wheel_rear = new Body(BodyType.DYNAMIC);
   wheel_rear.position.setxy(200, 106);
   var wheel_rearShape:Shape = new Circle(30, null, Material.rubber());
   wheel_rearShape.body = wheel_rear;  
   //wheel_rear.angularVel = 20;
   wheel_rear.compound = car;
   
   // Кузов
   cabin = new Body(BodyType.DYNAMIC);
   cabin.position.setxy(245, 70);
            var cabinShape:Shape = new Polygon(Polygon.box(200, 60),
    new Material(0, 1, 2, 2, 0.01));
   cabinShape.body = cabin;
   cabin.compound = car;
   
   //Ограничение для колес (держит межколесное растояние)
   wheels_joint = new DistanceJoint(wheel_front, wheel_rear, wheel_front.localCOM,
    wheel_rear.localCOM, 120, 120);
   wheels_joint.compound = car;
   
   //Передний амортизатор
   cabin_front_joint = new DistanceJoint(wheel_front, cabin,
    wheel_front.localCOM, cabin.localCOM, 90, 90);
   cabin_front_joint.compound = car;
   
   //Задний амортизатор
   cabin_rear_joint = new DistanceJoint(wheel_rear, cabin,
    wheel_rear.localCOM, cabin.localCOM, 90, 90);
   cabin_rear_joint.compound = car;
   
   //Выводим машинку со всеми ограничениями
   car.space = space;
   
   //Обратный вызов для переднего колеса
   cbCarType = new CbType();
   wheel_front.cbTypes.add(cbCarType);
   wheel_front.space.listeners.add(new BodyListener(CbEvent.SLEEP, cbCarType, carSleepHandler));
        }
  
     private function carSleepHandler(cb:BodyCallback):void 
     {
            //Как только засыпает правое колесо, едем назад!
      var wf:Body = cb.body;
      wf.angularVel = -100;
     }
 
        private function enterFrameHandler(ev:Event):void 
     {
            space.step(1 / stage.frameRate);
   
            debug.clear();
            debug.draw(space);
            debug.flush();
        }
    }
}

Реализацию этого примера можно посмотреть тут

1 комментарий:

  1. Правильно я понимаю, что при взаимодействии объектов листенер привязан не к объекту, а к паре cbType-ов? То есть, если у меня десять шаров, все сделали cbTypes.add( cbMySphere ), то мне достаточно одного обработчика на всех?
    InteractionListener( CbEvent.BEGIN, InteractionType.COLLISION, cbMySphere, cbMySphere, my_handler );

    ОтветитьУдалить