Uzupełnienie do prezentacji z XNA

by anomaly 16. stycznia 2012 22:51

Zdaję sobie sprawę, że moja prezentacja w zeszły czwartek o podstawach XNA nie była najlepszej jakości, o czym najlepiej świadczy fakt, że Andrzej ciągle nie udostępnił jej do oceniania udostępnił ją do oceniania dopiero wtedy, gdy wszyscy zdążyli już o niej zapomnieć. Jej jakość wynikała z mojego małego doświadczenia w robieniu prezentacji i tego, że jej termin był okołosesyjny. Postaram się, by następna prezentacja była zdecydowanie lepsza, a do tego czasu postaram się uzupełnić parę informacji które zapomniałem podać i poprawić to, co, niestety, powiedziałem źle.

 

Rysowanie za pomocą spritebatcha:

Zapomnijcie o wszystkim co powiedziałem o ostatnim argumencie metody SpriteBatch.Draw. Działa on jak należy. Pokażę teraz przykład jak go wykorzystać, oraz powiem coś więcej o parametrze origin, bo też wydaje się, że nie powiedziałem o nim wystarczająco jasno.

Na początek stwórzmy dwa przykładowe prostokąty, będą one korzystały z tego samego prostokąta lokalizacji, ale drugi będzie miał origin w jego środku, co w przypadku kwadratu 50X50 oznacza punkt 25X25. Poniżej kod z którym zaczynamy.

using System;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
 
namespace SpriteBatchDrawing
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        private Texture2D pixel, pixel2;
        private float rotation;
        private KeyboardState oldState = Keyboard.GetState();
 
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
 
        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            graphics.IsFullScreen = false;
            graphics.ApplyChanges();
            base.Initialize();
        }
 
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            pixel = new Texture2D(GraphicsDevice, 1, 1);
            pixel.SetData<Color>(new Color[] { Color.White }); //Nie można zmieniać w trakcie.
            pixel2 = new Texture2D(GraphicsDevice, 50, 50);
            Color[] whiteBoard = new Color[50 * 50];
            for (int i = 0; i < 50 * 50; i++)
            {
                whiteBoard[i] = Color.White;
            }
 
            pixel2.SetData<Color>(whiteBoard); //Nie można zmieniać w trakcie.
        }
 
        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            pixel.Dispose();
            pixel2.Dispose();
            // TODO: Unload any non ContentManager content here
        }
 
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();
 
            if (oldState.IsKeyUp(Keys.S) && Keyboard.GetState().IsKeyDown(Keys.S))
            {
                TakeScreenShot("ss");
            }
 
            // TODO: Add your update logic here
            rotation += 0.01f;
            base.Update(gameTime);
        }
 
        private void TakeScreenShot(string prefix)
        {
            int w = GraphicsDevice.PresentationParameters.BackBufferWidth;
            int h = GraphicsDevice.PresentationParameters.BackBufferHeight;
 
            //force a frame to be drawn (otherwise back buffer is empty)
            Draw(new GameTime());
 
            //pull the picture from the buffer
            int[] backBuffer = new int[w * h];
            GraphicsDevice.GetBackBufferData(backBuffer);
 
            //copy into a texture
            Texture2D texture = new Texture2D(GraphicsDevice, w, h, false, GraphicsDevice.PresentationParameters.BackBufferFormat);
            texture.SetData(backBuffer);
 
            //save to disk
            Stream stream = File.OpenWrite(prefix + "_" + Guid.NewGuid().ToString() + ".png");
            texture.SaveAsPng(stream, w, h);
            stream.Close();
            texture.Dispose();
 
        }
 
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
 
            GraphicsDevice.Clear(Color.Black);
            Rectangle rec1 = new Rectangle(100,100,50,50);
            Rectangle rec2 = new Rectangle(100+25, 100+25, 50, 50);
 
            spriteBatch.Begin();
 
            spriteBatch.Draw(pixel2, rec1, null, Color.Red, 0, Vector2.Zero, SpriteEffects.None, 0.5f);
 
            spriteBatch.Draw(pixel2, rec1, null, Color.Blue, 0, new Vector2(25,25), SpriteEffects.None, 0.5f);
 
 
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Na razie, gdy uruchomimy poniższy kod, naszym oczom powinien ukazać się następujący widok:

 

 

Jak widać na poprzednim screenie, prostokąt z ustawionym originem jest przesunięty względem czerwonego. Jest on narysowany tak, by współrzędne prostokąta, który jest drugim parametrem w wywołanej metodzie Draw, odpowiadały współrzędnym punktu przesuniętego od górnego lewego rogu prostokąta o origin. Dlatego defaultową wartością origin jest wektor zerowy i wtedy współrzędne drugiego parametru odpowiadają lokalizacji górnego lewego rogu rysowanej figury. Wszystko to odnosi się także, do metody Draw z drugim parametrem Vector 2,  oraz do metody DrawString.

