Pokaż wiadomości

Ta sekcja pozwala Ci zobaczyć wszystkie wiadomości wysłane przez tego użytkownika. Zwróć uwagę, że możesz widzieć tylko wiadomości wysłane w działach do których masz aktualnie dostęp.


Pokaż wątki - Ezzam

Strony: [1]
1
XNA / Kolizje w grach 2D
« dnia: 2012-09-14, 20:24 »
Tym razem tutorial będzie na temat ogólnogamedevowy, a mianowicie wykrywanie i odpowiadanie na kolizje. Jest to prosta i skuteczna technika, dobra do wykorzystania w grach opartych na tile'ach (chociaż bezproblemowo działa przy prawie każdej kolizji opartej na prostokątach). Przykład będę wyjaśniał na podstawie XNA. tak, obrazki też będą

Teoria
Sposób, o którym będę mówić, używa głębokości kolizji do określenia kierunku, z którego następuje. Jak to działa?
napisy na rysunkach są wzięte z kodu, tak dla łatwiejszego zrozumienia

Na obrazku widać poprzednią i aktualną pozycję naszego kwadratu. W miejscu, w którym znajduje się teraz, zderza się z terenem. Zajmiemy się teraz zamalowanym na niebiesko kawałkiem, który jest w stanie powiedzieć nam, skąd nadchodzi kolizja.


Na tym etapie obliczamy głębokość zderzenia na obu osiach. Jeśli zderzenie nastąpiło w pionie, wtedy głębokość kolizji będzie mniejsza w pionie i vice versa. Porównujemy ze sobą pozycję naszego prostokąta oraz przeszkody, znajdując kierunek zderzenia. Wtedy przesuwamy obiekt (gracza czy cokolwiek innego) o głębokość kolizji w znalezionym kierunku.

Kod
void CheckForCollisions()
        {
            foreach (Rectangle rect in tiles)
            {
                Rectangle collisionRectangle = Rectangle.Intersect(rect, player);

                if (collisionRectangle.Height < collisionRectangle.Width) // Kolizja w osi pionowej
                {
                    if (player.Center.Y > rect.Center.Y) // Kolizja od dołu
                    {
                        player.Y += collisionRectangle.Height;
                    }
                    else // Kolizja z góry
                    {
                        player.Y -= collisionRectangle.Height;
                    }
                }
                else if (collisionRectangle.Width < collisionRectangle.Height) // Kolizja w osi poziomej
                {
                    if (player.Center.X > rect.Center.X) // Kolizja z prawej
                    {
                        player.X += collisionRectangle.Width;
                    }
                    else // Kolizja z lewej
                    {
                        player.X -= collisionRectangle.Width;
                    }
                }
            }
        }
Trochę wyjaśnienia: player to zwykły prostokąt, a tiles to lista prostokątów - przeszkód. Użyłem tu metody, której nie używałem we wcześniejszych tutorialach - Rectangle.Intersect. Jej zadaniem jest stworzenie nowego prostokąta w miejscu, w którym zderzają się dwa inne. Odczytuję wtedy szerokość i długość nowego prostokąta (xDepth oraz yDepth na obrazku).

Ograniczenie
Ta metoda, chociaż dobra, ma pewne ograniczenie - prostokąt nie może poruszać się zbyt szybko w stosunku do wielkości przeszkód. Wyjaśnienie:


Chociaż widzimy, że kolizja nadchodzi z lewej, to prostokąt porusza się na tyle szybko, że jego środek jest położony na prawo od środka przeszkody. Przez to kolizja jest wykrywana i rozwiązywana niepoprawnie. Bezpieczna prędkość to poniżej połowy szerokości przeszkody na klatkę (to i tak naprawdę szybko).

Posłowie
Więc doszliśmy do końca tutoriala. Teraz powinniście wiedzieć, jak poprawnie radzić sobie ze zderzeniami w grach 2D.

Źródło: https://dl.dropbox.com/u/67704253/Xna%20Tutorial/4/source4.zip

2
XNA / Kolejny tutorial nt. XNA
« dnia: 2012-09-11, 18:13 »
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 pracy
Należ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 Target
Tworzymy 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.cs
Tworzymy 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 co
Do 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 BoundingBoxem
W 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 list
Przy 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łowie
Tego 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

3
Offtopic / Kontrowersyjna Kremówka
« dnia: 2012-09-08, 08:23 »


Primo: Dlaczego w naszym kraju za niesmaczne uważane są żarty na temat JP2, a kawały np. o żydach nie?

Secundo: Dlaczego Karol jest otoczony tak wielkim kultem, skoro niczego wielkiego nie dokonał?

Czekam przede wszystkim na opinię Luka, który najwyraźniej poczuł się urażony żartami na temat jotpedwa.

4
Twórczość / Ludum Dare #24 - moja gra
« dnia: 2012-08-26, 14:34 »
W ten łikent wziąłem udział w Ludum Dare, a oto efekt mojej pracy:
http://www.ludumdare.com/compo/ludum-dare-24/?action=preview&uid=11033

