Po jakiejś tam przerwie zdecydowałem się napisać kolejny tutek dotyczący XNA.
W tym tutorialu- Omówienie i wykorzystanie Rayów
- To samo, tylko z BoundingBoxami
- Sortowanie list
- Parę innych pierdół
Na końcu będziemy mieć coś na kształt strzelanki. A więc do roboty.
1. Rozpoczęcie pracyNależy zrobić nowy projekt i dodać w nim następujące pola:
Point mousePosition;
float angle;
SpriteFont font;
Texture2D texture;
Point center;
Random random;
Boolean canShoot;
Nazwy pól mówią same za siebie. Do przechowywania pozycji kursora wybrałem Point zamiast Vector2 z powodu, że Point operuje na wartościach int, a Vector2 na float. Jako że myszka operuje na intach, nie potrzebujemy wektora do zapisywania jej pozycji.
Nowym polom należy nadać wartości w metodzie LoadContent():
mousePosition = new Point(0, 0);
angle = 0;
font = Content.Load<SpriteFont>("Font");
texture = Content.Load<Texture2D>("white");
center = new Point(graphics.PreferredBackBufferWidth / 2, graphics.PreferredBackBufferHeight / 2);
random = new Random();
canShoot = true;
Oczywiście trzeba stworzyć nowego SpriteFonta. Jako teksturę należy wziąć biały piksel. PreferredBackBuffer Height oraz Width określają wymiary okna gry.
Przyda się również widoczny kursor:
IsMouseVisible = true;
2. Metody Update() oraz Draw()Do tej metody dodajemy:
mousePosition.X = Mouse.GetState().X;
mousePosition.Y = Mouse.GetState().Y;
Dzięki czemu aktualna pozycja myszki jest zapisana w polu mousePosition, oraz
angle = (float)Math.Atan2(mousePosition.Y - center.Y, mousePosition.X - center.X);
Tutaj trochę trygonometrii.
Dzięki metodzie Math.Atan2 obliczamy kąt (pomiędzy -π a π, wyrażony w radianach) między dwoma punktami w przestrzeni - w tym przypadku między pozycją myszki a środkiem okna gry.
Teraz czas na uzupełnienie metody Draw():
spriteBatch.Begin();
spriteBatch.DrawString(font, angle.ToString(), new Vector2(10, 10), Color.White);
spriteBatch.End();
Czyli po prostu wyświetlamy obliczany na bieżąco kąt.
3. Klasa TargetTworzymy nową klasę w pliku Target.cs:
class Target
{
public Rectangle Rectangle;
Color Color;
BoundingBox BoundingBox;
public Target(Rectangle rectangle,
Color color,
BoundingBox boundingBox)
{
Rectangle = rectangle;
Color = color;
BoundingBox = boundingBox;
}
public void Draw(SpriteBatch spriteBatch, Texture2D texture)
{
spriteBatch.Draw(texture, Rectangle, Color);
}
}
W tym przypadku zamiast stworzyć pole tektury w klasie, będziemy dostarczać ją jako argument metody Draw() (każdy obiekt będzie miał identyczną teksturę). BoundingBox omówię przy inicjalizacji obiektów stworzonej właśnie klasy, czyli za krótką chwilę.
4. Omówienie BoundingBoxów i uzupełnienie Game1.csTworzymy nowe pole w Game1.cs i inicjalizujemy je w LoadContent():
List<Target> targetList;
targetList = new List<Target>();
W metodzie LoadContent() piszemy:
for (int i = 0; i <= 9; i++)
{
int x = random.Next(0, 785);
int y = random.Next(0, 465);
Target target = new Target(new Rectangle(x, y, 32, 32), Color.White,
new BoundingBox(new Vector3(x, y, 0), new Vector3(x + 16, y + 16, 0)));
targetList.Add(target);
}
Prawie wszystko powinno być zrozumiałe - losujemy liczby x oraz y, tworzymy nowy obiekt klasy Target i dodajemy go do listy. Kłopoty może sprawiać BoundingBox, a więc omówię go poniżej.
BoundingBox jest określany dwoma trójwymiarowymi wektorami - wyznaczają one minimalny oraz maksymalny punkt BoundingBoxa. Chociaż jest to obiekt trójwymiarowy, można go użyć w grach 2D, ustawiając wartość "z" obu wektorów na 0 (albo cokolwiek, ważne żeby wartość "z" wszystkich obiektów 3D była jednakowa).
Dzięki temu ustawieniu, nasz box ma 16 pikseli szerokości, 16 pikseli długości i 0 pikseli głębokości.
Wyświetlmy teraz stworzone obiekty. W tym celu dodajmy do metody Draw():
foreach (Target target in targetList)
{
target.Draw(spriteBatch, texture);
}
5. Ray - co to i po coDo metody Update() dopiszmy:
if (Mouse.GetState().LeftButton == ButtonState.Pressed && canShoot)
{
Ray ray = new Ray(new Vector3(center.X, center.Y, 0), new Vector3((float)Math.Cos(angle), (float)Math.Sin(angle), 0));
canShoot = false;
}
if (Mouse.GetState().LeftButton == ButtonState.Released && !canShoot)
{
canShoot = true;
}
Znów powinno być zrozumiałe wszystko oprócz nowości, którą w tym przypadku jest Ray. Ray jest linią, która rozpoczyna się w podanym punkcie przestrzeni i dąży w danym kierunku. W ten sposób są symulowane np. pociski w FPSach.
6. Kolizje między Rayem a BoundingBoxemW tym tutorialu, przy trafieniu celu rayem będzie zmieniany jego kolor. Dopisujemy do Target.cs metodę:
public Boolean CheckForRayCollisions(Ray ray, Random random)
{
float? collision;
collision = ray.Intersects(BoundingBox);
if (collision != null)
{
Color = new Color(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256));
return true;
}
else
{
return false;
}
}
Metoda Ray.Intersects na pozór jest podobna do Rectangle.Intersects, jednak ta druga zwraca wartość boolean, a pierwsza wartość float?. Znak zapytania po float to skrót: float? to to samo co Nullable<float>. Wartością zwracaną przez Ray.Intersects jest długość raya podczas kolizji z jakimkolwiek BoundingBoxem, BoundingSphere'em lub BoundingFrustrumem. Można to łatwo przekształcić na booleana - jeśli kolizja następuje, wartość zmiennej collision jest różna od null.
Teraz należy zaktualizować Game1.cs. W metodzie Update() pod kodem odpowiadającym za wysyłanie raya wpisujemy:
foreach (Target target in targetList)
{
if (target.CheckForRayCollisions(ray, random))
{
break;
}
}
Jeśli zostaje wykryta kolizja, wybijamy się z pętli (nie chcemy trafiać wielu celów naraz).
7. Sortowanie listPrzy odpaleniu gierki można zobaczyć problem. Jeśli na drodze raya stoją dwa cele, kolorowany jest ten leżący wyżej w indeksie listy - nie ten, który jest najbliżej środka okna. Żeby to poprawić, trzeba posortować listę. W tym celu dodajemy nowe pole do klasy Target:
public float DistanceToCenter;
W Game1.cs w metodzie LoadContent() aktualizujemy kod inicjalizacji celów. Nad poleceniem dodania obiektu do listy dopisujemy:
target.DistanceToCenter = Vector2.Distance(new Vector2(center.X, center.Y),
new Vector2(target.Rectangle.Center.X, target.Rectangle.Center.Y));
Dzięki temu mamy coś, według czego możemy sortować listę obiektów klasy Target. Teraz należy przystosować samą klasę do bycia sortowaną:
class Target : IComparable<Target>
{
Dzięki temu możemy wprowadzić sortowanie z użyciem IComparable. Teraz do klasy należy dodać nową metodę:
public int CompareTo(Target other)
{
return this.DistanceToCenter.CompareTo(other.DistanceToCenter);
}
Teraz mamy jak posortować obiekty. W Game1.cs pod pętlą for, w której tworzymy cele dopisujemy:
targetList.Sort();
PosłowieTego by było na tyle. Jeśli macie jakieś pytania lub uwagi, piszcie na PW albo w temacie.
Źródło projektu z tutoriala:
https://dl.dropbox.com/u/67704253/Xna%20Tutorial/3/source3.zip