luq techblog

o tworzeniu słów kilka…

SQL, JSON i Ajax… czyli część 3. tworzenia mapy pod grę 7 lipca 2010

Filed under: Ajax,GameDev,GameMap,JS,Programowanie,SQL — Łukasz @ 17:55
Tags: , ,

Dość długo zwlekałem z tym wpisem… szczerze mówiąc, bardzo dużo czasu i sił kosztuje mnie opisywanie mojego, hm… projektu, bo tak to można nazwać. Tak naprawdę więcej czasu spędziłem nad pisaniem poprzednich dwóch wpisów niż nad pisaniem kodu do nich. Sam nie jestem fanem tutoriali programistycznych i od dziś nie będę pisał na ten temat rozwodząc się nad moją każdą decyzją podjętą w czas projektowania tego wszystkiego. Będę zwracał uwagę na elementy ciekawe pomijając te błahe.

 

Okej, ostatnio zakończyliśmy na GUI pod edytor map, dziś dopiszemy trochę kodu aby nasz edytor zapisywać dane dt. mapy do bazy a także odczytywał stworzoną mapę po przeładowaniu strony tak, aby można było kontynuować prace nad całym naszym światem. Dodatkowo stworzymy kod który umożliwi nam w grze przesuwanie mapy. Szkoda czasu czas zaczynać.

 

Baza

Oczywiście potrzebna nam jest baza danych i informacjami nt. mapy. Stworzyłem 2 tabelki:

 

game_map_sprite
spriteId | posX | posY | isWalkable | memo

game_map_field
fieldId | spriteId | mapPosX | mapPosY

 

Pierwsza zawiera informacje o kafelkach. posX i posY to dane w którym miejscu leży dana kafelka na obrazku wszystkich spritów, czyli zgodnie z obrazkiem:

All map sprites

isWalkable mówi czy wolno chodzić po tym polu, memo w sumie nie jest używane ale zawiera opis obrazka. Wszystkie moje wpisy w tej tabelce prezentują się w ten sposób (dodałem 2 nowe sprity: wolf i mashroom):

(1, 0, 0, 0, 'tree_top_left'),
(2, 1, 0, 0, 'tree_top_right'),
(3, 0, 1, 0, 'tree_bottom_left'),
(4, 1, 1, 0, 'tree_bottom_right'),
(5, 2, 0, 1, 'extra_grass'),
(6, 2, 1, 0, 'bush'),
(7, 3, 0, 1, 'grass'),
(10, 4, 0, 0, 'mashroom'),
(9, 3, 1, 0, 'wolf');

W drugiej tabelce, której zawartość będzie generowana przez edytor map, są informacje jaki sprit leży na jakiej pozycji x,y na mapie. No i to by było na temat struktury bazy.

Ajax action

Teraz przyszła pora na napisanie akcji jaka będzie odbywać się po wypełnieniu komórki mapy w edytorze map. Oczywiście ta akcja będzie wywoływana przez asynchroniczne zapytanie co będzie sprawiało że nasz edytor będzie aplikacją typu RIA (Rich Internet Aplication). Wrące tylko, że bardzo dobrą praktyką jest tworzenie w jednym pliku wszystkich akcji ajaxowych, dzięki temu łatwiej to zabezpieczyć i zarządzać, np. osobny kontroler i poszczególne akcje w modelu MVC.

/ajax/setFoo/foo
/ajax/getFoo  

ja nie będę w taki sposób pisać bo mnie chodzi o przedstawienie problemu i jego rozwiązania a stosując tutaj jakiś framework pracujący zgodnie z wzorcem MVC kodu byłoby więcej, poza tym nie każdy znałby danego framerowka… Stworzyłem plik ajaxAction.php:

require_once './class/DataBase.class.php';
	
	$action = $_GET['act']; 
	
	switch( $action ){
		case 'set_field':
			$sId	= intval( $_GET['sid'] );
			$x 		= intval( $_GET['x'] );
			$y 		= intval( $_GET['y'] );
			
			$db = new DataBase();
			
			$select = $db->select(
				array( 'fieldId' ),
				'game_map_field',
				"WHERE mapPosX = $x AND mapPosY = $y"
			);
			$fieldId = $select[0]['fieldId'];
			
			if( !$fieldId ){
				$db->insert(
					'game_map_field',
					array(
						'spriteId'	=> $sId,
						'mapPosX'	=> $x,
						'mapPosY'	=> $y
					)
				);
			}
			else{
				$db->update(
					'game_map_field',
					array(
						'spriteId'	=> $sId,
						'mapPosX'	=> $x,
						'mapPosY'	=> $y
					),
					"fieldId = $fieldId"
				);
			}
		break;
}

Dołączona plik DataBase.class.php to nic innego jak klasa zarządzająca bazą, prosty DataBaseHandler umożliwiający w prosty sposób wykonywać zapytania SQL`owe. Nie będę tego opisywać. Skrypt jest prościutki. Wpisując w pasek adresu:

.../ajaxAction.php?act=set_field&x=0&y=0&sId=6

Dodajemy (lub edytujemy jeśli już istnieje wpis o takim x,y) do tabelki game_map_field rekord:

NULL, 6, 0, 0

Co mówi, że na pozycji (x;y) = (0;0) będzie znajdował się obrazek krzaczka (sprit o spriteId == 6).

Ajax request

Teraz wystarczy w odpowiednim miejscu wstawić asynchroniczne wysłanie zapytania HTTP w naszym edytorze map. Dokładniej rzecz biorąc wstawimy to w miejsce komentarza:

// ajax query
// not jet...

z ostatniego listeningu ostatniego wpsiu. Cała funkcja wywołana zdarzeniem click na komórce mapy prezentuje się następująco:

$('td').click(function(){
	if( actualField ){
		// set image in HTML table 
		var xSprite, ySprite;
		
		xSprite = actualFieldX * -CORE_SIZE_FIELD;
		ySprite = actualFieldY * -CORE_SIZE_FIELD;

		$(this).css( 'background-image', 'url(../gfx/my_map.png)' );
		$(this).css( 'background-position', xSprite+'px '+ySprite+'px' );
		$(this).text('');
			

// new code
		// ajax query
		$.get( 
			'../ajaxAction.php', 
			{
				act		: 'set_field',
				sid		: actualField, // id wybranego sprita
				x		: $(this).attr( 'x' ), // pozycja X na mapie
				y		: $(this).attr( 'y' ) // pozycja Y na mapie
			}
		);
	}
// ~new code end
});

Jedna funkcja w jQuery i już, gotowe ;)

Tworzenia JSON`a z całej mapy

Teraz jeszcze potrzebne nam jest aby po załadowaniu edytora, komórki przedstawiające pola mapy zostały wypełnione. Powinno być to rozwiązane po stronie PHP ale tymczasowo jest to zrobione tak samo w technologii Ajax, zresztą i tak potrzebne na głównej stronie jest pobieranie JSON`a z mapy (ostatecznie będą to kawałki z mapy a nie cała mapa). W ajaxAction.php mam jeszcze jedną akcję:

(...)
	case 'get_all_map':
		$db = new DataBase();
			
		$fields = $db->select(
			array( 'm.spriteId', 'm.mapPosX', 'm.mapPosY', 's.posX', 's.posY' ),
			'game_map_field AS m LEFT JOIN game_map_sprite AS s ON m.spriteId = s.spriteId',
			'ORDER BY mapPosX, mapPosY'
		);
			
		$map = array();
		foreach( $fields as $k => $v ){
			$map[ $v['mapPosY'] ][ $v['mapPosX'] ] = new JsonField( $v['posX'], $v['posY'] ); 
		}
		echo json_encode( $map );
	break;
}
	
class JsonField
{
	public $x;
	public $y;
		
	public function __construct( $x, $y ){
		$this->x = $x;
		$this->y = $y;
	}
}

Pobieramy wszystkie rekordy dotyczące pól mapy (wiążemy obie tabelki relacją), następnie tworzymy na tej podstawie JSON`a o strukturze:

[y_mapy][x_mapy] = { 
    x: x_sprita_na_obrazku, 
    y: y_sprita_na_obrazku 
}