Zasadą jamu jest to, że wszystkie assety oraz kod muszą być zrobione w jedynie dwa dni. Swój cel osiągnąłem w 100%, bo było nim samo skończenie gry :F

5
Twórczość / Mój schowek
« dnia: 2012-08-19, 12:05 »
Do tego tematu będę wrzucał luźne materiały.

Zrobione na szybko dynamiczne oświetlenie:
[media]
https://www.youtube.com/watch?v=sH1KtP1jIFQ
[/media]
Użyłem 180 raysów, a światło jest symulowane w komórkach 16x16 pikseli. W dodatku nagrywanie dość mocno odbiło się na wydajności, więc film musiałem nagrać w zamkniętym pomieszczeniu :wink:

Jak skończę platformówkę, to postaram się wykorzystać to w jakiejś gierce.

6
Twórczość / Projekt platformówka
« dnia: 2012-08-11, 11:50 »
Robiony od około tygodnia po godzinkę lub dwie dziennie dla rozwinięcia umiejętności.

Gotowe ficzery:
  • wykrywanie kolizji
  • edytor poziomów wraz z zapisywaniem mapy do pliku
  • podstawowa mechanika gry, czyli skakanie etc.
Jeszcze do zrobienia:
  • sklecenie paru mapek
  • npc i proste questy (typu przynoszenie pierdół do enpeca)
  • ładniejsze przejścia między poszczególnymi tile'ami
Screen z gry:


Wideo z edytora (stara wersja, nowa różni się tylko mechanizmem zapisu mapy):
[media]
https://www.youtube.com/watch?v=f-4PgVL02yQ
[/media]

7
XNA / Podstawy XNA - część druga
« dnia: 2012-08-04, 14:44 »
W tym tutorialu rozbudujemy stworzoną wcześniej przez nas grę. A więc do dzieła.

1. Rozbudowanie klasy Weight
Klasę Weight musimy wzbogacić o booleana:
       public Boolean IsFlying;Chcemy mieć do niego dostęp z pliku Game1.cs, więc musi być on publiczny. Upubliczniamy również teksturę, ponieważ będzie ona zmieniana w trakcie gry.
Dodajemy parametr do konstruktora i przypisujemy do niego pole:
       public Weight(Texture2D texture, Rectangle rectangle, Vector2 position, float gravity, Boolean isFlying)
        {
            Texture = texture;
            Rectangle = rectangle;
            Position = position;
            Gravity = gravity;
            IsFlying = isFlying;
        }
2. Nowe pola w Game1.cs
Stwórzmy teraz kolejne cztery pola:
       int score;
        Boolean isPlayerAlive;
        Random random;
        Texture2D weightTexture_2;
Int i boolean nie wymagają tłumaczenia, natomiast random jest generatorem liczb losowych. Do nowych pól przypiszmy teraz wartości w metodzie LoadContent()
           score = 0;
            isPlayerAlive = true;
            random = new Random();
            weightTexture_2 = Content.Load<Texture2D>("weight2");
Nowa tekstura:


3. Zmiany w metodzie Update()
Pierwszym, co rzuca się w oczy jest podkreślenie pod deklaracją nowego obiektu klasy Weight. Jest to spowodowane dodaniem nowego parametru do rzeczonej klasy. Żeby nie było błędów, do deklaracji trzeba dodać nowy parametr:
               Weight weight = new Weight(weightTexture, new Rectangle(mouseRectangle.X, mouseRectangle.Y, 64, 64), new Vector2(mouseRectangle.X, mouseRectangle.Y), 0f, true);Graczem chcemy ruszać tylko wtedy, kiedy jest żywy. By to osiągnąć, sterowanie postacią trzeba wsadzić w warunek if:
           if (isPlayerAlive == true)
            {
                if (Keyboard.GetState().IsKeyDown(Keys.Right))
                {
                    playerRectangle.X += 3;
                }
                if (Keyboard.GetState().IsKeyDown(Keys.Left))
                {
                    playerRectangle.X -= 3;
                }
            }
Dodajmy też warunek if do zrzucania odważników, by nie było to możliwe po śmierci postaci.
           if (Mouse.GetState().LeftButton == ButtonState.Pressed && canClick == true && isPlayerAlive == true)
            {
                Weight weight = new Weight(weightTexture, new Rectangle(mouseRectangle.X, mouseRectangle.Y, 64, 64), new Vector2(mouseRectangle.X, mouseRectangle.Y), 0f, true);

                weightList.Add(weight);
                canClick = false;
            }
Coś co zapomniałem wytłumaczyć w poprzednim tutorialu - && oznacza logiczne 'and', czyli by blok if był wykonany, wszystkie warunki muszą być spełnione.

W poprzednim tutorialu było małe niedopatrzenie - zrobiłem dwie pętle foreach w miejscu, gdzie można załatwić wszystko w jednej. Kopiujemy zawartość jednej pętli do drugiej, a niepotrzebną usuwamy.
           foreach (Weight weight in weightList)
            {
                weight.Position.Y += weight.Gravity;
                weight.Rectangle.Y = (int)weight.Position.Y;
                weight.Gravity += 0.1f;

                if (weight.Rectangle.Intersects(playerRectangle))
                {
                    playerRectangle.Y = -100;
                }
            }
