Obiekty graficzne na przykładzie prostej gry. Część 1
Ten materiał rozpoczyna krótki kurs związany z funkcjami obsługi plików graficznych przy użyciu języka C# i kompilatora Visual Studio na przykładzie prostej gry. Na tą chwilę gra nie określonego scenariusz. Kod aplikacji będzie wykorzystywał standardową bibliotekę System.Drawing. Biblioteka ta zawiera podstawowe klasy i metody związane z obsługą grafiki.
W tej części przygotujemy naszą klasę o nazwie bitmapy. Przeznaczeniem tej klasy będzie:
- załadowanie pliku graficznego z zasobami gry
- podział pliku graficznego na tak zwane klatki (duszki itp.) świata 2D
- wyświetlanie żądanej klatki we skazanych współrzędnych ekranowych
- wyczyszczenie przypisanej pamięci przy zakończeniu działania aplikacji
Na tą chwilę tyle wystarczy.
Tworzymy plik zasobów graficznych
Na przykład przy użyciu dowolnego programu graficznego (tu został wykorzystany GIMP) tworzymy naszą grafikę. Grafikę zorganizujemy z szeregu klatek o wymiarach 48x48 pikseli przechowywanych w jednym pliku graficznym zapisanym z kanałem przezroczystości w formacie PNG.
Utworzoną grafikę w programie GIMP eksportujemy do pliku, który koniecznie umieszczamy w folderze przechowywanym w katalogu bin\Debug\g oraz bin\Release\g
Tworzymy klasę obsługującą zasoby graficzne
Dodanie klasy obsługującej nasze zasoby graficzne wykonamy wybierając Menu/ Projekt/ Dodaj klasę lub wykonując kolejne kliknięcia zgodnie z poniższą ilustracją
W oknie dialogowym Dodaj nowy element postępujemy jak poniżej. Uwaga: Przypisana nazwa tworzonego pliku klasy jest również nazwa tej klasy. W omawianym kodzie programu jest to nazwa pliku: bitmapy.cs
Dodajemy bibliotekę System.Drawing i tworzymy zmienną publiczną do przechowywania wczytanej grafiki: public Bitmap zasobyBmp; Dodajemy również zalążek kodu destruktora. Zwróć uwagę, że destruktor inicjuje się przez wpisanie znaku ~ (tylda) poprzedzającego nazwę tworzonej klasy. Patrz poniższy kod.
bitmapy.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 bitmapy
{
public Bitmap zasobyBmp;
//destruktor
~bitmapy()
{
}
}
}
Destruktor jest wymagany w naszym projekcie, ponieważ nie chcemy zaśmiecać pamięci systemu pozostawiając w niej wczytane zasoby graficzne.
Test ładowania grafiki
Mamy przygotowaną grafikę oraz zalążek klasy, przy pomocy której możemy będziemy wczytywać wskazany ścieżką plik graficzny. Do testy przygotujemy główną formatkę aplikacji. W jej obszarze osadzimy kontrolkę typu Panel oraz jedne klawisz- Button
Obszar kontrolki typu Panel będzie służyć jako ekran tworzonej gierki 2D
Dodajemy kod do tworzonej klasy
Na tą chwilę przewidujemy, że nasza klasa powinna obsłużyć wczytanie pliku graficznego. Co jest bardzo proste. Odbywa się instrukcją
Wskazówka:
public void WczytajBmp(string plik)
{
//funkcja wczytuje bitmapę z pliku graficznego
zasobyBmp = new Bitmap(plik);
}
Gdzie argument plik typu string jest ścieżką do lokalizacji przechowywanego pliku z grafiką naszej gry.
W tworzonej klasie implementujemy konstruktora klasy, zmienne parametrów wymiaru klatki, kod funkcji testowej rysującej fragment wyciętej grafiki na wskazanym ekranie.
Uzupełnij plik tworzonej klasy poniższym kodem
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;
private int w, h;//szerkość klatki
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 _w,int _h)
{
this.w = _w;
this.h = _h;
}
public void WczytajBmp(string plik)
{
//funkcja wczytuje bitmapę z pliku graficznego
zasobyBmp = new Bitmap(plik);
}
public void testPokazBmp(Graphics g, int x, int y, int w, int h)
{
//funkcja testowa, pokazuje pierwszą klatkę ze współrzędnych (0,0,0+w,0+h)
g.DrawImage(zasobyBmp, 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();
}
}
}
Skompiluj program i sprawdź czy nie zawiera błędów
Przygotowujemy główne okno aplikacji do pierwszego testu graficznego
Okno główne powinno zawierać
- inicjalizację zmiennej bazującej na utworzonej klasie bitmapy
- metodę wczytania pliku graficznego
- klawisz do testu rysowania fragmentu wczytanej grafiki
Uzupełnij kod pliku głównej formatki jak poniżej
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;
public Form1()
{
InitializeComponent();
bmp=new bitmapy(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 button1_Click(object sender, EventArgs e)
{
//test sprawdzajacy czy prawidłowo załadowano grafikę
Graphics g = Graphics.FromHwnd(panel1.Handle);
//wklej wyciętą klatkę grafiki we współrzędnych x=100, y=100
bmp.testPokazBmp(g, 100, 100, 48, 48);
g.Dispose();
}
}
}
Skompiluj program i sprawdź efekt działania. Prawidłowo wykonane czynności przedstawia poniższa ilustracja
Test odczytu wybranej klatki
Modyfikujemy główne okno aplikacji do postaci jak poniżej. Wybrana klatkę będziemy mogli podać poprzez jej indeks ewentualnie licznikiem kliknięć zwiększającym wspomniany indeks.
W omawianym pomyśle po wczytaniu pliku zasobu graficznego szatkujemy go na kolejne klatki o wymiarach 48 x 48 pikseli. Otrzymane kafelki zasobów graficznych 2D będziemy przechowywać w liście bazującej na typie Bitmap. W kodzie to
private List<Bitmap> bitmaps = new List<Bitmap>();Tworzona klasę wzbogacamy o kod
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;
//lista klatek wycinanych bitmapek
private List<Bitmap> bitmaps = new List<Bitmap>();
private int w, h;//szerkość klatki
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 _w,int _h)
{
this.w = _w;
this.h = _h;
}
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 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();
foreach (Bitmap b in bitmaps) { b.Dispose(); }
}
}
}
Modyfikujemy główne okno aplikacji
W oknie głównym dodaj wywołanie funkcji wyświetlającej wybrana klatkę
Form1.cs:
private void button2_Click(object sender, EventArgs e)
{
int idKlatka=int.Parse(textBox1.Text);
Graphics g = Graphics.FromHwnd(panel1.Handle);
bmp.pokazKlatke(g, idKlatka, 0, 0);
g.Dispose();
}
Skompiluj program i sprawdź efekt wyświetlania dowolnej wyciętej klatki
W tej części to na tyle. Poniżej pełny kod klasy bitmapy
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;
//lista klatek wycinanych bitmapek
private List<Bitmap> bitmaps = new List<Bitmap>();
private int w, h;//szerkość klatki
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 _w,int _h)
{
this.w = _w;
this.h = _h;
}
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 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();
foreach (Bitmap b in bitmaps) { b.Dispose(); }
}
}
}
Pełny kod głównego okna tworzonej formatki
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;
public Form1()
{
InitializeComponent();
bmp=new bitmapy(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 button1_Click(object sender, EventArgs e)
{
//test sprawdzajacy czy prawidłowo załadowano grafikę
Graphics g = Graphics.FromHwnd(panel1.Handle);
bmp.testPokazBmp(g, 100, 100, 48, 48);
g.Dispose();
}
private void button2_Click(object sender, EventArgs e)
{
int idKlatka=int.Parse(textBox1.Text);
Graphics g = Graphics.FromHwnd(panel1.Handle);
bmp.pokazKlatke(g, idKlatka, 0, 0);
g.Dispose();
}
}
}