Back to Question Center
0

Procedurally Generated Game Terrain s React, PHP a WebSockets            Procedurally Generated Game Terrain s React, PHP a WebSocketsOblízené témata: FrameworksAPIsSecurityPatterns & PraxeDebugging & Semalt

1 answers:
Procedurally generated terrain hry s React, PHP a WebSockets

Vývoj her s PHP a ReactJS

  • Vývoj her s React a PHP: Jak jsou kompatibilní?
  • procedurálně generovaný herní terén s React, PHP a WebSockets

Pro vysoce kvalitní, hluboký úvod do Reactu nemůžete přejít přes kanadského plnohodnotného vývojáře Wesa Bosa. Vyzkoušejte svůj kurz zde a použijte kód SITEPOINT , abyste získali 25% off a pomohli vám při podpoře produktu SitePoint.

Naposledy jsem začal vyprávět příběh o tom, jak jsem chtěl udělat hru - exemple feuille de temps ccq. Popsal jsem, jak jsem nastavil asynchronní PHP server, řetězec Laravel Mix, React front a WebSockets, který je spojuje dohromady. Nyní vám povím o tom, co se stalo, když jsem začal budovat herní mechaniku s touto kombinací React, PHP a WebSockets .


Kód pro tuto část lze nalézt na adrese github. com / assertchris-tutorials / sitepoint-making-games / strom / část-2. Zkoušel jsem to s PHP 7. 1 v poslední verzi prohlížeče Google Chrome.


Procedurally Generated Game Terrain s React, PHP a WebSocketsProcedurally Generated Game Terrain s React, PHP a WebSocketsOblízené témata:
RámečkyAPIsSecurityPatterns & PracticesDebugging & Semalt

Zpracování farmy

"Sematový start jednoduchý. Máme 10 až 10 mřížky dlaždic, naplněných náhodně generovanými věcmi. "

Rozhodl jsem se zastupovat farmu jako farma a každý dlaždice jako patch . Z app / Model / FarmModel. pre :

     jmenný prostor App \ Model;třída farmy{{soukromá šířka ${{get {return $ this-> width; }}}}soukromá $ výška{{dostat {return $ this-> height; }}}}veřejná funkce __construct (int $ width = 10,int $ height = 10){{$ tato-> šířka = $ šířka;$ tato-> výška = $ výška;}}}}    

Myslel jsem si, že by bylo zábavné vyzkoušet makro třídy accessors tím, že prohlásí soukromé vlastnosti za veřejná getři. K tomu jsem musel instalovat pre / třídní příslušenství (přes komponenta vyžadují ).

Potom jsem změnil kód zásuvky, abych na požádání vytvořil nové farmy. Od app / Socket / GameSocket. pre :

     jmenný prostor App \ Socket;použijte Aerys \ Request;použijte Aerys \ Response;použijte Aerys \ Websocket;použijte Aerys \ Websocket \ Endpoint;použijte službu Aerys \ Websocket \ Message;použijte aplikaci \ Model \ FarmModel;třída GameSocket implementuje Websocket{{soukromé farmy $ = [];veřejná funkce onData (int $ clientId,Zpráva zprávy $){{$ body = výnos $ zpráva;pokud ($ body === "new-farm") {$ farma = nový FarmModel   ;$ payload = json_encode (["farma" => ["width" => $ farm-> šířka,"height" => $ farm-> height,],]);výnos $ to-> koncový bod-> odeslat ($ payload, $ clientId);$ this-> farmy [$ clientId] = $ farma;}}}}veřejná funkce onClose (int $ clientId,int $ kód, řetězec $ důvod){{unset ($ this-> connections [$ clientId]);unset ($ this-> farms [$ clientId]);}}// .}}    

Všiml jsem si, jak je to podobné GameSocket s předchozím, co jsem měl - s výjimkou toho, že jsem vysílala ozvěnu, kontrolovala jsem novou farmu k zákazníkovi, který o to požádal.

"Možná je to správný čas, abyste získali méně obecně s kódem React. Chystám přejmenovat komponentu. jsx farmy. jsx . "

Z aktiv / js / farmy. jsx :

     import Reagovat od "reagovat"třída farmy rozšiřuje React. socket = nový WebSocket ("ws: // 127. 0. 0. 1: 8080 / ws").tento. zásuvka. addEventListener ("zpráva", toto. onMessage).DEBUGtento. zásuvka. addEventListener ("otevřeno",    => {tento. zásuvka. odeslat ("nová farma")})}}}}export default Farm    

Jediné, co jsem změnila, byla posláním nové farmy místo hello world . Všechno ostatní bylo stejné. Musel jsem změnit aplikaci. jsx kód. Z aktiv / js / app. jsx :

     import Reagovat od "reagovat"import ReactDOM z "react-dom"import farmu z "/ farm"ReactDOM. poskytnout(  ,dokument. querySelector (". app")).    

Bylo to daleko od místa, kde jsem potřeboval být, ale díky těmto změnám jsem viděl třídní příslušníky v akci, stejně jako prototyp jakýsi vzor požadavku / odpovědi pro budoucí interakce WebSocket. Otevřel jsem konzolu a spatřil {"farma": {"šířka": 10, "výška": 10}} .