Co więcej, gdybyśmy zmodyfikowali program i dali drugiemu prostokątowi pixel 1X1, to efekt byłby następujący

 

 

Jak widać program licząc origin nie bierze pod uwagę wymiarów prostokąta do którego dopasowujemy teksturę, tylko wymiary tekstury i jeżeli przekraczają one jej szerokość lub wysokość efekty mogą być nie najlepsze.Zmodyfikujmy zatem nasz program z powrotem, dodajmy 25 do współrzędnych drugiego prostokąta, by pokrywał się z pierwszym i dodajmy rotację do obu z nich.

protected override void Draw(GameTime gameTime)
        {
 
            GraphicsDevice.Clear(Color.Black);
            Rectangle rec1 = new Rectangle(100,100,50,50);
            Rectangle rec2 = new Rectangle(100+25, 100+25, 50, 50);
 
            spriteBatch.Begin();
 
            spriteBatch.Draw(pixel, rec1, null, Color.Red, rotation, Vector2.Zero, SpriteEffects.None, 0.5f);
 
            spriteBatch.Draw(pixel2, rec2, null, Color.Blue, rotation, new Vector2(25,25), SpriteEffects.None, 0.5f);
 
 
            spriteBatch.End();
            base.Draw(gameTime);
        }



Gdy uruchomimy zmieniony program oba prostokąty będą się obracać, ale nieco inaczej.






Jak widać, oba prostokąty rotują, ale nie tak samo. Oba rotują względem swojego parametru origin. W pierwszym przypadku, jest to wektor zerowy, czyli czerwony prostokąt rotuje wokół swojego lewego górnego rogu. Natomiast niebieski prostokąt rotuje wokół swojego środka.

Przejdźmy teraz do zagadnienia które, niestety, kompletnie zawaliłem na prezentacji - argumentu layerDepth.
Zapewne pamiętacie jak mówiłem, że ostatni parametr metody Draw decyduje o tym, który element będzie przesłaniał który i że to źle działa. Otóż, działa to jak najbardziej poprawnie, tylko wyszła tam moja ignorancja.

Otóż metoda spritebatch.begin ma parę różnych wersji. Nas interesuje jedna z nich, a mianowicie.

spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.Opaque);


Jak widać metoda przyjmuje dwa parametry.

SpriteSortMode decyduje o kolejności rysowania spritów.

Deterred - sprity rysowane są w kolejności wystąpienia oraz są rysowane dopiero po wywołaniu End() - opcja defaultowa
BackToFront / FrontToBack - są rysowane tak jak w deterred, tylko są odpowiednio sortowane względem layerDepth, gdzie 1 to background a 0 to foreground.
Textute - są rysowane tak jak w deterred, tylko są sortowane po użytych teksturach
Immidiate - rysowanie spritów następuje od razu, bez czekania na spritebatch.End()

Natomiast argument typu BlendState odpowiada za to jak łączone są już zapisane piksele z nowo powstającymi:
Additive -dodawanie kolorów do siebie. W naszym przykładzie z niebieskim i czerwonym kwadratem w tym samym miejscu da to następujący efekt.

 

Inne w tym przykładzie zadziałają tak samo, więc nie będę o nich pisać.

Teraz, gdy wybierzemy np. SpriteSortMode.BackToFront ostatni argument będzie jak najbardziej brany pod uwagę.

To tyle (mam nadzieję) jeżeli chodzi o moje kompromitujące pomyłki, więc przejdę teraz do rzeczy, o których zapomniałem powiedzieć.

Skalowanie spritów:

Metoda Spritebatch.Draw dla zwykłych tekstur, których nie chcemy rozciągać do prostokąta ma dodatkowo parametr Scale (zarówno w wersji float jak i Vector 2), dzięki której można rozszerzać i zmniejszać daną teksturę równomiernie w obu kierunkach (float) lub też np. tylko w jednym
(wersja z Vectorem2).

Więcej o klasie GameComponent:


GameComponent (i dziedziczący po nim DrawableGameComponent) ma pole IsEnabled, które określa, czy dany komponent powinien być aktualizowany w danym updacie - użyłem tego do wyłączenia prawie wszystkich komponentów, gdy gracz pauzuje grę (Prawie wszystkich, bo musimy aktualizować input wprowadzany przez gracza.)

Dodatkowo GameComponent ma pole UpdateOrder, w którym możemy ustawić wartość określającą kiedy komponent będzie updatowany (przypominam, że update komponentów odbywa się w base.Update() w klasie gry). Komponent z mniejszą wartością zostanie wywołany wcześniej. Dodatkowo istnieje też zdarzenie UpdateOrderChanged.

 

Na koniec słowo o metodzie TakeScreenShot(), która zapisuje screenshot do pliku .png. Aby móc wykorzystać tą metodę, należy zmienić Game Profile na HiDef. Aby to zrobić klikamy prawym przyciskiem myszy na projekt w Solution Explorer. Następnie wybieramy z rozwijanego menu opcję Properties. Wybieramy zakładkę XNA Game Studio i tam wybieramy odpowiedni profil.

 