Oczywiście aby funkcja json_encode() stworzyła nam w 2-wymiarowej tablicy obiekt z właściwościamy x oraz y, musimy posiadać klasę o takich właściwościach z dostępem publicznym, do tego właśnie służy klasa JsonField. Teraz wpisując w pasek adresu:

ajaxAction.php?act=get_all_map

Odbieramy coś takiego:

[
	[
		{"x":"0","y":"1"},
		{"x":"1","y":"1"},
		{"x":"0","y":"1"},
		{"x":"1","y":"1"},
		{"x":"0","y":"1"},
		{"x":"1","y":"1"},
		{"x":"2","y":"1"},
		{"x":"0","y":"0"},
		{"x":"1","y":"0"},
		{"x":"0","y":"1"},
		{"x":"1","y":"1"},
		{"x":"0","y":"0"},
		{"x":"1","y":"0"},
		{"x":"0","y":"0"},
		{"x":"1","y":"0"},
		{"x":"0","y":"1"}
	],
	[
		{"x":"1","y":"0"},
		(...)
	]
	(...)
]

Teraz to załadujemy zaraz po utworzeniu drzewka DOM edytora:

$.get( 
	'../ajaxAction.php', 
	{ act: 'get_all_map' },
	function( data ){
		var JSONmap = JSON.parse( data );
		var x, y;
		var xSprite, ySprite;
			
		$('td').each(function( index ){
			y = $(this).parent().find('th').html();
			x = index - ( y * 15 /* 15 is cnt columns */ ) - y;
			$(this).html(
				x+','+y
			);
		
			$(this).attr( 'x', x );
			$(this).attr( 'y', y );
				
			if( JSONmap[y][x] ){
				xSprite = JSONmap[y][x].x * -CORE_SIZE_FIELD;
				ySprite = JSONmap[y][x].y * -CORE_SIZE_FIELD;
		
				$(this).css( 'background-image', 'url(../gfx/my_map.png)' );
				$(this).css( 'background-position', xSprite+'px '+ySprite+'px' );
					
				$(this).html( '');
			}
		});
	}
);

kod ten zastąpił ten oto wycinek:

var x, y;
$('td').each(function( index ){
	y = $(this).parent().find('th').html();
	x = index - ( y * 10 ) - y;
	$(this).html(
		x+','+y
	);
});

Dzięki temu:

  • 1 wykonujemy asynchroniczne zapytanie o JSON`a z mapą
  • 2 jeśli odbierzemy odpowiedź to:
    • 2.1 parsujemy wynik do JSON`a
    • 2.2 liczymy x i y każdego pola mapy i wpisujemy te dane do komórki (to już mieliśmy), przypisujemy te dane do atrybutów x i y
    • 2.3 jeśli istnieje w naszym JSON`ie wpis mówiący o spricie w danym polu to:
      • 2.3.1 wypełnij go odpowiednim spritem i skasuj z niego tekst.

Na dziś to tyle. Cały edytor został zbudowany na szybko i wiele rzeczy trzeba podrasować, idea będzie niezmieniona ale wiele należałoby zmienić, szablon generowany przez PHP, pobieranie zakresu mapy a nie jej całej, możliwość przesuwania osi x i y (wtedy zostanie pobrany nowy zakres), pobieranie spritów z bazy… Jednak nie będzie to już tutaj opisywane, chodziło mi o przedstawienie właśnie idei a nie gotowego sposobu.

 

Chciałem jeszcze dziś opisać system przesuwania mapy, który jest już stworzony ale wpis rozrósł by się do ogromnych rozmiarów a więc zrobię to innym razem. Tymczasem można się bawić edytorem i mapą

Reklamy
 

2 Responses to “SQL, JSON i Ajax… czyli część 3. tworzenia mapy pod grę”

  1. Marcin Says:

    Witam !
    Czy jest możliwość pobrania tej całej mapy ?

  2. luq Says:

    Hej,

    już dawno miałem wrzucić paczkę z pełnym kodem ale jak widać po wpisach brak czasu daje się we znaki. Postaram się to zrobić jak najprędzej, poinformuję o tym w nowym wpisie ;)

    Pozdrawiam


Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Zdjęcie na Google+

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s