Odważniki powinny spadać i uderzać w gracza tylko wtedy, kiedy są w powietrzu, a gracz żyje. W tym celu wstawiamy warunek do pętli foreach odpowiadającej za spadanie:
           foreach (Weight weight in weightList)
            {
                if (weight.IsFlying == true)
                {
                    weight.Position.Y += weight.Gravity;
                    weight.Rectangle.Y = (int)weight.Position.Y;
                    weight.Gravity += 0.1f;

                    if (weight.Rectangle.Intersects(playerRectangle) && isPlayerAlive == true)
                    {
                        playerRectangle.Y = -100;
                    }
                }
            }
Kiedy odważnik uderzy w ziemię, chcemy by się zatrzymał. W tym celu dodajemy kolejny warunek do pętli foreach:
if (weight.Rectangle.Intersects(groundRectangle))
                    {
                        weight.Rectangle.Y = 384;
                        weight.IsFlying = false;
                    }
Jeśli wykryta jest kolizja między odważnikiem a podłożem, odważnik jest ustawiany na wysokości 384 i zmieniana jest wartość pola odpowiadającego za jego latanie oraz zabijanie postaci.
Czas zaktualizować kolizje odważnika z graczem:
                   if (weight.Rectangle.Intersects(playerRectangle))
                    {
                        isPlayerAlive = false;
                        weight.Texture = weightTexture_2;
                    }
Teraz zamiast przemieszczać gracza w niewidoczne miejsce, zmieniamy wartość booleana. Zmieniamy również teksturę odważnika, który uderzył w gracza.

4. Zmiany w metodzie Draw()
Jeśli uruchomiliście grę po przeprowadzeniu zmian w Update(), zobaczyliście, że po uderzeniu odważnika w gracza, gracz nadal jest wyświetlany. Pora uzależnić jego wyświetlanie od booleana isPlayerAlive.
           if (isPlayerAlive == true)
            {
                spriteBatch.Draw(playerTexture, playerRectangle, Color.White);
            }
Mogliście zauważyć również, że gracz jest wyświetlany za odważnikami. Spowodowane jest to kolejnością rysowania - każda kolejna rzecz jest rysowana na poprzedniej. Żeby zmienić kolejność rysowania, wystarczy zmienić kolejność kodu - blok kodu stworzony przed chwilą należy przenieść pod pętlę foreach, która odpowiada za rysowanie odważników:
           foreach (Weight weight in weightList)
            {
                weight.Draw(spriteBatch);
            }
            if (isPlayerAlive == true)
            {
                spriteBatch.Draw(playerTexture, playerRectangle, Color.White);
            }
5. Pisanie tekstu i wyniki
Żeby mieć możliwość napisania czegokolwiek, trzeba stworzyć nowy SpriteFont. W tym celu zadeklarujmy:
       SpriteFont font;Teraz trzeba utworzyć plik czcionki. Żeby to zrobić, prawoklik na [Nazwa projektu]Content (Content)Add...New Item...Sprite Font. Wybieramy nazwę fonta (w moim przypadku score) i klikamy OK.
Teraz chwila o pliku XML stworzonego fonta. Omówię tu trzy najważniejsze rzeczy:
   <FontName>Segoe UI Mono</FontName>To oczywiście nazwa czcionki.

   <Size>14</Size>Kolejna oczywistość - rozmiar czcionki.

   <CharacterRegions>
      <CharacterRegion>
        <Start>& #32;</Start>
        <End>& #126;</End>
      </CharacterRegion>
    </CharacterRegions>
Ten kawałek kodu odpowiada za to, jakie znaki wpakować do czcionki. Domyślnie nie ma w niej polskich znaków. Żeby były one dostępne, trzeba rozszerzyć CharacterRegion - między znacznikami <End> i </End> wpisać & #399;Dzięki temu czcionka będzie zawierała cały pakiet znaków Łaciński Rozszerzony - B. Ważne - Kody symboli nie mają spacji między & i #.

Wracamy do Game1.cs. W LoadContent() przypisujemy polu font naszego spritefonta:
           font = Content.Load<SpriteFont>("score");Teraz możemy zająć się aktualizacją wyniku. Za każdy odważnik którego uniknęliśmy zostanie dodany punkt. Należy zaktualizować kod kolizji odważnika z podłożem.
                   if (weight.Rectangle.Intersects(groundRectangle))
                    {
                        weight.Rectangle.Y = 384;
                        weight.IsFlying = false;

                        if (isPlayerAlive == true)
                        {
                            score += 1;
                        }
                    }
Punkt jest dodawany, jeśli odważnik uderza w ziemię, a gracz żyje. Wynik trzeba jeszcze narysować.
W metodzie Draw() dopisujemy:
           spriteBatch.DrawString(font, "Wynik: " + score.ToString(), new Vector2(10, 10), Color.Black);DrawString przyjmuje cztery parametry: czcionkę, tekst, wektor i kolor. Przed napisaniem wyniku (pole score) trzeba go skonwertować do stringa, czym zajmuje się metoda ToString.

