Blog (9)
Komentarze (25)
Recenzje (2)

HTML5 w tworzeniu gier (cz. 2)

@lukasz.developerHTML5 w tworzeniu gier (cz. 2)18.01.2011 01:37

Hej,

przepraszam za dłuższą przerwę, ale ostatnio miałem trochę mniej czasu na pisanie niniejszego tutoriala. W dzisiejszej części cyklu o HTML5 i JS w tworzeniu interaktywnych treści pozostaniemy w zimowym klimacie, ale nieco przekształcimy stworzoną wcześniej aplikację w bardziej interaktywną :).

Tło

Na początku zajmiemy się graficzną stroną naszego dzieła ;). Zamiast nijakiego, jednokolorowego tła wstawimy piękny gradient :).

Wartości poszczególnych kolorów będziemy podawać w 24-bitowym formacie #rrggbb (tzw. Hex, format heksadecymalny lub szesnastkowy), gdzie rr, gg, bb to odpowiednio czerwona, zielona i niebieska składowa koloru. Wszystkie wartości są wyrażane za pomocą dwucyfrowych liczb w systemie szesnastkowym, co umożliwia podanie wartości z przedziału [0;255].

Uwaga Można również spotkać się z zapisem 32-bitowym, który zawiera o dwa znaki więcej na początku, oznaczające wartość przezroczystości piksela (kanału alfa).

Jeśli nie podoba ci się wizja ręcznego rozkładania koloru piksela na kanały kolorystyczne i przeliczania je do odpowiedniego systemu liczbowego, niewątpliwie ucieszy cię informacja, że niewiele osób chciałoby to robić :P. Z tego też względu prawie każdy, nawet prosty, program graficzny umożliwia wybranie koloru z palety barw i podaje jego reprezentację w pomocnej nam postaci :).

Dla potrzeb naszego tutoriala wybrałem kolory: czarny : #000000 niebieski (no, nie do końca, ale nie czepiajmy się niuansów kolorystycznego nazewnictwa :P): #70D8FF

Dla prostych kolorów możemy również posługiwać się ich angielskimi nazwami, więc w przypadku czarnego napiszemy po prostu black.

Nasz gradient nie będzie się zmieniał, więc policzymy go tylko raz, w funkcji inicjalizującej. Najpierw dodajmy do zmiennych globalnych naszego skryptu (na początku pliku, tam, gdzie jest zdefiniowana np.: zmienna canvasSzerokosc) definicję zmiennej z gradientem:


var gradientTla;

Następnie w funkcji inicjalizujGre, w ifie sprawdzającym poprawność canvasa, dodajmy tworzenie obiektu gradientu. Metoda będzie wówczas wyglądać mniej więcej tak (z dokładnością do wielokropków, które oznaczają kod bez zmian ;)):


function inicjalizujGre(glownyCanvas)
{
	...
	/* jeśli element canvas jest obsługiwany */
	if (canvasElement.getContext)
	{
		context = canvasElement.getContext("2d");
		
		gradientTla = context.createLinearGradient(0, 0, 0, wysokoscCanvas);
		gradientTla.addColorStop(0, "black");
		gradientTla.addColorStop(1, "#70D8FF");
		
		...
		
		/* uruchom glowna petle gry */
		setInterval(glownaPetla, dlugoscKlatki);
	}
	...
}

Na koniec zmieńmy kod czyszczenia tła planszy:


function glownaPetla()
{
	/* wyczysc ekran po poprzedniej klatce */
	//context.fillStyle = "rgba(0, 0, 0, 1)";
	context.fillStyle = gradientTla;
	context.fillRect(0, 0, szerokoscCanvas, wysokoscCanvas);
...
}

Zakomentowaliśmy poprzedni kod i dodaliśmy nowy, którzy rysuje nasz gradient :).

Obrazki i rotacje

Ja zapewne zauważyliście, płatki śniegu mają trochę bardziej skomplikowane kształty niż kółka, których używaliśmy do tej pory :P. Moglibyśmy stworzyć je implementując rysowanie odpowiednio złożonego kształtu w postaci ścieżki (za pomocą poleceń moveTo i lineTo), ale my posłużymy się innym sposobem :). Narysujcie płatek śniegu w dowolnym programie graficznym i zapiszcie go jako plik platek_sniegu.png w katalogu ze skryptem. Jeśli nie chcecie rysować, możecie się posłużyć obrazkiem płatka stworzonym na szybko przeze mnie.



[image=img1]

Mamy już plik graficzny, teraz pora umieścić go w naszej "grze" :).

Zmieńmy kod w następujący sposób:


function inicjalizujGre(glownyCanvas)
{
	...

	/* jeśli element canvas jest obsługiwany */
	if (canvasElement.getContext)
	{
		context = canvasElement.getContext("2d");
		
		platekImg = new Image();
		platekImg.src = "platek_sniegu.png";
		platki[0] = new PlatekSniegu(100, 0);
		...
	}
...
}

Następnie znajdźmy w głównej pętli wcześniejszy kod rysujący płatki:


function glownaPetla()
{
...
for(var j = 0; j < platki.length; j++)
{
...
//rysuj platek
		context.fillStyle = "#fff";
		context.beginPath();
		context.arc(platki[j].x, platki[j].y, platki[j].r, 0, 2*Math.PI, true);
		context.closePath();
		context.fill();
}
...
}

i zamieńmy go na następujący:


function glownaPetla()
{
...
for(var j = 0; j < platki.length; j++)
{
...
	//rysuj platek
	context.shadowBlur = 10;
	context.shadowColor = "white";
	//oblicz skladowa pozioma polozenia platka tak, aby skalowanie wygladalo jak obrot wokol pionowej osi symetrii
	var xPlatka = platki[j].x - platki[j].aktualneR/2;
	//oblicz skladowa pionowa polozenia platka
	var yPlatka = platki[j].y - platki[j].r;
	context.drawImage(platekImg, xPlatka, yPlatka, platki[j].aktualneR, platki[j].r);
}

Przy okazji dodaliśmy też efekt rozmytego białego cienia, który sprawi, że płatki będą się lepiej prezentować na ekranie. Zauważmy też, że użyte zostało nowe pole aktualneR. Będziemy je wykorzystywać do obrotu, który w przybliżeniu ma polegać na ściskaniu i rozciąganiu obrazka z poziomie. Do obsłużenia efektu dodajmy też klasy płatka pole deltaR informujące czy obrazek należy aktualnie rozciągać (wartość 1) czy ściskać (wartość -1).

Uwaga Nazwy r i aktualneR zostały przyjęte ze względu na zmniejszenie liczby potrzebnych zmian w kodzie względem poprzedniej części tutoriala. Oznaczają one odpowiednio wysokość i szerokość obrazku płatka i nie należy ich mylić z oznaczeniem promienia.

Dodajmy więc odpowiednią inicjalizację w w metodzie tworzącej obiekty płatków:


function PlatekSniegu(startX, startY)
{
	this.x = startX;
	this.y = startY;
	this.r = 20;
	this.aktualneR = this.r;
	this.deltaR = -1;
	
	this.kierunek = 0; /* wartosc ze zbioru {-1, 0, 1} */
	this.dlugosc = 0;
}

Ze względu na zmianę znaczenia pola r, zwiększyłem również jego domyślną wartość.

Następnie w głównej pętli gry dodajmy kod zmieniający rozmiar płatka:


function glownaPetla()
{
...
for(var j = 0; j < platki.length; j++)
{
...

//obrot platka wokol osi Y

		//jesli platek został ściśnięty do szerokosci 0
		if (platki[j].aktualneR <= 0)
		{
		//zmien flage na rozciaganie
			platki[j].deltaR = 1;
		}
		//jesli platek zostal rozciagniety ponad swoj oryginalny rozmiar
		else if (platki[j].aktualneR >= platki[j].r)
		{
		//zmien flage na sciskanie
			platki[j].deltaR = -1;
		}
		//zmodyfikuj aktualna szerokosc zgodnie z ustawiona flaga
		platki[j].aktualneR += platki[j].deltaR;

		//rysuj platek
...
}
...

Zmodyfikujmy też fragment odpowiadający za tworzenie nowych płatków:


function glownaPetla()
{
...
//co 10 klatek dodaj nowy platek sniegu az do osiagniecia maksymalnej ich liczby
if (licznikKlatek++ == 10 && platki.length < liczbaPlatkowMax)
{
	//dodaj nowy platek
	var nowyPlatek = new PlatekSniegu(Math.floor(Math.random()*szerokoscCanvas), 0);
	nowyPlatek.r = Math.floor(Math.random()*rozmiarPlatkaMax) + 10;
	//losuj liczbe od 1 do r, dzieki czemu platki beda roznic sie faza "obrotu"
	nowyPlatek.aktualneR = Math.floor(Math.random()*nowyPlatek.r) + 1;
	nowyPlatek.dlugosc = Math.floor(Math.random()*dlugoscProstegoToruMax) + 2;
	platki[platki.length] = nowyPlatek;
	licznikKlatek = 0;
...
}
...

W celu lepszego dopasowania rozmiarów płatków zwiększyłem również wartość zmiennej globalnej rozmiarPlatkaMax do 30.

Reakcja na zdarzenia użytkownika (klawiatura)

W grze zwykle gracz ma jakiś wpływ na to co się dzieje, u nas jednak może póki co kibicować opadającym płatkom :P. Dlatego też, aby zwiększyć jego możliwości interakcji, dodamy obsługę klawiatury. Koncepcja jest następująca: gracz kieruje śniegową kulą, która toczy się po dolnej krawędzi planszy. Za pomocą strzałek w lewo i w prawo może zmieniać jej kierunek. Aktualnie nie będzie to miało wpływu na płatki, ale będzie już pewną interakcją. Wiedząc co chcemy oprogramować, zabierzmy się do pisania :).

Dodajmy zmienną wcisnietyKlawisz do deklaracji na początku pliku. Będzie ona przechowywała kod ostatnio wciśniętego klawisza.


var wcisnietyKlawisz = 0;

Zdefiniujmy następnie "klasę" gracza. Potrzebna nam będzie tylko jedna jej instancja, więc możemy zrobić to w nieco odmienny sposób niż w przypadku płatków śniegu. Dodajmy zatem zaprezentowaną definicję zmiennej gracz do zmiennych globalnych.


var gracz = {
				x : 10,
				y : 10, 
				r : 5, 
				szybkosc : 5
			};

Powyższy kawałek kodu przypisuje zmiennej gracz obiekt o podanych polach x, y, r oraz szybkosc. Taka metoda powoduje, że nie możemy zdefiniować kolejnych obiektów klasy takiej jak zmienna gracz, a więc mamy do czynienia z tzw. singletonem. Nie jest to jednak dla nas przeszkodą, bo gracz jest jeden :). Jeśli chcecie poczytać więcej o definiowaniu klas w JS, polecam artykuł 3 ways to define a JavaScript class.

W pętli inicjalizującej oprogramujmy następnie reakcję na wciśnięcie klawisza:


function inicjalizujGre(glownyCanvas)
{
...
	/* jeśli element canvas jest obsługiwany */
	if (canvasElement.getContext)
	{
		...
		gracz.y = wysokoscCanvas - gracz.r;
		document.onkeydown = function(e)
								{
									var e = window.event || e;
									
									wcisnietyKlawisz = e.keyCode;
								};
		
		/* uruchom glowna petle gry */
		setInterval(glownaPetla, dlugoscKlatki);
	}
...
}

To wystarczy, aby w pętli głównej posiadać informację o klawiszu ostatnio naciśniętym przez użytkownika. Zdarzeń, które możemy obsłużyć jest dużo i mogą one dotyczyć nie tylko obsługi myszki i klawiatury :). Więcej o zdarzeniach w Java Script możecie poczytać na JavaScript Events (strona w języku angielskim), na Zdarzenia i ich obsługa - Kurs języka JavaScript (artykuł po polsku :)) lub użyć Google'a. Opisów jest sporo :).

