Статьи

Обнаружение столкновений на уровне пикселей на основе цветов пикселей

В этом уроке я буду следовать подходу, предложенному Ричардом Дейви (спасибо, Ричард!) И использованным им и другими, для обнаружения коллизий между растровыми изображениями с тонкой модификацией. Я также сравниваю производительность между различными подходами обнаружения коллизий растровых изображений с использованием жгута проводов Grant Skinner PerformanceTest .

Примечание. Эта статья не только входит в сессию Shoot-‘Em-Up , но и является частью Обнаружения столкновений и их реакции .


Я опишу этот альтернативный подход вкратце здесь.

  1. Проверьте, нет ли совпадений между двумя растровыми изображениями.
  2. Если есть, перейдите к # 3. В противном случае, бросьте.
  3. Проверьте, не перекрывает ли область перекрытия непрозрачные пиксели.
  4. Если это так, растровые изображения перекрываются. В противном случае, бросьте.

Во-первых, мы проверяем, перекрывают ли ограничивающие рамки двух битовых карт, используя Rectangle. Сценарии как ниже. Сначала переменные.

1
2
3
4
private var enemy1:Bitmap, myShip:Bitmap;
private var myShipSp:Sprite;
private var rec_e:Rectangle, rec_m:Rectangle;
private var intersec:Rectangle;
01
02
03
04
05
06
07
08
09
10
11
enemy1 = new E1 as Bitmap;
myShip = new My as Bitmap;
myShipSp = new Sprite;
myShipSp.addChild(myShip);
 
enemy1.x = stage.stageWidth >> 1;
enemy1.y = stage.stageHeight * 0.2;
 
//drawing boxes around the sprite
draw(enemy1.getBounds(stage), this, 0);
draw(myShipSp.getBounds(stage), this, 0);

Здесь мы проверяем наличие перекрывающихся областей между полями. Проверьте DetectVisible.as в исходной загрузке для полного сценария

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private function refresh(e:Event):void
{
    //determining the bounding box of intersection area
    rec_e = enemy1.getBounds(stage);
    rec_m = myShipSp.getBounds(stage);
    intersec = rec_e.intersection(rec_m);
     
    //redraw the bounding box of both sprites
    this.graphics.clear();
    draw(enemy1.getBounds(stage), this, 0);
    draw(myShipSp.getBounds(stage), this, 0);
     
    //only draw bounding box of intersection area if there’s one
    if (!intersec.isEmpty()){
        lines.graphics.clear();
        draw(intersec, lines);
         
        t.text =»Intersection area by red rectangle.»
    }
    else {
        t.text =»No intersection area.»
    }
}

Вот демо. Перетащите меньший космический корабль вокруг.

(Не беспокойтесь о красной коробке, которая «осталась позади», когда корабль вытащил из ограничительной рамки другого.)


Поэтому, если есть пересекающаяся область блока, мы приступаем к проверке, есть ли перекрывающиеся пиксели в этой области. Однако сначала попробуем нарисовать растровое изображение в этой области пересечения. Полный скрипт в DetectVisible2.as

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private function refresh(e:Event):void
{
    //determining the bounding box of intersection area
    rec_e = enemy1.getBounds(stage);
    rec_m = myShipSp.getBounds(stage);
    intersec = rec_e.intersection(rec_m);
     
    //redraw the bounding box of both sprites
    this.graphics.clear();
    draw(enemy1.getBounds(stage), this, 0);
    draw(myShipSp.getBounds(stage), this, 0);
     
    //only draw bounding box of intersection area if there’s one
    if (!intersec.isEmpty()){
        lines.graphics.clear();
        draw(intersec, lines);
         
        //to draw the intersection area and checking for overlapping of colored area
        var eM:Matrix = enemy1.transform.matrix;
        var myM:Matrix = myShipSp.transform.matrix;
         
        bdt_intersec = new BitmapData(intersec.width, intersec.height, false, 0)
        eM.tx -= intersec.x;
        myM.tx -= intersec.x;
         
        bdt_intersec.draw(enemy1, eM);
        bdt_intersec.draw(myShip, myM);
         
        bm_intersec.bitmapData = bdt_intersec;
        bm_intersec.x = 10
        bm_intersec.y = stage.stageHeight * 0.8 — bm_intersec.height;
         
        t.text = «Intersection area by red rectangle.\n»
    }
    else {
        t.text =»No intersection area.»
    }
}

Обратите внимание, что, поскольку мы рисуем область с использованием матрицы, все масштабирование, перекос и другие преобразования в обоих растровых изображениях принимаются во внимание. Вот демо; проверьте коробку в левом нижнем углу.


Итак, как мы можем проверить правильный пиксель? Ну, во-первых, мы даем цвет этой ячейки пересечения оттенком черного (красный = 0, зеленый = 0, синий = 0). Затем тень меньшего космического корабля будет окрашена в эту темную коробку в зеленый цвет с режимом наложения ADD. Точно так же тень более крупного стационарного космического корабля пришельцев будет окрашена в красный цвет.

Так что теперь для космических кораблей будут выделены красные и зеленые области, а также черные, если нет перекрывающихся областей. Однако если есть пиксели из этих двух растровых изображений, которые перекрываются, они будут отображаться желтым цветом (красный = 255, зеленый = 255, синий = 0). Мы используем метод Bitmapdata.getColorBoundsRect чтобы проверить наличие этой области.

