Podstawy XNA - Wykrywanie kolizji w przestrzeni 2D  Udostępnij na: Facebook

Autor: Kuba Ostrowski

Opublikowano: 2012-04-02

Kolizja służy w grach do wykrywania interakcji obiektów ze środowiskiem gry. W tym samouczku zostanie opisany podstawowy sposób wykrywania kolizji.

Przed wykonaniem zadań powinieneś:

Po wykonaniu zadań nauczysz się:

  • jak zaimplementować proste wykrywanie kolizji.

Implementacja

Naszym zadaniem będzie utworzenie prostego systemu wykrywania kolizji. Do tego celu wykorzystamy obraz piłeczki. Jeśli dwie wyświetlone tekstury nałożą się na siebie, to kolor czyszczenia ekranu zostanie zmieniony na czerwony. Efekt został przedstawiony na Rys. 1.

Rys. 1. Efekt końcowy aplikacji.

W celu wykrycia kolizji w aplikacji oraz ujrzenia jej skutków musimy:

  • dodać plik graficzny do Content,
  • dodać pola do klasy, w której będziemy je wykorzystywali,
  • wczytać teksturę oraz zainicjalizować pozostałe zmienne w metodzie LoadContent(),
  • określić kolor czyszczenia ekranu w zależności od zmiennej,
  • wyświetlić obrazki przedstawiające obiekty w metodzie Draw(),
  • dodać wykrywanie kolizji oraz zmianę pozycji elementów w metodzie Update().

Dodawanie zasobów do aplikacji

W pierwszym kroku musimy dodać grafikę do projektu.

  1. Uruchom program Microsoft Visual Studio 2011.
  2. Utwórz nowy projekt i nazwij go "WykrywanieKolizji2D".
  3. Pobierz plik, niezbędny do wykonania ćwiczenia, oraz dodaj go do projektu:
  • pobierz i rozpakuj plik KursXNA.zip,
  • w strukturze katalogów odnajdź plik pilka.png,
  • następnie, dodaj plik do projektu " WykrywanieKolizji2DContent (Content)".

Utworzenie zmiennych pomocniczych

Kolejnym krokiem jest utworzenie zmiennych pomocniczych oraz wczytanie tekstury.

  1. Dodaj nowe pola do klasy Game1:
  • otwórz plik Game1.cs,
  • w klasie Game1, poniżej linii SpriteBatch spriteBatch; dodaj:
Texture2D pilka;
Rectangle rectPilka1,rectPilka2;
Color color;
float pred1X,pred1Y,pred2X,pred2Y;
Informacja

Opis utworzonych zmiennych:

  • pilka – zmienna do przechowywania tekstury piłeczki,
  • rectPilka1, rectPilka2 – opisują prostokąt, w jakim znajdują się elementy na ekranie,
  • color – ta zmienna będzie służyć do operowania kolorem czyszczenia ekranu, jeśli wystąpi kolizja będzie przechowywać kolor czerwony, jeżeli nie – domyślny - jasno niebieski,
  • pred1X, pred1Y, pred2X, pred2Y – prędkość przemieszczania się elementów.
  1. Wczytaj teksturę oraz zainicjalizuj zmienne, w celu późniejszego ich wykorzystania:
  • przejdź do metody LoadContent(), po linijce // TODO wpisz następujący kod:
//pobieranie tekstury
pilka = Content.Load<Texture2D>("pilka");
//właściwości wyświetlanych elementów
pred1X = 0.25f;
pred1Y = 0.25f;
pred2X = -0.25f;
pred2Y = -0.25f;
rectPilka1 = new Rectangle(GraphicsDevice.Viewport.Width / 2 - pilka.Width / 2, GraphicsDevice.Viewport.Height/2, pilka.Width, pilka.Height);
rectPilka2 = new Rectangle(GraphicsDevice.Viewport.Width / 2 - pilka.Width / 2, GraphicsDevice.Viewport.Height / 2 + pilka.Height/2, pilka.Width, pilka.Height);
color = Color.CornflowerBlue;