Na ten wpis to wszystko, jeżeli udało mi się zepsuć prezentację jeszcze bardziej piszcie śmiało w komentarzach, postaram się odpowiedzieć.

W załączniku umieściłem projekt który zrobiłem na zeszłoczwartkową prezentację.

XNAPodstawy.zip (3,92 mb)

Tags:

Piszemy w XNA!

by Wąsik 15. stycznia 2012 19:00

Dziś stworzymy prostą gierkę w XNA jaką jest slider kafelków, które tworzą jeden cały obraz. Naszym zadaniem będzie przesuwanie kafelków w taki sposób, aby stworzyły kompletny obraz. Wymagana znajomość XNA (na litość boską, choć trochę.. albo i więcej) Zaczynajmy.

Koncept intersejfu:

Jak więc widać - nasz główny obraz będzie rozmiaru 450x450 px oraz składał się z 25 kafelków (dokładnie 24, jeden bedzię pusty). Jeden kafelek będzie miał 450*450/25 = 90 px.

Przepraszam za błędne dane na rysunku ale zrobiłem to zanim policzyłem dane - lepiej żeby wymiary były całkowite :)

Teraz przejdźmy do konceptu struktury programu.

-Główna klasa programu

-Klasa reprezentująca jeden kafelek obrazu

-Klasa reprezentująca obraz złożony z 25 kafelków klasy powyżej

 

Ok - stwórzmy więc nowy projekt w VS2010 (oczywiście XNA dla WP 7.1). Nazwa dowolna. 

Zajmijmy się teraz bazową klasą reprezentującą pojedynczy kafelek, którą nazwiemy Tile.

Teraz dodajmy następujące pola globalne:

private const int WIDTH = 90; //rozmiar kafelka

private Vector2 _position; //pozycja kafelka względem początku obrazu (wektor)
private Vector2 _positionInImage; //pozycja prostokąta, w którym przechowywany jest oryginalny kawałek obrazka
private Vector2 _newPosition; //nowy pozycja, na którą kafelek ma się przesunąć
public bool _isEmpty = false; //flaga, czy kafelek powinien być pustym
private TimeSpan _framerate; //czas mmiędzy wiświetlaniem kolejnych klatek

Nasz pojedynczy kafelek będzie reprezentować 3 wektory. Wektor _position będzie odpowiadać za pozycję kafelka względem początku obrazka, _positionInImage będzie oznaczać stały fragment lewego górnego prostokąta 90x90 z oryginalnego obrazka (czyli jeszcze nie pomieszanego) a natomiast _newPosition będzie oznaczać wektor, na który nasz kafelek powinien się przesunąć (wygodne przy tworzeniu animacji). 

Natomiast _framerate będzie zmienną przechowywującą swojego rodzaju czasomierz, który odlicza czas od upłynięcia poprzedniej klatki (dzięki nie niemu będziemy przemieszczać klocek np. 3 razy na sekundę,  a nie 33 (tyle jest klatek na sekundę domyślnie) - co by było za szybko.

Przejdźmy teraz do konstruktora naszej klasy Tile:

public Tile(Vector2 position, Vector2 positionInImage)
{
_position = position; //inicjujemy zmienne
_newPosition = position;
_positionInImage = positionInImage;
_framerate = TimeSpan.FromMilliseconds(0); 
}

Powinien on się wydawać dość prosty :)

Teraz kilka przydatnych metod:

public Vector2 GetPositionInImage 
//opisujemy którą cześć obrazka kafelek przechowywuje
{
 get { return _positionInImage; }
}

public void Move(Vector2 vector)
//ustalamy na którą pozycję kafelek powinien się przesunąć
{
  _newPosition = vector;
}

public Vector2 GetTheVector
//zwracmy pozycję kafelka albo pozcyję, na którą dopiero dąży
{
 get
  {
     return _newPosition;
    }
}

public Vector2 GetOldTheVector
//zwracamy pozycję kafelka albo pozycję z której zaczął animacje
 {
    get
      {
            return _position;
       }
 }

public bool IsMoving()
//jeśli wektor starego położenia jest różny od tego z nowym położenim, to znaczy że jesteśmy w trakcie animacji
{
   return _position != _newPosition;
}

public void SetTheVector(Vector2 vector)
//ustawiamy pozycję kafelka, pomijając animację
{
 _position = vector;
  _newPosition = _position;
}

public bool Conflict(int x, int y, Vector2 globalVector)
//sprawdza, czy zadane punkty znajdują się w obrębie kafelka. Potrzebne będzie do reagowania na dotyk użytkownika. Współrzędne położenia palca pobierzmy z zewnątrz.
{
  //tworzymy prostkokąt w okół zadanej współrzędnej o wymiarach 4x4, gdzie zadany punkt znajduje się dokładnie w jego środku
   var collision = new Rectangle((int)_newPosition.X + (int)globalVector.X, (int)_newPosition.Y + (int)globalVector.Y, WIDTH, WIDTH); 
 //metoda intersects sprawdza, czy dwa prostokąty są w kolizji i zwraca wartość logiczną
            return collision.Intersects(new Rectangle(x-2, y-2, 4, 4));
}

