Back to Question Center
0

Secara prosedural Generated Game Terrain dengan React, PHP, dan WebSockets            Secara prosedural Generated Game Terrain dengan React, PHP, dan WebSocketsRelated Topics: FrameworksAPIsSecurityPatterns & PraktikDebugging & Semalt

1 answers:
Kejadian Terapan Secara Statis dengan React, PHP, dan WebSockets

Pengembangan Game dengan PHP dan ReactJS

  • Pengembangan Game dengan Bereaksi dan PHP: Seberapa Kompatibelnya?
  • Terapan Game Generalized dengan React, PHP, dan WebSockets

Untuk pengantar React yang berkualitas tinggi dan mendalam, Anda tidak bisa melewati pengembang full-stack Kanada Wes Bos. Cobalah kursusnya di sini, dan gunakan kode SITEPOINT untuk mendapatkan diskon 25% dan untuk membantu mendukung SitePoint.

Terakhir kali, saya mulai bercerita tentang bagaimana saya ingin membuat permainan. Saya menjelaskan bagaimana saya mengatur server PHP async, rantai pembuatan Laravel Mix, front end React, dan WebSockets yang menghubungkan semua ini bersama-sama. Sekarang, mari saya ceritakan tentang apa yang terjadi ketika saya mulai membangun mekanika permainan dengan campuran React, PHP, dan WebSockets ini .


Kode untuk bagian ini dapat ditemukan di github. com / assertchris-tutorials / sitepoint-making-games / tree / part-2. Saya telah mengujinya dengan PHP 7. 1 , dalam versi terbaru Google Chrome.


Secara prosedural Generated Game Terrain dengan React, PHP, dan WebSocketsSecara prosedural Generated Game Terrain dengan React, PHP, dan WebSocketsRelated Topics:
FrameworksAPIsSecurityPatterns & PracticesDebugging & Semalt

Membuat Peternakan

"Semalt mulai sederhana. Kami memiliki 10 x 10 kotak ubin, diisi dengan barang-barang yang dibuat secara acak. "

Saya memutuskan untuk mewakili pertanian itu sebagai Farm , dan setiap ubin sebagai Patch . Dari app / Model / FarmModel. pra :

  namespace App \ Model;kelas pertanian{private $ width{dapatkan {return $ this-> width; }}tinggi $ pribadi{dapatkan {return $ this-> height; }}fungsi publik __construct (int $ width = 10,int $ height = 10){$ this-> width = $ width;$ this-> height = $ height;}}   

Saya pikir akan menyenangkan saat mencoba makro pengakses kelas dengan mendeklarasikan properti pribadi dengan getter publik. Untuk ini saya harus menginstal pre / class-accessors (via komposer membutuhkan ).