Wyświetlanie na ekranie

Gdy mamy już utworzone i odpowiednio zainicjalizowane zmienne, możemy przejść do wyświetlenia elementów na ekranie w celu ujrzenia efektów naszej pracy.

  1. Ustaw kolor czyszczenia ekranu w zależności od zmiennej color:
  • przejdź do metody Draw(), w kodzie podmień argument wysyłany do metody GraphicsDevice.Clear(Color.CornflowerBlue); na zmienną color:
GraphicsDevice.Clear(color);
  1. Wyświetl obrazki:
  • poniżej //TODO, w metodzie Draw() dodaj kod odpowiedzialny za rysowanie elementów:
spriteBatch.Begin();
spriteBatch.Draw(pilka, rectPilka1, Color.White);
spriteBatch.Draw(pilka, rectPilka2, Color.Green);
spriteBatch.End();
  • zapisz zmiany w pliku,
  • wciśnij F5 lub wybierz Debug->Start Debugging,
  • sprawdź, czy Twoja aplikacja wyświetliła obrazki tak, jak na Rys. 2.

Rys. 2. Wyświetlenie elementów na ekranie.

Kolizja

W celu ukończenia aplikacji musimy jeszcze dodać wykrywanie kolizji między piłeczkami oraz odpowiednio obsłużyć sytuacje, w których taka kolizja zajdzie.

Najprostszym sposobem wykrywania kolizji jest sprawdzenie, czy  obszar wyświetlanych elementów pokrywa się. Elementy w przestrzeni 2D opisane są za pomocą prostokątów składających się z czterech głównych właściwości: pozycji x , pozycji y, szerokości oraz wysokości elementu. W celu zaimplementowania kolizji należałoby napisać parę linijek kodu. Na szczęście, w XNA struktura Rectangle zawiera metodę Intersect(Rectangle inny prostokąt), która sprawdza, czy prostokąt ma wspólny obszar z prostokątem przesłanym jako jej argument. Jako wynik zwraca true - jeśli go posiada oraz false - jeśli nie.

  1. Zaimplementuj kolizję:
  • przejdź do metody Update(), poniżej //TODO: dopisz linijki odpowiadające za zmianę pozycji piłeczek w czasie:
rectPilka1.X += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred1X);
rectPilka1.Y += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred1Y);
rectPilka2.X += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred2X);
rectPilka2.Y += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred2Y);
//kolizja
  • następnie, poniżej //kolizja dodaj obsługę mechanizmu kolizji:
if (rectPaletka.Intersects(rectPilka))
     color = Color.Red;
else color = Color.CornflowerBlue;
//sprawdzenie
  • kolejnym ważnym zadaniem w aplikacji jest sprawdzenie, czy elementy znajdują się w granicach ekranu. Dodaj osobną metodę CheckBounds, która ma na celu skorygowanie pozycji i prędkości piłeczek, jeżeli te wyjdą poza ekran:
public void CheckBounds(ref Rectangle rect,ref float predX,ref float predY)
{
  if (rect.X + rect.Width > GraphicsDevice.Viewport.Width) {
    rect.X = GraphicsDevice.Viewport.Width - rect.Width;
    predX = -predX;
  }
  if (rect.X < 0) {
    rect.X = 0;
    predX = -predX;
  }
  if (rect.Y + rect.Height > GraphicsDevice.Viewport.Height) {
    rect.Y = GraphicsDevice.Viewport.Height - rect.Height;
    predY = -predY;
  }
  if (rect.Y < 0) {
    rect.Y = 0;
    predY = -predY;
  }
}
  • przejdź do metody Update(), poniżej linii //sprawdzenie wywołaj metodę CheckBounds dla piłeczek:
CheckBounds(ref rectPilka1, ref pred1X,ref pred1Y);
CheckBounds(ref rectPilka2, ref pred2X, ref pred2Y);
  • zapisz zmiany w pliku,
  • wciśnij F5 lub wybierz Debug->Start Debugging,
  • sprawdź, czy Twoja aplikacja wyświetliła czerwony kolor ekranu podczas kolizji, tak jak na Rys. 1.