public void Draw(SpriteBatch sprite, Texture2D image, Vector2 globalImage)
  //rysujemy pojedynczą klatkę
{
     if (_isEmpty) return; //jeśli nasz kafelek jest pusty, to opuszczamy procedurę rysowania
 //klasa sprite reprezentuje zbiór method do rysowania na ekranie
      sprite.Begin(); //rozpocznij proces rysowania
      sprite.Draw(image, (globalImage + _position), new Rectangle((int)_positionInImage.X, (int)_positionInImage.Y, WIDTH, WIDTH), Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0);
//(globalImage + _position) - wektor położenia całego obrazka dodany do położenia kafelka względem obrazka dam nam położenia kafelka względem całego ekranu
//new Rectangle((int)_positionInImage.X, (int)_positionInImage.Y, WIDTH, WIDTH) - wycinamy odpowiedni kawałek z oryginalnego obrazka o wymiarach 90x90)

        public void Update(GameTime gameTime)
//aktualizujemy logikę kafelka
        {
            _framerate -= gameTime.ElapsedGameTime; //aktualizujemy czas pozostały do następnej klatki

            if (_position != _newPosition && _framerate.TotalMilliseconds <= 0) //jeżeli mamy zmienić pozycję i nastąpił czas aktualizji animacji
            {
                if (_position.X > _newPosition.X) _position.X -= 5; //przesuwamy w dół jeśli trzeba;
                if (_position.X < _newPosition.X) _position.X += 5; //przesuwamy w górę jeśli trzeba;
                if (_position.Y > _newPosition.Y) _position.Y -= 5; //przesuwamy w lewo jeśli trzeba;
                if (_position.Y < _newPosition.Y) _position.Y += 5; //przesuwamy w prawo jeśli trzeba;
                _framerate = TimeSpan.FromMilliseconds(25); /odliczamy teraz 25 ms aby narysować nastepną klatkę 
            }
        }
       sprite.End(); //konczymy rysowanie
}

Ok - nasza klasa bazowa Tile została ukończona. Teraz przydałoby się stworzyć jakąś klasę, która będzie koordynować nasze 25 kafelków. Nazwiemy ją GameBoard. Jak zwykle zmienne:

private List<Tile> _container; // przechowywujemy tutaj nasze kafelki;
        private Texture2D _mainImage; //zmienna przechowywująca naszą teksturę
        private Vector2 _emptyVector; //wskaźnik na jeden pusty kafelek - ułatwi nam to nawigowanie
        private readonly SpriteBatch _sprite; //menager do rysowania
        private readonly Vector2 _globalVector; //główny wektor położenia naszej planszy - czyli głównego obrazka

Kontruktor naszej nowej klasy:

public GameBoard(Texture2D image, SpriteBatch sprite, Vector2 globalVector)
        {
            _mainImage = image; //aktualizujemy teksturę
            _container = new List<Tile>(); //tworzymy nową listę
            _sprite = sprite; //oraz przekazujemy menagera.

            for (var j = 0; j < 5; j++)
            {
                for (var i = 0; i < 5; i++) //tworzymy 25 kafelków obok siebie. wektory wskazują na lewy górny róg pojedynczego kafelka. Chwilo obraz nie jest jeszcze "pocięty" więc fragment oryginalnego obrazka ma taki sam wektor położenia w obrazku jak położenie kafelka
                {
                    var item = new Tile(new Vector2(j * 90, i * 90), new Vector2(j * 90, i * 90));
                    _container.Add(item);
                }
            }
            _emptyVector = new Vector2(0, 0); //nasz pusty kafelek (lewy górny róg)
            _container[0]._isEmpty = true;
            _globalVector = globalVector; //położenie całego obrazka
        }

Teraz zajmijmy się pojedynczymi metodami:

public void SetNewMainImage(Texture2D image)
//zmieniamy obrazek, który jest "pocięty".
        {
            _mainImage = image;
        }

        public void Solve()
//ustawia kafelki ustawia tak, aby tworzyły kompletny obrazek, animując je przy okazji
        {
            foreach (var tile in _container)
            {
                tile.Move(tile.GetPositionInImage); //przesuwamy każdy kafelek, na tą pozycję, na której położony jest fragment obrazu który przetrzymuje
            }

            _emptyVector = FindEmpty();

        }

        private List<E> ShuffleList<E>(List<E> inputList)
//metoda pomocnicza, sortuje listę dowolnego typu
        {
            var randomList = new List<E>();
            var r = new Random();

            while (inputList.Count > 0)
            {
                var randomIndex = r.Next(0, inputList.Count);
                randomList.Add(inputList[randomIndex]); //add it to the new, random list
                inputList.RemoveAt(randomIndex); //remove to avoid duplicates
            }

            return randomList; //return the new random list
        }

        public void ShuffleTiles(bool animationEnabled)