Saya kemudian mengganti kode soket untuk memungkinkan peternakan baru dibuat berdasarkan permintaan. Dari app / Socket / GameSocket. pra :

  namespace App \ Socket;gunakan Aerys \ Request;gunakan Aerys \ Response;gunakan Aerys \ Websocket;gunakan Aerys \ Websocket \ Endpoint;gunakan Aerys \ Websocket \ Message;gunakan App \ Model \ FarmModel;class GameSocket mengimplementasikan Websocket{private $ farms = [];fungsi publik onData (int $ clientId,Pesan $ message){$ body = menghasilkan $ message;jika ($ body === "new-farm") {$ farm = new FarmModel   ;$ payload = json_encode (["pertanian" => [lebar "=> $ farm-> width,"tinggi" => tinggi $ farm->,],]);menghasilkan $ this-> endpoint-> send ($ payload, $ clientId);$ this-> farms [$ clientId] = $ farm;}}fungsi publik onClose (int $ clientId,int $ code, string $ reason){unset ($ this-> connections [$ clientId]);tidak diset ($ this-> farms [$ clientId]);}// .}   

Saya melihat betapa miripnya ini GameSocket yang sebelumnya saya miliki - kecuali, alih-alih menyiarkan gema, saya memeriksa pertanian baru dan mengirim pesan kembali kepada klien yang telah bertanya.

"Mungkin ini saat yang tepat untuk menjadi kurang generik dengan kode Reaktan. Saya akan mengganti nama komponen. jsx sampai pertanian. jsx . "

Dari aset / js / pertanian. jsx :

  impor Bereaksi dari "reaksi"Kelas Farm memperpanjang Reaktansi. soket = WebSocket baru"ws: // 127. 0. 0. 1: 8080 / ws")ini. stopkontak. addEventListener"pesan", ini onMessage)// DEBUGini. stopkontak. addEventListener ("open",    => {ini. stopkontak. kirim ("ladang baru")})}}ekspor default Farm   

Sebenarnya, satu-satunya hal yang saya ubah adalah mengirim pertanian baru dan bukan halo dunia . Semua hal lainnya sama. Aku memang harus mengubah app. jsx kode sekalipun. Dari aset / js / app. jsx :

  impor Bereaksi dari "reaksi"impor ReactDOM dari "react-dom"mengimpor Pertanian dari ". / pertanian"ReactDOM. memberikan(,dokumen. querySelector (". app"))   

Jauh dari tempat saya harus berada, namun dengan menggunakan perubahan ini, saya dapat melihat pengakses kelas dalam tindakan, serta prototipe semacam pola permintaan / tanggapan untuk interaksi WebSocket di masa depan. Saya membuka konsol, dan melihat {"farm": {"width": 10, "height": 10}} .

"Bagus!"

Kemudian saya membuat sebuah kelas Patch untuk mewakili setiap ubin. Saya pikir ini adalah di mana banyak logika permainan akan terjadi. Dari app / Model / PatchModel. pra :

  namespace App \ Model;kelas PatchModel{pribadi $ x{dapatkan {return $ this-> x; }}private $ y{dapatkan {return $ this-> y; }}fungsi publik __construct (int $ x, int $ y){$ this-> x = $ x;$ this-> y = $ y;}}   

Saya perlu membuat banyak tambalan karena ada banyak ruang di Peternakan baru . Saya bisa melakukan ini sebagai bagian dari konstruksi FarmModel . Dari app / Model / FarmModel. pra :

  namespace App \ Model;kelas FarmModel{private $ width{dapatkan {return $ this-> width; }}tinggi $ pribadi{dapatkan {return $ this-> height; }}private $ patches{dapatkan {return $ this-> patches; }}fungsi publik __construct ($ width = 10, $ height = 10){$ this-> width = $ width;$ this-> height = $ height;$ this-> createPatches   ;}fungsi pribadi createPatches   {untuk ($ i = 0; $ i <$ this-> width; $ i ++) {$ this-> patches [$ i] = [];untuk ($ j = 0; $ j <$ this-> height; $ j ++) {$ this-> patches [$ i] [$ j] =PatchModel baru ($ i, $ j);}}}}   

Untuk setiap sel, saya membuat sebuah objek baru PatchModel . Ini cukup sederhana untuk dimulai, tapi mereka membutuhkan unsur keacakan - cara menanam pohon, gulma, bunga .setidaknya untuk memulai. Dari app / Model / PatchModel. pra :

  fungsi publik mulai (int $ width, int $ height,array $ patches){jika (! $ this-> started && random_int (0, 10)> 7) {$ this-> begin = true;kembali benar;}kembali salah;}   

Saya pikir saya akan mulai dengan secara acak menumbuhkan sebuah patch. Ini tidak mengubah keadaan eksternal patch, tapi itu memberi saya cara untuk menguji bagaimana mereka dimulai oleh peternakan. Dari app / Model / FarmModel. Sebagai permulaan, saya memperkenalkan kata kunci fungsi async menggunakan makro. Anda lihat, Amp menangani kata kunci menghasilkan dengan menyelesaikan Janji. Lebih tepatnya: ketika Amp melihat kata kunci yield , ia mengasumsikan apa yang dihasilkan adalah Coroutine (dalam banyak kasus).

Saya dapat membuat createPatches fungsi fungsi normal, dan baru saja mengembalikan sebuah Coroutine dari itu, tapi itu adalah kode yang umum sehingga saya mungkin juga telah menciptakan makro khusus untuk itu. Pada saat yang sama, saya bisa mengganti kode yang telah saya buat di bagian sebelumnya. Dari pembantu. pra :

  async function mix ($ path) {$ manifest = hasil Amp \ File \ get (."/ public / mix-manifest. json");$ manifest = json_decode ($ manifest, true);jika (isset ($ manifest [$ path])) {kembalikan $ manifest [$ path];}buang Exception baru ("{$ path} not found");}   

Sebelumnya, saya harus membuat generator, lalu membungkusnya dengan Coroutine baru

:

  menggunakan Amp \ Coroutine;bauran fungsi ($ path) {$ generator =    => {$ manifest = hasil Amp \ File \ get (."/ public / mix-manifest. json");$ manifest = json_decode ($ manifest, true);jika (isset ($ manifest [$ path])) {kembalikan $ manifest [$ path];}buang Exception baru ("{$ path} not found");};kembalikan Coroutine baru ($ generator   );}   

Saya memulai metode createPatches seperti sebelumnya, membuat objek baru PatchModel untuk masing-masing x dan y di grid. Kemudian saya memulai lagi loop, untuk memanggil metode start pada setiap patch. Saya akan melakukan ini pada langkah yang sama, tapi saya ingin metode awal saya untuk bisa memeriksa tambalan di sekitarnya. Itu berarti saya harus menciptakan semuanya terlebih dahulu, sebelum mengerjakan tambalan mana yang ada di sekitar satu sama lain.

Saya juga mengganti FarmModel untuk menerima penutupan onGrowth . Idenya adalah bahwa saya bisa memanggil penutupan itu jika sebuah patch tumbuh (bahkan selama fase bootstrap).

Setiap kali sebuah patch tumbuh, saya mereset variabel $ changes . Hal ini memastikan tambalan akan terus tumbuh sampai seluruh lahan pertanian tidak menghasilkan perubahan. Saya juga meminta penutupan onGrowth . Saya ingin membiarkan onGrowth menjadi penutupan normal, atau bahkan untuk mengembalikan Coroutine . Itu sebabnya saya perlu membuat createPatches fungsi async .

Catatan: Diakui, membiarkan pada peristiwa pertumbuhan sangat rumit, tapi saya menganggapnya penting untuk membiarkan tindakan async lain saat sebuah patch tumbuh. Mungkin nanti saya ingin mengirim pesan soket, dan saya hanya bisa melakukannya jika menghasilkan bekerja di dalam onGrowth . Saya hanya bisa menghasilkan onGrowth jika createPatches adalah fungsi async . Dan karena createPatches adalah fungsi async , saya perlu menyerahkannya ke dalam GameSocket .

"Mudah dimatikan oleh semua hal yang perlu dipelajari saat membuat aplikasi PHP async pertama. Semalt menyerah terlalu cepat! "

Bit kode terakhir yang perlu saya tulis untuk memeriksa apakah ini semua bekerja di GameSocket . Dari app / Socket / GameSocket. pra :

  jika ($ body === "new-farm") {$ patches [];$ farm = new FarmModel (10, 10,fungsi (PatchModel $ patch) gunakan (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,]);});menghasilkan $ farm-> createPatches   ;$ payload = json_encode (["pertanian" => [lebar "=> $ farm-> width,"tinggi" => tinggi $ farm->,],"tambalan" => $ patch,]);menghasilkan $ this-> endpoint-> send ($ payload, $ clientId);$ this-> farms [$ clientId] = $ farm;}   

Ini hanya sedikit lebih rumit dari kode sebelumnya. Setelah itu, saya hanya perlu melewati sebuah snapshot dari tambalan ke muatan soket.

Secara prosedural Generated Game Terrain dengan React, PHP, dan WebSocketsSecara prosedural Generated Game Terrain dengan React, PHP, dan WebSocketsRelated Topics:
FrameworksAPIsSecurityPatterns & PracticesDebugging & Semalt

"Bagaimana jika saya memulai setiap tambalan sebagai kotoran kering? Lalu aku bisa membuat beberapa tambalan memiliki gulma, dan ada pula yang memiliki pohon ."

Saya mengatur tentang menyesuaikan tambalan. Dari app / Model / PatchModel. pra :

  private $ started = false;private $ wet {mendapatkan {return $ this-> wet?: false; }};private $ type {dapatkan {return $ this-> type?: "kotoran"; }};fungsi publik mulai (int $ width, int $ height,array $ patches){jika ($ this-> started) {kembali salah;}jika (random_int (0, 100) <90) {kembali salah;}$ this-> begin = true;$ this-> type = "weed";kembali benar;}   

Saya mengubah urutan logika sekitar sedikit, keluar lebih awal jika patch sudah dimulai. Saya juga mengurangi peluang pertumbuhan. Jika tidak satu pun dari kejadian awal ini terjadi, jenis tambalan akan diubah menjadi gulma.

Saya kemudian bisa menggunakan tipe ini sebagai bagian dari muatan socket message. Dari app / Socket / GameSocket. pra :

  $ farm = new FarmModel (10, 10,fungsi (PatchModel $ patch) gunakan (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,"basah" => $ patch-> basah,tipe "=> $ patch->,]);});   

Rendering the Farm

Saatnya untuk menunjukkan pertanian, menggunakan alur kerja React yang telah saya siapkan sebelumnya. Saya sudah mendapatkan lebar dan tinggi lahan pertanian, jadi saya bisa membuat setiap blok kotoran kering (kecuali jika seharusnya menumbuhkan rumput liar). Dari aset / js / app. jsx :

  impor Bereaksi dari "reaksi"Kelas Farm memperpanjang Reaktansi. Komponen{konstruktor   {super  ini. onMessage = ini onMessage. mengikat (ini)ini. negara = {"tanah pertanian": {"lebar": 0,"tinggi": 0,},"tambalan": [],};}componentWillMount   {ini. soket = WebSocket baru"ws: // 127. 0. 0. 1: 8080 / ws")ini. stopkontak. addEventListener"pesan", ini onMessage)// DEBUGini. stopkontak. addEventListener ("open",    => {ini. stopkontak. kirim ("ladang baru")})}onMessage (e){biarkan data = JSON parse (data);jika (data peternakan) {ini. setState ({"farm": data peternakan})}jika (data patch) {ini. setState ({"patches": data patches})}}komponenWillUnmount   {ini. stopkontak. removeEventListener (ini. onMessage)ini. socket = null}render    {biarkan baris = []biarkan pertanian = ini negara. tanah pertanianbiarkan statePatches = ini negara. tambalanuntuk (biarkan y = 0; y  {jika (patch x === x & & patch y === y) {className + = "" + patch. mengetikjika (patch basah) {className + = "" + basah}}})tambalan Dorong(
)}baris. Dorong(
{patches}
)}kembali
{rows}
)}}ekspor default Farm

Saya lupa menjelaskan banyak tentang apa yang dilakukan komponen 33 Farm sebelumnya. Bereaksi komponen adalah cara berpikir yang berbeda tentang bagaimana membangun antarmuka. Saya bisa menggunakan metode seperti komponenWillMount dan komponenWillUnmount sebagai cara untuk menghubungkan ke titik data lainnya (seperti WebSockets). Dan saat menerima pembaruan melalui WebSocket, saya bisa memperbarui keadaan komponen, asalkan saya telah menyetel status awal di constructor.

Hal ini menghasilkan sebuah bentuk div yang jelek, meski fungsional. Aku mulai menambahkan beberapa styling. Dari app / Action / HomeAction. pra :

  ruang nama App \ Action;gunakan Aerys \ Request;gunakan Aerys \ Response;kelas HomeAction{fungsi publik __invoke (Permintaan $ request,Respon $ response){$ js = campuran hasil ("js / app js");$ css = hasil campuran ("/ css / app css");$ response-> end ("
March 1, 2018