XNA is een enorme bibliotheek vol met klassen die de besturing van de hardware op zich nemen. Wij hoeven alleen onze spullen erbij te maken en Klaar is Kees.

Maar Kees lijkt nog een eind ver weg te zijn. Zo we nu ondervonden hebben in het voorbeeld VBGame, is het praktisch nog niet zo gemakkelijk om een spel in elkaar te zetten. Zelfs ik ben nog niet zo ver om goed met XNA overweg te kunnen, maar om te weten hoe de klassen werken is al een pluspunt.

Wel, het terugkaatsen van de bal tegen de paddle was al een vooruitgang, maar waarom de bal zomaar in de paddle terechtkomt, is de vraag. Een vraag waar helaas niet een goed antwoord op te vinden is. Zoeken naar een goed antwoord is zoeken naar een speld in een hooiberg. Tientallen programmeurs proberen de juiste Intersection te vinden om de bal correct te laten botsen. Daar komen verschillende codefragmenten uit waarvan de meeste fragmenten niet correct werken.

De Linesegment AABB techniek

Er bestaat een techniek die het meest gebruikt wordt en goed de Intersection berekend. De code, die ik ook laat zien, is een C# versie. De meeste codefragmenten worden in C# geschreven, maar dat komt ook omdat de Content Pipeline gebruikt wordt voor de objecten en gegevensbestanden - u weet waarschijnlijk nog wel, de Content van de XNA bibliotheek die we in Visual Basic helaas moeten missen. Bovendien is er nog een probleem waar ik het nog niet over gehad heb: de sprite-eigenschappen in de Content, ook die ontbreken in Visual Basic. Om welke eigenschappen het gaat, ziet u in de volgende Bulletin.

Wat is een linesegment?
Een linesegment is een lijndeel van een rechthoek. Dit lijnsegment moet met een ander linesegment worden gecontroleerd of ze elkaar tegenkomen of niet.

Het codefragment
Om te zoeken naar een snijpunt tussen 2D lijnen, is de volgende wiskundige berekening nodig:

Line:    Ax+By = C

A = y2-y1
B = x1-x2
C = A*x1+B*y1

Vind nu het snijpunt:

float det = A1*B2 - A2*B1
   
if(det == 0)
   {
        lijnen zijn parallel
   } else
   {
        float x = (B
2*C1 - B1*C2)/det
        float y = (A
1*C2 - A2*C1)/det
   }

De wiskundige schets kunnen we in code opschrijven in een methode die Vector2 punten heeft voor de 2 lijnen en resulteert de intersection punt;

De methode geeft een Vector2.Zero terug als de lijnen parallel zijn en er daardoor geen intersection plaatsvindt.

Vector2 LineIntersectionPoint(Vector2 ps1, Vector2 pe1,
Vector2 ps2, Vector2 pe2)
{
    // Neem A, B, C van eerste lijn - punten : ps1 tot pe1
    float A1 = pe1.y-ps1.y;
    float B1 = ps1.x-pe1.x;
    float C1 = A1*ps1.x+B1*ps1.y;

    // Neem A, B, C van tweede lijn - punten : ps2 tot pe2
    float A2 = pe2.y-ps2.y;
    float B2 = ps2.x-pe2.x;
    float C2 = A2*ps2.x+B2*ps2.y;

    // Neem delta en controleer of de lijnen parallel zijn
    float delta = A1*B2 - A2*B1;
    if(delta == 0)
        return Vector2.Zero;    // lijnen zijn parallel
    // resulteer de Vector2 intersection punt
    return new Vector2((B2*C1 - B1*C2)/delta,
(A1*C2 - A2*C1)/delta);
}

De punten die doorgegeven worden zijn altijd start - eind punten van de lijnen. Vandaar dat de variabelen ps1, pe1 en ps2, pe2 worden genoemd.

U kunt bovenstaande AABB techniek uitproberen, maar er zijn nog veel meer soorten voorbeelden op internet te vinden. Lijkt het u niks om wiskundig bezig te zijn, dan is er nog een manier om met lijnsegmenten te controleren: Gebruik maken van een rechthoek die teruggegeven wordt door de Intersect() functie. De functie geeft namelijk aan op welke plek de overlapping heeft plaatsgevonden. Misschien is dat wel wat voor u. De functie werkt als volgt:

Cr = Rectangle.Intersect(Ar, Br)

Verwar de functie niet met de Intersects() functie, die zo werkt:

Boolean = Ar.Intersects(Br)

De Vector2.Distance() methode
In plaats van bovenstaande techniek te gebruiken, kunnen we natuurlijk ook alle vier zijden van een rechthoek controleren met Intersects(). Het is echter beter en simpeler om door middel van een afstand te bepalen of de rechthoek van de speler (de bal) de rechthoek van een object (de paddle) tegenkomt. Die manier lijkt op de wiskundige berekening, maar nu resulteert de Distance() methode de berekende afstand tussen die twee.

Eerst hebben we onderstaande lengtes nodig van beide rechthoeken, gedeeld door 2 - het centerpunt. Voor algemeen gebruik neem ik als voorbeeld een player en een enemy, de lengtes kunnen dus bij u anders zijn.

float playerSize = player.Width / 2; // het centerpunt van de rechthoek
float enemySize = enemy.Width / 2;

Voordat we de Intersects() methode gaan vervangen, berekenen we eerst de lokaties van de player en de enemy. De lokaties moeten centerpunten zijn om de afstand te kunnen bepalen tussen de player en de enemy:

Vector2 playerLocation = new Vector2(player.Width / 2,
player.Height /
2);
Vector2 enemyLocation = new Vector2(enemy.Width / 2,
enemy.Height /
2);

Onderstaande conditie bepaald of de halfsizes bij elkaar opgeteld groter of gelijk is dan de berekende afstand tussen het centerpunt van de player en het centerpunt van de enemy. Hier resulteer ik de conditie aan een boolean variabele b, maar u kunt de conditie ook direct gebruiken in een if statement:

b = (playerSize + enemySize >= Vector2.Distance(playerLocation, enemyLocation));

Als de playerSize plus de enemySize opgeteld groter of gelijk is dan de berekende afstand van de lokaties, dan is er een intersection. Waarom? Dat kan ik u hieronder laten zien.

Op deze tekening kunt u het nu duidelijker zien wat er precies gebeurd. Hoe dichter de enemy bij de player komt, hoe kleiner de afstand wordt. Tot het zo dichtbij komt dat de afstand kleiner zal worden dan de halve lengtes van de player en de enemy. Vandaar dat die ook nodig zijn.

Mocht de grootte van de twee rechthoeken verschillen, dan nog zou de conditie prima werken. De conclusie is dat het om de totale som gaat van de twee halve lengtes van twee rechthoeken met een berekende afstand van twee centerpunten van de twee rechthoeken.