//losuje listę wektorów położenia
        {
            var vectorList = _container.Select(item => item.GetTheVector).ToList();
//budujemy najpierw listę wektorów poło��enia kafelków za pomocą wyrażenia LINQ           
 vectorList = ShuffleList(vectorList); //losujemy
            for (var i = 0; i < vectorList.Count; i++) if (!animationEnabled) _container[i].SetTheVector(vectorList[i]); else _container[i].Move(vectorList[i]);
  //aktualizujemy położenia kafelka (ustawiamy mu nową pozycję wraz z odtworzeniem animacji
            _emptyVector = FindEmpty(); //szukamy pozycji pustego kafelka
        }

        public void Draw()
//rysujemy po kolei każdy kafelek
        {
            foreach (Tile t in _container) t.Draw(_sprite, _mainImage, _globalVector);
        }

        private Vector2 FindEmpty()
//szukamy pustego kafelka i zwracamy wektor jego położenia
        {
            var i = 0;
            for (; i < _container.Count; i++)
            {
                var tile = _container[i];
                if (tile._isEmpty) return tile.GetTheVector;
            }

            return Vector2.Zero;
        }

private bool MoveIsAvailable(Tile tile, out Vector2 vector)
//metoda sprawdza czy w sąsiedztwie zadanego kafelka jest puste miejsce (aby przesunąc) i w razie jeśli tak, to zwraca położenie tego pustego miejsca
        {

            if (((int)_emptyVector.X + 90 == (int)tile.GetTheVector.X && (int)_emptyVector.Y == (int)tile.GetTheVector.Y) |
               ((int)_emptyVector.X - 90 == (int)tile.GetTheVector.X && (int)_emptyVector.Y == (int)tile.GetTheVector.Y) |
               ((int)_emptyVector.X == (int)tile.GetTheVector.X && (int)_emptyVector.Y + 90 == (int)tile.GetTheVector.Y) |
               ((int)_emptyVector.X == (int)tile.GetTheVector.X && (int)_emptyVector.Y - 90 == (int)tile.GetTheVector.Y))
            {
                vector = _emptyVector;
                return true;
            }
            else
            {
                vector = Vector2.Zero;
                return false;
            }
        }

        public Tile FindEmptyTile()
//zwraca pusty kafelek
        {
            foreach (var item in _container)
            {
                if (item._isEmpty) return item;
            }
            return null;
        }

 public void Update(GameTime gameTime)
//aktualizuje logikę wszystkich kafelków
        {
            var touches = TouchPanel.GetState(); //pobierz wszystkie punkty dotyku ekranu przez użytkownika

            foreach (var item in _container) //aktualizujemy każdy kafelek osobno
            {
                if (touches.Count == 1) //jeśli mamy jeden punkt dotyku
                {
                    if (item.Conflict((int)touches[0].Position.X, (int)touches[0].Position.Y, _globalVector)) //jesli kafelek został dodatknięty
                    {
                        Vector2 position;
                        if (MoveIsAvailable(item, out position) && !item.IsMoving())
//sprawdzmy, czy jest pusty kafelek obok
                        {
                            _emptyVector = item.GetOldTheVector;
                            FindEmptyTile().SetTheVector(_emptyVector);
                            item.Move(position); //przeuswamy kafelek i aktualizujemy położenie pustego
                        }
                    }
                }

                item.Update(gameTime); //aktualizujemy odrębną logikę kafelka (animacje)
            }
        }

Zostało nam teraz już tylko napisać główny kod programu, który znajdziecie tutaj (sposób jaki jak przechowywuje i aktualizuje dane może być nietrywialne ;P)

public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        private GameBoard _gameBoard;
        private readonly Vector2 globalVector = new Vector2(15, 800 - 450 - 15);

        private int _currentImage = 0; //numer głownego obrazka, może być kilka

        private List<Texture2D> _imagesList = new List<Texture2D>(); //lista głównych obrazków
        private Texture2D _arrowText; //tekstury przycisków,
        private Texture2D _shuffleButtonText;
        private Texture2D _bg;
        private Texture2D _solve;
        private SoundEffect _tapSound; //efekt dźwiękowy musi być, może załapie się jeszcze w GeekClubie :D

//położenia wszystkich głównych elementów na ekranie
        private Rectangle _lArrow = new Rectangle(480 - 100 - 15, 15, 100, 100);
        private Rectangle _rArrow = new Rectangle(15, 15, 100, 100);
        private Rectangle _shuffleButton = new Rectangle(100+15+25, 15, 200, 100);
        private Rectangle _solveRect = new Rectangle(15,115, 450, 100);

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this) {PreferredBackBufferWidth = 480, PreferredBackBufferHeight = 800}; //odpowiednie wymiary ekranu wybieramy
            graphics.ApplyChanges();

            Content.RootDirectory = "Content";
            TouchPanel.EnabledGestures = GestureType.Tap;
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            InactiveSleepTime = TimeSpan.FromSeconds(1);
        }

        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }


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

