Kollisioner med pixlar

Inledning

Tidigare artiklar har tagit upp hur man detekterar kollisioner med hjälp av cirklar och rektanglar. Dessa metoder är relativt enkla och snabba men tyvärr inte så exakta. För perfekt kollisionsdetektering så får vi testa kollision på pixelnivå. För att göra metoden någorlunda effektiv så kombinerar vi kollision med rektanglar (eller cirklar) för att sedan gå vidare för att undersöka pixlarna.

bild

Bilderna ovan illustrerar problemet. I bilden till vänster hade indikerat en kollision om vi enbart använde oss av rektanglar. Bilden till höger visar ett exempel på riktigt en kollision. Vi vill veta om vi har en kollision då pixlar från båda föremålen överlappar varandra.

Överlappande rektangel

Vi utgår från rektanglar för att avgöra om det kan tänkas ske en kollision. Så först beräknar vi den överlappande rektangeln (om det finns en sådan) med funktionen Intersection som beskrivs mer i artikeln Kollisioner med rektanglar .

Om vi har en överlappande rektangel så måste denna ha en Width och en Height som båda är större än 0. Då vet vi att vi måste undersöka noggrannare.


Rectangle playerBox = new Rectangle((int)playerX, (int)playerY, 48, 60);
Rectangle meteorBox = new Rectangle((int)meteorX, (int)meteorY, 110, 110);

//Överlappar vi?
kollision = Intersection(playerBox, meteorBox);

if (kollision.Width > 0 && kollision.Height > 0)
{
    Rectangle r1 = Normalize(playerBox, kollision);
    Rectangle r2 = Normalize(meteorBox, kollision);
    bHit = TestCollision(ship, r1, meteor, r2);
}
else
    bHit = false;

Räkna ut pixelområden

Den överlappande rektanglen är ett område som delas av båda texturerna som kolliderar. Därför är det dessa texturer och pixlarna i dem som måste undersökas. Tittar vi bara på texturerna så har de ett eget koordinatsystem som börjar på (0,0) längst upp i vänstra hörnet. Rektangeln kollision (i exemplet ovan) har koordinater som stämmer överrens med skärmens koordinater. Första steget är att normalisera och räkna fram rektanglar som motsvarar områden i texturerna. Funktionen Normalize gör just detta.


public static Rectangle Normalize(Rectangle reference, Rectangle overlap)
{
    //Räkna ut en rektangel som kan användas relativt till referensrektangeln
    return new Rectangle(
      overlap.X - reference.X,
      overlap.Y - reference.Y,
      overlap.Width,
      overlap.Height);
}

Testa pixlarna!

När vi har två rektanglar som beskriver pixelområdet i de båda texturerna så kan vi fortsätta. Dessa områden är lika stora då de kommer från den överlappande rektangeln. Pixlarna kopieras ut som rådata med funktionen GetData och lägs i separata vektorer.

Därefter så loop'ar vi igenom vektorerna och jämför varje överlappande pixel. Villkoret är att det ska finnas en färg som inte är helt genomskinlig på samma plats i båda vektorerna för att det ska räknas som en träff!

Nu behövs det en kort introduktion till grafik. Det är kanske bekant att grafik byggs upp av RGB (röd, grön och blått) värden. Utöver detta så finns det också ett alpha värde som bestämmer hur genomskinlig färgen är. Effekten av alpha syns bästa när två föremål ritas ovanpå varandra. Ett konkret exempel är den röda rektangeln i bilden ovan. Den ritas ut med ett lågt alpha-värde för att indikera att den är aningen genomskinlig.

Allt som nämnts nu går att sätta ihop till en funktion som testar kollision, t.ex. TestCollision. Som inparametrar tar den två texturer och två rektanglar. Rektanglarna beskriver då vilka områden i respektive textur som ska jämföras. Funktionen returnerar då true om det är kollision, annars false.


public static bool TestCollision(Texture2D t1, Rectangle r1, Texture2D t2, Rectangle r2)
{
    //Beräkna hur många pixlar som finns i området som ska undersökas
    int pixelCount = r1.Width * r1.Height;
    uint[] texture1Pixels = new uint[pixelCount];
    uint[] texture2Pixels = new uint[pixelCount];

    //Kopiera ut pixlarna från båda områdena
    t1.GetData(0, r1, texture1Pixels, 0, pixelCount);
    t2.GetData(0, r2, texture2Pixels, 0, pixelCount);
    
    //Jämför om vi har några pixlar som överlappar varandra i områdena
    for (int i = 0; i < pixelCount; ++i)
    {
        if (((texture1Pixels[i] & 0xff000000) > 0)&& ((texture2Pixels[i] & 0xff000000) > 0))
        {
            return true;
        }
    }
    return false;
}

Koden texture1Pixels[i] & 0xff000000 "klipper av" RGB informationen för pixeln så att bara alpha-värdet för varje pixel jämförs. Vidare läsning kring logiska operationer på bitnivå rekommenderas. Talet 0xff000000 är skrivet på hexadecimal form, därav 0x i början. Informationen är i följande ordning; alpha röd grön blå, där varje del är 8 bitar stor, dvs. två hexadecimala siffror. texture1Pixels[i] & 0xff000000 blir alltså att vi sparar alla alpha-information och klipper bort resten då den inte är intressant för oss.

DEMO

Allt finns såklart som en liten demonstration att ladda hem. För att illustrera grafiskt så har grafiken delats upp till följande för att kunna visa rektanglarna runt föremålen.

bild

bild

Bugg i XNA?

När denna artikel konstruerades så dök följande exception upp vid användning av GetData.

bild

Efter en del testande så framgick det att den textur som sist ritades ut i Draw kraschade då man i Update anropade GetData på samma textur. Bara genom att ändra ordningen i Draw fixade problemet. Dock kan det bli ett problem om det bara finns 2 texturer och båda ska användas i samband med GetData metoden. Tydligen så markeras inte den sist använda texturen som klar i uppritningen av XNA.

MYCKET märkligt men en varning till er andra om ni skulle råka ut för samma problem.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Scroll to top