6. Liczby losowe
Chcemy, by odważniki były zrzucane w losowych miejscach. Najpierw zmieńmy trochę kod aktualizacji pozycji odważników:
weight.Position.Y += weight.Gravity;
                    weight.Rectangle.Y = (int)weight.Position.Y;
                    weight.Rectangle.X = (int)weight.Position.X;
                    weight.Gravity += 0.1f;
Dzięki temu pozycja pozioma prostokątów jest równa pozycji poziomej wektora.
Teraz zmieńmy kod odpowiadający za zrzut odważników:
Weight weight = new Weight(weightTexture, new Rectangle(0, 0, 64, 64), new Vector2(random.Next(0,737), -64), 0f, true);Prostokąt ustawiamy w pozycji 0,0 ponieważ aktualizuje się on względem wektora. Do wektora z kolei przypisujemy wartość X losowaną spomiędzy 0 a 736 (736, nie 737) i wartość Y równą -64.

7. Posłowie
Kolejny tutorial ukończony. Oczywiście czekam na komentarze, krytykę, pytania etc.

Źródło projektu: https://dl.dropbox.com/u/67704253/Xna%20Tutorial/2/source2.zip

8
XNA / Podstawy XNA
« dnia: 2012-08-03, 11:40 »
Witam w moim pierwszym tutorialu na temat XNA. Obejmie on stworzenie prostej gierki. Do pełnego zrozumienia tutoriala przyda się znajomość podstaw C#, chociaż będę próbował tłumaczyć zagadnienia z języka dla tych, którzy go nie znają. Link do szybkiego kursu C#: http://rbwhitaker.wikidot.com/c-sharp-tutorials. A więc zaczynamy.

1. Przygotowanie potrzebnych narzędzi
Na początek należy ściągnąć Visual C# 2010 Express (http://www.microsoft.com/visualstudio/en-us/products/2010-editions/visual-csharp-express) oraz XNA Game Studio 4.0 (http://www.microsoft.com/en-us/download/details.aspx?id=23714). Najpierw trzeba zainstalować Visual C# Express, ponieważ XNA Game Studio jest dodatkiem do niego.

2. Tworzenie nowego projektu
Po odpaleniu Visual C#, klikamy na New Project.... Wyświetla się okno. Wybieramy Windows Game (4.0), ustawiamy nazwę dla swojego projektu (ja wybrałem XnaTut) i wybieramy jego lokalizację na dysku. Po kliknięciu OK przechodzimy do kolejnego punktu.

3. Przegląd kodu
Czas na omówienie sekcji kodu.

public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
Jest to miejsce, w którym deklarujemy pola klasy Game1.

       protected override void Initialize()
        {
            base.Initialize();
        }
Inicjalizacja gry. Kod, który tu wpiszemy zostanie wykonany przy uruchamianiu gry.

       protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }
Pełni taką samą funkcję jak Initialize(), ale jest przeznaczony do wczytywania zasobów graficznych.

       protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }
Metoda Update() jest wywoływana co klatkę. To tutaj wykonywany jest ruch, wykrywanie kolizji etc.

       protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            base.Draw(gameTime);
        }
Jak nazwa wskazuje, ta metoda odpowiada za rysowanie.

4. Tworzenie nowej klasy
Gra stworzona podczas tego tutoriala będzie polegała na unikaniu spadających przedmiotów. Każdy z tych przedmiotów musi posiadać parę parametrów, takich jak prędkość, pozycja etc. Jako że tych przedmiotów musi być wiele, należy stworzyć nową klasę. W tym celu prawoklik na nazwie projektu w Solution Explorer (w moim przypadku XnaTut) → Add...Class.... Nazywamy swoją klasę (ja nazwałem ją 'Weight') i klikamy OK. Zostawmy na chwilę nową klasę i przejdźmy do kolejnego punktu.

5. Przypisywanie wartości i wczytywanie tekstur
Musimy teraz zadeklarować dwie tekstury i dwa prostokąty. W tym celu piszemy w Game1.cs:
       Texture2D playerTexture;
        Texture2D groundTexture;

        Rectangle playerRectangle;
        Rectangle groundRectangle;
Do zadeklarowania tekstur będą nam potrzebne same tekstury. Oto te arcydzieła:



Kolejno: ground, player, weight. Żeby dodać je do naszego projektu, prawoklik na [nazwa projektu]Content (Content)Add...Existing Item..., szukamy plików i lewoklik na Add, żeby znalazły się w naszym projekcie.

Stwórzmy teraz prostokąty. W funkcji LoadContent() wpisujemy:
           playerRectangle = new Rectangle(0, 416, 32, 32);
            groundRectangle = new Rectangle(0, 448, 800, 32);
Zadeklarowanym wcześniej prostokątom zostały przypisane wartości X, Y, Width oraz Height.

