Kuidas luua brauseris lihtne mäng Phaser 3 ja TypeScripti abil

Foto Phil Botha saidil Unsplash

Olen arendaja propageerija ja taustaprogrammi arendaja ning minu esiplaanide arendusteadmised on suhteliselt nõrgad. Mõni aeg tagasi tahtsin lõbutseda ja brauseris mängu teha; Valisin raamistikuks Phaser 3 (see tundub tänapäeval üsna populaarne) ja keeleks TypeScripti (kuna eelistan staatilist tippimist dünaamilise asemel). Selgus, et selle kõige toimimiseks peate tegema igavaid asju, nii et kirjutasin selle õpetuse, et aidata teistel minusugustel inimestel kiiremini alustada.

Keskkonna ettevalmistamine

IDE

Valige oma arengukeskkond. Võite soovi korral alati kasutada lihtsat vana märkmikku, kuid soovitaksin kasutada midagi kasulikumat. Minu jaoks eelistan lemmikloomaprojektide arendamist Emacsis, seetõttu olen paigaldanud loode ja järginud selle seadistamise juhiseid.

Sõlm

Kui arendaksime JavaScripti, oleks meil täiesti hea alustada kodeerimist ilma kõigi nende ettevalmistamisetappideta. Kuna tahame kasutada TypeScripti, peame siiski seadistama taristu, et edaspidine areng oleks võimalikult kiire. Seega peame installima sõlme ja npm.

Selle õpetuse kirjutamisel kasutan sõlme 10.13.0 ja npm 6.4.1. Pange tähele, et kasutajaliidese maailma versioonid värskendatakse ülikiirelt, nii et võtate lihtsalt uusimad stabiilsed versioonid. Soovitan tungivalt kasutada sõlme ja npm käsitsi installimise asemel nvm; see säästab palju aega ja närve.

Projekti seadistamine

Projekti ülesehitus

Projekti ehitamiseks kasutame npm, nii et projekti käivitamiseks minge tühja kausta ja käivitage npm init. npm küsib teie projekti omaduste kohta mitu küsimust ja loob seejärel faili package.json. See näeb välja umbes selline:

{
  "nimi": "tähelangus",
  "versioon": "0.1.0",
  "description": "Starfall mäng (Phaser 3 + TypeScript)",
  "main": "index.js",
  "skriptid": {
    "test": "echo \" Viga: testi pole täpsustatud \ "&& exit 1"
  },
  "autor": "Mariya Davydova",
  "litsents": "MIT"
}

Paketid

Installige vajalikud paketid järgmise käsuga:

npm install -D masinakirjas veebipakett webpack-cli ts-loader faaseri live-server

-D suvand (a.k.a.-save-dev) paneb npm need paketid automaatselt pakettijs.json sõltuvuste loendisse lisama:

"devDependencies": {
   "reaalajas server": "^ 1.2.1",
   "phaser": "^ 3.15.1",
   "ts-loader": "^ 5.3.0",
   "masinakiri": "^ 3.1.6",
   "webpack": "^ 4.26.0",
   "webpack-cli": "^ 3.1.2"
 }

Veebipakett

Webpack käivitab TypeScripti kompilaatori ja kogub nii saadud JS-failide kui ka teekide hunniku ühte minimeeritud JS-i, et saaksime selle oma lehele lisada.

Lisage oma projekt.jsoni lähedusse veebpack.config.js:

const path = nõuda ('tee');
moodul.exports = {
  kanne: './src/app.ts',
  moodul: {
    reeglid: [
      {
        test: /\.tsx?$/,
        kasutamine: 'ts-loader',
        välista: / node_modules /
      }
    ]
  },
  lahenda: {
    laiendid: ['.ts', '.tsx', '.js']
  },
  väljund: {
    failinimi: 'app.js',
    tee: tee.resolve (__ dirname, 'dist')
  },
  režiim: 'areng'
};