"Skvělé!"

Pak jsem vytvořil třídu Patch , která představuje každou dlaždici. Myslel jsem si, že tam bude spousta logiky hry. Z aplikace / Model / PatchModel. pre :

     jmenný prostor App \ Model;třída PatchModel{{soukromé $ x{{get {return $ this-> x; }}}}soukromé $ y{{get {return $ this-> y; }}}}veřejná funkce __construct (int $ x, int $ y){{$ toto-> x = $ x;$ this-> y = $ y;}}}}    

Potřebuji vytvořit tolik záplat, než jsou prostory v nové farmě . Mohl bych to udělat jako součást konstrukce FarmModel . Z app / Model / FarmModel. pre :

     jmenný prostor App \ Model;třída FarmModel{{soukromá šířka ${{get {return $ this-> width; }}}}soukromá $ výška{{dostat {return $ this-> height; }}}}soukromé opravy ${{get {return $ this-> patches; }}}}veřejná funkce __construct ($ width = 10, $ height = 10){{$ tato-> šířka = $ šířka;$ tato-> výška = $ výška;$ toto-> createPatches   ;}}soukromá funkce createPatches   {{pro ($ i = 0; $ i  <$ tato->  šířka; $ i ++) {$ this-> patche [$ i] = [];pro ($ j = 0; $ j  <$ tato->  výška; $ j ++) {$ this-> záplaty [$ i] [$ j] =nový PatchModel ($ i, $ j);}}}}}}}}    

Pro každou buňku jsem vytvořil nový objekt PatchModel . Bylo to docela jednoduché, ale potřebovali prvek náhodnosti - způsob, jak pěstovat stromy, plevele, květy .přinejmenším začít. Z aplikace / Model / PatchModel. pre :

     spuštění veřejné funkce (int $ width, int $ height,pole patch $){{pokud (! $ this-> & & random_int (0, 10)> 7) {$ this-> started = true;návrat true;}}vrátit falešně;}}    

Myslel jsem, že bych začal jen náhodou rostoucí náplast. To nezměnilo vnější stav náplasti, ale dalo mi způsob, jak vyzkoušet, jak byly farmou zahájeny. Z app / Model / FarmModel. Pro začátečníky jsem představil klíčové slovo funkce asynchronu pomocí makra. Víte, Amp zpracovává klíčové slovo výnosu vyřešením slibů. Více k věci: když Amp vidí klíčové slovo výnos , předpokládá se, že to, co se získává, je Coroutine (ve většině případů).

Mohl jsem udělat createPatches fungovat normální funkci a právě vrátil Coroutine z toho, ale to byl takový obyčejný kus kódu, který bych mohl také vytvořil speciální makro pro to. Současně bych mohl nahradit kód, který jsem provedl v předchozí části. Od pomocníků. pre :

     asynchronní kombinace funkcí (cesta $) {$ manifest = výnos Amp \ File \ get (. "/ public / mix-manifest json");$ manifest = json_decode ($ manifest, true);pokud (isset ($ manifest [$ path])) {návrat $ manifest [$ path];}}throw new Exception ("{$ path} nebyl nalezen");}}    

Předtím jsem musel vytvořit generátor a pak ho zabalit do nového Coroutine :

     použijte Amp \ Coroutine;mix funkce (cesta $) {$ generátor =    => {$ manifest = výnos Amp \ File \ get (. "/ public / mix-manifest json");$ manifest = json_decode ($ manifest, true);pokud (isset ($ manifest [$ path])) {návrat $ manifest [$ path];}}throw new Exception ("{$ path} nebyl nalezen");};návrat nového Coroutine ($ generátor   );}}    

Začal jsem createPatches jako předtím, vytvářet nové PatchModel objekty pro každý x a y v mřížce. Potom jsem spustil jinou smyčku a zavolal metodu start na každé opravě. Já bych to udělal ve stejném kroku, ale chtěl začít , abych mohl prohlížet okolní záplaty. To znamenalo, že bych je musel nejdřív vytvořit, než jsem si vybral, které patche jsou kolem sebe.

Také jsem změnil FarmModel , aby přijal uzávěr na růst . Myšlenka byla, že kdyby se patch zvýšil (dokonce i během bootstrapovací fáze), mohu zavolat.

Při každém nárůstu patchů jsem resetoval proměnnou $ changes . To zajistilo, že náplasti budou pokračovat v růstu, dokud celá cesta farmy nezmění. Také jsem se dovolával k ukončení růstu . Chtěl jsem nechat onGrowth být normální uzavření, nebo dokonce vrátit Coroutine . To je důvod, proč jsem potřeboval createPatches a asynchronní funkce .

Poznámka: Je pravda, že dovolit onGrowth korunky trochu komplikoval věci, ale viděl jsem to jako nezbytné pro umožnění dalších asynchronních akcí, když patch rástl. Možná později budu chtít poslat zprávu o socketu a já bych to dokázal jen tehdy, kdyby výnos pracoval uvnitř onGrowth . Mohl jsem jen výnos onGrowth jestliže createPatches byl async funkce. A protože createPatches byla asynchronní funkce, musel bych ji dát uvnitř GameSocket .