//bezczelnie ładujemy dane z plików
            for (int i = 1; i <= 8; i++ )
            {
                Texture2D item = Content.Load<Texture2D>("images/" + i);
                _imagesList.Add(item);
            }

            _arrowText = Content.Load<Texture2D>("images/larrow");
            _shuffleButtonText = Content.Load<Texture2D>("images/shuffle");
            _bg = Content.Load<Texture2D>("images/bg");
            _solve = Content.Load<Texture2D>("images/solve");
            _tapSound = Content.Load<SoundEffect>("tap");

             _gameBoard = new GameBoard(_imagesList[_currentImage], spriteBatch, globalVector); //towrzymy sobie plansze
            _gameBoard.ShuffleTiles(false); //mieszamy kafelki, bez animacji (bo po co przy starcie?)
        }

  
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
//olać pamięć :D
        }


        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            _gameBoard.Update(gameTime); //aktualizujemy plansze

            while (TouchPanel.IsGestureAvailable) //śmieci - sprawdzmy tylko czy jakiś przycisk nie został "tap"nięty i jak coś obsługujemy zdażenie
            {
                var gesure = TouchPanel.ReadGesture();

                Rectangle touchRect = new Rectangle((int)gesure.Position.X - 2, (int)gesure.Position.Y - 2, 4, 4);

                if (touchRect.Intersects(_lArrow) && _currentImage < _imagesList.Count - 1)
                {
                    _currentImage++; 
                    _gameBoard.SetNewMainImage(_imagesList[_currentImage]);
                    _tapSound.Play();
                }
                else if (touchRect.Intersects(_rArrow) && _currentImage > 0)
                {
                    _currentImage--;
                    _gameBoard.SetNewMainImage(_imagesList[_currentImage]);
                    _tapSound.Play();
                }
                else if (touchRect.Intersects(_shuffleButton))
                {
                    _gameBoard.ShuffleTiles(true);
                    _tapSound.Play();
                }
                else if (touchRect.Intersects(_solveRect))
                {
                    _gameBoard.Solve();
                    _tapSound.Play();
                }
            }

            base.Update(gameTime);
        }

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

            spriteBatch.Begin(); //rysujemy przyciski, tło, plansze.
            spriteBatch.Draw(_bg, Vector2.Zero, Color.White);
            spriteBatch.Draw(_solve, _solveRect, Color.White);
            spriteBatch.Draw(_arrowText, _lArrow, Color.White);
            spriteBatch.Draw(_arrowText, _rArrow, null, Color.White, 0, Vector2.Zero, SpriteEffects.FlipHorizontally, 0);
            spriteBatch.Draw(_shuffleButtonText, _shuffleButton, Color.White);
            spriteBatch.End();

            _gameBoard.Draw();

            base.Draw(gameTime);
        }
    }

Uff.. pewnie dużo nierozumiałego kodu, namieszanego, nieoptymalnego.. ale co tam, ważne że działa:

Jeśli filmik z youtube nie działa, to klik.

Wrzucone przeze mnie na marketplace'a, może się przyjmie :)

Powodzenia w kompilowaniu, w razie co zawalać pytaniami Money mouth

Tags:

System kontroli wersji - SVN oraz Visual Studio

by endrju 14. stycznia 2012 00:39

Dzisiejszy wpis będzie poświęcony przechowywaniu naszego projektu z Visual Studio w repozytorium. Repozytorium jest miejscem, gdzie przechowujemy uporządkowane pliki, pamiętając historię zmian ze szczegółami, możliwością cofania się do przeszłej wersji. Aby w pełni cieszyć się możliwościami repozytorium potrzebny nam będzie system kontroli wersji, który udostępni nam wachlarz funkcji. W naszym przypadku będzie to Subversion, inaczej SVN. Akurat ten produkt został wybrany z powodu łatwiejszej kontroli zmianami projektu oraz ilością dodatków do Visual Studio wspierających pracę z repozytorium.

Zacznijmy od wymagań. Posłużę się dwoma narzędziami, które są w pełni darmowe:

1.       TortoiseSVN – http://tortoisesvn.net/downloads.html

2.       AnkhSVN - http://ankhsvn.open.collab.net/downloads

Jak już zainstalujemy oba programy będziemy potrzebowali zdalnego repozytorium, chybaże chcemy postawić to na własnym komputerze J

Pierwszą opcją jest skorzystanie z uczelnianego SVN. Wystarczy wysłać prośbę do labolatorium podając nazwę, loginy użytkowników. Więcej informacji na lk.mimuw.edu.pl

Alternatywą jest skorzystanie z zewnętrznych serwisów oferujących repozytorium SVN. W większości przypadków otrzymamy wiki, issue tracker itd. Opcją jest codeplex.com (wszystkie projekty są publiczne!), bądź xp-dev.com (istnieją projekty prywatne do 100mb).

