Obiekty graficzne na przykładzie prostej gry- pocisk

W tej części tworzenia prostej gry 2D stworzymy klasę o nazwie Pocisk. Przeznaczeniem klasy będzie nadzorowanie obiektów gry imitujących wystrzelony pocisk przez czołg. Podstawowe zadania jakie ma spełniać to:

Część 6 bazuje na kodzie części 5. Kod części 5 mozesz pobrać z tego linku: pobierz część 5

Warstwa pocisków a model warstwowy świata gry

Przyjmujemy, że wyświetlanie klatek pocisku będzie realizowane w warstwie pomiędzy warstwą ruchomych obiektów i warstwą eksplozji. Na poniższym rysunku jest to obszar pomiędzy 3 a 4 warstwą licząc od lewej strony.

warstwy świata gry 2D Visual studio C#

Przed utworzeniem klasy odpowiedzialnej za zachowanie się pocisku musimy utworzyć odpowiedni zasób graficzny

Tworzymy grafikę pocisku

W celach dydaktycznych pociski w świecie gry będzie reprezentowany stosunkowo dużą bitmapą- o rozmiarach 8 x 8 pikseli. Dodatkowo narzucimy animacje poklatkową, która przy tych rozmiarach nie będzie widoczna. Ale gdyby był to banan, to możemy zrealizować animację obrotów w czasie lotu.

Poniżej przykładowy zrzut widoku na tworzony zasób grafiki pocisku w programie GIMP

pocisk świata gry 2D Visual studio C#

Docelową animację pocisku przedstawia poniższy gif

animacja pocisk świata gry 2D Visual studio C#

Utworzoną grafikę zapisujemy w folderze bin/Debug/g pod wybrana nazwą oraz w folderze bin/Release/g tworzonego projektu w kompilatorze Visual Studio.

Zmiana w klasie bitmapy

Obsługe innego rozmiaru grafiki pojedynczej klatki pocisku można zrealizować w już istniejącym zasobie graficznym. Ale będzie to wymagać większych zmian w już istniejącym kodzie. Łatwiej będzie dopisać nowy zasób, a jego obsługę zrealizować na tych samych zasadach co już mamy oprogramowane.

Przechodzimy do pliku bitmapy.cs i dodajemy zmienną o nazwie zasobyBmpPocisk typu Bitmap oraz zmienną typu List o nazwie bitmapsPocisk. W tej liście będziemy przechowywać wycięte klatki bitmap pocisku

Wskazówka:


internal class bitmapy
{
	public Bitmap zasobyBmp,
				  zasobyBmpPocisk;
	//lista klatek wycinanych bitmapek
	private List<Bitmap> bitmaps = new List<Bitmap>();
	private List<Bitmap> bitmapsPocisk = new List<Bitmap>();

Tworzymy funkcję wczytującą zasób graficzny pocisku

Wskazówka:


public void WczytajBmpPocisku(string plik,int _w,int _h) 
{
	//funkcja wczytuje bitmapę pocisku z pliku graficznego
	zasobyBmpPocisk = new Bitmap(plik);
	//wczytany plik zasobów potnij na klatki o wymiarach _w x _h
	//w programie przyjałem 8 x 8 piksele
	int ileKolumn = zasobyBmpPocisk.Width / _w;
	int ileWierszy = zasobyBmpPocisk.Height / _h;
	for (int i = 0; i < ileWierszy; i++)
		for (int j = 0; j < ileKolumn; j++)
		{
			bitmapsPocisk.Add(
			                new Bitmap(_w,_h,
			                System.Drawing.Imaging.PixelFormat.Format32bppArgb)
							);
			Graphics g = Graphics.FromImage(bitmapsPocisk[bitmapsPocisk.Count - 1]);
			g.DrawImage(zasobyBmpPocisk, new Rectangle(0, 0, _w, _h),
								new Rectangle(j * _w, i * _h, _w, _h),
								GraphicsUnit.Pixel);
			g.Dispose();
		}
}

Modyfikujemy destruktora klasy. Dodajemy jedna linijkę kodu: zasobyBmpPocisk.Dispose();

Wskazówka:


//destruktor
~bitmapy()
{
	//zwolnij pamięc zajmowaną przez zasoby graficzne 
	zasobyBmp.Dispose();
	zasobyBmpPocisk.Dispose();
	foreach (Bitmap b in bitmaps) { b.Dispose(); }
	//zwolnij bufor rysowania ekranu gry
	bmpBuforEkranGry.Dispose();
	gEkranGry.Dispose();
}	

Przechodzimy do pliku Form1.cs. Wczytanie dodatkowego zasobu pocisków wykonamy w funkcji void From1_Load (klasa Form1.cs)

Wskazówka:


private void Form1_Load(object sender, EventArgs e)
{
	//wczytaj bitmape na sztwyno z pliku
	//zakładamy, że plik grafiki istnieje
	//więc nie musimy sprawdzać czy istnieje
	bmp.WczytajBmp("./g/czolg.png");
	bmp.WczytajBmpPocisku("./g/pocisk.png",8,8);
}

Klasa Pocisk

Przechodzimy do tworzenia klasy Pocisk

tworzenie klasy pocisk Visual studio C#

Klasa Pocisk w sposobie rysowania i animowania klatek niczym nie odbiega od rozwiązania przyjętego dla eksplozji (klasa Eksplozja). Można więc skopiować część rozwiązania z klasy Eksplozja i zmodyfikować.

Konstruktor klasy będzie zawierać dodatkowy argument związany z kierunkiem i zwrotem ruchu wystrzelonego pocisku

Wskazówka:


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace czolg_1
{
    internal class Pocisk
    {
        int[] klatki = {0,1,2,3};
        private int wys, szer;//wysokośc i szerokośc klatki świata gry
        public float x, y;//pozycja we współrzednych ekranowych
        private float x0, y0;//pozycja współrzednych wystrzału
        public int idKlatka = 0;
        public bool fZniszcz = false;
        //opóźnienie
        private const byte GRANICA_OPOZNIENIE = 3;
        private byte licznikOpoznienia = 0;
        private byte kierunek;
        private float v = 10.0f;//predkośc pocisku
        private float zasieg = 0;//zasięg pocisku
        
        //konstruktor
        public Pocisk(float _x, float _y, int _wys, 
		              int _szer, byte _kierunek)
        {
            wys = _wys;
            szer = _szer;
            //zapamietaj współrzędne strzalu
            x = _x;
            y = _y;
            x0 = _x;
            y0 = _y;
            //kierunek strzalu
            kierunek = _kierunek;
            //ustaw zasięg na 6 kafli świata
            zasieg = 6 * 48;
        }

        public void Rysuj(Graphics g, List<Bitmap> bmp, int id)
        {
            g.DrawImage(bmp[klatki[id]],
                        new Rectangle((int)x - szer / 2, 
						              (int)y - wys / 2, 
									   szer, wys),
                        new Rectangle(0, 0, szer, wys),
                        GraphicsUnit.Pixel);
        }
    }
}

Modyfikacje w klasie Swiat

Przechodzimy do pliku Swiat.cs i dodajemy listę przy pomocy, której będziemy obsługiwać wszystkie aktualnie żyjące pociski

Wskazówka:


internal class Swiat
{
	static int ileKlatekX = 20, ileKlatekY = 15;
	int[,] plansza=new int[ileKlatekY,ileKlatekX];
	public List<Eksplozja> listaEksplozji = new List<Eksplozja>();
	public List<Pocisk> listaPociskow = new List<Pocisk>();

Nadal jesteśmy w klasie Swiat. Dodajemy funkcję, która obsłuży pociski w świecie gry

Wskazówka:


public void RysujWszystkiePociski(Graphics g, List<Bitmap> bmp)
{
	foreach (var e in listaPociskow)
	{
		e.Rysuj(g, bmp, e.idKlatka);
		e.LicznikKlatek();
	}
	//przeglądnij listę i usun te które zakończyły zycie
	//czyli trafiły w cel, przeszkodę lub osiągneły maksymalny zasięg
	for (int i = 0; i < listaPociskow.Count; i++)
		if (listaPociskow[i].fZniszcz)
		{
			listaPociskow.RemoveAt(i);
		}
}

Druga modyfikacja w klasie bitmapy

Wracamy do pliku bitmapy.cs i wprowadzamy zmiany w funkcji RysujSwiatGryBufor. Ta funkcja odpowiedzialna jest za rysowanie wszystkich statycznych i dynamicznych obiektów gry. Kolejnośc rysowania odpowiada wirtualnym warstwom.

Wskazówka:


private void RysujSwiatGryBufor(Swiat swiat,Czolg czolgA,Czolg czolgB)
{
	swiat.RysujSwiat(gEkranGry, bitmaps,W,H);
	//na teraz testowe rysowania czolgu graczaA
	czolgA.Rysuj(gEkranGry, bitmaps);
	//na teraz testowe rysowania czolgu graczaB
	czolgB.Rysuj(gEkranGry, bitmaps);
	//rysuj pociski
	swiat.RysujWszystkiePociski(gEkranGry, bitmapsPocisk);
	//ryuj eksplozje na koncu, tak jak na ostatniej warstwie
	//która przykrywa wszystkie obiekty graficzne
	swiat.RysujWszystkieEksplozje(gEkranGry, bitmaps);
}

Test losowego rysowania pocisku

Mamy prawie wszystko przygotowane aby przetestować rysowanie pocisku. Napiszemy funkcję, która po każdorazowym jej wywołaniu (np. klawiszem Enter) utworzy w losowym miejscu pocisk. Przechodzimy do pliku Form1.cs i dodajemy funkcje o nazwie TestPocisku()

Wskazówka:


private void TestPocisku()
{
	int x, y;//tymczasowe wspołrzedne testowych eksplozji
	Random los = new Random();
	x = los.Next(8, this.Width - 8);
	y = los.Next(8, this.Height - 8);
	//zero to strzal na pólnoc
	swiat.listaPociskow.Add(new Pocisk(x, y, 8, 8, 0));
}

Wywołanie funkcji zrealizujemy w funkcji obsługującej zdarzenie wciśnięcia klawisza

Wskazówka:


private void Form1_KeyDown(object sender, KeyEventArgs e)
{
	//klawiszologia
	//dla gracza A
	if (e.KeyCode == Keys.A) czolgA.Obracaj(-1);
	if (e.KeyCode == Keys.D) czolgA.Obracaj(1);
	if (e.KeyCode == Keys.W) 
	{ 
		czolgA.fRuch = true;
		czolgA.v= czolgA.Vmax;
	}
	if (e.KeyCode == Keys.S)
	{
		czolgA.fRuch = true;
		czolgA.v = -czolgA.Vmax;
	}
	//dla gracza B
	if (e.KeyCode == Keys.Left) czolgB.Obracaj(-1);
	if (e.KeyCode == Keys.Right) czolgB.Obracaj(1);
	if (e.KeyCode == Keys.Up) 
	{ 
	   czolgB.fRuch = true;
	   czolgB.v = czolgB.Vmax;
	}
	if (e.KeyCode == Keys.Down)
	{
		czolgB.fRuch = true;
		czolgB.v = -czolgB.Vmax;
	}
	//testowe eksplozje
	if (e.KeyCode == Keys.T)
	{
		TestEksplozji();
	}
	//testowe pociski
	if (e.KeyCode==Keys.Enter)
	{
		TestPocisku();
	}
}

Dopiero teraz możemy skompilować program i sprawdzić efekt działania. Przy każdorazowym wciśnięciu klawisza ENTER w świecie gry pojawi się w losowym miejscu pocisk. Patrz poniższy rysunek

pocisk świata gry 2D Visual studio C#

Ruch pocisku

Na tym etapie pociski nie poruszają się. Wstępnie opracujemy ruch pocisku w jednym kierunku. Powiedzmy pionowo w górę. Jeżeli wszystko będzie działać to na podstawie tego rozwiązania zrobimy pozostałe kierunki. Przyjmiemy początkowy zasięg 6 kafli naszego świata gry. Czyli 6 x 48pikseli = 288 pikseli. Pociski w dalszej części można rozwijać do większych zasięgów, mocy itp.

Piszemy funkcję Ruch w klasie Pocisk. Przechodzimy do pliku Pocisk.cs i dodajemy poniższy kod

Wskazówka:


public void Ruch()
{
	//kierunek pólnoc
	switch (kierunek)
	{
		//na pólnoc
		case 0:y -= v;break;
	}
	if (Math.Abs(x - x0) > zasieg 
	    || Math.Abs(y - y0) > zasieg)
		fZniszcz = true;
}

Funkcję Ruch wywołujemy w klasie Swiat w funkcji RysujWszystkiePociski. Przechodzimy do pliku Swiat.cs i modyfikujemy funkcję RysujWszystkiePociski do poniższej postaci (dodajemy jedną linijkę kodu e.Ruch())

Wskazówka:


public void RysujWszystkiePociski(Graphics g, List<Bitmap> bmp)
{
	foreach (var e in listaPociskow)
	{
		e.Rysuj(g, bmp, e.idKlatka);
		e.LicznikKlatek();
		e.Ruch();
	}
	//przeglądnij listę i usun te które zakończyły zycie
	//czyli trafiły w cel, przeszkodę lub osiągneły maksymalny zasięg
	for (int i = 0; i < listaPociskow.Count; i++)
		if (listaPociskow[i].fZniszcz)
		{
			listaPociskow.RemoveAt(i);
		}
}

Teraz przetestuj program. Na ekranie zobaczysz tyle pocisków ile razy wcisnąłeś klawisz Enter. Każdy z pocisków powinien poruszać się pionowo w górę i ginąć po osiągnięciu maksymalnego zasięgu

ruch pocisk świata gry 2D Visual studio C#

Eksplozja pocisku

Eksplozję ma wywołać trafienie pocisku lub osiągnięcie maksymalnego zasięgu. Modyfikujemy funkcję RysujWszystkiePociski klasy Swiat. Dodajemy kod związany z obsługą listy eksplozji

Wskazówka:


public void RysujWszystkiePociski(Graphics g, List<Bitmap> bmp)
{
	foreach (var e in listaPociskow)
	{
		e.Rysuj(g, bmp, e.idKlatka);
		e.LicznikKlatek();
		e.Ruch();
	}
	//przeglądnij listę i usun te które zakończyły zycie
	//czyli trafiły w cel, przeszkodę lub osiągneły maksymalny zasięg
	for (int i = 0; i < listaPociskow.Count; i++)
		if (listaPociskow[i].fZniszcz)
		{
			listaEksplozji.Add(new Eksplozja(listaPociskow[i].x,
										listaPociskow[i].y, 48, 48));
			listaPociskow.RemoveAt(i);
		}
}

Testujemy

test ruch pocisk świata gry 2D Visual studio C#

Jeżeli test wypadł pomyślnie możemy przyspieszyć lot pocisku. Testowo należy dobrać wartość parametru prędkości. W tej części to tyle. Poniżej pełne kody plików tworzonej gry

plik Form1.cs:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;


namespace czolg_1
{
    public partial class Form1 : Form
    {
        bitmapy bmp;
        Swiat swiat;
        Czolg czolgA, czolgB;



