Pora na kolejną aktualizację mojej sierpniowej „gry co miesiąc”. Tym razem dodałem dwa nowe elementy, jeden duży i jeden mały 🙂 .
Pierwszy nowy element to kolejny wróg, który porusza się po generowanych losowo ścieżkach. Za każdym razem ścieżka ta jest inna! Drugi element to licznik, dzięki któremu gra może „dozować” przeciwników. Bardziej zaawansowani wrogowie pojawiać się będą dopiero po upłynięciu określonego czasu.
Aktualną wersję gry przetestować można klikając w obrazek powyżej. Na moim githubie znajduje się repozytorium zawierające jak najaktualniejszy stan gry. Wersja przedstawiona w poście dostępna jest po zajrzeniu w źródło strony gry.
Zacznę od mniejszej zmiany. Do głównego stanu gry (plik main.js) w metodzie create, dodałem ten krótki fragment kodu:
1 2 3 4 5 6 7 |
game.time.events.loop(Phaser.Timer.SECOND, function(){ if(this.player.alive){ this.turrets.meters += 1; this.enemyShips.meters += 1; this.fEnemyShips.meters += 1; } }, this) |
Kod ten sprawia, że co sekundę wartość pola meters, w obiektach wrogów zwiększy się o jeden. W ten sposób obliczam odległość jaką przeleciał statek gracza. Na początku na swojej drodze napotykać on będzie tylko asteroidy, ale im dalej doleci tym więcej rodzajów wrogów będzie się pojawiać.
Każdy obiekt przeciwnika, ma swoje pole z informacją o ilości pokonanych przez gracza metrów. Dzięki temu nie mam problemu aby dopisać logikę blokującą pojawianie się przeciwnika, zanim gracz nie pokona odpowiedniej odległości.
Wystarczyło zmienić metodę update wrogów. Tak wygląda ta metoda w obiekcie wieżyczki (plik turret.js):
1 2 3 4 5 6 |
/* kod metody */ if(game.currTime - this.lastTurret > this.turretsEvery && this.meters >= 60){ /* kod metody */ this.spawn(); /* kod metody */ } |
Po prostu, jeżeli meters, nie będzie miało odpowiedniej wartości, nie zostanie wywołana metoda spawn.
Taki mechanizm dodałem do trzech obiektów wrogów nie będących asteroidami. Dwóch z nich już znamy. Trzeci przeciwnik jest nowy. Otrzymał on wymowną nazwę flyingEnemy, ponieważ w odróżnieniu do istniejącego już w grze wrogiego statku, porusza się po planszy.
Kod z logiką nowego przeciwnika znajduje się w pliku flyingEnemy.js. Schemat jest bardzo podobny, plik zawiera metodę, przygotowującą obiekt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
SpaceShooter.createFlyingEnemyShips = function(game, powerUp){ fEnemyShips = game.add.group(); fEnemyShips.enableBody = true; fEnemyShips.createMultiple(4, 'fEnemyShip'); fEnemyShips.lastShip = 0 fEnemyShips.meters = 0 fEnemyShips.shipEvery = 3200; fEnemyShips.bullets = game.add.group(); fEnemyShips.bullets.enableBody = true; fEnemyShips.bullets.createMultiple(15, 'enemyBullet'); fEnemyShips.spawn = function() { var ship = this.getFirstDead(); if (!ship) {return;} ship.path = []; var verticalPos = game.rnd.integerInRange(50, game.world.height - 100); var points = { 'x': [game.world.width, 608, 512, 384, 256, 128, 32, -64 ], 'y': [verticalPos,] }; for (var i = 0; i < points.x.length-1; i++) { points.y.push(game.rnd.integerInRange(100,500)); } var x = 1 / game.width; for (var i = 0; i <= 1; i += x){ var px = game.math.catmullRomInterpolation(points.x, i); var py = game.math.catmullRomInterpolation(points.y, i); ship.path.push( { x: px, y: py }); } ship.pos = 0; ship.reset(game.world.width, verticalPos); ship.anchor.setTo(0.5); ship.shootEvery = game.rnd.integerInRange(3000, 5000); ship.lastShoot = -500; ship.powerUps = powerUp; } } fEnemyShips.shoot = function(location){ var shot = this.bullets.getFirstDead(); if (!shot) {return;} shot.anchor.setTo(0.5); shot.reset(location.x, location.y); shot.body.velocity.x = -200; shot.body.velocity.y = 0; } fEnemyShips.update = function(){ if(game.currTime - this.lastShip > this.shipEvery && this.meters >= 30){ if(game.rnd.integerInRange(0, 1)){ this.spawn(); } this.lastShip = game.currTime; } this.forEachExists(function(item){ if(game.currTime - item.lastShoot > item.shootEvery){ this.shoot(item) item.lastShoot = game.currTime; } item.x = item.path[item.pos].x; item.y = item.path[item.pos].y; item.pos++; if (item.pos >= item.path.length) { item.kill(); } },this) } return fEnemyShips; } |
Większość tego kodu powinna wyglądać znajomo. Funkcja createFlyingEnemyShips tworzy phaserową grupę, którą na końcu zwraca. Po drodze dodaję logikę to przedstawicieli grupy. W odróżnieniu od pozostałych przeciwników, tym razem na obiektach nie deklaruję ani poziomej ani pionowej prędkości (velocity). Ten wróg porusza się na trochę innych zasadach.
Najważniejsze elementy to pola path oraz pos. Omówię to w dużym uproszczeniu, a to dlatego, że sam nie do końca orientuje się jak działają bebechy tego co tu zaimplementowałem. Wiem tylko, że to sporo matematycznej magii 🙂 .
Chodzi tu o to, że pole path jest tablicą obiektów zawierających współrzędne. Obiektów tych jest naprawdę sporo. Na tyle dużo, że statek wroga, co każdy cykl gry (czyli ~60 razy na sekundę), przyjmuje położenie z kolejnego obiektu, a ludzkie oko widzi to jako płynny ruch 🙂 . Aby to osiągnąć używam pola pos, które zawiera po prostu indeks obiektu przechowującego aktualnie wykorzystywane przez statek wroga współrzędne.
Ale skąd bierze się zawartość tablicy path, przecież nie wpisuję jej ręcznie 🙂 . Cała magia dzieje się na początku metody spawn. W niej znajduje się obiekt points, który zawiera dwie tablice: x oraz y. Pierwsza tablica zawiera 8 elementów, których wartości są takie same. Druga tablica otrzymuje losowe wartości z przedziału od 100 do 500.
A oto kod, który sprawia, że dzieje się magia:
1 2 3 4 5 6 |
var x = 1 / game.width; for (var i = 0; i <= 1; i += x){ var px = game.math.catmullRomInterpolation(points.x, i); var py = game.math.catmullRomInterpolation(points.y, i); ship.path.push( { x: px, y: py }); } |
Tak jak pisałem, nie będę tłumaczył dokładnie co tu się dzieje, bo nie jestem pewny tego jak działają te metody. Wiem tylko, że w ich wyniku, do tablicy push trafia 1 dzielone na szerokość gry, obiektów ze współrzędnymi. Gdy ułożyć punkty opisane tymi współrzędnymi na obszarze gry, powstanie ścieżka, którą podążać będzie przeciwnik. Najlepsze jest to, że ścieżka ta podąża pomiędzy punktami opisanymi w points. A ponieważ points są losowe dla każdego wygenerowanego wroga, każdy z tego typu wrogów poruszać będzie się trochę inaczej.
Najlepiej zaobserwować to w grze. W moim przypadku wrogowie będą poruszać się po fali, ale nic nie stoi na przeszkodzie, aby poukładać punkty bardziej fantazyjnie, na przykład tak aby obiekty robiły pętelki :).
To tyle na dziś! Jeżeli chcesz być na bieżąco z postami na blogu zachęcam do polubienia mojej strony na facebooku. Zawsze zamieszczam tam informacje o wszystkich nowościach. Jest to też dobre miejsce na kontakt ze mną. Na wszystkie pytania zawsze odpowiem :). Do przeczytania.