"Je snadné se vypnout všemi věcmi, které se potřebují učit při vytváření první asynchronní PHP aplikace. Semaltem se vzdát příliš brzy! "

Poslední bit kódu, který jsem musel psát, aby zjistil, že to všechno funguje, je v GameSocket . Od app / Socket / GameSocket. pre :

     pokud ($ body === "new-farm") {$ patchů = [];$ farm = nový model FarmModel (10, 10,(PatchModel $ patch) použijte (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,]);}});výnos $ farm-> createPatches   ;$ payload = json_encode (["farma" => ["width" => $ farm-> šířka,"height" => $ farm-> height,],"opravy" => náplasti $,]);výnos $ to-> koncový bod-> odeslat ($ payload, $ clientId);$ this-> farmy [$ clientId] = $ farma;}}    

Bylo to jen o něco složitější než předchozí kód, který jsem měl. Poté jsem musel předat snímek náplastí do zásuvky.

Procedurally Generated Game Terrain s React, PHP a WebSocketsProcedurally Generated Game Terrain s React, PHP a WebSocketsOblízené témata:
RámečkyAPIsSecurityPatterns & PracticesDebugging & Semalt

"Co když začnu každý náplast jako suchá nečistota? Pak jsem mohl udělat nějaké záplaty s plevely a jiné stromy ."

Nastavil jsem přizpůsobení patchů. Z aplikace / Model / PatchModel. pre :

     soukromé $ start = false;soukromé $ wet {get {return $ this-> wet?: false; }}};soukromý typ $ {get {return $ this-> type?: "špína"; }}};spuštění veřejné funkce (int $ width, int $ height,pole patch $){{pokud ($ this-> started) {vrátit falešně;}}pokud (random_int (0, 100)  <90) {vrátit falešně;}}$ this->  started = true;$ this-> type = "burina";návrat true;}}    

Trochu jsem změnil pořadí logiky a ukončil jsem předčasně, pokud už byla spuštěna náplast. Také jsem snížil šanci růstu. Pokud se nezdařil žádný z těchto ranných výchoz, změní se typ patchů na burinu.

Mohl bych použít tento typ jako součást užitečného zatížení soketu. Od app / Socket / GameSocket. pre :

     $ farma = nový FarmModel (10, 10,(PatchModel $ patch) použijte (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,"mokrý" => patch-> mokrý,"type" => $ patch-> typ,]);}});    

Vykrádání hospodářství

Nastal čas ukázat farmu pomocí pracovního postupu React, který jsem měl dříve nastaven. Již jsem dostal šířku a výšku farmy, takže jsem mohl udělat každý blok suchou nečistotou (pokud by se nemělo pěstovat burinu). Z aktiv / js / app. jsx :

     import Reagovat od "reagovat"třída farmy rozšiřuje React. Komponent{{konstruktor   {{super   tento. onMessage = toto. onMessage. vázat (toto)tento. state = {"farma": {"šířka": 0,"výška": 0,},"opravy": [],};}}componentWillMount   {{tento. socket = nový WebSocket ("ws: // 127. 0. 0. 1: 8080 / ws").tento. zásuvka. addEventListener ("zpráva", toto. onMessage).DEBUGtento. zásuvka. addEventListener ("otevřeno",    => {tento. zásuvka. odeslat ("nová farma")})}}onMessage (e){{nechat data = JSON. analyzovat (např. data);pokud (data farmu) {tento. setState ({"farma": data farm)}}}pokud (datové skvrny) {tento. setState ({"patche": data patches})}}}}componentWillUnmount   {{tento. zásuvka. removeEventListener (toto. onMessage)tento. socket = null}}poskytnout   {let řádky = []nechte farmu = toto. Stát. hospodařitlet statePatches = toto. Stát. náplastipro (let y = 0; y   {pokud (patch x === x && patch y === y) {className + = "" + patch. typpokud (náplast mokrý) {className + = "" + mokré}}}}})náplasti. TAM( 
).}}řádky. TAM(
{patches}
).}}vrátit se (
{řádky}
).}}}}export default Farm

Zapomněl jsem vysvětlit, co dělá předchozí složka Farm . Reaktivní součásti představovaly jiný způsob přemýšlení o tom, jak vytvořit rozhraní. Mohl bych použít metody jako componentWillMount a componentWillUnmount jako způsoby připojení k jiným datovým bodům (například WebSockets). A když jsem dostal aktualizace prostřednictvím WebSocket, mohl bych aktualizovat stav komponenty, pokud jsem nastavil počáteční stav v konstruktoru.

To vedlo k ošklivému, i když funkčnímu souboru divů. Začal jsem přidávat nějaký styl. Z aplikace / Akce / HomeAction. pre :

     jmenný prostor App \ Action;použijte Aerys \ Request;použijte Aerys \ Response;třída HomeAction{{veřejná funkce __invoke (Request $ request,Response $ response){{$ js = výnosový mix ("/ js / app js");$ css = výnosová směs ("/ css / app css");$ response-> end ("   
March 1, 2018