        private void TestEksplozji()
        {
            int x, y;//tymczasowe wspołrzedne testowych eksplozji
            Random los= new Random();
            x = los.Next(48, this.Width - 48);
            y = los.Next(48, this.Height - 48);
            swiat.listaEksplozji.Add(new Eksplozja(x,y,bmp.H,bmp.W));
        }

        private void TestPocisku()
        {
            int x, y;//tymczasowe wspołrzedne testowych eksplozji
            Random los = new Random();
            x = los.Next(8, this.Width - 8);
            y = los.Next(8, this.Height - 8);
            //zero to strzal na pólnoc
            swiat.listaPociskow.Add(new Pocisk(x, y, 8, 8, 0));
        }

        public Form1()
        {
            InitializeComponent();
            swiat = new Swiat("swiat1.txt");
            //buduj swiat 20x15- 20 kolumn na 15 wierszy po 48x48 pikseli
            bmp =new bitmapy(swiat.IleKlatekX,swiat.IleKlatekY,48,48);
            //rob czolg zwrocony na południe
            czolgA = new Czolg(2,0, 0, bmp.H, bmp.W);
            czolgA.LadujKlatkiKierunku(0,1,2,3);
            //rób czołg zwrócony na połnoc
            czolgB = new Czolg(0,14, 19, bmp.H, bmp.W);
            czolgB.LadujKlatkiKierunku(8, 9, 10, 11);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //wczytaj bitmape na sztwyno z pliku
            //zakładamy, że plik grafiki istnieje
            //więc nie musimy sprawdzać czy istnieje
            bmp.WczytajBmp("./g/czolg.png");
            bmp.WczytajBmpPocisku("./g/pocisk.png",8,8);
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            bmp.PokazEkranGry(this.Handle, swiat, czolgA, czolgB);
        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            //klawiszologia
            //dla gracza A
            if (e.KeyCode == Keys.A) czolgA.Obracaj(-1);
            if (e.KeyCode == Keys.D) czolgA.Obracaj(1);
            if (e.KeyCode == Keys.W) 
            { 
                czolgA.fRuch = true;
                czolgA.v= czolgA.Vmax;
            }
            if (e.KeyCode == Keys.S)
            {
                czolgA.fRuch = true;
                czolgA.v = -czolgA.Vmax;
            }
            //dla gracza B
            if (e.KeyCode == Keys.Left) czolgB.Obracaj(-1);
            if (e.KeyCode == Keys.Right) czolgB.Obracaj(1);
            if (e.KeyCode == Keys.Up) 
            { 
               czolgB.fRuch = true;
               czolgB.v = czolgB.Vmax;
            }
            if (e.KeyCode == Keys.Down)
            {
                czolgB.fRuch = true;
                czolgB.v = -czolgB.Vmax;
            }
            //testowe eksplozje
            if (e.KeyCode == Keys.T)
            {
                TestEksplozji();
            }
            //testowe pociski
            if (e.KeyCode==Keys.Enter)
            {
                TestPocisku();
            }
        }
        
