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:
- rysowanie bitmapy pocisku
- animacja klatek pocisku
- ruch pocisku
- po trafieniu w cel lub osiągnięciu maksymalnego zasięgu następuje animacja eksplozji
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.
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
Docelową animację pocisku przedstawia poniższy gif
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
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
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
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
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;
}
}
}
}