Podsumowanie

W tym artykule nauczyliśmy się wykrywać kolizję między elementami wyświetlonymi na ekranie w przestrzeni 2D.

Zadanie

W celu utrwalenia nabytej wiedzy wykonaj proste zadanie. Stwórz kolejną piłeczkę, obsłuż jej kolizję oraz dodaj zmianę jej pozycji w czasie.

Aby zobaczyć podpowiedzi i rozwiązania zadań przełącz widok tej strony na >> klasyczny <<.

  • wykorzystaj dodatkową zmienną, opisującą obszar wyświetlania typu Rectangle,
  • dodaj zmienną pomocniczą typu bool w metodzie Update(), służącą za flagę informującą, czy zaszła jakaś kolizja na ekranie. Na początku metody ustaw flagę na false, jeśli zajdzie kolizja zmień na true. Pod koniec Update(), jeżeli nie było kolizji, a flaga przechowuje wartość false, przywróć domyślny kolor ekranu.
Texture2D pilka;
Rectangle rectPilka1,rectPilka2,rectPilka3;
Color color;
float pred1X,pred1Y,pred2X,pred2Y,pred3X,pred3Y;

protected override void LoadContent()
     {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            //pobranie tekstury
            pilka = Content.Load<Texture2D>("pilka");
            //właściwości wyświetlanych elementów
            pred1X = 0.25f;
            pred1Y = 0.25f;
            pred2X = -0.25f;
            pred2Y = -0.25f;
            pred3X = -0.35f;
            pred3Y =  0.35f;
            rectPilka1 = new Rectangle(GraphicsDevice.Viewport.Width / 2 - pilka.Width / 2, GraphicsDevice.Viewport.Height/2, pilka.Width, pilka.Height);
            rectPilka2 = new Rectangle(GraphicsDevice.Viewport.Width / 2 - pilka.Width / 2, GraphicsDevice.Viewport.Height / 2 + pilka.Height/2, pilka.Width, pilka.Height);
            rectPilka3 = new Rectangle(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2 - pilka.Height/2, pilka.Width, pilka.Height);
            color = Color.CornflowerBlue;
}

protected override void Update(GameTime gameTime)
{
            // TODO: Add your update logic here
            rectPilka1.X += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred1X);
            rectPilka1.Y += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred1Y);
            rectPilka2.X += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred2X);
            rectPilka2.Y += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred2Y);
            rectPilka3.X += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred3X);
            rectPilka3.Y += (int)(gameTime.ElapsedGameTime.TotalMilliseconds * pred3Y);
            //kolizja
            bool isCollision = false;
            if (rectPilka1.Intersects(rectPilka2))
            {
                color = Color.Red;
                isCollision = true;
            }
            if (rectPilka1.Intersects(rectPilka3))
            {
                color = Color.Red;
                isCollision = true;
            }
            if (rectPilka2.Intersects(rectPilka3))
            {
                color = Color.Red;
                isCollision = true;
            }
            if(!isCollision)
                color = Color.CornflowerBlue;
            //sprawdzenie
            CheckBounds(ref rectPilka1, ref pred1X,ref pred1Y);
            CheckBounds(ref rectPilka2, ref pred2X, ref pred2Y);
            CheckBounds(ref rectPilka3, ref pred3X, ref pred3Y);
            base.Update(gameTime);
 }
 protected override void Draw(GameTime gameTime)
 {
            GraphicsDevice.Clear(color);
            // TODO: Add your drawing code here
            spriteBatch.Begin();
            spriteBatch.Draw(pilka, rectPilka1, Color.White);
            spriteBatch.Draw(pilka, rectPilka2, Color.Green);
            spriteBatch.Draw(pilka, rectPilka3, Color.Red);
            spriteBatch.End();
            base.Draw(gameTime);
 }