Siin näeme, et veebipakk peab hankima allikad alates src / app.ts (mille me lisame üsna kiiresti) ja koguma kõik faili dist / app.js.

TypeScript

Samuti vajame TypeScripti kompilaatori jaoks väikest konfiguratsioonifaili (tsconfig.json), kus selgitame, millisesse JS-i versiooni me allikad kompileerime ja kust neid allikaid leida:

{
  "compilerOptions": {
    "sihtmärk": "es5"
  },
  "hõlmama": [
    "src / *"
  ]
}

TypeScripti määratlused

TypeScript on staatiliselt trükitud keel. Seetõttu nõuab see kompileerimise tüübi määratlusi. Selle õpetuse kirjutamise ajal polnud Phaser 3 definitsioonid veel npm-paketina saadaval, nii et peate võib-olla need ametlikust hoidlast alla laadima ja faili oma projekti src-alamkataloogi panema.

Skriptid

Oleme projekti ettevalmistamise peaaegu lõpetanud. Praegu peaksite olema loonud package.json, webpack.config.js ja tsconfig.json ning lisanud src / phaser.d.ts. Viimane asi, mida peame tegema enne koodi kirjutamist, on selgitada, mis täpselt npm projektiga pistmist on. Uuendame paketi.jsoni skriptide jaotist järgmiselt:

"skriptid": {
  "build": "webpack",
  "start": "webpack - vaadata ja live-server --port = 8085"
}

Kui teostate npm buildi, ehitatakse fail app.js vastavalt veebipaketi konfiguratsioonile. Ja kui käivitate npm starti, ei pea te enam ehitamisprotsessis vaeva nägema: niipea, kui olete mis tahes allika salvestanud, ehitab Webpack selle rakenduse uuesti üles ja reaalajas server laadib selle uuesti teie vaikebrauserisse. Rakendust hostitakse aadressil http://127.0.0.1:8085/.

Alustamine

Nüüd, kui oleme infrastruktuuri üles seadnud (see osa, mida ma projekti alustamisel isiklikult vihkan), saame lõpuks hakata kodeerimist. Selles etapis teeme lihtsat asja: joonistame oma brauseriaknasse tumesinise ristküliku. Suure mängu arendamise raamistiku kasutamine selleks on natuke… hmmm… ülepaisutamine. Siiski vajame seda järgmistel etappidel.

Selgitan lühidalt Phaser 3 põhimõisteid. Mäng on klassi Phaser.Game (või selle järeltulija) näide. Iga mäng sisaldab ühte või mitut Phaser.Scene järeltulija juhtumit. Iga stseen sisaldab mitmeid objekte, kas staatilisi või dünaamilisi, ning tähistab mängu loogilist osa. Näiteks on meie triviaalses mängus kolm stseeni: tervituskuva, mäng ise ja skoori ekraan.

Alustame kodeerimist.

Esiteks looge mängu jaoks minimalistlik HTML-i konteiner. Tehke index.html fail, mis sisaldab järgmist koodi:



  
     Tähtkuju 
     
  
  
    
  

Siin on ainult kaks olulist osa: esimene on skriptikanne, mis ütleb, et hakkame siin kasutama meie ehitatud faili, ja teine ​​on div-kirje, mis on mängu konteiner.

Nüüd looge fail src / app.ts järgmise koodiga:

import "phaser";
const config: GameConfig = {
  pealkiri: "Starfall",
  laius: 800,
  kõrgus: 600,
  vanem: "mäng"
  backgroundColor: "# 18216D"
};
ekspordiklass StarfallGame laiendab Phaser.Game {
  konstruktor (konfiguratsioon: GameConfig) {
    super (konfiguratsioon);
  }
}
window.onload = () => {
  var mäng = uus StarfallGame (konfiguratsioon);
};

See kood on iseenesestmõistetav. GameConfigil on palju erinevaid atribuute, saate neid siit vaadata.

Ja nüüd saate lõpuks käivitada npm starti. Kui selle ja eelmiste toimingutega tehti kõik õigesti, peaksite oma brauseris nägema midagi nii lihtsat:

Jah, see on sinine ekraan.

Tähtede langemine

Oleme loonud elementaarse rakenduse. Nüüd on aeg lisada stseen, kus midagi juhtub. Meie mäng saab olema lihtne: tähed kukuvad maapinnale ja eesmärk on püüda võimalikult palju.

Selle eesmärgi saavutamiseks looge uus fail gameScene.ts ja lisage järgmine kood:

import "phaser";
ekspordiklass GameScene laiendab Phaser.Scene {
ehitaja () {
    Super({
      võti: "GameScene"
    });
  }
init (params): tühine {
    // TEGEMA
  }
eellaadimine (): tühine {
    // TEGEMA
  }
  
  luua (): tühine {
    // TEGEMA
  }
värskendus (kellaaeg): tühine {
    // TEGEMA
  }
};

Konstruktor sisaldab siin klahvi, mille all teised stseenid võivad seda stseeni kutsuda.

Näete siin nelja meetodi tüvesid. Selgitan lühidalt nende vahelist erinevust:

  • init ([params]) kutsutakse stseeni käivitumisel; see funktsioon võib aktsepteerida parameetreid, mis edastatakse muudest stseenidest või mängust, helistades scene.start (klahv, [params])
  • eellaadimist () kutsutakse enne stseeniobjektide loomist ja see sisaldab vara laadimist; need varad on vahemällu salvestatud, nii et stseeni taaskäivitamisel neid uuesti ei laadita
  • luua () kutsutakse vara laadimisel ja see sisaldab tavaliselt peamiste mänguobjektide (taust, mängija, takistused, vaenlased jne) loomist
  • värskendust ([aeg]) nimetatakse igaks linnukeseks ja see sisaldab stseeni dünaamilist osa - kõike, mis liigub, vilgub jne.

Kui soovite olla kindel, et me seda hiljem ei unusta, lisame game.ts-i kiiresti järgmised read:

import "phaser";
impordi {GameScene} kaustast "./gameScene";
const config: GameConfig = {
  pealkiri: "Starfall",
  laius: 800,
  kõrgus: 600,
  vanem: "mäng",
  stseen: [GameScene],
  Füüsika: {
    vaikimisi: "arcade",
    mängusaal: {
      silumine: vale
    }
  },
  backgroundColor: "# 000033"
};
...

Meie mäng teab nüüd mängupilti. Kui mängu konfiguratsioon sisaldab stseenide loendit, käivitatakse esimene mängu käivitamisel ja kõik ülejäänud luuakse, kuid neid ei käivitata enne, kui seda konkreetselt kutsutakse.

Oleme siia lisanud ka arkaadifüüsika. See on vajalik selleks, et meie tähed langeksid.

Nüüd saame liha oma mängupildi luudele panna.

Esiteks kuulutame välja mõned omadused ja objektid, mida me vajame:

