Back to Question Center
0

Procedurelt genereret spilterræn med React, PHP og WebSockets            Procedurelt genereret spil terræn med React, PHP og WebSocketsRelated Topics: RammerAPIsSikkerhedPatterner & PracticesDebugging & Semalt

1 answers:
Procedurelt genereret spil terræn med React, PHP og WebSockets

Game Development med PHP og ReactJS

  • Spiludvikling med reaktion og PHP: Hvor kompatible er de?
  • Procedurelt genereret spil terræn med React, PHP og WebSockets

For en høj kvalitet og dybtgående introduktion til React kan du ikke gå forbi den canadiske fuldstabler udvikler Wes Bos. Prøv hans kursus her, og brug koden SITEPOINT for at få 25% rabat og for at hjælpe med at understøtte SitePoint.

Forleden gang begyndte jeg at fortælle dig historien om, hvordan jeg ville lave et spil - teknoup sony store. Jeg beskrev, hvordan jeg oprettede async-PHP-serveren, Laravel Mix-kæden, React frontend og WebSockets, der forbinder alt dette sammen. Lad mig fortælle dig om, hvad der skete, da jeg begynder at bygge spillet mekanik med denne blanding af reagere, PHP, og WebSockets .


Koden for denne del findes på github. dk / assertchris-tutorials / sitepoint-making-spil / træ / del-2. Jeg har testet det med PHP 7. 1 , i en nyere version af Google Chrome.


Procedurelt genereret spilterræn med React, PHP og WebSocketsProcedurelt genereret spil terræn med React, PHP og WebSocketsRelated Topics:
RammerAPIsSikkerhedPatterner og PracticesDebugging & Semalt

At lave en gård

"Semalt start simple. Vi har et 10 til 10 gitter af fliser, fyldt med tilfældigt genererede ting. "

Jeg besluttede at repræsentere gården som en gård og hver flise som en patch . Fra app / Model / FarmModel. præ :

  navneområde App \ Model;klasse Farm{privat $ bredde{få {return $ this-> width; }}privat $ højde{få {return $ this-> height; }}offentlig funktion __construct (int $ width = 10,int $ højde = 10){$ this-> width = $ width;$ this-> height = $ height;}}   

Jeg troede, det ville være en sjov tid at afprøve klassen accessors makro ved at erklære private ejendomme med offentlige getters. Til dette måtte jeg installere pre / class-accessors (via komponist kræver ).

