Obiekty graficzne na przykładzie prostej gry- mapa świata gry. Część 3
W tej części cyklu o obiektach graficznych omówimy sposób ładowania prostego świata gry 2D zapisanego w pliku tekstowym w postaci numerów indeksów graficznych klatek wycinanych z obrazu zasobów gry. Do pracy potrzebny jest kod z poprzedniej części, którego jeżeli nie masz możesz pobrać z tego linku: pobierz kod
Cel ćwiczenia
Utwórz klasę odpowiedzialną za odczyt pliku świata gry oraz zmodyfikuj istniejący kod, tak aby można było wczytany świat pokazać na ekranie planszy gry
Modyfikujemy okno głównej formatki tak aby panel przewidziany na ekran gry zajmował całą przestrzeń okna
Rozmiar głównego okna formatki ustawiamy na 976;758. Rozmiar ten pozwoli otrzymać świat wielkości 20x15 klatek po 48x48 pikseli każda
Kontrolce panel1 właściwość Dock ustawiamy na Fill
Modyfikujemy plik zasobów graficznych
W dowolnym programie graficznym na przykład GIMP?ie rysujemy nasze zasoby graficzne
Zasoby graficzne rysujemy tak aby były zorganizowane w kolejne klatki o przyjętym rozmiarze (tu 48x48 pikseli). Takie klatki będą mieć ustalone indeksy wyliczone z ich pozycji w pliku graficznym zasobów
Utworzonym plikiem graficznym podmieniamy plik graficzny zapisany w części 2 omawianego cyklu.
Tworzymy tekstowy plik zapisu planszy świata gry
Kompilator Visual Studio ma również możliwość tworzenia plików tekstowych. W takim pliku tekstowym zapiszemy indeksy tworzące graficzny wygląd danego poziomu gry.
Wybieramy opcję tworzenia pliku tekstowego jak poniżej
Plik powinien zawierać 20 liczb zapisanych w 15 wierszach. Każda z liczb ma być oddzielona spacją, która to będzie separatorem kolejnych indeksów. Utworzony plik powinien być zapisany w folderze, który zawiera plik *.exe tworzonej aplikacji
Na tym etapie pisania prostej gry 2D mój plik zawiera tylko i wyłącznie liczbę 16, która jest indeksem graficznego kafelka z obrazem trawy
Tworzymy klasę Swiat
Klasa Swiat będzie odpowiedzialna za odczytanie pliku świata gry oraz jego narysowanie. Tworzymy w projekcie plik klasy Swiat.cs i wstępnie piszemy jej konstruktor. Patrz poniższy kod
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;
int[,] plansza=new int[ileKlatekY,ileKlatekX];
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);
}
}
}
Na tym etapie możemy sprawdzić czy tworzona klasa potrafi odczytać wskazany plik tekstowy. W kodzie głównej formatki wprowadzamy poniższe modyfikacje
Wskazówka:
namespace czolg_1
{
public partial class Form1 : Form
{
bitmapy bmp;
Swiat swiat;
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);
}
W pliku kodu klasy Swiat ustawimy pułapkę debugera w miejscu wskazanym poniżej. Po skompilowaniu i uruchomieniu projektu w podglądzie stany zmiennych możemy sprawdzić czy plik z zapisem świata gry jest odczytywany
Odczytujemy indeksy kafli graficznych
Klasę Swiat modyfikujemy do odczytu kolejnych indeksów kafli graficznych. Odczytane indeksy zapiszemy do tablicy dwuwymiarowej. Kod konstruktora wygląda jak poniżej
Wskazówka:
//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;
}
}
}
Dodajemy funkcję rysującą odczytany świat planszy gry
Wskazówka:
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);
}
}
Utworzoną funkcję rysującą planszę świata gry wywołamy w klasie bitmapy w taki sposób, że będzie również spełniać późniejszą rolę mazania/ odświeżania stanu grafiki gry. Poniżej zmodyfikowany kod funkcji RysujSwiatGryBufor
Wskazówka:
private void RysujSwiatGryBufor(Swiat swiat,int idKlatka,int _x,int _y)
{
//na ten czas prób czyść mapę swiata kolorem
//gEkranGry.Clear(System.Drawing.Color.White);
swiat.RysujSwiat(gEkranGry, bitmaps,W,h);
//na teraz testowe rysowania jednego czolgu gracza
pokazKlatke(gEkranGry, idKlatka,_x,_y);
}
Ta modyfikacja wymusza modyfikację argumentów funkcji PokazEkranGry
Wskazówka:
public void PokazEkranGry(IntPtr uchwyt, Swiat swiat, int idKlatka, int _x, int _y)
{
Graphics g = Graphics.FromHwnd(uchwyt);
RysujSwiatGryBufor(swiat,idKlatka,_x,_y);
g.DrawImage(bmpBuforEkranGry, 0, 0);
g.Dispose();
}
Aby sprawdzić czy świat jest prawidłowo odczytany i rysowany w kodzie głównej formatki w zdarzeniu Paint wprowadzimy wywołanie
Wskazówka:
private void Form1_Paint(object sender, PaintEventArgs e)
{
bmp.PokazEkranGry(panel1.Handle, swiat, 1, 0, 0);
}
Skompiluj program i sprawdź efekt działania. Na tę chwilę powinieneś otrzymać poniższy obraz
Projektujemy wygląd świata gry
W tej chwili mamy gotową obsługę metody rysowania świata gry w tylnym buforze i po jego narysowaniu wysyłamy gotową grafikę na ekran. Tera zostaje nam zaprojektowania układu planszy gry. Gra w założeniach ma być prosta. Gracze poruszają się czołgami na przykład w układzie korytarzy.
Za ładne to nie jest, ale jak na razie na te potrzeby wystarczy. Poniżej pełne kody
Plik Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace czolg_1
{
public partial class Form1 : Form
{
bitmapy bmp;
Swiat swiat;
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);
}
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");
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
bmp.PokazEkranGry(panel1.Handle, swiat, 1, 0, 0);
}
}
}
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 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);
}
}
}
}
Plik bitmapy.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 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);
}
}
}
}