Stwórzmy też nową funkcję implementującą reakcje na obsługiwane zdarzenia:


function ruchGracza()
{
if ((wcisnietyKlawisz == 37) && (gracz.x > gracz.szybkosc))
	{
		gracz.x -= gracz.szybkosc;
	}
	else if ((wcisnietyKlawisz == 39) && (gracz.x < (szerokoscCanvas - gracz.szybkosc)))
	{
		gracz.x += gracz.szybkosc;
	}
}

i dodajmy jej wywołanie w funkcji glownaPetla() (w pętli głównej). Warto zwrócić uwagę na liczby 37 i 39, które są kodami klawiszy ze strzałkami odpowiednio w lewo i w prawo.

Pozostało jeszcze wyświetlenie samej kuli, którą kieruje gracz. Wykorzystamy do tego kod analogiczny jak w poprzedniej części do rysowania śniegowych kulek.


function glownaPetla()
{
	...
	
	ruchGracza();

	for(var j = 0; i < platki.length; j++)
	{
		...
	}
	...
	//rysuj postac gracza
	context.fillStyle = "#fff";
	context.beginPath();
	context.arc(gracz.x, gracz.y, gracz.r, 0, 2*Math.PI, true);
	context.closePath();
	context.fill();
}

W ten sposób upodobniliśmy nasz zimowy przykład do czegoś co niedługo można będzie już szumnie nazwać grą :). W kolejnej części rozszerzymy interakcję gracza tak, aby nasze demo przypominało grę, która ma określony cel ;). Pobawimy się też dźwiękiem i innymi elementami na stronie. Następna część tutoriala wkrótce :).

Pozdrawiam,

Łukasz

P.S.

Jeśli macie wybrane przez siebie funkcjonalności, o których chcielibyście przeczytać w kolejnych częściach, przyślijcie wiadomość albo napiszcie komentarz :).

Przydatne linki