Вот фрагмент в Main.as

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
//to draw the intersection area and checking for overlapping of colored area
var eM:Matrix = enemy1.transform.matrix;
var myM:Matrix = myShipSp.transform.matrix;
 
bdt_intersec = new BitmapData(intersec.width, intersec.height, false, 0)
eM.tx -= intersec.x;
myM.tx -= intersec.x;
 
//tweak color
bdt_intersec.draw(enemy1, eM, new ColorTransform(1,1,1,1,255,-255,-255), BlendMode.ADD);
bdt_intersec.draw(myShip, myM, new ColorTransform(1,1,1,1,-255,255,-255), BlendMode.ADD);
 
bm_intersec.bitmapData = bdt_intersec;
bm_intersec.x = 10
bm_intersec.y = stage.stageHeight * 0.8 — bm_intersec.height;
 
t.text = «Intersection area by red rectangle.\n»
 
//check for the existance of the right color
intersec_color = bdt_intersec.getColorBoundsRect(0xffffff, 0xffff00);
if (!intersec_color.isEmpty()) t.appendText(«And there are interesecting pixels in the area.»);

Обратите внимание, что мы подавляем красный и синий компоненты в строке 113, чтобы максимально использовать зеленый цвет для небольшого космического корабля. На линии 112 мы делаем то же самое с космическим кораблем пришельцев с компонентами Blue и Green.


Поэтому после получения комментариев по вопросам производительности, связанным с обнаружением столкновений, я решил провести несколько быстрых и грязных тестов на этих подходах. Я создал 20 вражеских космических кораблей и один космический корабль игрока и проверил обнаружение столкновений между этим одним кораблем игрока против другого 20. Эти спрайты упакованы в одной и той же окрестности, чтобы вызвать обнаружение столкновений для всех заходов на посадку.

Первый подход самый простой. BitmapData при инициации, и для каждого кадра обнаружение столкновений проверяется с помощью BitmapData.hitTest() . Для второго подхода BitmapData обновляется каждый кадр, и обнаружение столкновений выполняется на основе захваченных BitmapData . Третий относится к подходу, предложенному в этом руководстве.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
bitmapdata fixed (1000 iterations)
Player version: WIN 11,1,102,55 (debug)
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
method……………………………………………ttl ms…avg ms
bitmapdata fixed 168 0.17
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
 
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
bitmapdata updates (1000 iterations)
Player version: WIN 11,1,102,55 (debug)
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
method……………………………………………ttl ms…avg ms
bitmapdata updates 5003 5.00
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
 
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
custom method (1000 iterations)
Player version: WIN 11,1,102,55 (debug)
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
method……………………………………………ttl ms…avg ms
custom method 4408 4.41
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

PerformanceTest дает разные результаты, когда я запускаю тест. Я запустил его несколько раз и вывел среднее время. Вывод: самый быстрый метод — первый, затем третий и второй подход.

Таким образом, хранение BitmapData для растровых изображений, когда они впервые вводятся в сцену, и проверка для hitTest каждый кадр после фактически эффективны, при условии, что эти спрайты не выполняют никаких преобразований, кроме преобразования (таких как вращение, наклон и масштабирование) во времени. В противном случае вы будете вынуждены принять второй или третий подход, а третий более эффективен, как показано на рисунке выше.

Вы можете проверить Collisions.as и Results.as для полного сценария.


После этого я начал искать конкретные строки кода, которые занимали больше времени на вычисления. Второй и третий подход заняли больше времени, поэтому я извлек из них несколько функций, каждая из которых ломалась в разных точках. Проверьте один из результатов ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
default hitTest (1000 iterations)
Player version: WIN 11,1,102,55 (debug)
include bounds
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
method……………………………………………ttl ms…avg ms
default hitTest 189 0.19
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
 
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
default hitTest (1000 iterations)
Player version: WIN 11,1,102,55 (debug)
include transform
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
method……………………………………………ttl ms…avg ms
default hitTest 357 0.36
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
 
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
default hitTest (1000 iterations)
Player version: WIN 11,1,102,55 (debug)
include hittest
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
method……………………………………………ttl ms…avg ms
default hitTest 4427 4.43
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
 
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
custom method (1000 iterations)
Player version: WIN 11,1,102,55 (debug)
inlcude bounds and transform
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
method……………………………………………ttl ms…avg ms
custom method 411 0.41
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
 
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
custom method (1000 iterations)
Player version: WIN 11,1,102,55 (debug)
include draw and bounds
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
method……………………………………………ttl ms…avg ms
custom method 3320 3.32
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

Первый, второй и третий раз относятся ко второму подходу в разных точках останова, а четвертый и пятый раз относятся к третьему подходу. Глядя на третий и пятый раз, BitmapData.draw похоже, занимает много времени на вычисления. И время, затрачиваемое на рисование с использованием второго подхода, кажется более дорогим во времени вычислений, что заставляет меня думать, что размеры для работы с BitmapData.draw имеют значение. Вы можете проверить Collisions2.as и Results2.as для полных сценариев.

Одной вещью, которую я нахожу немного тревожной, является непоследовательность этих тестов — я всегда не получаю одинаковые результаты времени, хотя они почти всегда следуют одному и тому же рейтингу. Итак, его достаточно, чтобы сделать простое сравнение между функциями.

Что ж, спасибо, что уделили время на чтение этого маленького совета. Надеюсь, это было полезно. Оставляйте комментарии, если вы не согласны ни с чем в этом уроке. Я хотел бы ответить на отзыв!