ekspordiklass GameScene laiendab Phaser.Scene {
  delta: arv;
  lastStarTime: arv;
  starsCaught: arv;
  starsFallen: arv;
  liiv: Phaser.Physics.Arcade.StaticGroup;
  teave: Phaser.GameObjects.Text;
...

Seejärel lähtestame numbrid:

init (/ * params: any * /): tühine {
    see 8 = 1000;
    this.lastStarTime = 0;
    this.starsCaught = 0;
    see.starsFallen = 0;
  }

Nüüd laadime paar pilti:

eellaadimine (): tühine {
    this.load.setBaseURL (
      "https://raw.githubusercontent.com/mariyadavydova/" +
      "starfall-phaser3-typecript / master /");
    this.load.image ("täht", "varad / täht.png");
    this.load.image ("liiv", "varad / liiv.jpg");
  }

Pärast seda saame oma staatilised komponendid ette valmistada. Loome pinnase, kuhu tähed langevad, ja teksti, mis teavitab meid praegusest skoorist:

luua (): tühine {
    this.sand = this.physics.add.staticGroup ({
      võti: 'liiv',
      kaadri suurus: 20
    });
    Phaser.Actions.PlaceOnLine (this.sand.getChildren (),
      uus Phaser.Geom.Line (20, 580, 820, 580));
    this.sand.refresh ();
this.info = this.add.text (10, 10, '',
      {font: '24px Arial Bold', täitke: '#FBFBAC'});
  }

Grupp Phaser 3-s on viis, kuidas luua hunnik objekte, mida soovite koos juhtida. Objekte on kahte tüüpi: staatilised ja dünaamilised. Nagu võite arvata, ei liigu staatilised objektid (maapind, seinad, mitmesugused takistused), samas kui dünaamilised objektid teevad seda tööd (Mario, laevad, raketid).

Loome maatükkidest staatilise rühma. Need tükid asetatakse mööda joont. Pange tähele, et joon on jagatud 20 võrdseks lõiguks (mitte 19, nagu olete võinud arvata) ja alusplaadid asetatakse igasse sektsiooni vasakpoolses otsas plaatide keskpunktiga selles kohas (ma loodan, et see seletab neid numbrid). Grupi piirdekasti värskendamiseks peame kutsuma ka värskenduse () (vastasel juhul kontrollitakse kokkupõrkeid vaikeasukohaga, mis on stseeni vasak ülanurk).

Kui vaatate brauseris nüüd oma rakendust, peaksite nägema midagi sellist:

Sinise ekraani areng

Oleme lõpuks jõudnud selle stseeni kõige dünaamilisema osani - update () funktsioonini, kus tähed langevad. Seda funktsiooni kutsutakse umbes kord 60 ms jooksul. Tahame iga sekund kiirgata uut langevat tähte. Me ei kasuta selleks dünaamilist rühma, kuna iga tähe elutsükkel on lühike: see hävitatakse kas kasutaja klõpsuga või maapinnaga kokku põrkades. Seetõttu loome funktsiooni emitStar () sees uue tähe ja lisame kahe sündmuse töötlemise: onClick () ja onCollision ().

värskendus (aeg: arv): tühine {
    var diff: number = aeg - this.lastStarTime;
    if (diff> this.delta) {
      this.lastStarTime = aeg;
      if (this.delta> 500) {
        see.delta = = 20;
      }
      see.emitStar ();
    }
    this.info.text =
      this.starsCaught + "püütud -" +
      this.starsFallen + "langenud (max 3)";
  }
privaatne onClick (täht: Phaser.Physics.Arcade.Image): () => tühine {
    tagastamise funktsioon () {
      star.setTint (0x00ff00);
      star.setVelocity (0, 0);
      this.starsCaught + = 1;
      this.time.delayedCall (100, funktsioon (täht) {
        star.destroy ();
      }, [täht], see);
    }
  }
privaatne sügisel (täht: Phaser.Physics.Arcade.Image): () => tühine {
    tagastamise funktsioon () {
      star.setTint (0xff0000);
      see.starsFallen + = 1;
      this.time.delayedCall (100, funktsioon (täht) {
        star.destroy ();
      }, [täht], see);
    }
  }
privaatne emitStar (): tühine {
    var star: Phaser.Physics.Arcade.Image;
    var x = Phaser.Math.Between (25, 775);
    var y = 26;
    täht = see.füüsika.add.kujutis (x, y, "täht");
star.setDisplaySize (50, 50);
    star.setVelocity (0, 200);
    star.setInteractive ();
star.on ('pointerdown', see.onClick (star), this);
    see.physics.add.collider (täht, see.sand,
      this.onFall (täht), null, this);
  }

Lõpuks on meil mäng! Sellel pole veel võidu tingimust. Lisame selle oma õpetuse viimasesse ossa.

Mul on halvasti tähti püüda ...

Kääri see kõik kokku

Tavaliselt koosneb mäng mitmest stseenist. Isegi kui mängimine on lihtne, vajate avamisstseeni (mis sisaldab vähemalt nuppu Esita!) Ja lõppstseeni (näidatakse teie mängu sessiooni tulemust, näiteks skoori või saavutatud maksimaalset taset). Lisage need stseenid meie rakendusse.

Meie puhul on need üsna sarnased, kuna ma ei taha mängu graafilisele kujundusele liiga palju tähelepanu pöörata. Lõppude lõpuks on see programmeerimisõpetus.

Tervitusstseenil on aadressil welcomeScene.ts järgmine kood. Pange tähele, et kui kasutaja klõpsab sellel stseenil kuskil, ilmub mängu stseen.

import "phaser";
ekspordiklass WelcomeScene laiendab Phaser.Scene {
  pealkiri: Phaser.GameObjects.Text;
  vihje: Phaser.GameObjects.Text;
ehitaja () {
    Super({
      võti: "WelcomeScene"
    });
  }
luua (): tühine {
    var titleText: string = "Starfall";
    this.title = this.add.text (150, 200, pealkiriTekst,
      {font: '128px Arial Bold', täitke: '#FBFBAC'});
var hintText: string = "Klõpsake alustamiseks";
    this.hint = this.add.text (300, 350, hintText,
      {font: '24px Arial Bold', täitke: '#FBFBAC'});
this.input.on ('pointerdown', function (/ * pointer * /) {
      this.scene.start ("GameScene");
    }, see);
  }
};

Hinde stseen näeb välja peaaegu sama, mis viib tervitusstseeni klõpsamiseni (scoreScene.ts).

import "phaser";
ekspordiklass ScoreScene laiendab Phaser.Scene {
  skoor: arv;
  tulemus: Phaser.GameObjects.Text;
  vihje: Phaser.GameObjects.Text;
ehitaja () {
    Super({
      võti: "ScoreScene"
    });
  }
init (params: any): tühine {
    this.score = params.starsCaught;
  }
luua (): tühine {
    var tulemusText: string = 'Teie tulemus on' + see.score + '!';
    this.result = this.add.text (200, 250, resultText,
      {font: '48px Arial Bold', täitke: '#FBFBAC'});
var hintText: string = "Klõpsake taaskäivitamiseks";
    this.hint = this.add.text (300, 350, hintText,
      {font: '24px Arial Bold', täitke: '#FBFBAC'});
this.input.on ('pointerdown', function (/ * pointer * /) {
      this.scene.start ("WelcomeScene");
    }, see);
  }
};

Peame nüüd värskendama oma peamist rakenduse faili: lisage need stseenid ja muutke WelcomeScene loendis esimeseks:

import "phaser";
importige {WelcomeScene} kaustast ./welcomeScene;
impordi {GameScene} kaustast "./gameScene";
importige {ScoreScene} kaustast ./scoreScene;
const config: GameConfig = {
  ...
  stseen: [WelcomeScene, GameScene, ScoreScene],
  ...

Kas olete märganud, mis puudu on? Õige, me ei nimeta ScoreScene'i veel kuskilt! Helistame siis, kui mängija on kolmanda tähe vahele jätnud:

privaatne sügisel (täht: Phaser.Physics.Arcade.Image): () => tühine {
    tagastamise funktsioon () {
      star.setTint (0xff0000);
      see.starsFallen + = 1;
      this.time.delayedCall (100, funktsioon (täht) {
        star.destroy ();
        if (this.starsFallen> 2) {
          this.scene.start ("ScoreScene",
            {starsCaught: this.starsCaught});
        }
      }, [täht], see);
    }
  }

Lõpuks näeb meie Starfall mäng välja nagu päris mäng - see algab, lõpeb ja sellel on isegi eesmärk arhiivida (kui palju tähti saate püüda?).

Loodan, et see õpetus on teile sama kasulik, kui see oli minu jaoks, kui ma seda kirjutasin :) Igasugune tagasiside on väga teretulnud!

Selle õpetuse lähtekoodi leiate siit.