Новый nape. Обработка событий.

Новый nape:
1. Новый nape. Hello world
2. Новый nape. Обработка событий. (Его вы сейчас читаете)
3. Новый nape. Testbed. И немного теории
4. Новый nape. Создание тел, материалы, фильтрация столкновений
5. Новый nape. Соединение тел
6. Новый nape. Приложение сил к телам и raycast

В новом nape предусмотрена обработка самых разных событий:
1. Засыпание/просыпание тела;
2. Засыпание/просыпание соединения (ограничения взаимного движения, joint) тел;
3. Превышение допустимой нагрузки и разрыв соединения тел;
4. Начало/конец/продолжение контакта между телами.

На сегодняшний день впрочем не реализована большая часть этих возможностей.

Засыпание тела

Но, например, засыпание тела реализовано, а поскольку обработка всех событий будет сделана единообразно, рассмотрим как отреагировать на это событие. Самый простой способ это сделать такой:
space.listeners.add(new BodyListener(CbEvent.SLEEP, CbType.DEFAULT, bodySleep));
private function bodySleep(body:Body):void { body.position.y = 0; }

Так тело будет сразу после засыпания снова падать сверху на землю.

Другой вариант добавления слушателя:
var l:BodyListener = new BodyListener(CbEvent.SLEEP, CbType.DEFAULT, bodySleep);
l.space = space;

Оба подхода к добавлению в мир применимы не только к слушателям, но и к телам, и к многим другим объектам.

В nape есть понятия типа тела, CbType. Обработчики событий в nape задаются по типам. Например можно задать телу тип «главный герой» или «бонус», и назначить обработчик для засыпания главного героя или столкновения главного героя с бонусом.

Добавим еще одно тело и зададим ему тип.
public var someType:CbType = new CbType(); // Новый тип тела
...
var body2:Body = new Body(BodyType.DYNAMIC, new Vec2(200, 300)); 
body2.cbType = someType; // Зададим тип тела
body2.shapes.add(new Circle(50, null, Material.rubber())); 
body2.align(); 
body2.space = space; 


Немного поменяем код создания обработчика события:
space.listeners.add(new BodyListener(CbEvent.SLEEP, someType, bodySleep ));

Раньше в качестве типа было указано значение CbType.DEFAULT — это тип тела присваиваемый по умолчанию. Теперь обычное тело заснет, а наше новое тело при попытке заснуть будет телепортировать наверх.

Обработка столкновений

Но конечно самым важным и интересным для нас является обработка столкновений. К сожалению автор пока еще не реализовал эту обработку через описанный механизм. Но у нас есть несколько альтернатив:

1. Часто нам не нужно оповещение, что произошло столкновение, достаточно знать что определенное тело взаимодействует с каким-то другим телом. У объекта Body в nape есть функция normalImpulse, она возвращает значение импульса, полученного телом в результате столкновений в течение последнего вызова space.step() (точнее только ту часть импульса, которая пришлась вдоль линии соединяющей центры масс столкнувшихся тел.) Можно получить либо сумму всех импульсов, либо импульс от столкновения с конкретным телом. Также можно получить импульсы только от новых столкновений. В нашем примере мы можем вызывать каждый шаг такой код:
trace(body.normalImpulse(ground));

И когда тело лежит на земле будем получать вектор направленный вверх.

2. Если же нам нужно узнать с какими телами соприкасается определенное тело, то можно воспользоваться свойством arbiters. Например так:
for (var i:int = 0; i < body.arbiters.length; ++i)
{
        var arb:Arbiter = body.arbiters.at(i);
        if (arb.isCollisionArbiter())
        trace("Collides with " + (arb.body1 == body ? arb.body2 : arb.body1));
}


3. Ну и наконец автор nape сделал для нас некий класс, который, пока не сделан основной механизм, позволяет все-таки получить именно обработчики событий столкновений. Код этого класса можно взять здесь. Вот он:
package {
    import nape.space.Space;
    import nape.phys.Body;
        import nape.callbacks.CbType;
    import nape.dynamics.Arbiter;

    public class Collisions {
        private var interesting:Object;
        private function pairId(i1:int, i2:int):int {
            return i1<i2 ? (i1<<16)|i2 : (i2<<16)|i1;
        }
        
        private var interacting:Object;
        
        public function Collisions() { interesting = {}; interacting = {}; }
      
        public function addPair(c1:CbType, c2:CbType, begin:Function, end:Function):Boolean {
            var id:int = pairId(c1.id,c2.id);
            if(interesting[id]!=null) return false;
            interesting[id] = {begin:begin, end:end, c1:c1, c2:c2, val:false, stamp:-1};
            return true;
        }
        
        public function remPair(c1:CbType,c2:CbType):Boolean {
            var id:int = pairId(c1.id,c2.id);
            var ret:Boolean = interesting[id]!=null;
            if(ret) interesting[id] = null;
            return ret;
        }
        
        public function handleCallbacks(space:Space):void {
            //handle begin
            var arbs:int = space.arbiters.length;
            for(var i:int = 0; i<arbs; i++) {
                var arb:Arbiter = space.arbiters.at(i);
                if(!arb.isCollisionArbiter()) continue;
                
                var id:int = pairId(arb.body1.cbType.id,arb.body2.cbType.id);
                var obj:Object = interesting[id];
                if(obj==null) continue;
                
                var id2:int = pairId(arb.body1.id,arb.body2.id);
                var tin:Object = interacting[id2];
                if(tin!=null) { tin.stamp = space.timeStamp; continue; }
                
                var b1:Body, b2:Body;
                if(obj.c1 == arb.body1.cbType) {
                    b1 = arb.body1; b2 = arb.body2;
                }else {
                    b1 = arb.body2; b2 = arb.body1;
                }
                
                if(obj.begin!=null)
                    obj.begin(b1,b2);
                
                interacting[id2] = {obj:obj, b1:b1, b2:b2, stamp:space.timeStamp};
            }
            
            //handle end
                        for (var val:* in interacting) {
                                tin = interacting[val];
                if(tin.stamp!=space.timeStamp) {
                    //ensure it's not due to sleeping
                    if(!((!tin.b1.isDynamic() || tin.b1.isSleeping)
                    && (!tin.b2.isDynamic() || tin.b2.isSleeping))) {
                        if (tin.obj.end != null) tin.obj.end(tin.b1, tin.b2);
                                                interacting[val] = null;
                                                delete interacting[val];
                    }
                }
            }
        }
    }
}


