Obiekty graficzne na przykładzie prostej gry- ruch obiektu graficznego. Część 4

W tej części zajmiemy się tworzeniem klasy symbolizującej graczy. Gracze będą reprezentowani przez dwa czołgi, które w kodzie programu są obsługiwani tak samo, różnią się tylko graficznym obrazem. Kod czwartej części bazuje na kodzie części poprzedniej, którą możesz pobrać z tego linku: (pobierz kod)

Założenia wstępne dla klasy Czolg

Na tę chwilę zadania stawiane przed klasą reprezentującą graczy to

Sterowanie obiektami będzie się odbywać w głównym oknie aplikacji

Tworzymy klasę Czolg

Wybieramy menu funkcję Projekt/Dodaj klasę Wprowadzamy poniższy kod

Wskazówka:


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 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; }
		}
	}
}

Konstruktor klasy powinien obsłużyć bieżącą aktywną klatkę obrazu oraz położenie w świecie gry. Plik z kodem klasy uzupełniamy o ten kod

Wskazówka:


//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;
}

Wstępnie napiszemy kod funkcji rysującą obraz czołgu. Mając tą funkcję będziemy mogli przetestować aplikację czy prawidłowo tworzymy i odczytujemy obiekt, którym będziemy mogli grać. Dodajemy poniższy kod ładujący klatki animacji kierunków ruchu czołgu

Wskazówka:


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
}

Funkcja rysująca aktywną klatkę ma postać jak poniżej

Wskazówka:


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);
        }

Tworzymy obiekty graczy w głównym oknie aplikacji

Przechodzimy do pliku Form1.cs. W kodzie deklarujemy dwie zmienne typu Czolg o nazwie czolgA, czolgB.

Wskazówka:


public partial class Form1 : Form
{
	bitmapy bmp;
	Swiat swiat;
	Czolg czolgA, czolgB;
W konstruktorze okna formatki Form1() inicjujemy obiekty graczy. Wprowadzamy poniższy kod

Wskazówka:


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);
}

Modyfikujemy sposób rysowania świata gry. Od tej pory nie będziemy rysować ekranu gry w kontrolce panel1 lecz bezpośrednio w oknie formatki. Usuwamy kontrolkę panel1. Aktywujemy zdarzenie Paint dla formatki

rysowanie świata gry 2D Visual studio C#

Wprowadzamy poniższy kod

Wskazówka:


private void Form1_Paint(object sender, PaintEventArgs e)
{
	bmp.PokazEkranGry(this.Handle, swiat, czolgA, czolgB);
}

Skompiluj program i sprawdź efekt działania. Jeżeli kod programu nie zawiera błędów, to ekran aplikacji powinien być podobny do poniższego

świat gry 2D Visual studio C#

Obrót i ruch obiektu gracza

Klasę Czolg wzbogacamy o funkcję obracającą obiekt gracza oraz funkcje ruchu. Idea obrotu jak i ruchu jest prosta. W przypadku obrotu (lewo/ prawo) będzie to zmiana klatek ustawienia czołgu. Należy przewidzieć zabezpieczenie bieżącego indeksu klatki. Nie może być on mniejszy od zera i większy od maksymalnej wartości. W tym przypadku od 3.

W klasie Czolg dodajemy dwie publiczne zmienne z wiązane z prędkością i ruchem (float v i bool fRuch)

Wskazówka:


	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

Do klasy Czolg dopisujemy ten kod

Wskazówka:


public void Obracaj(int i)
{
	idKlatka += i;
	if (idKlatka < 0) idKlatka = 3;
	if (idKlatka > 3) idKlatka = 0;
}

Funkcja ruchu na tę chwilę ma jeden cel: przesunąć obiekt wzdłuż osi x lub y bez sprawdzania czy taki ruch jest możliwy. Dodajemy funkcję ruchu

Wskazówka:


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;
	}
}

Skompiluj program i sprawdź czy nie zwiera błędów.

Klawisze sterujące

Przyjmujemy, że obroty obiektów graczy będziemy realizować klawiszami dla gracza A: klawisz A w lewo, klawisz D w prawo. Dla gracza B będą to odpowiednio klawisze: strzałka w lewo, to obrót w lewo, strzałka w prawo to obrót w prawo. Klawisze ruchu dla gracza A, to W- naprzód, S- wstecz. Dla gracza B, to strzałka w górę- naprzód , strzałka w dół- wstecz.

Do wywołania kontroli wciskanych klawiszy wykorzystamy zdarzenie KeyDown i KeyUp podpięte do okna głównej formatki.

Kod zdarzenia KeyDown

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;
	}
}

Zdarzenie KeyUp

Wskazówka:


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;
}

Skompiluj program i sprawdź efekt działania. Na tym etapie program pozwala obracać lewo/ prawo oba obiekty graczy

obrót obiektu gry 2D Visual studio C#

Zegar gry

Zegar gry jest sercem całej rozgrywki. W tym miejscu odbywa się odświeżanie ekranu gry, aktualizują się klatki obiektów, paski życia, kolizje itp. W programie wykorzystamy kontrolkę Timer, którą osadzamy w głównym oknie

zegar gry 2D Visual studio C#

Właściwość Enabled ustawiamy na true, a Interval na 5 milisekund

W zdarzeniu Tick kontrolki wprowadzamy wywołanie naszej funkcji ZegarGry

Wskazówka:


private void timer1_Tick(object sender, EventArgs e)
{
	zegarGry();
}

private void zegarGry()
{
	czolgA.Ruch();
	czolgB.Ruch();
	bmp.PokazEkranGry(this.Handle, swiat, czolgA, czolgB);
}

Skompiluj program i sprawdź efekt działania. W tej chwili można poruszać się czołgami po planszy

ruch obiektu gry 2D Visual studio C#

Poniżej podaję pełny kod klasy Czolg oraz kod głównej formatki tworzonej aplikacji desktopowej

Klasa 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;
            }
        }
        
    }
}

Klasa 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;

        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");
            
        }

        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;
            }
        }
        
        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);
        }
    }
}

Klasa 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);
                }
        }
    }
}
Klasa bitmapy

Wskazówka:


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(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);
        }

        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 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();
        }
    }
}
Alkomat- wirtualny test

Alkomat- darmowa aplikacja na Androida

Pobierz ze sklepu Google Play