Przygotowując pierwsze dwie części minitutoriala korzystałem między innymi z podanych poniżej linków. Zawierają one więcej informacji o możliwościach elementu Canvas. Jeśli chcecie poszerzyć swoją wiedzę w tym temacie, mogą one stanowić dobre źródło informacji :). Canvas 2D API Specification 1.0 Drawing Graphics with Canvas Let’s Call It A Draw(ing Surface)

A artykule ponadto pojawiły się inne przydatne linki: JavaScript Events Zdarzenia i ich obsługa - Kurs języka JavaScript Artykuł o singletonie na WIkipedii 3 ways to define a JavaScript class

Szanowna Użytkowniczko! Szanowny Użytkowniku!
×
Aby dalej móc dostarczać coraz lepsze materiały redakcyjne i udostępniać coraz lepsze usługi, potrzebujemy zgody na dopasowanie treści marketingowych do Twojego zachowania. Twoje dane są u nas bezpieczne, a zgodę możesz wycofać w każdej chwili na podstronie polityka prywatności.

Kliknij "PRZECHODZĘ DO SERWISU" lub na symbol "X" w górnym rogu tej planszy, jeżeli zgadzasz się na przetwarzanie przez Wirtualną Polskę i naszych Zaufanych Partnerów Twoich danych osobowych, zbieranych w ramach korzystania przez Ciebie z usług, portali i serwisów internetowych Wirtualnej Polski (w tym danych zapisywanych w plikach cookies) w celach marketingowych realizowanych na zlecenie naszych Zaufanych Partnerów. Jeśli nie zgadzasz się na przetwarzanie Twoich danych osobowych skorzystaj z ustawień w polityce prywatności. Zgoda jest dobrowolna i możesz ją w dowolnym momencie wycofać zmieniając ustawienia w polityce prywatności (w której znajdziesz odpowiedzi na wszystkie pytania związane z przetwarzaniem Twoich danych osobowych).

Od 25 maja 2018 roku obowiązuje Rozporządzenie Parlamentu Europejskiego i Rady (UE) 2016/679 (określane jako "RODO"). W związku z tym chcielibyśmy poinformować o przetwarzaniu Twoich danych oraz zasadach, na jakich odbywa się to po dniu 25 maja 2018 roku.

Kto będzie administratorem Twoich danych?

Administratorami Twoich danych będzie Wirtualna Polska Media Spółka Akcyjna z siedzibą w Warszawie, oraz pozostałe spółki z grupy Wirtualna Polska, jak również nasi Zaufani Partnerzy, z którymi stale współpracujemy. Szczegółowe informacje dotyczące administratorów znajdują się w polityce prywatności.

O jakich danych mówimy?

Chodzi o dane osobowe, które są zbierane w ramach korzystania przez Ciebie z naszych usług, portali i serwisów internetowych udostępnianych przez Wirtualną Polskę, w tym zapisywanych w plikach cookies, które są instalowane na naszych stronach przez Wirtualną Polskę oraz naszych Zaufanych Partnerów.

Dlaczego chcemy przetwarzać Twoje dane?

Przetwarzamy je dostarczać coraz lepsze materiały redakcyjne, dopasować ich tematykę do Twoich zainteresowań, tworzyć portale i serwisy internetowe, z których będziesz korzystać z przyjemnością, zapewniać większe bezpieczeństwo usług, udoskonalać nasze usługi i maksymalnie dopasować je do Twoich zainteresowań, pokazywać reklamy dopasowane do Twoich potrzeb. Szczegółowe informacje dotyczące celów przetwarzania Twoich danych znajdują się w polityce prywatności.

Komu możemy przekazać dane?

Twoje dane możemy przekazywać podmiotom przetwarzającym je na nasze zlecenie oraz podmiotom uprawnionym do uzyskania danych na podstawie obowiązującego prawa – oczywiście tylko, gdy wystąpią z żądaniem w oparciu o stosowną podstawę prawną.

Jakie masz prawa w stosunku do Twoich danych?

Masz prawo żądania dostępu, sprostowania, usunięcia lub ograniczenia przetwarzania danych. Możesz wycofać zgodę na przetwarzanie, zgłosić sprzeciw oraz skorzystać z innych praw wymienionych szczegółowo w polityce prywatności.

Jakie są podstawy prawne przetwarzania Twoich danych?

Podstawą prawną przetwarzania Twoich danych w celu świadczenia usług jest niezbędność do wykonania umów o ich świadczenie (tymi umowami są zazwyczaj regulaminy). Podstawą prawną przetwarzania danych w celu pomiarów statystycznych i marketingu własnego administratorów jest tzw. uzasadniony interes administratora. Przetwarzanie Twoich danych w celach marketingowych realizowanych przez Wirtualną Polskę na zlecenie Zaufanych Partnerów i bezpośrednio przez Zaufanych Partnerów będzie odbywać się na podstawie Twojej dobrowolnej zgody.