Wybór należy do Was J

Najważniejsze rzeczy za nami, teraz czas na zabawę w commit-y i update-y.

Pokażę Wam jak pobrać i pracować na projekcie, który sami stworzycie i wrzucicie do repozytorium oraz jak pobrać i zintegrować VS do pracy z projektem, który jest już w repozytorium.

Opcja 1.

1.      1.  Otwieramy Visual Studio oraz wybieramy New Project.

2.       2. Wprowadzamy dane i zaznaczamy Add to Subversion

3. Zostaniemy poproszenie o wprowadzenie danych dotyczących SVN.

3.       

1.       Nazwa projektu

2.       Adres repozytorium

3.       Miejsce w repozytorium, gdzie umieścimy nasz projekt

4.       Zatwierdzamy OK

UWAGA! Podczas wykonywania tych operacji będziemy musieli podać dane do zalogowania się do repozytorium!

4. Podajemy opcjonalnie informacje dotyczące naszego projektu, które będą dostępne w repozytorium.

5.       Jak już uda nam się dodać projekt do repozytorium będziemy chcieli kontrolować wszelkie zmiany w nim. Aby ułatwić do wybieramy z menu View -> Pending Changes.

6.       Naszym oczom ukarze się nowe okienko, gdzie będziemy widzieć jakie pliki zostały zmienione lokalnie od ostatniego Update z repozytorium.

1.       Wrzuca zaznaczone pliki do repozytorium wraz z podaną wiadomością

2.       Pobiera najnowszą wersję z repozytorium

3.       Informacja dotycząca commit-a

4.       Pliki zmienione lokalnie od ostatniego update-a

UWAGA! Jeśli dwaj użytkownicy będą równocześnie edytować jeden plik mogą powstać konflikty. Program nas o tym poinformuje i sam spróbuje rozwiązać problem poprzez merge.

Udało nam się opanować podstawy wrzucania projektu do repozytorium.

Teraz czas na pobierania projektu już istniejącego w Repozutrium.


Opcja 2.

1.       Wchodzimy do katalogu, gdzie będziemy chcieli trzymać nasz projekt oraz klikamy prawym przyciskiem myszy. Naszym oczom ukażą się opcje związane z kontrolą wersji.

2.       Wybieramy SVN Checkout, aby pobrać nowy folder (projekt) z repozytorium SVN.

3.       Wypełniamy dane.

1.       Adres do naszego projektu w repozytorium

2.       Miejsce gdzie chcemy zrobić checkout

3.       Opcje dotyczące głębokości pobrania folderów

4.       Opcja dotycząca wersji SVN projektu, którą chcemy pobrać

5.       Zatwierdzamy OK.

4. Pobraliśmy nasz nowy projekt i możemy z nim pracować jak w pierwszej opcji.

Jeśli wejdziemy do katalogu, gdzie znajduje się projekt pod kontrolą SVN to możemy sprawdzić, czy posiadamy najnowszą wersję z repozytorium.

a.       Zielone kółko oznacza zgodność z HEADem, czyli najnowszym commit-em

   

b.      Czerwone kółko oznacza, że wystąpił nowszy commit i należy zrobić update.

    

 

To na tyle, mam nadzieję, że choć trochę zaznajomiłem Was z systemem kontroli wersji SVN oraz możliwościami integracji z SVN.

W razie pytań piszcie na mail.

Zapraszam do komentowania J 

Tags:

UAC w aplikacji

by Wąsik 13. stycznia 2012 20:10

Czasami, pisząc nasze aplikacje w Visual Studio, potrzebujemy wymusić uruchomienie aplikacji z uprawnieniami administratora - gdyż chcemy przeprowadzić jakieś zaawansowane operacje. Operacje jak zapis w rejestrze, czy zapisywanie plików w specjalnych katalogach. 

Domyślnym sposobem wymuszenia uruchomienia aplikacji z UAC'em jest podpisanie aplikacji certyfikatem oraz utworzenie specjalnego zewnętrznego pliku manifest.  Visual Studio umożliwia nam przeprowadzenie tych operacji prawie automatycznie, włącznie z wbudowaniem pliku manifest w nasze assembly. 

Utwórzmy sobie przykładowy projekt, lub otwórzmy już zrobioną aplikację. Następnie przechodzimy do Project -> Add new item..

Z widocznego okna wybieramy pozycję Application Manifest File.

Otwiera nam się w edytorze nasz plik manifest, który automatycznie został dodany do Project Solution. Szukamy wpisu (mniej więcej w środku pliku):

Teraz podajemy własne wartości w polu level:

asInvoker - aplikacja zostanie uruchomia z domyślnymi uprawnieniami

highestAvailable - aplikacja zostanie uruchomiona z najwyższymi uprawnieniami, jakie są dostępne