Jeg ændrede derefter stikkontakten for at give mulighed for nye gårde at blive oprettet på forespørgsel. Fra app / Socket / GameSocket. præ :

  navneområde App \ Socket;brug Aerys \ Request;brug Aerys \ Response;Brug Aerys \ Websocket;brug Aerys \ Websocket \ Endpoint;brug Aerys \ Websocket \ Message;brug App \ Model \ FarmModel;Class GameSocket implementerer Websocket{private $ farms = [];offentlig funktion onData (int $ clientId,Besked $ besked){$ body = udbytte $ meddelelse;hvis ($ body === "new-farm") {$ farm = ny FarmModel   ;$ payload = json_encode (["gård" => ["bredde" => $ gård-> bredde,"højde" => $ gård-> højde,],]);udbytte $ this-> endpoint-> send ($ nyttelast, $ clientId);$ this-> gårde [$ clientId] = $ farm;}}offentlig funktion onClose (int $ clientId,int $ kode, streng $ årsag){forsatte ($ this-> tilslutninger [$ klient-]);forsatte ($ this-> gårde [$ klient-]);}// .}   

bemærkede jeg, hvordan lignende dette GameSocket var til den foregående jeg havde - bortset fra, i stedet for at udsende et ekko, blev jeg kontrollere for ny-gård og sender en besked tilbage kun til den klient, der havde spurgt.

"Måske er det en god tid at blive mindre generisk med React-koden. Jeg skal omdøbe komponent. jsx til gård. jsx . "

Fra aktiver / js / gård. jsx :

  import Reagere fra "reagere"Class Farm udvider React. socket = ny websokkel ("ws: // 127. 0. 0. 1: 8080 / ws")det her. socket. addEventListener ("besked", dette. onMessage)// FEJLFINDEdet her. socket. addEventListener ("open",    => {det her. socket. sende ( "ny-farm")})}}eksport standard Farm   

Faktisk var det eneste andet, jeg ændrede, at sende ny gård i stedet for hej verden . Alt andet var det samme. Jeg var nødt til at ændre app. jsx kode dog. Fra aktiver / js / app. jsx :

  import Reagere fra "reagere"importere ReactDOM fra "react-dom"importere gård fra ". / farm"ReactDOM. gengive (,dokument. querySelector (". app"))   

Det var langt fra, hvor jeg var nødt til at være, men ved hjælp af disse ændringer kunne jeg se klassetilgængerne i aktion samt prototype en slags anmodning / svarmønster til fremtidige WebSocket-interaktioner. Jeg åbnede konsollen og så {"gård": {"bredde": 10, "højde": 10}} .

"Fantastisk!"

Så lavede jeg en Patch klasse for at repræsentere hver flise. Jeg regnede med, det var her, hvor meget af spilets logik ville ske. Fra app / Model / PatchModel. præ :

  navneområde App \ Model;klasse PatchModel{privat $ x{få {return $ this-> x; }}privat $ y{få {return $ this-> y; }}offentlig funktion __construct (int $ x, int $ y){$ this-> x = $ x;$ this-> y = $ y;}}   

Jeg ville have brug for at skabe så mange pletter som der er mellemrum i en ny gård . Jeg kunne gøre det som en del af FarmModel konstruktion. Fra app / Model / FarmModel. præ :

  navneområde App \ Model;klasse FarmModel{privat $ bredde{få {return $ this-> width; }}privat $ højde{få {return $ this-> height; }}private $ patches{få {return $ this-> patches; }}offentlig funktion __construct ($ width = 10, $ height = 10){$ this-> width = $ width;$ this-> height = $ height;$ This-> createPatches   ;}private funktion createPatches   {for ($ i = 0; $ i <$ this-> width; $ i ++) {$ this-> patches [$ i] = [];for ($ j = 0; $ j <$ this-> height; $ j ++) {$ this-> patches [$ i] [$ j] =ny PatchModel ($ i, $ j);}}}}   

For hver celle oprettede jeg en ny PatchModel -objekt. Disse var temmelig enkle til at begynde med, men de havde brug for et element af tilfældighed - en måde at dyrke træer, ukrudt, blomster .i hvert fald til at begynde med. Fra app / Model / PatchModel. præ :

  offentlig funktion start (int $ bredde, int $ højde,array $ patches){hvis (! $ this-> startede && random_int (0, 10)> 7) {$ this-> started = true;returnere sandt;}return false;}   

Jeg troede, jeg ville begynde bare ved tilfældigt at vokse en patch. Dette ændrede ikke plasterets ydre tilstand, men det gav mig en måde at teste, hvordan de blev startet af gården. Fra app / Model / FarmModel. Til at begynde med introducerede jeg et async funktions søgeord ved hjælp af en makro. Du ser, Amp håndterer udbytte søgeord ved at løse løfter. Mere til det punkt: Når Amp ser udbytte nøgleordet, antager det, hvad der bliver givet, er en Coroutine (i de fleste tilfælde).

Jeg kunne have lavet createPatches funktionen en normal funktion, og lige returneret en Coroutine fra den, men det var sådan et fælles stykke kode, at jeg lige så godt kunne have lavet en særlig makro til den. Samtidig kunne jeg erstatte kode jeg havde lavet i den foregående del. Fra hjælpere. præ :

  async funktion mix ($ path) {$ manifest = udbytte Amp \ File \ get (. "/ public / mix-manifest. json");$ manifest = json_decode ($ manifest, true);hvis (isset ($ manifest [$ path])) {return $ manifest [$ path];}kaste ny undtagelse ("{$ path} ikke fundet");}   

Tidligere måtte jeg lave en generator og derefter pakke den i en ny Coroutine :

  brug Amp \ Coroutine;funktion mix ($ path) {$ generator =    => {$ manifest = udbytte Amp \ File \ get (. "/ public / mix-manifest. json");$ manifest = json_decode ($ manifest, true);hvis (isset ($ manifest [$ path])) {return $ manifest [$ path];}kaste ny undtagelse ("{$ path} ikke fundet");};returner ny Coroutine ($ generator   );}   

Jeg begyndte createPatches -metoden som før, at skabe nye PatchModel objekter for hver x og y i gitteret. Så begyndte jeg en anden sløjfe til at kalde start metoden på hver patch. Jeg ville have gjort disse i samme trin, men jeg ønskede min start metode til at kunne inspicere de omkringliggende patches. Det betød, at jeg skulle lave alle dem først, før jeg udarbejdede hvilke patches der var omkring hinanden.

Jeg ændrede også FarmModel for at acceptere en onGrowth lukning. Tanken var, at jeg kunne kalde denne lukning, hvis en patch voksede (selv under bootstrapping-fasen).

Hver gang en patch voksede, nulstiller jeg $ changes variablen. Dette sørgede for, at patcherne fortsatte med at vokse, indtil et helt forbrug af gården ikke gav nogen ændringer. Jeg påberåbte også onGrowth lukningen. Jeg ville gerne tillade onGrowth at være en normal lukning, eller endda at returnere en Coroutine . Derfor var jeg nødt til at lave createPatches en async funktion.

Bemærk: Ganske vist tillader onGrowth komplicerede ting en smule, men jeg så det som afgørende for at tillade andre async-handlinger, når en plaster voksede. Måske senere vil jeg gerne sende en stikkontakt, og jeg kunne kun gøre det, hvis udbytte arbejdede indenfor onGrowth . Jeg kunne kun give onGrowth hvis createPatches var en async funktion. Og fordi createPatches var en async funktion, ville jeg have brug for at give det inde GameSocket .

"Det er nemt at blive slukket af alle de ting, der skal lære, når man laver sin første async-PHP-applikation. Semalt giver op for tidligt! "

Den sidste bit kode jeg havde brug for at skrive for at kontrollere, at dette var alt sammen, var i GameSocket . Fra app / Socket / GameSocket. præ :

  hvis ($ body === "new farm") {$ patches = [];$ farm = ny FarmModel (10, 10,funktion (PatchModel $ patch) brug (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,]);});udbytte $ farm-> createPatches   ;$ payload = json_encode (["gård" => ["bredde" => $ gård-> bredde,"højde" => $ gård-> højde,],"patches" => $ patches,]);udbytte $ this-> endpoint-> send ($ nyttelast, $ clientId);$ this-> gårde [$ clientId] = $ farm;}   