Teraz pora na wczytanie tekstur:
           playerTexture = Content.Load<Texture2D>("player");
            groundTexture = Content.Load<Texture2D>("ground");
Nic skomplikowanego, przypisujemy nazwę tekstury siedzącej w Content Pipeline do pola Texture2D zadeklarowanej wcześniej.

6. Metoda Update() - poruszanie postacią
Skoro mamy już gotowe pola dotyczące postaci, pora puścić ją w ruch. W tym celu w funkcji Update() należy napisać:
           if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                playerRectangle.X += 3;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                playerRectangle.X -= 3;
            }
Co oznacza: jeżeli wciśnięty jest dany przycisk (w tym przypadku strzałki lewa i prawa), do parametru X naszego prostokąta są dodawane lub odejmowane 3 piksele.

Oczywiście to nie wystarczy, żebyśmy mogli biegać naszą postacią, trzeba ją jeszcze narysować.

7. Rysowanie
Rysowanie sprite'ów w XNA odbywa się za pomocą SpriteBatcha. Żeby cokolwiek narysować, trzeba mu wyznaczyć granice działania:
           spriteBatch.Begin();

            spriteBatch.End();
Między te dwie funkcje wpakujemy kod odpowiadający za rysowanie.

           spriteBatch.Draw(groundTexture, groundRectangle, Color.White);
            spriteBatch.Draw(playerTexture, playerRectangle, Color.White);
Podstawowa metoda spritebatch.Draw wymaga trzech parametrów: tekstury, prostokąta na który ma nałożyć teksturę oraz koloru (biały jest często najodpowiedniejszy, ponieważ nie zmienia kolorów tekstury).

8. Debugging
W tym momencie można odpalić projekt (DebugStart Debugging), by zobaczyć efekt dotychczasowej pracy.

Uwaga: jeśli podczas próby debugowania wyskakuje okno mówiące o tym, że karta graficzna nie wspiera profilu HiDef, Project[Nazwa projektu] Properties... → zakładka XNA Game StudioGame profile: Use Reach.

W tej chwili można biegać gościem po szarym podłożu, ale niedługo zaczną na niego spadać odważniki :lol:

9. Klasa Weight - ciąg dalszy
Wcześniej stworzyliśmy klasę, lecz zostawiliśmy ją pustą. Pora to zmienić. W tym celu w klasie należy wpisać:
       Texture2D Texture;
        Rectangle Rectangle;
        Vector2 Position;
        float Gravity;
Jak widać, typy obiektów nie są rozpoznane. Żeby to zmienić, prawoklik na typie obiektuResolve...using [coś].

Skoro zadeklarowaliśmy już pola, pora napisać konstruktor klasy. To za jego pomocą można tworzyć nowe obiekty danej klasy.
       public Weight(Texture2D texture, Rectangle rectangle, Vector2 position, float gravity)
        {
            Texture = texture;
            Rectangle = rectangle;
            Position = position;
            Gravity = gravity;
        }
Stworzyliśmy konstruktor klasy Weight, który wymaga podania czterech parametrów: tekstury, prostokąta, dwuwymiarowego wektora oraz floata. W kodzie konstruktora pola (np. Texture) zostały przypisane do parametrów (np. texture).

Przed definicją prostokąta, wektora i floata dopisujemy keyword 'public':
       public Rectangle Rectangle;
        public Vector2 Position;
        public float Gravity;
Teraz te trzy pola można zmienić w innych plikach projektu. To ważne, ponieważ update stanu gry będzie wywoływany w Game1.cs.

Żeby ułatwić sobie rysowanie obiektów danej klasy, można napisać w klasie funkcję Draw.
       public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(Texture, Rectangle, Color.White);
        }
Ta metoda jest dostępna wszędzie (public) i nie zwraca wartości (void). Kod rysowania jest identyczny jak w przypadku rysowania w głównym pliku gry.

10. Wzbogacenie Game1.cs o potrzebne elementy
Klasa odważnika jest już gotowa, teraz trzeba przygotować Game1.cs do współpracy z nią. Zadeklarujmy nowe pola:
       List<Weight> weightList;

        Rectangle mouseRectangle;
        Boolean canClick;

        Texture2D weightTexture;
Stworzyliśmy listę odważników, nowy prostokąt i Booleana (będą potrzebne do sterowania myszką) oraz nową teksturę. Teraz pora przypisać do nich wartości w LoadContent().
           weightList = new List<Weight>();

            mouseRectangle = new Rectangle(0, 0, 1, 1);
            canClick = true;

            weightTexture = Content.Load<Texture2D>("weight");
            IsMouseVisible = true;
Lista jest zrobiona bez parametrów, nowy prostokąt ma 1 piksel szerokości i 1 piksel wysokości. Dzięki ostatniej linijce kursor jest widoczny w naszej grze.