Пользоваться им легко и просто. Добавим еще тип тел groundType для земли и напишем следующий код:
public var collisions:Collisions = new Collisions();
...
collisions.addPair(groundType, someType, collisionBegin, collisionEnd);
...
collisions.handleCallbacks(space);
...
private function collisionBegin(b1:Body, b2:Body):void { trace("begin " + b1 + b2); }
private function collisionEnd(b1:Body, b2:Body):void { trace("end " + b1 + b2); }

Очень удобно, что тела приходят в функцию-обработчик уже в нужном порядке.

Полный исходный код получившегося примера:
package 
{
        import flash.Boot;
        import flash.display.*;
        import flash.events.*;
        import nape.dynamics.*;
        import nape.geom.*;
        import nape.phys.*;
        import nape.shape.*;
        import nape.space.*;
        import nape.util.*;
        import nape.callbacks.*;
        
        public class Main extends Sprite 
        {
                
                public var space:Space = new Space(new Vec2(0, 100)); // Мир
                public var debug:Debug = new ShapeDebug(800, 600, 0xFFFFFF); // Отладочный вывод
                
                public var someType:CbType = new CbType(); // Новый тип тела
                public var groundType:CbType = new CbType(); // Тип тела земли

                public var body:Body; // Тела
                public var body2:Body;
                public var ground:Body;
                
                public var collisions:Collisions = new Collisions(); // Класс для обработки событий столкновений
                
                public function Main():void 
                {
                        new Boot(); // Обязательно нужно для работы nape
                        
                        if (stage) init();
                        else addEventListener(Event.ADDED_TO_STAGE, init);
                }
                
                private function init(e:Event = null):void 
                {
                        removeEventListener(Event.ADDED_TO_STAGE, init);
                        
                        body = new Body(BodyType.DYNAMIC, new Vec2(400, 300)); // Новое тело
                        body.shapes.add(new Circle(50, null, Material.wood())); // Добавим фигуру
                        body.align(); // Нужно совместить центр фигуры и ее центр масс, иначе будут глюки
                        body.space = space; // Добавим тело в мир
        
                        body2 = new Body(BodyType.DYNAMIC, new Vec2(200, 300)); 
                        body2.cbType = someType; // Зададим тип тела
                        body2.shapes.add(new Circle(50, null, Material.rubber())); 
                        body2.align(); 
                        body2.space = space; 
                        
                        ground = new Body(BodyType.STATIC); // Земля
                        ground.cbType = groundType;
                        ground.shapes.add(new Polygon(Polygon.rect(0, 580, 800, 100)));
                        ground.space = space;                   
                        
                        // Добавим типы тел столкновения которых мы хотим отслеживать
                        collisions.addPair(groundType, someType, collisionBegin, collisionEnd); 
                        
                        // Добавим обработку события засыпания тела
                        space.listeners.add(new BodyListener(CbEvent.SLEEP, someType, bodySleep ));
                        
                        addChild(debug.display);
                        
                        addEventListener(Event.ENTER_FRAME, enterFrame);
                }
                
                private function bodySleep(body:Body):void 
                {
                        body.position.y = 0;
                }
                
                private function collisionBegin(b1:Body, b2:Body):void 
                {
                        trace("begin " + b1 + b2);
                }
                
                private function collisionEnd(b1:Body, b2:Body):void 
                {
                        trace("end " + b1 + b2);
                }
                
                private function enterFrame(e:Event):void 
                {
                        space.step(1 / 30.0); 
                        
                        // Найдем какой импульс приложили окружающие предметы к телу body.
                        //trace(body.normalImpulse(ground));
                        
                        // Найдем все тела, с которыми соприкасается тело body
                        for (var i:int = 0; i < body.arbiters.length; ++i)
                        {
                                var arb:Arbiter = body.arbiters.at(i);
                                if (arb.isCollisionArbiter())
                                        trace("Collides with " + (arb.body1 == body ? arb.body2 : arb.body1));
                        }
                        
                        // Обработаем столкновения
                        collisions.handleCallbacks(space);
                        
                        // Вывод на экран
                        debug.clear();
                        debug.draw(space);
                        
                }
        }
}
  • +18

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

+2
за обработку столкновений спасибо!
конечно хочется чтобы Лука поскорее дописал все события)
0
Спасибо.Продолжение будет?
0
Будет. Но уже не сегодня :-)
0
Спасибо за пост!
0
Оба поста были перенесены в коллективный блог, посвященный физическим движкам.
  • admin
  • admin
0
так их!
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.