requireAdministrator - aplikacja zostanie uruchomina tylko i wyłącznie z uprawnieniami administratora. Jeśli to nie jest możliwe (np. użytkownik nie ma prawa uzyskać praw administratora) aplikacja nie zostanie uruchomiona.

Uwaga: jeśli wybierzemy requireAdministrator, to przed pierwszym debugowaniem Visual Studio sam poprosi o uruchonienie samego siebie w trybie administratora - jeśli potwierdzimy - zrestartuje się z odpowiednimi uprawnieniami. Inaczej nie będziemy mogli uruchomić debugowania.

Działa tylko w Windows 7 oraz Vista.

Tags:

Nasze aplikacje

by Wąsik 13. stycznia 2012 00:48

Lista wydziałowych aplikacji

Aktualizacja: lista znajduje się tutaj. Można również przejść na podstronę z odpowiedniego menu z boku.

Tags:

Spotkanie grypy zaawansowanej - wprowadzenie do Orchard CMS

by endrju 11. stycznia 2012 13:13

Chciałbym Was serdecznie zaprosić na kolejne spotkanie grupy zaawansowanej, kóre odbędzie się w najbliższy czwartek (12.01) o godz. 16.15 w sali 5060.

Oto bezpośredni link do spotkania na CodeGuru - http://www.codeguru.pl/kalendarium/podglad-wydarzenia/spotkanie-grupy-net---grupa-zaawansowana---orchard-cms,6057

 

Prezentację na temat framework-u Orchard CMS poprowadzi Szymon Seliga, który uczestniczył w wielu projektach opratych o tą technologię.

Krótko o prezentacji:

Orchard jest rozbudowanym i wysoce rozszerzalnym systemem zarządzania treścią (CMS). Na zajęciach opowiem o zaletach i wadach, a także jak zabrać się do pisania lub tworzenia stron w Orchard'zie.

Serdecznie zapraszam !

Tags:

Jak napisać aplikację w Silverlight po polsku?

by Bartłomiej Zalewski 10. stycznia 2012 22:54

Wbrew pozorom sama umiejętność posługiwania się językiem polskim nie wystarcza :P

Jeżeli chcemy, aby nasza aplikacja była napisana w języku polskim to musimy zmienić domyślny język aplikacji z angielskiego na polski. Aby to zrobić otwieramy AssemblyInfo.cs

 

 

a następnie zmieniamy "en-US" na "pl-PL"

 

i gotowe! :) Nasza aplikacja już jest po polsku ;)

Tags:

Spotkanie grupy początkującej - wprowadzenie do XNA

by endrju 9. stycznia 2012 15:48

Po przerwie swiątecznej wracamy do pracy i zaczynamy od nowej technologii jaką jest

XNA ( XNA is not an Acronym!!)

Prezentację poprowadzi nasz kolega Maciej Kukawski, który pasjonuje się tą technologią od

dłuższego czasu i ma w swoim dorobku zawodowym kilka większych i mniejszych projektów :)

Oto przewidziana agenda spotkania:

 

1. Przykładowa aplikacja: gra Pong

2. Co to jest XNA?

3. Standardowo tworzone funkcje

4. Inicjalizacja

5. Zarządzanie zasobami

   ○ Dodawanie zasobów

   ○ Inicjalizacja zasobów

   ○ Pozbywanie się zasobów nie z content managera

6. Ustawienia gry

   ○ Rozdzielczość, pełny ekran i nazwa okna z grą

7. Rysowanie

   ○ Spritebatch

   ○ Rysowanie tekstur

   ○ Rysowanie stringów

   ○ Rysowanie kolorowych prostokątów i innych figur geometrycznych

8. Obsługa dźwięku

   ○ Sound effect

    ○ Dlaczego nie grać sound effectu jako muzyki?

9. Updatowanie

   ○ Sterowanie za pomocą pada, klawiatury i myszki

   ○ Playerindex i sprawdzanie, czy pad jest podłączony

10. Game Components

11. Model ekranów (wzmianka)

12. Gra przez sieć (krótka wzmianka)

13. Stworzenie gry

   ○ Gra labirynt: jesteś białym kwadratem, idziesz po czarnym tle z żółtego

       do niebieskiego kwadratu unikając czerwonych ścian 

   ○ Sterowanie myszką, klawiaturą

   ○ Muzyka w tle z możliwością wyłączenia

   ○ Efekty dźwiękowe przy dotknięciu ściany

 

Agenda jest ambitna, więc będzie ciekawe spotkanie. Serdecznie zapraszam :)

Oto link do rejestracji:

http://www.codeguru.pl/kalendarium/podglad-wydarzenia/spotkanie-grupy-net---wprowadzenie-do-xna,6056

 

Przypominam, że nasze spotkania odbywają się w czwartki.

Najbliższe spotkanie odbędzie się w czwartek 12.01 o godz. 14.15 w sali 5060!

Tags:

O grupie

Prężnie rozwijąca się grupa .NET funkcjonująca na Wydziale MIM na Uniwersytecie Warszawskim.

Month List