Pojawiające się do tej pory w sierpniowej grze asteroidy (tak, tak te zielone kwadraty to kosmiczne głazy 🙂 ), przeszkadzają graczowi, ale ciężko nazwać je wrogami. Dlatego w dzisiejszej aktualizacji, do gry dodałem dwa rodzaje prawdziwych przeciwników.
Pierwszy z nich to wieżyczki, które z powierzchni planety próbują zestrzelić sterowany przez gracza statek. Drugim rodzajem przeciwnika są okręty przedstawicieli wrogiej bohaterowi frakcji, również wyposażone w broń dalekosiężną 😉 .
Aktualną wersję gry można przetestować 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.
Lwia część zmian w tej aktualizacji znajduje się w plikach enemyShip.js oraz turret.js. Jak łatwo się domyślić, zawierają one logikę, w której tworzę obiekty przeciwników.
Zacznę od omówienia zawartości drugiego pliku, czyli turret.js:
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 |
SpaceShooter.createTurrets = function(game){ turrets = game.add.group(); turrets.enableBody = true; turrets.createMultiple(5, 'turret'); turrets.lastTurret = 0 turrets.turretsEvery = 8200; turrets.bullets = game.add.group(); turrets.bullets.enableBody = true; turrets.bullets.createMultiple(15, 'enemyBullet'); turrets.spawn = function() { var turret = this.getFirstDead(); turret.frame = 0; if (!turret) { return; } turret.reset(game.world.width, game.world.height-90); turret.body.velocity.x = -90; turret.shootEvery = game.rnd.integerInRange(5000, 8000); turret.lastShoot = -500; turret.vulnerable = false; turret.checkWorldBounds = true; turret.outOfBoundsKill = true; } turrets.shoot = function(location){ var shtNum = game.rnd.integerInRange(2, 4); for (var i = 0; i < shtNum; i++) { var shot = this.bullets.getFirstDead(); if (!shot) { return; } var verticalspeed = game.rnd.integerInRange(-80, -180); var hotizonstalspeed = game.rnd.integerInRange(-150, 150); shot.anchor.setTo(0.5); shot.reset(location.x, location.y); shot.body.velocity.x = hotizonstalspeed; shot.body.velocity.y = verticalspeed; shot.checkWorldBounds = true; shot.outOfBoundsKill = true; } } turrets.update = function(){ if(game.currTime - this.lastTurret > this.turretsEvery){ if(game.rnd.integerInRange(0, 1)){ this.spawn(); } this.lastTurret = game.currTime; } this.forEachExists(function(item){ if(game.currTime - item.lastShoot > item.shootEvery){ this.shoot(item) item.vulnerable = true; item.frame = 1; game.time.events.add(2000, function(){ this.vulnerable = false; this.frame = 0; }, item); item.lastShoot = game.currTime; } },this) } return turrets; } |
Ta konstrukcja podąża za utartym już schematem, w którym do głównego modułu gry dodaję funkcję zwracającą nowy obiekt. Tym obiektem będzie turrets czyli wieżyczki pojawiające się na powierzchni planety, nad którą leci gracz. Podobnie jak w przypadku asteroidów, jest to phaserowa grupa.
Grupa zawiera pięć elementów i ma włączoną fizykę arcade. Mam tu też dwa pola: lastTurret oraz turretEvery, odpowiadające za częstotliwość pojawiania się nowych wieżyczek. Do tego, w polu bullets znajduje się kolejna grupa zawierająca obiekty pocisków, które wyrzucać będą wieżyczki.
Do tego turrets posiada trzy metody: spawn, shoot oraz update. Ta ostatnia wywoływana jest co każdy obieg game loopa. Najpierw sprawdza czy już czas na nową wieżyczkę. Jeżeli tak, szansa, że się pojawi to 50%. Chciałem aby przeciwnicy nie pojawiali się tak regularnie jak asteroidy, stąd element losowości.
Następnie dla każdego aktywnego elementu grupy, sprawdzam czy już czas na to aby wystrzelił pociski. Jeżeli tak, wywołuję metodę shoot, zmieniam klatkę grafiki obiektu, oraz ustawiam wartość jego pola vulnerable na true. Na koniec za pomocą phaserowego obiektu time, dodaję do gry timeout trwający 2 sekundy. Po ich upłynięciu, grafika obiektu wraca do normy, a pole vulnerable, zmieniane jest na false. Dzięki temu mechanizmowi, wieżyczka jest wrażliwa na strzały tylko 2 sekundy po wyrzuceniu z siebie pocisków. Po tym czasie „zamyka się”.
Kolejna metoda to spawn. Odpowiada ona za pojawienie się kolejnych wieżyczek w obszarze gry. Schemat działania tego mechanizmu powinien już być znajomy. Najpierw pobieram pierwszy nieaktywny element grupy, następnie ustawiam jego położenie na świecie (wszystkie wieżyczki pojawiają się w tym samym miejscu, czyli na dole planszy w jej prawym rogu) oraz prędkość poruszania się. Odstęp czasu strzału jest losowy dla każdej wieżyczki. Początkowo wszystkie mają ustawione pole vulnerable na false. Jeżeli opuszczą obszar gry, są deaktywowane.
Ostatnia metoda to shoot. Jest ona wywoływana gdy jedna z wieżyczek ma oddać strzał. Ta metoda jako jedyna przyjmuje argument. Zawiera on lokację oddającego strzał elementu. Ilość wyrzuconych pocisków jest losowa. Dla każdego z nich z osobna pobieram nieaktywny element grupy bullets oraz ustawiam losową prędkość poziomą oraz horyzontalną. Wszystko to dzieje się wewnątrz prostej pętli for. Każdy pocisk jest deaktywowany po opuszczeniu obszaru gry.
Plik enemyShip.js, zawiera podobną logikę. Służy dodawaniu do gry wrogich statków:
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 70 71 |
SpaceShooter.createEnemyShips = function(game){ enemyShips = game.add.group(); enemyShips.enableBody = true; enemyShips.createMultiple(3, 'enemyShip'); enemyShips.lastShip = 0; enemyShips.shipEvery = 5200; enemyShips.bullets = game.add.group(); enemyShips.bullets.enableBody = true; enemyShips.bullets.createMultiple(15, 'enemyBullet'); enemyShips.spawn = function() { var ship = this.getFirstDead(); if (!ship) { return; } var verticalPos = game.rnd.integerInRange(50, game.world.height - 100); ship.reset(game.world.width, verticalPos); ship.anchor.setTo(0.5); ship.body.velocity.x = -150; ship.shootEvery = game.rnd.integerInRange(5000, 8000); ship.lastShoot = -500; ship.HP = 3; ship.vulnerable = true; ship.hit = function(){ this.tint = 0xff0000; this.vulnerable = false; game.time.events.add(500, function(){ this.vulnerable = true; this.tint = 0xFFFFFF; }, this); } } enemyShips.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; shot.checkWorldBounds = true; shot.outOfBoundsKill = true; } enemyShips.update = function(){ if(game.currTime - this.lastShip > this.shipEvery){ 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.vulnerable = true; item.lastShoot = game.currTime; } if(item.x <= 750 && item.body.velocity.x != 0) { console.log("stop") item.body.velocity.x = 0; } if(item.HP <= 0) { item.kill(); } },this) } return enemyShips; } |
Schemat wygląda bardzo podobnie jak w przypadku wieżyczek. Pierwsza partia kodu jest niemal identyczna. Zmieniam jedynie wartości liczbowe. Statków będzie mniej niż wieżyczek i będą one rzadziej strzelały.
Zwracany obiekt także posiada metody update, shoot oraz spawn. Wewnątrz spawn również stosuje losowe podejście do częstotliwości pojawiania się obiektów. Losowa jest również pozycja wrogiego statku na osi y. Na osi x, zawsze pojawiają się przy prawym krańcu ekranu. Do tego, każdy indywidualny obiekt, posiada pole HP, którego wartość oznacza ilość trafień, którą może wytrzymać statek. Ponad to nowotworzone statki otrzymują metodę hit, która obsługuje zachowanie statku po tym jak zostanie on trafiony. Staje się wtedy na pół sekundy czerwony oraz niewrażliwy na kolejne pociski.
Metoda shoot. Jest w tym wypadku bardzo prosta. Po prostu co określoną ilość czasu, wrogi statek wyrzuca z siebie pocisk, który leci prosto ku lewej krawędzi obszaru gry. Gdy osiągnie kranie obszaru gry, jest usuwany.
Ostatnia metoda, upadte, najpierw sprawdza czy może dodać do gry nowego wroga. I w tym wypadku, nawet jeżeli jest już na to pora, istnieje 50% szans, że nowy obiekt zostanie dodany. Następnie dla każdego istniejącego elementu z grupy wrogich statków, testuję trzy warunki. Najpierw, czy nie pora na wystrzelenie nowego pocisku. Jeżeli tak, wywoływana jest metoda shoot. Kolejne sprawdzenie dotyczy położenia obiektu. Wrogie statki przelatują tylko małą odległość od prawej krawędzi a następnie trzymają dystans, z którego mogą ostrzeliwać gracza. Jeżeli obiekt przekroczył daną wartość x, jego prędkość na tej osi jest zerowana. Na końcu, sprawdzane jest czy wartość HP aktualnego elementu nie jest mniejsza lub równa zero. W takim wypadku, obiekt usuwany jest z gry.
Tak przygotowane funkcje do tworzenia przeciwników, wykorzystuje w głównym stanie gry:
1 2 |
this.turrets = SpaceShooter.createTurrets(game); this.enemyShips = SpaceShooter.createEnemyShips(game); |
Te dwie linijki umieszczam wewnątrz metody create, zaraz przed sprawdzaniem rodzaju urządzenia na którym uruchomiona jest gra.
Następnie w metodzie update, tego samego stanu dodaję wykrycie kolizji pomiędzy pociskami gracza a nowymi grupami:
1 2 |
game.physics.arcade.overlap(this.player.bullets, this.turrets, SpaceShooter.turretHit, null, this); game.physics.arcade.overlap(this.player.bullets, this.enemyShips, SpaceShooter.enemyShipHit, null, this); |
W przypadku kolizji pocisków gracza z wieżyczkami lub wrogimi statkami, wywoływane są kolejno metody: turretHit oraz enemyShipHit. Są one przypisane bezpośrednio do głównego modułu gry, a ich treść znajduje się w pliku helpers.js. Oto ona:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
SpaceShooter.turretHit = function(bullet,turret){ bullet.kill(); if(turret.vulnerable){ turret.kill(); } } SpaceShooter.enemyShipHit = function(bullet,enemy){ bullet.kill(); if(enemy.vulnerable){ enemy.HP -= 1; enemy.hit(); } } |
Obie metody deaktywują pocisk gracza. W wypadku metody turretHit (kolizji z wieżyczką), najpierw sprawdzam, czy cel jest akurat „otwarty” (vulnerable równe true). Wieżyczka jest deaktyowowana tylko w takim wypadku. Kolizja z wrogim statkiem (metoda enemyShipHit), również przynosi efekty tylko wtedy gdy przeciwnik jest wrażliwy na strzały. W takim wypadku wartość jego pola HP obniżana jest o jeden i wywoływana jest jego metoda hit.
Tak w skrócie działają elementy dzisiejszej aktualizacji. Kolejne kroki to dodanie „powerUp’ów” dla gracza oraz kolizji pomiędzy jego statkiem a resztą świata. Ale to już temat na kolejne updatey.
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.