Teraz czas na dostosowanie funkcji Update(). Dopiszmy następujące rzeczy:
           if (Mouse.GetState().LeftButton == ButtonState.Pressed && canClick == true)
            {
                Weight weight = new Weight(weightTexture, new Rectangle(mouseRectangle.X, mouseRectangle.Y, 64, 64), new Vector2(mouseRectangle.X, mouseRectangle.Y), 0f);

                weightList.Add(weight);
                canClick = false;
            }
            if (Mouse.GetState().LeftButton == ButtonState.Released)
            {
                canClick = true;
            }
Tutaj dzieje się dość sporo. Jeżeli lewy przycisk myszki jest wciśnięty i jest możliwość kliknięcia (pole canClick), wtedy jest tworzony nowy obiekt klasy Weight. Obiekt ten jest później dodawany do stworzonej wcześniej listy, a pole canClick przyjmuje wartość false. Z kolei jeśli lewy przycisk nie jest wciśnięty, to canClick staje się true, dzięki czemu pierwszy warunek znowu jest wykonalny.

Przejdźmy teraz do funkcji Draw().
Żeby narysować wszystkie elementy z listy weightList, należy zastosować pętlę for lub foreach. W tym przypadku posłużymy się tą drugą. W miejscu, w którym rysowaliśmy inne rzeczy dopisujemy:
           foreach (Weight weight in weightList)
            {
                weight.Draw(spriteBatch);
            }
Oznacza to: dla każdego obiektu klasy Weight w liście weightList wykonywana jest metoda Draw stworzona w klasie Weight. jako argument do niej dostarczamy spriteBatch. Mała uwaga: w pętli foreach, weight (z małej litery) jest zmienną lokalną, więc można nazwać ją jakkolwiek się chce.

Jeśli teraz odpalimy grę, będzie można ustawiać unoszące się w powietrzu odważniki. Pora zrobić coś, by spadały jak na odważniki przystało.

11. Update() dla odważników oraz wykrywanie kolizji
Żeby odważniki spadały jak należy, trzeba je oczywiście zaktualizować. W tym celu piszemy w funkcji Update():
           foreach (Weight weight in weightList)
            {
                weight.Position.Y += weight.Gravity;
                weight.Rectangle.Y = (int)weight.Position.Y;
                weight.Gravity += 0.1f;
            }
Znów pętelka foreach. Dla każdego odważnika (konkretnie jego wektora) aplikujemy grawitację oraz aktualizujemy pozycję prostokąta, by siedział on tam, gdzie wektor. Mała uwaga: (int) to cast, jest on potrzebny, ponieważ prostokąt przyjmuje jedynie wartości int, a wektor operuje na wartościach float. Jest to więc nic innego niż doraźna konwersja jednostek.

Jeśli teraz odpalimy grę, zobaczymy efekt - postawione odważniki spadają.

Pora zabrać się za wykrywanie kolizji. W funkcji Update() należy wpisać:
           foreach (Weight weight in weightList)
            {
                if (weight.Rectangle.Intersects(playerRectangle))
                {
                    playerRectangle.Y = -100;
                }
            }
Kolejna pętla foreach. Dla każdego odważnika sprawdzamy, czy wystąpiła kolizja z prostokątem gracza. Jeśli tak, przemieszczamy gracza tam, gdzie jest niewidoczny (tylko dlatego, że w tym tutorialu nie chce mi się bawić w boolean flag dla gracza).

12. Posłowie
Tego by było na tyle. Jeśli będę robił następne tutoriale, wytłumaczę to, na co zabrakło mi chęci dzisiaj. Komentarze i krytyka mile widziane.

Jeszcze jedno - dla ćwiczeń możecie próbować rozbudować ten projekt.

Pliki projektu, w razie gdyby ktoś się gdzieś zgubił: https://dl.dropbox.com/u/67704253/Xna%20Tutorial/1/source1.zip

9
Twórczość / [WIP] Dwuwymiarowy RTS
« dnia: 2012-07-23, 12:53 »
Dla rozwinięcia umiejętności zacząłem robić RTSa. Na tą chwilę zaimplementowane są: rekrutacja jednostek (jak na razie tylko jednego typu) i prosta walka. Sprajty wzięte stąd: http://www.oryxdesignlab.com/sprites/. W najbliższym czasie zamierzam zrobić więcej jednostek i możliwość wydawania prostych rozkazów.


10
Twórczość / Colorful Square Invasion!1
« dnia: 2012-07-07, 17:09 »
Pomysł na tą grę wpadł mi do głowy wczoraj po południu. Trochę o samej grze:

ATAKUJĄ CIĘ KRWIOŻERCZE KOLOROWE KWADRATY!!!!!!!!!!111111 (czyli brak fabuły)
Żeby (w miarę) nic ci nie zrobiły, musisz dopasować do nich swój kolor. Najechanie na wraży kwadrat pokazuje jego wartości R,G oraz B.

Sterowanie:
Spoiler
Q - wartość R ↑
W - wartość G ↑
E - wartość B ↑
A - wartość R ↓
S - wartość G ↓
D - wartość B ↓
Myszka - infiltracja nieprzyjaciół!!1
Link:
Colorful Square Invasion!!!1

Instalator jest również skrótem do gry.

