Obiekty graficzne na przykładzie prostej gry- tylny bufor grafiki. Część 2
W tej części ćwiczeń dotyczących wykorzystani modułu graficznego zrealizujemy model wykorzystani tylnego bufora grafiki. Tylny bufor grafiki to metoda rysowania obiektów graficznych poza widoczną częścią ekranu. Cała grafika w tym kolejne klatki animacji, przesunięci a obiektów graficznych do nowych współrzędnych ekranowych odbywają się na przygotowanej wcześniej wirtualnej bitmapie.
Wirtualna bitmapa, to obiekt klasy Bitmap (w kodzie programu zmienna o nazwie bmpBuforEkranGry). Stosowanie metody rysowania w tylnym buforze zmniejsza nieporządane efekty migotania ekranu komputera podczas odświeżania grafiki (nie mówimy tu o synchronizacji pionowej). Tylny bufor powinien mieć rozmiary (wysokość, szerokość w pikselach) takie jak przygotowana widoczna w aplikacji powierzchnia do rysowania ruchomych grafik.
Cel ćwiczenia
Celem jest utworzenie aplikacji desktopowej, która wykorzysta metodę tylnego bufora do rysowania obiektu graficznego złożonego z kilku różnych klatek. Po narysowaniu obiektu gotowa grafika wysyłana jest na widoczny ekran graficzny
Jeżeli nie masz kody z poprzedniej lekcji to pobierz klikając na ten link: pobierz kod
Modyfikujemy klasę bitmap
Do utworzonej klasy bitmap w części pierwszej dodajemy te zmienne
Wskazówka:
private Bitmap bmpBuforEkranGry;//bufor graficzny
private Graphics gEkranGry;
private int w, h,//szerkość klatki
ileKolumn, ileWierszy;//ilosc kolumn i wierszy swiata gry
Zmieniamy konstruktor klasy
W prowadzamy zmiany do konstruktora klasy. Do wywołania konstruktora dodamy dwa parametry związane z rozmiarem tworzonego świata gry. Wielkość świata gry podamy w ilości kolumn i wierszy widocznych klatek na ekranie. W pomyśle rozwiązania zakładamy, że tworzony świat nie będzie duży- nie będzie przekraczać rozmiaru ekranu komputera.
Wskazówka:
//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);
}
Wprowadzone zmiany w konstruktorze wymagają zmian w kodzie pliku głównej formatki (plik Form1.cs)
Wskazówka:
public Form1()
{
InitializeComponent();
//buduj swiat 6x8- 6 kolumn na 8 wierszy po 48x48 pikseli
bmp=new bitmapy(6,8,48,48);
}
Zmieniamy zawartość kodu destruktora, w którym musimy dopisać instrukcje zwalniające przydzieloną pamięć na bufor graficzny i moduł rysujący
Wskazówka:
//destruktor
~bitmapy()
{
//zwolnij pamięc zajmowaną przez zasoby graficzne
zasobyBmp.Dispose();
foreach (Bitmap b in bitmaps) { b.Dispose(); }
//zwolnij bufor rysowania ekranu gry
bmpBuforEkranGry.Dispose();
gEkranGry.Dispose();
}
Rysujemy grafikę w tylnym buforze
Tylny bufor został przygotowany w konstruktorze klasy bitmap. Moduł graficzny został w konstruktorze skojarzony z buforem instrukcją
gEkranGry= Graphics.FromImage(bmpBuforEkranGry);Od tej pory wszystkie instrukcje rysowania będą się odbywać poza ekranem w zmiennej gEkranGry. Na tą chwilę napiszemy testowe rozwiązanie sprawdzające czy prawidłowo rysujemy w utworzonym buforze graficznym.
Wskazówka:
private void RysujSwiatGryBufor(int idKlatka,int _x,int _y)
{
//na ten czas prób czyść mapę swiata kolorem
gEkranGry.Clear(System.Drawing.Color.White);
//na teraz testowe rysowania jednego czolgu gracza
pokazKlatke(gEkranGry, idKlatka,_x,_y);
}
Teraz musimy napisać publiczną funkcję, która narysuje świat gry i wyśle go na ekran monitora w głównym oknie aplikacji
Wskazówka:
public void PokazEkranGry(IntPtr uchwyt, int idKlatka, int _x, int _y)
{
Graphics g = Graphics.FromHwnd(uchwyt);
RysujSwiatGryBufor(idKlatka,_x,_y);
g.DrawImage(bmpBuforEkranGry, 0, 0);
g.Dispose();
}
Pełny kod klasy bitmap wygląda jak poniżej
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 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(int idKlatka,int _x,int _y)
{
//na ten czas prób czyść mapę swiata kolorem
gEkranGry.Clear(System.Drawing.Color.White);
//na teraz testowe rysowania jednego czolgu gracza
pokazKlatke(gEkranGry, idKlatka,_x,_y);
}
public void PokazEkranGry(IntPtr uchwyt, int idKlatka, int _x, int _y)
{
Graphics g = Graphics.FromHwnd(uchwyt);
RysujSwiatGryBufor(idKlatka,_x,_y);
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 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(); }
//zwolnij bufor rysowania ekranu gry
bmpBuforEkranGry.Dispose();
gEkranGry.Dispose();
}
}
}
Wysyłamy tylny bufor na ekran monitora
Wyświetlenie utworzonej grafiki na widocznym ekranie jest już proste. W testowym rozwiązaniu wyślemy bieżącą klatkę czołgu we współrzędne ekranowe: x=10 pikseli, y= 10 pikseli. W kodzie głównej formatki modyfikujemy funkcję zdarzenia kliknięcia w klawisz zapisaną w części pierwszej
Wskazówka:
int id = 0;
private void button2_Click(object sender, EventArgs e)
{
bmp.PokazEkranGry(panel1.Handle,id, 10, 10);
id++;
if(id>3)id = 0;
}
Pełny kod pliku Form1.cs zapisany jest 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();
//buduj swiat 6x8- 6 kolumn na 8 wierszy po 48x48 pikseli
bmp=new bitmapy(6,8,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);
//osadx grafike we wspołrzędnych x=64, y=128
//szerokośc i wysokośc klatki 48x48 pikseli
bmp.testPokazBmp(g, 64, 128, 48, 48);
g.Dispose();
}
int id = 0;
private void button2_Click(object sender, EventArgs e)
{
bmp.PokazEkranGry(panel1.Handle,id, 10, 10);
id++;
if(id>3)id = 0;
}
}
}
Skompiluj program i sprawdź efekt działania. Prawidłowo działająca aplikacja wyświetla na białym tle bieżąca klatkę czołgu, którą można zmieniać
Test prostego przesuwania obiektu graficznego
Utworzone funkcje w klasie bitmap pozwalają już przetestować ruch obiektu. Wystarczy, że będziemy modyfikować współrzędne X i Y wyświetlanego obiektu graficznego w buforze grafiki. W tym celu dodamy do głównego okna formatki klawisz zmiany współrzędnej X, tak aby obiekt poruszał się od lewej do prawej strony.
W oknie tworzonej formatki dodajemy kontrolkę Button (patrz poniższy rysunek)
W kodzie zdarzenia kliknięcia w dodany klawisz dopisujemy te instrukcje (zwróć uwagę, że została dodana zmienna lokalna wspX)
Form1.cs:
int wspX = 10;
private void button3_Click(object sender, EventArgs e)
{
bmp.PokazEkranGry(panel1.Handle, id, wspX, 10);
wspX++;
if (wspX > 100) wspX=10;
}
Skompiluj program i sprawdź efekt działania. Prawidłowo działająca aplikacja pokazuje ruch obiektu wymazując obraz z poprzedniej pozycji. Na te samej zasadzie można dodać klawisze ruchu obsługujące pozostałe kierunki i zwroty ruchu
W następnej części zajmiemy się tłem graficznym gry- czyli światem prostej gry 2D utworzonej jako aplikacja desktopowa w Visual Studio C#.