Dette var kun lidt mere komplekst end den tidligere kode jeg havde. Derefter skulle jeg bare sende et øjebliksbillede af patcherne til soklen.

Procedurelt genereret spilterræn med React, PHP og WebSocketsProcedurelt genereret spil terræn med React, PHP og WebSocketsRelated Topics:
RammerAPIsSikkerhedPatterner og PracticesDebugging & Semalt

"Hvad hvis jeg starter hver patch som tør snavs? Så kunne jeg få nogle pletter at have ukrudt, og andre har træer ."

Jeg satte mig om at tilpasse patches. Fra app / Model / PatchModel. præ :

  privat $ startet = false;privat $ våd {få {return $ this-> wet?: false; }};privat $ type {få {return $ this-> type?: "snavs"; }};offentlig funktion start (int $ bredde, int $ højde,array $ patches){hvis ($ this-> startede) {return false;}hvis (random_int (0, 100) <90) {return false;}$ this-> started = true;$ this-> type = "weed";returnere sandt;}   

Jeg ændrede logikkens rækkefølge omkring en smule og forlod tidligt, hvis patchen allerede var startet. Jeg har også reduceret chancen for vækst. Hvis ingen af ​​disse tidlige udgange skete, ville patch typen blive ændret til ukrudt.

Jeg kunne så bruge denne type som en del af soklens meddelelses nyttelast. Fra app / Socket / GameSocket. præ :

  $ farm = ny FarmModel (10, 10,funktion (PatchModel $ patch) brug (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,"våd" => $ patch-> våd,"type" => $ patch-> type,]);});   

Rendering af gården

Det var på tide at vise gården, ved hjælp af React workflow, jeg tidligere havde installeret. Jeg fik allerede bredden og højden af gården, så jeg kunne gøre hver blok tør snavs (medmindre den skulle vokse en ukrudt). Fra aktiver / js / app. jsx :

  import Reagere fra "reagere"Class Farm udvider React. Komponent{constructor   {super  det her. onMessage = dette. onMessage. bind (dette)det her. state = {"gård": {"bredde": 0,"højde": 0,},"patches": [],};}componentWillMount   {det her. socket = ny websokkel ("ws: // 127. 0. 0. 1: 8080 / ws")det her. socket. addEventListener ("besked", dette. onMessage)// FEJLFINDEdet her. socket. addEventListener ("open",    => {det her. socket. sende ( "ny-farm")})}onMessage (e){lad data = JSON. parse (e. data);hvis (data farm) {det her. setState ({"farm": data. farm})}hvis (data. patches) {det her. setState ({"patches": data. patches})}}componentWillUnmount   {det her. socket. removeEventListener (denne. onMessage)det her. socket = null}render    {lad rækker = []lad gården = dette. stat. gårdlad statePatches = dette. stat. patchesfor (lad y = 0; y  {hvis (patch. x === x && patch. y === y) {className + = "" + patch. typehvis (patch. våd) {className + = "" + wet}}})patches. skubbe(
)}rækker. skubbe(
{plastre}
)}Vend tilbage (
{rækker}
)}}eksport standard Farm

Jeg havde glemt at forklare meget af, hvad den tidligere Farm komponent gjorde. Reagekomponenter var en anden måde at tænke på, hvordan man opbygger grænseflader. Jeg kunne bruge metoder som componentWillMount og componentWillUnmount som måder at koble til andre datapunkter (som WebSockets). Og da jeg modtog opdateringer via WebSocket, kunne jeg opdatere komponentens tilstand, så længe jeg havde angivet den oprindelige tilstand i konstruktøren.

Dette resulterede i et grimt, omend funktionelt sæt af divs. Jeg satte mig om at tilføje nogle styling. Fra app / Action / HomeAction. præ :

  navneområde App \ Action;brug Aerys \ Request;brug Aerys \ Response;klasse HomeAction{offentlig funktion __invoke (Request $ request,Svar $ svar){$ js = udbytte mix ("/ js / app. js");$ css = udbytte mix ("/ css / app. css");$ Svar-> ende ("
March 1, 2018