Obiekty graficzne na przykładzie prostej gry- kolizje
Do ósmej części będziemy potrzebować kod z części siódmej, który można pobrać z tego linku kod części 7
W tej części zajmiemy się kolizjami. Co będziemy rozumieć przez kolizję w naszym rozwiązaniu? Przez kolizję będziemy rozumieć moment wejścia graficznego obrysu obiektu poruszającego się na planszy gry w obszar innego obiektu graficznego utworzonego świata gry.
Nie wszystkie obiekty graficzne widoczne w świecie gry muszą wykrywać kolizje. Na pewno kolizja musi być wykrywana dla pocisku w przypadku uderzenia w cel lub przeszkodę. Kolizja powinna być również wykrywana przy ruchu obiektów graczy. W naszym przypadku poruszające się czołgi do tej pory mogę przenikać przez siebie jaki i przejeżdżać przez przeszkody. W tej części postaramy się to rozwiązać.
Wykrywanie kolizji z przeszkodą
W przyjętym rozwiązaniu w indeksach graficznych bitmap kostek świata pod numerem 16 jest trawa- grunt po którym można jeździć, a pod numerem 17 jest obraz bitmapy przeszkody (skrzynki) przez którą ni powinno się dać przejechać. Patrz poniższy rysunek
Aby zapewnić wykrywalność kolizji z przeszkodą należy odczytać czy w przewidzianej ruchem kolejnej klatce siatki mapy gry nie ma przeszkody. W pseudokodzie możemy zapisać taką instrukcję jako warunek przedstawiony poniższym schematem blokowym
Przechodzimy do pliku Czolg.cs i dodamy funkcję ograniczającą ruch do dozwolonych pól. W funkcji dodamy sprawdzenie czy gracz nie wyjechał poza planszę
Wskazówka:
private bool CzyMoznaZrobicRuch(int[,] plansza,int ileKlatekX,int ileKlatekY)
{
//tymczasowe współrzędne położenia
float _x = x,
_y = y;
//sprawdzaj możliwośc ruchu z dokładnością
//do osmiokrotnego przesunięcia pozycji
//wartośc nalezy dopasowac do obrysu duszka względem
//pełengo rozmiaru klatki
switch (idKlatka)
{
//na północ
case 0: _y -= 8*v; break;
//na wschód
case 1: _x += 8*v; break;
//na południe
case 2: _y += 8*v; break;
//na zachód
case 3: _x -= 8*v; break;
}
int wiersz = (int)(_y / wys),
kolumna= (int)(_x/szer);
//nie wyjeżdzja poza planszę
if (kolumna < 0 ||
kolumna > ileKlatekX-1 ||
wiersz < 0 ||
wiersz > ileKlatekY-1) return false;
//klatka o indeksie 16 jest trawą-gruntem
//po którym można jeździć, po pozostałych nie można
if (plansza[wiersz,kolumna]!=16)return false;
return true;
}
Wywołanie funkcji zrobimy we wcześniej napisanej metodzie Ruch, którą zmodyfikujemy o podanie strzech argumentów: planszy świata gry oraz ilości kolumn i wierszy
Wskazówka:
public void Ruch(int[,] plansza, int ileKlatekX, int ileKlatekY)
{
if (!fRuch||!CzyMoznaZrobicRuch(plansza, ileKlatekX, ileKlatekY)) 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;
}
}
Przechodzimy do pliku Form1.cs i dokonujmy poprawek w ciele funkcji zegarGry. Patrz poniżej
Wskazówka:
private void zegarGry()
{
czolgA.Ruch(swiat.plansza,swiat.IleKlatekX,swiat.IleKlatekY);
czolgB.Ruch(swiat.plansza,swiat.IleKlatekX,swiat.IleKlatekY);
bmp.PokazEkranGry(this.Handle, swiat, czolgA, czolgB);
}
Skompiluj program i sprawdź efekt działania. Obiekty nie powinny przejeżdżać przez przeszkody
Kolizja z przeciwnikiem
Wykrycie kolizji z przeciwnikiem w najprostszym rozwiązaniu może polegać na sprawdzeniu czy klatka pola obszaru świata gry jest pusta czy zajmują ją przeciwnik. Oznacza to, że wystarczy sprawdzić czy kolumna i wiersz jednego gracza i drugiego będą jednakowe w przewidywanym ruchu. Jeżeli będą jednakowe to występuje kolizja.
Modyfikujemy wcześniej napisaną funkcję CzyMoznaZrobicRuch. Dodamy czwarty argument- czołg przeciwnika. W ciele funkcji wykonamy test sprawdzający kolumny i wiersze dla obu graczy
Wskazówka:
private bool CzyMoznaZrobicRuch(int[,] plansza,int ileKlatekX,int ileKlatekY,Czolg B)
{
//tymczasowe współrzędne położenia
float _x = x,
_y = y;
//sprawdzaj możliwośc ruchu z dokładnością
//do osmiokrotnego przesunięcia pozycji
//wartośc nalezy dopasowac do obrysu duszka względem
//pełengo rozmiaru klatki
switch (idKlatka)
{
//na północ
case 0: _y -= 8*v; break;
//na wschód
case 1: _x += 8*v; break;
//na południe
case 2: _y += 8*v; break;
//na zachód
case 3: _x -= 8*v; break;
}
int wiersz = (int)(_y / wys),
kolumna= (int)(_x/szer);
//nie wyjeżdzja poza planszę
if (kolumna < 0 ||
kolumna > ileKlatekX-1 ||
wiersz < 0 ||
wiersz > ileKlatekY-1) return false;
//czy kolizja z czolgiem przeciwnika
int wierszB = (int)(B.Y / wys),
kolumnaB = (int)(B.X / szer);
if(wiersz==wierszB && kolumna==kolumnaB) return false;
//klatka o indeksie 16 jest trawą-gruntem
//po którym można jeździć, po pozostałych nie można
if (plansza[wiersz,kolumna]!=16)return false;
return true;
}
Dodanie czwartego argument wymaga modyfikacji w kolejnej funkcji. Jest to funkcja Ruch
Wskazówka:
public void Ruch(int[,] plansza, int ileKlatekX, int ileKlatekY,Czolg B)
{
if (!fRuch||
!CzyMoznaZrobicRuch(plansza, ileKlatekX, ileKlatekY,B))
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;
}
}
Wskazówka:
private void zegarGry()
{
czolgA.Ruch(swiat.plansza,swiat.IleKlatekX,swiat.IleKlatekY,czolgB);
czolgB.Ruch(swiat.plansza,swiat.IleKlatekX,swiat.IleKlatekY,czolgA);
bmp.PokazEkranGry(this.Handle, swiat, czolgA, czolgB);
}
Teraz kompilujemy program i sprawdzamy efekt działania. Prawidłowo wprowadzone modyfikacje zablokują możliwość przenikania obiektów graczy- czołgi nie wjeżdżają na siebie
Kolizja pocisku z przeszkodą i czołgiem przeciwnika
Problem wykrywani kolizji przez pocisk podzielimy na dwa podproblemy:
- kolizja z przeszkodą
- kolizja z czołgiem przeciwnika
Kolizje z przeszkodą rozwiążemy w ten sam sposób co kolizję czołgu z przeszkodą z tą różnicą, że nie będziemy blokować ruchu tylko powodować eksplozję.
Przechodzimy do pliku Pocisk.cs i piszemy kod funkcji wykrywającej kolizję. Jej argument to: plansza świata gry oraz ilość klatek w poziomie i pionie w przyjętym świecie gry. Patrz poniżej
Wskazówka:
private bool CzyKolizja(int[,] plansza, int ileKlatekX, int ileKlatekY)
{
//dzielimy przez 48 bo taka jest szerokośc
//i wysokośc pola świata gry
int wiersz = (int)(y / 48),
kolumna = (int)(x / 48);
//eksplozaj na grnicach swiata
if (kolumna < 0 || wiersz < 0 ||
kolumna >= ileKlatekX ||
wiersz >= ileKlatekY) return true;
//klatka o indeksie 16 jest trawą-gruntem
//po którym można jeździć, po pozostałych nie można
if (plansza[wiersz, kolumna] != 16) return true;
return false;
}
Wywołanie tej funkcji robimy w warunku zniszczenia pocisku w funkcji Ruch, w której nagłówku dopisujemy trzy argumenty: int[,] plansza, int ileKlatekX, int ileKlatekY
Wskazówka:
public void Ruch(int[,] plansza, int ileKlatekX, int ileKlatekY)
{
//kierunek pólnoc
switch (kierunek)
{
//na Pólnoc
case 0:y -= v;break;
//na Wschod
case 1:x += v;break;
//na Południe
case 2: y += v; break;
//na Zachód
case 3: x -= v; break;
}
if (CzyKolizja(plansza, ileKlatekX, ileKlatekY)||
Math.Abs(x - x0) > zasieg ||
Math.Abs(y - y0) > zasieg)
fZniszcz = true;
}
Wprowadzone zmiany w nagłówku funkcji Ruch wymagają modyfikacje w pliku Swiat.cs. Przechodzimy do tego pliku i uaktualniamy kod do postaci. Zmiana jest w jednej linijce. Było e.Ruch(), zmieniamy na e.Ruch(plansza, ileKlatekX, ileKlatekY);. Patrz poniżej
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(plansza, ileKlatekX, ileKlatekY);
}
//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);
}
}
Kompilujemy program i sprawdzamy efekt działania. Przy prawidłowo wprowadzonych zmianach zauważysz eksplozje gdy pocisk trafi w przeszkodę
Kolizja z czołgiem przeciwnika
Kolizje z czołgiem przeciwnika wykryjemy na zasadzie porównania współrzędnych pocisku i czołgu przeciwnika. Porównywać będziemy pewien obszar wokół współrzędnych czołgu przeciwnika.
Jeżeli otworzymy plik zasobów graficznych, to po zmierzeniu obrysu rysunku czołgu otrzymujemy, że obrys ma wymiary 32 x 32 piksele
Obszar do wykrywania kolizji z czołgiem będzie prostokątem o bokach 32 x 32 piksele rozciągniętym wokół punktu zaczepienia. Punktem zaczepienia są współrzędne czołgu przeciwnika. Przechodzimy do nagłówka klasy Pocisk i dodajemy zmienną związaną z czołgiem przeciwnika
Wskazówka:
internal class Pocisk
{
//
public Czolg wlasciciel,przeciwnik;
Zmieniamy konstruktora klasy Pocisk
Wskazówka:
//konstruktor
public Pocisk(float _x, float _y, int _wys,
int _szer, byte _kierunek,
Czolg _w,Czolg _p)
{
//przesuniecie dla konca lufy
float dx = 0, dy = 0;
wys = _wys;
szer = _szer;
//kierunek strzalu
kierunek = _kierunek;
switch (kierunek)
{
//Północ
case 0:dy = -24.0f;break;
//Wschód
case 1:dx = 24.0f; break;
//Polódnie
case 2:dy = 24.0f;break;
//Zachód
case 3:dx = -24.0f;break;
}
//zapamietaj współrzędne strzalu
x = _x + dx;
y = _y + dy;
x0 = _x;
y0 = _y;
//ustaw zasięg na 6 kafli świata
zasieg = 6 * 48;
wlasciciel = _w;
przeciwnik = _p;
}
Modyfikujemy wcześniej napisaną funkcję kolizji pocisku z przeszkodą.
Wskazówka:
private bool CzyKolizja(int[,] plansza, int ileKlatekX, int ileKlatekY)
{
//kolizja z czołgiem przeciwnika
if(x > przeciwnik.X - 16 &&
x < przeciwnik.X + 16 &&
y > przeciwnik.Y - 16 &&
y < przeciwnik.Y + 16) return true;
//dzielimy przez 48 bo taka jest szerokośc
//i wysokośc pola świata gry
int wiersz = (int)(y / 48),
kolumna = (int)(x / 48);
//eksplozaj na grnicach swiata
if (kolumna < 0 || wiersz < 0 ||
kolumna >= ileKlatekX ||
wiersz >= ileKlatekY) return true;
//klatka o indeksie 16 jest trawą-gruntem
//po którym można jeździć, po pozostałych nie można
if (plansza[wiersz, kolumna] != 16) return true;
return false;
}
Wprowadzone zmiany w klasie Pocisk wymuszają zmiany w innych plikach. Przechodzimy do pliku Form1.cs. Modyfikujemy poniżesz funkcje tak jak poniżej
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,
czolgA,czolgB));
}
private void Strzal(Czolg cz, Czolg czPrzdeciwnik)
{
swiat.listaPociskow.Add(new Pocisk(cz.X, cz.Y,
8, 8, cz.IdKlatka,
cz, czPrzdeciwnik));
}
Oraz ostatnie linijki funkcji Form1_KeyDown
Wskazówka:
//strzela czołg A
if (e.KeyCode == Keys.Space)
{
Strzal(czolgA, czolgB);
}
//strzela czołg B
if (e.KeyCode==Keys.Enter)
{
Strzal(czolgB, czolgA);
}
Kompilujemy program i sprawdzamy efekt działania. Prawidłowo działający program pozwala trafić w czołg przeciwnika
I to byłoby na tyle w tej części cyklu lekcji związanych z klasami, obiektami i funkcjami graficznymi. Można dalej projekt rozwijać, na przykład mając już podane do pocisku zmienne typu Czolg właściciela pocisku i czołg przeciwnika łatwo dodać jest system naliczania punktów, tracenia punktów życia. Można pokusić się o zdobywanie zasobów typu amunicja, punkty życia itp. Model prostego silnika gry 2d został tu przedstawiony.
Poniżej pełne kody plików użytych klas
Wskazówka:
using czolg_1;
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,
czolgA,czolgB));
}
private void Strzal(Czolg cz, Czolg czPrzdeciwnik)
{
swiat.listaPociskow.Add(new Pocisk(cz.X, cz.Y,
8, 8, cz.IdKlatka,
cz, czPrzdeciwnik));
}
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;
//czolgA.fRuch = czolgA.CzyMoznaZrobicRuch(swiat.plansza);
}
if (e.KeyCode == Keys.S)
{
czolgA.fRuch = true;
czolgA.v = -czolgA.Vmax;
//czolgA.fRuch = czolgA.CzyMoznaZrobicRuch(swiat.plansza);
}
//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;
}
//strzela czołg A
if (e.KeyCode == Keys.Space)
{
Strzal(czolgA, czolgB);
}
//strzela czołg B
if (e.KeyCode==Keys.Enter)
{
Strzal(czolgB, czolgA);
}
}
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(swiat.plansza,swiat.IleKlatekX,swiat.IleKlatekY,czolgB);
czolgB.Ruch(swiat.plansza,swiat.IleKlatekX,swiat.IleKlatekY,czolgA);
bmp.PokazEkranGry(this.Handle, swiat, czolgA, czolgB);
}
}
}
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 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; } }
//zwróć wpsółrzędne obiektu ze świata gry
public float X { get { return x; } }
public float Y { get { return y; } }
//zwróc klatke kierunku ruchu, potrzebne do strzelania
public byte IdKlatka { get { return (byte)idKlatka; } }
//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;
}
private bool CzyMoznaZrobicRuch(int[,] plansza,int ileKlatekX,
int ileKlatekY,Czolg B)
{
//tymczasowe współrzędne położenia
float _x = x,
_y = y;
//sprawdzaj możliwośc ruchu z dokładnością
//do osmiokrotnego przesunięcia pozycji
//wartośc nalezy dopasowac do obrysu duszka względem
//pełengo rozmiaru klatki
switch (idKlatka)
{
//na północ
case 0: _y -= 8*v; break;
//na wschód
case 1: _x += 8*v; break;
//na południe
case 2: _y += 8*v; break;
//na zachód
case 3: _x -= 8*v; break;
}
int wiersz = (int)(_y / wys),
kolumna= (int)(_x/szer);
//nie wyjeżdzja poza planszę
if (kolumna < 0 ||
kolumna > ileKlatekX-1 ||
wiersz < 0 ||
wiersz > ileKlatekY-1) return false;
//czy kolizja z czolgiem przeciwnika
int wierszB = (int)(B.Y / wys),
kolumnaB = (int)(B.X / szer);
if(wiersz==wierszB && kolumna==kolumnaB) return false;
//klatka o indeksie 16 jest trawą-gruntem
//po którym można jeździć, po pozostałych nie można
if (plansza[wiersz,kolumna]!=16)return false;
return true;
}
public void Ruch(int[,] plansza, int ileKlatekX,
int ileKlatekY,Czolg B)
{
if (!fRuch||
!CzyMoznaZrobicRuch(plansza, ileKlatekX, ileKlatekY,B))
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;
}
}
}
}
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 pocisku
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
//
public Czolg wlasciciel,przeciwnik;
//konstruktor
public Pocisk(float _x, float _y, int _wys,
int _szer, byte _kierunek,
Czolg _w,Czolg _p)
{
//przesuniecie dla konca lufy
float dx = 0, dy = 0;
wys = _wys;
szer = _szer;
//kierunek strzalu
kierunek = _kierunek;
switch (kierunek)
{
//Północ
case 0:dy = -24.0f;break;
//Wschód
case 1:dx = 24.0f; break;
//Polódnie
case 2:dy = 24.0f;break;
//Zachód
case 3:dx = -24.0f;break;
}
//zapamietaj współrzędne strzalu
x = _x + dx;
y = _y + dy;
x0 = _x;
y0 = _y;
//ustaw zasięg na 6 kafli świata
zasieg = 6 * 48;
wlasciciel = _w;
przeciwnik = _p;
}
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);
}
private bool CzyKolizja(int[,] plansza, int ileKlatekX, int ileKlatekY)
{
//kolizja z czołgiem przeciwnika
if(x > przeciwnik.X - 16 &&
x < przeciwnik.X + 16 &&
y > przeciwnik.Y - 16 &&
y < przeciwnik.Y + 16) return true;
//dzielimy przez 48 bo taka jest szerokośc
//i wysokośc pola świata gry
int wiersz = (int)(y / 48),
kolumna = (int)(x / 48);
//eksplozaj na grnicach swiata
if (kolumna < 0 || wiersz < 0 ||
kolumna >= ileKlatekX ||
wiersz >= ileKlatekY) return true;
//klatka o indeksie 16 jest trawą-gruntem
//po którym można jeździć, po pozostałych nie można
if (plansza[wiersz, kolumna] != 16) return true;
return false;
}
public void Ruch(int[,] plansza, int ileKlatekX,
int ileKlatekY)
{
//kierunek pólnoc
switch (kierunek)
{
//na Pólnoc
case 0:y -= v;break;
//na Wschod
case 1:x += v;break;
//na Południe
case 2: y += v; break;
//na Zachód
case 3: x -= v; break;
}
if (CzyKolizja(plansza, ileKlatekX,
ileKlatekY) ||
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;
}
}
}
}
Wskazówka:
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;
public int[,] plansza = new int[ileKlatekY, ileKlatekX];
public List<Eksplozja> listaEksplozji = new List<Eksplozja>();
public List<Pocisk> listaPociskow = new List<Pocisk>();
/*
public int[,] Plansza
{
get{return plansza;}
}
*/
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(plansza, ileKlatekX, ileKlatekY);
}
//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);
}
}
}
}
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 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;
}
}
}
}