11
Gry / Battle for Wesnoth
« dnia: 2012-06-25, 18:25 »
Battle for Wesnoth jest strategią turową rozgrywaną na mapach złożonych z heksów. W podstawowej wersji gry występuje 6 frakcji, oczywiście każda z nich ma do dyspozycji innych sołdatów. BfW można też modować za pomocą języka WML (Wesnoth Markup Language), dzięki czemu powstają różne dodatki, jak nowe frakcje, kampanie do singla, a również tryby gry do multiplayera (np. Creep War, który zmienia BfW w grę DOTA-podobną).

W multi gra się świetnie, może znalazłoby się kilka osób chcących pograć od czasu do czasu.
Ale wygląda na to, że jak na razie Tales of Valor okupuje pozycję theModdersowej gry

Oficjalna strona:
http://wesnoth.org/


12
Twórczość / Pong
« dnia: 2012-03-22, 17:13 »
Moja pierwsza gierka sklecona w XNA. Co tu dużo gadać - pong jak pong. W tej wersji naszym przeciwnikiem jest niezbyt wymagające AI.
Za pomocą instalatora można również odpalić grę.

Sterowanie - strzałki w górę i w dół.

Changelog:
Spoiler
v0.01
- Initial release

v0.02
- zmiana zachowania kostki przy odbiciu
- dodane parę dźwięków
- zmiana czcionki
- automatyczne update'y
Link

13
Gry / Realm of The Mad God
« dnia: 2012-02-21, 13:14 »
Chciałbym zaprezentować pewną gierkę, a mianowicie Realm of The Mad God.

Krótko mówiąc - jest to MMO shooter PvE nastawiony na kooperację graczy. Obecnych jest 13 klas postaci, 5 typów broni i 3 typy pancerza. Grafika jest utrzymana w stylu pixelartowym - większość sprite'ów ma 8x8 pikseli. Chyba najważniejszym wyróżnikiem gry jest permadeath (nieco naciągany - istnieje sposób na zachowanie postaci, jednak jest on bardzo drogi). Jest również system gildii i parę innych rzeczy.

A więc zachęcam do grania - http://www.realmofthemadgod.com/, lub znaleźć gierkę na Steamie.

Jeśli ktoś się zdecyduje, podaję link do FAQ - http://forums.wildshadow.com/node/45212

Jeśli ktoś chciałby się ze mną skontaktować, mój IGN to Ezzam.

14
Twórczość / Gierka (ścigałka)
« dnia: 2011-08-21, 17:33 »
Jest to moja pierwsza gierka (w dodatku nie miałem pomysłu jak ją nazwać). Żaden majstersztyk, ale w najbliższym czasie będę rozwijał ten projekt. Gra polega na przejechaniu trasy (jak to w ścigałkach), w tym przypadku w prawą stronę. W grze jest zaimplementowany silnik fizyczny.

Na screenie widać buga związanego z silnikiem gry, czyli zbugowane fonty (występuje chyba tylko na kartach intela).

Sterowanie:
strzałki lewo/prawo - ruch
strzałka w górę - skok (działa, gdy nie przyspieszamy strzałką)
escape - restart (podczas gry)

Changelog:
Spoiler
v0.2:
-proste menu
-możliwość restartu

v0.1:
-pierwsza wersja
W planie:
-ogólne dopracowanie
-nowe trasy
-nowe pojazdy (że tak powiem) do odblokowania
-parę innych pierdół
Link
Zachęcam do ściągania i dzielenia się wynikami  :lol:

15
Skrypty / Nowy czar bojowy
« dnia: 2011-07-23, 11:45 »
A wiec zajmiemy się tworzeniem czaru bojowego. Jest to nieco podobne do tworzenia czarów teleportacji, które opisał sawik. A więc do dzieła - najpierw trzeba napisać skrypt zaklęcia w AI\Magic\Spells. Naszym czarem będzie Błękitny płomień.
// ************
// SPL_BlueFlame
// ************

const int SPL_Cost_BlueFlame = 5;
const int SPL_Damage_BlueFlame = 25;


INSTANCE Spell_BlueFlame (C_Spell_Proto)
{
time_per_mana = 0;
damage_per_level = SPL_Damage_BlueFlame;
damageType = DAM_MAGIC;
};

func int Spell_Logic_BlueFlame (var int manaInvested)
{
if (Npc_GetActiveSpellIsScroll(self) && (self.attribute[ATR_MANA] >= SPL_Cost_Scroll))
{
return SPL_SENDCAST;
}
else if (self.attribute[ATR_MANA] >= SPL_Cost_BlueFlame)
{
return SPL_SENDCAST;
}
else //nicht genug Mana
{
return SPL_SENDSTOP;
};
};

func void Spell_Cast_BlueFlame()
{
if (Npc_GetActiveSpellIsScroll(self))
{
self.attribute[ATR_MANA] = self.attribute[ATR_MANA] - SPL_Cost_Scroll;
}
else
{
self.attribute[ATR_MANA] = self.attribute[ATR_MANA] - SPL_Cost_BlueFlame;
};

self.aivar[AIV_SelectSpell] += 1;
};