        private void Form1_KeyUp(object sender, KeyEventArgs e)
        {
            //klawiszologia
            //dla gracza A
            if (e.KeyCode == Keys.W ||
                e.KeyCode == Keys.S)
                czolgA.fRuch = false;
            //dla gracza B
            if (e.KeyCode == Keys.Up ||
                e.KeyCode == Keys.Down)
                czolgB.fRuch = false;
            
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            zegarGry();
        }

        private void zegarGry()
        {
            czolgA.Ruch();
            czolgB.Ruch();
            bmp.PokazEkranGry(this.Handle, swiat, czolgA, czolgB);
        }
    }
}

plik Swiat.cs


using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;

namespace czolg_1
{
    internal class Swiat
    {
        static int ileKlatekX = 20, ileKlatekY = 15;
        int[,] plansza=new int[ileKlatekY,ileKlatekX];
        public List<Eksplozja> listaEksplozji = new List<Eksplozja>();
        public List<Pocisk> listaPociskow = new List<Pocisk>();

        public int IleKlatekX
        {   //zwróc ilośc kolumn
            get { return ileKlatekX; }
        }

        public int IleKlatekY
        {   //zwróc ilośc wierszy
            get { return ileKlatekY; }
        }

        //konstruktor
        public Swiat(string plik)
        {
            //odczytaj wskazany plik z mapą świata gry
            string[] txt=System.IO.File.ReadAllLines(plik);
            for(int i= 0; i < txt.Count(); i++)
            {
                //odczytaj wiersze i ustaw znak separatora na spację
                string[]wiersz=txt[i].Split(' ');
                //czytaj liczby zapisane w wierszach
                for (int j = 0; j < wiersz.Length; j++)
                {
                    int k = int.Parse(wiersz[j]);
                    //przypisz odczyatne indeksy klatek do
                    //tablicy planszy mapy świata gry
                    plansza[i, j] = k;
                }
            }
        }
        public void RysujSwiat(Graphics g, List<Bitmap> bmp,int w,int h)
        {
            for(int i=0;i< ileKlatekY;i++)
            for (int j = 0; j < ileKlatekX; j++)
                {
                    int idKlatka = plansza[i,j];
                    g.DrawImage(bmp[idKlatka], 
					            new Rectangle(j*w, i*h, w, h), 
								new Rectangle(0, 0, w, h), 
								GraphicsUnit.Pixel);
                }
        }
        public void RysujWszystkieEksplozje(Graphics g,List<Bitmap> bmp)
        {
            foreach (var e in listaEksplozji)
            {
                e.Rysuj(g, bmp, e.idKlatka);
                e.LicznikKlatek();
            }
            //przeglądnij listę i usun te które zakończyły animację
            for(int i = 0; i < listaEksplozji.Count; i++)
                if(listaEksplozji[i].fZniszcz)listaEksplozji.RemoveAt(i);
        }
        public void RysujWszystkiePociski(Graphics g, List<Bitmap> bmp)
        {
            foreach (var e in listaPociskow)
            {
                e.Rysuj(g, bmp, e.idKlatka);
                e.LicznikKlatek();
                e.Ruch();
            }
            //przeglądnij listę i usun te które zakończyły zycie
            //czyli trafiły w cel, przeszkodę lub osiągneły maksymalny zasięg
            for (int i = 0; i < listaPociskow.Count; i++)
                if (listaPociskow[i].fZniszcz)
                {
                    listaEksplozji.Add(new Eksplozja(listaPociskow[i].x,
                                                listaPociskow[i].y, 48, 48));
                    listaPociskow.RemoveAt(i);
                }
        }
    }
}

plik bitmapy.cs


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace czolg_1
{
    internal class bitmapy
    {
        public Bitmap zasobyBmp,
                      zasobyBmpPocisk;
        //lista klatek wycinanych bitmapek
        private List<Bitmap> bitmaps = new List<Bitmap>();
        private List<Bitmap> bitmapsPocisk = new List<Bitmap>();
        private Bitmap bmpBuforEkranGry;//bufor graficzny
        private Graphics gEkranGry;
        private int w, h,//szerkość klatki
                    ileKolumn, ileWierszy;//ilosc kolumn i wierszy swiata gry

        public int W {//zwroc,ustaw szerokość klatki 
                       get { return w; } 
                       set { w = value;}
                     }
        public int H
        {   //zwroc,ustaw wysokość klatki
            get { return h; }
            set { h = value; }
        }

        //konstruktor
        public bitmapy(int ileK,int ileW,int _w,int _h)
        {
            this.w = _w;
            this.h = _h;
            this.ileKolumn = ileK;
            this.ileWierszy = ileW;
            //przygotuj bufor rysowania ekranu gry
            bmpBuforEkranGry = new Bitmap(this.ileKolumn*this.w,
                               this.ileWierszy*this.h,
                               System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            //skojarz moduł graficzny z buforem rysowania ekranu gry
            gEkranGry= Graphics.FromImage(bmpBuforEkranGry);
        }

        private void RysujSwiatGryBufor(Swiat swiat,Czolg czolgA,Czolg czolgB)
        {
            swiat.RysujSwiat(gEkranGry, bitmaps,W,H);
            //na teraz testowe rysowania czolgu graczaA
            czolgA.Rysuj(gEkranGry, bitmaps);
            //na teraz testowe rysowania czolgu graczaB
            czolgB.Rysuj(gEkranGry, bitmaps);
            //rysuj pociski
            swiat.RysujWszystkiePociski(gEkranGry, bitmapsPocisk);
            //ryuj eksplozje na koncu, tak jak na ostatniej warstwie
            //która przykrywa wszystkie obiekty graficzne
            swiat.RysujWszystkieEksplozje(gEkranGry, bitmaps);
        }

        public void PokazEkranGry(IntPtr uchwyt, Swiat swiat, Czolg czolgA, Czolg czolgB) 
        {
            Graphics g = Graphics.FromHwnd(uchwyt);
            RysujSwiatGryBufor(swiat, czolgA, czolgB);
            g.DrawImage(bmpBuforEkranGry, 0, 0);
            g.Dispose();
        }

        public void WczytajBmp(string plik)
        {
            //funkcja wczytuje bitmapę z pliku graficznego
            zasobyBmp = new Bitmap(plik);
            //wczytany plik zasobów potnij na klatki o wymiarach W x H
            //w programie przyjałem 48 x 48 pikseli
            int ileKolumn = zasobyBmp.Width / W;
            int ileWierszy= zasobyBmp.Height / H;
            for (int i = 0; i < ileWierszy; i++)
             for (int j = 0; j < ileKolumn; j++)
                {
                    bitmaps.Add(new Bitmap(w, h, 
					                    System.Drawing.Imaging.PixelFormat.Format32bppArgb)
										);
                    Graphics g = Graphics.FromImage(bitmaps[bitmaps.Count-1]);
                    g.DrawImage(zasobyBmp, new Rectangle(0, 0, w, h), 
                                        new Rectangle(j*w, i*h, w, h),
                                        GraphicsUnit.Pixel);
                    g.Dispose();
                }
        }
        public void WczytajBmpPocisku(string plik,int _w,int _h) 
        {
            //funkcja wczytuje bitmapę pocisku z pliku graficznego
            zasobyBmpPocisk = new Bitmap(plik);
            //wczytany plik zasobów potnij na klatki o wymiarach _w x _h
            //w programie przyjałem 8 x 8 piksele
            int ileKolumn = zasobyBmpPocisk.Width / _w;
            int ileWierszy = zasobyBmpPocisk.Height / _h;
            for (int i = 0; i < ileWierszy; i++)
                for (int j = 0; j < ileKolumn; j++)
                {
                    bitmapsPocisk.Add(new Bitmap(_w,_h, 
					                System.Drawing.Imaging.PixelFormat.Format32bppArgb)
									);
                    Graphics g = Graphics.FromImage(bitmapsPocisk[bitmapsPocisk.Count - 1]);
                    g.DrawImage(zasobyBmpPocisk, new Rectangle(0, 0, _w, _h),
                                        new Rectangle(j * _w, i * _h, _w, _h),
                                        GraphicsUnit.Pixel);
                    g.Dispose();
                }
        }
        public void testPokazBmp(Graphics g, int x, int y, int w, int h)
        {
            //funkcja testowa, pokazuje pierwszą klatkę ze współrzednych (0,0,0+w,0+h)
            g.DrawImage(zasobyBmp, new Rectangle(x,y,w,h), 
			            new Rectangle(0, 0, w, h), 
						GraphicsUnit.Pixel);
        }
        public void pokazKlatke(Graphics g, int idKlatka, int x, int y)
        {
            //funkcja testowa, pokazuje dowolną klatkę 
            g.DrawImage(bitmaps[idKlatka], new Rectangle(x, y, w, h),
			            new Rectangle(0, 0, w, h),
						GraphicsUnit.Pixel);
        }
        //destruktor
        ~bitmapy()
        {
            //zwolnij pamięc zajmowaną przez zasoby graficzne 
            zasobyBmp.Dispose();
            zasobyBmpPocisk.Dispose();
            foreach (Bitmap b in bitmaps) { b.Dispose(); }
            //zwolnij bufor rysowania ekranu gry
            bmpBuforEkranGry.Dispose();
            gEkranGry.Dispose();
        }
    }
}

plik Czolg.cs


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace czolg_1
{
    internal class Czolg
    {
        int[] klatki = new int[4];
        static float maxV = 2.0f;
        private int idKlatka;
        private int wiersz,kolumna;//pozycja w klatkach swiata gry
        private int wys, szer;//wysokośc i szerokośc klatki świata gry
        private float x, y;//pozycja we współrzednych ekranowych
        public float v = maxV;//predkosc
        public bool fRuch = false;
        
        
        public int Wiersz
        {//zwroc,ustaw pozycje w klatkach swiata gry 
            get { return wiersz; }
            set { wiersz = value; }
        }
        public int Kolumna
        {//zwroc,ustaw pozycje w klatkach swiata gry 
            get { return kolumna; }
            set { kolumna = value; }
        }
        //funkcja odczytu max predkosci
        public float Vmax { get { return maxV; } }
        //konstruktor
        public Czolg(int _idKlatka,int _wiersz,int _kolumna,int _wys,int _szer)
        {
            idKlatka = _idKlatka;
            Wiersz = _wiersz;
            Kolumna = _kolumna; 
            wys =_wys;
            szer = _szer;
            //oblicz współrzedne startu
            x = Kolumna * szer + szer/2;
            y = Wiersz * wys + wys/2;
        }

        public void LadujKlatkiKierunku(int _N,int _E,int _S,int _W)
        {
            klatki[0] = _N;//na północ
            klatki[1] = _E;//na wschód
            klatki[2] = _S;//na południe
            klatki[3] = _W;//na zachód
        }

        public void Rysuj(Graphics g, List<Bitmap> bmp)
        {
            g.DrawImage(bmp[klatki[idKlatka]],
                        new Rectangle((int)x-szer/2, (int)y-wys/2, szer, wys),
                        new Rectangle(0, 0, szer, wys),
                        GraphicsUnit.Pixel);
        }

        public void Obracaj(int i)
        {
            idKlatka += i;
            if (idKlatka < 0) idKlatka = 3;
            if (idKlatka > 3) idKlatka = 0;
        }
        public void Ruch()
        {
            if (!fRuch) return;
            switch (idKlatka){
                //na północ
                case 0:y -= v;break;
                //na wschód
                case 1: x += v; break;
                //na południe
                case 2: y += v; break;
                //na zachód
                case 3: x -= v; break;
            }
        }
        
    }
}

plik Eksplozja.cs


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace czolg_1
{
    internal class Eksplozja
    {
        int[] klatki = {24,25,26,27,28,29,30};
        private int wys, szer;//wysokośc i szerokośc klatki świata gry
        private float x, y;//pozycja we współrzednych ekranowych
        public int idKlatka = 0;
        public bool fZniszcz = false;
        //opóźnienie
        private const byte GRANICA_OPOZNIENIE = 5;
        private byte licznikOpoznienia = 0;

        //konstruktor
        public Eksplozja(float _x,float _y, int _wys, int _szer)
        {
            wys = _wys;
            szer = _szer;
            //zapamietaj współrzędne wybuchu
            x = _x;
            y = _y;
        }

        public void Rysuj(Graphics g, List<Bitmap> bmp, int id)
        {
            g.DrawImage(bmp[klatki[id]],
                        new Rectangle((int)x - szer / 2, 
						            (int)y - wys / 2, szer, wys),
                        new Rectangle(0, 0, szer, wys),
                        GraphicsUnit.Pixel);
        }

        public void LicznikKlatek()
        {
            licznikOpoznienia++;
            if (licznikOpoznienia > GRANICA_OPOZNIENIE)
            {
                idKlatka++;
                licznikOpoznienia = 0;
            }
            if (idKlatka > 6)
            {
                idKlatka = 0;
                fZniszcz = true;
            }
        }
    }
}

plik Pocisk.cs


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace czolg_1
{
    internal class Pocisk
    {
        int[] klatki = {0,1,2,3};
        private int wys, szer;//wysokośc i szerokośc klatki świata gry
        public float x, y;//pozycja we współrzednych ekranowych
        private float x0, y0;//pozycja współrzednych wystrzału
        public int idKlatka = 0;
        public bool fZniszcz = false;
        //opóźnienie
        private const byte GRANICA_OPOZNIENIE = 3;
        private byte licznikOpoznienia = 0;
        private byte kierunek;
        private float v = 10.0f;//predkośc pocisku
        private float zasieg = 0;//zasięg pocisku
        
        //konstruktor
        public Pocisk(float _x, float _y, int _wys, int _szer, byte _kierunek)
        {
            wys = _wys;
            szer = _szer;
            //zapamietaj współrzędne strzalu
            x = _x;
            y = _y;
            x0 = _x;
            y0 = _y;
            //kierunek strzalu
            kierunek = _kierunek;
            //ustaw zasięg na 6 kafli świata
            zasieg = 6 * 48;
        }

        public void Rysuj(Graphics g, List<Bitmap> bmp, int id)
        {
            g.DrawImage(bmp[klatki[id]],
                        new Rectangle((int)x - szer / 2,
						            (int)y - wys / 2, szer, wys),
                        new Rectangle(0, 0, szer, wys),
                        GraphicsUnit.Pixel);
        }
        public void Ruch()
        {
            //kierunek pólnoc
            switch (kierunek)
            {
                //na pólnoc
                case 0:y -= v;break;
            }
            if (Math.Abs(x - x0) > zasieg 
			    || Math.Abs(y - y0) > zasieg)
                fZniszcz = true;
        }
        public void LicznikKlatek()
        {
            licznikOpoznienia++;
            if (licznikOpoznienia > GRANICA_OPOZNIENIE)
            {
                idKlatka++;
                licznikOpoznienia = 0;
            }
            if (idKlatka > 3)
            {
                idKlatka = 0;
                //flagę zniszcenia trzeba wywołac gdy trafiono w cel
                //przeszkodę lub osiągnieto maksymalny zasięg
                //fZniszcz = true;
            }
        }
    }
}
Układ okresowy- kod qr
Układ okresowy

Układ okresowy pierwiastków- darmowa aplikacja na Androida

Pobierz ze sklepu Google Play
Alkomat- wirtualny test kod qr
Alkomat- wirtualny test

Alkomat- darmowa aplikacja na Androida

Pobierz ze sklepu Google Play
Taklarz- olinowanie stałe kod qr
Olinowanie stałe- kalkulator średnic

Olinowanie stałe- darmowa aplikacja na Androida

Pobierz ze sklepu Google Play
przepis na gogfry

Przepis na gofry

zobacz
przepis na bitą śmietanę

Przepis na bitą śmietanę

zobacz