W instance czaru można dopisać coś dodatkowego, np.
targetCollectType = TARGET_TYPE_UNDEAD;sprawi, ze czar będzie działał tylko na nieumarłych. Wszystkie te linijki są w pliku C_Spell_Proto.d

Następnie trzeba czar dopisać do pliku Spell_ProcessMana.d w ten sposób:
if (activeSpell == SPL_BlueFlame ) { return Spell_Logic_BlueFlame (manaInvested); };Teraz trzeba zająć się plikiem Constants.d, a mianowicie są tam do dopisania 3 rzeczy (z czego bez dwóch czar może działać):
W linijkach 510-646 są id zaklęć. Teraz trzeba wstawić nasze zaklęcie do jednego z nieużywanych slotów. Po zamianie wygląda to tak:
const int SPL_BlueFlame = 90;Niżej (linijki 654-791) są określone nazwy spellFx dla zaklęć. Można zdefiniować je samemu w plikach PfxInstMagic.d i VisualFxInst.d, ale my damy naszemu czarowi efekt kuli ognia. W tym celu wpisujemy
"InstantFireball", // 90 SPL_BlueFlamew odpowiedniej linijce.

Jeszcze niżej jest część odpowiadająca za animacje. Aby nasze zaklęcie miało animację ognistego pocisku, wpisujemy
"FBT",   // 90 SPL_BlueFlameNa koniec w Text.d należy dopisać nazwę zaklęcia:
const string NAME_SPL_BlueFlame = "Błękitny płomień";Żeby użyć zaklęcia trzeba oczywiście stworzyć zwój albo runę, ale ich tworzenie to akurat oczywistość  ;p  .
I na tym kończę tutorial. mam nadzieję że się komuś przyda.

16
Skrypty / Palenie tytoniu w fajce wodnej
« dnia: 2011-06-01, 13:16 »
Na podstawie skryptu wykuwania broni napisałem monolog, którego zadaniem było dawanie wyboru typu tytoniu do palenia w fajce wodnej. W Spacerze dałem fajce wodnej onStateFunc Waterpipe i zdefiniowałem stałą w AI_Constants.d. Skrypt jest technicznie poprawny (nie wykrywa błędów przy reparsowaniu), a mimo to monolog nie wywołuje się. Czy ktoś mógłby powiedzieć co mogłoby być nie tak? Skrypt:
Spoiler
//*******************************************************
// Waterpipe
//*******************************************************
INSTANCE PC_Waterpipe_End (C_Info)
{
npc = PC_Hero;
nr = 999;
condition = PC_Waterpipe_End_Condition;
information = PC_Waterpipe_End_Info;
permanent = TRUE;
description = DIALOG_ENDE;
};

FUNC INT PC_Waterpipe_End_Condition ()
{
if (PLAYER_MOBSI_PRODUCTION == MOBSI_WATERPIPE)
{
return TRUE;
};
};

FUNC VOID PC_Waterpipe_End_Info()
{
B_ENDPRODUCTIONDIALOG ();
};
//*******************************************************
INSTANCE PC_Zielsko (C_INFO)
{
npc = PC_Hero;
nr = 1;
condition = PC_Zielsko_Condition;
information = PC_Zielsko_Info;
permanent = TRUE;
description = "Pal tytoń ziołowy";
};

FUNC INT PC_Zielsko_Condition()
{
if(PLAYER_MOBSI_PRODUCTION == MOBSI_WATERPIPE)
{
return TRUE;
};
};

FUNC VOID PC_Zielsko_Info ()
{
if (Npc_HasItems (hero, ItMi_SumpfTabak) >= 1)
{
Npc_RemoveInvItems (hero, ItMi_SumpfTabak, 1);
Wld_PlayEffect ("SLOW_TIME", self, self, 0, 0, 0, FALSE);
}
else
{
Print ("Nie masz tytoniu ziołowego");
};
B_ENDPRODUCTIONDIALOG ();
};
//*********************************************************
INSTANCE PC_Jablko (C_INFO)
{
npc = PC_Hero;
nr = 2;
condition = PC_Jablko_Condition;
information = PC_Jablko_Info;
permanent = TRUE;
description = "Pal tytoń jabłkowy";
};

FUNC INT PC_Jablko_Condition()
{
if(PLAYER_MOBSI_PRODUCTION == MOBSI_WATERPIPE)
{
return TRUE;
};
};

FUNC VOID PC_Jablko_Info ()
{
if (Npc_HasItems (hero, ItMi_ApfelTabak) >= 1)
{
Npc_RemoveInvItems (hero, ItMi_ApfelTabak, 1);
B_GivePlayerXP (10);
}
else
{
Print ("Nie masz tytoniu jabłkowego");
};
B_ENDPRODUCTIONDIALOG ();
};

17
Spacer / Problem z łączeniem zenów
« dnia: 2009-10-02, 16:47 »
Mam problem z łączeniem zenów w Spacerze. Myślę że screenshoty wszystko wyjaśnią.
Link do screenów:
Link

Strony: [1]
Do góry