ภูมิประเทศของเกมที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSockets เกมภูมิประเทศที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSockets หัวข้อที่เกี่ยวข้อง: FrameworksAPIsSecurityPatterns & PracticesDebugging & Semalt
การพัฒนาเกมกับ PHP และ ReactJS
- การพัฒนาเกมกับ React และ PHP: เข้ากันได้ดีแค่ไหน?
- ภูมิประเทศของเกมที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSockets
สำหรับการแนะนำเชิงลึกที่มีคุณภาพสูงสำหรับ React คุณจะไม่สามารถไปยัง Wes Bos ที่เป็นนักพัฒนาเต็มรูปแบบของแคนาดาได้ ทดลองใช้หลักสูตรที่นี่และใช้รหัส SITEPOINT เพื่อรับ 25% และ และเพื่อช่วยสนับสนุน SitePoint
ครั้งสุดท้ายที่ผมเริ่มเล่าเรื่องที่ผมอยากจะเล่นเกม ฉันอธิบายวิธีที่ฉันตั้งค่าเซิร์ฟเวอร์ async PHP, ห่วงโซ่การสร้าง Laravel Mix, React front end และ WebSockets ที่เชื่อมต่อกันทั้งหมด ตอนนี้ให้ฉันบอกคุณเกี่ยวกับสิ่งที่เกิดขึ้นเมื่อฉันเริ่มสร้างกลศาสตร์เกมด้วย React, PHP และ WebSockets นี้ .
รหัสสำหรับส่วนนี้สามารถหาได้ที่ github co.th / assertchris-บทเรียน / sitepoint ทำเกม / ต้น / ส่วนที่ 2 ฉันได้ทดสอบด้วย PHP 7. 1
ใน Google Chrome เวอร์ชันล่าสุด
การทำฟาร์ม
"Semalt เริ่มต้นง่ายๆ เรามี 10 ถึง 10 ตารางของกระเบื้องที่เต็มไปด้วยสิ่งที่สร้างแบบสุ่ม "
ฉันตัดสินใจที่จะเป็นตัวแทนฟาร์มเป็นฟาร์ม
และแต่ละแผ่นเป็นชุด
จาก แอป / โมเดล / FarmModel ก่อน
:
namespace App \ Model;คลาสฟาร์ม{ความกว้าง $ ส่วนตัว{ได้รับ {return $ this-> width; }}ความสูงส่วนตัว ${ได้รับ {return $ this-> height; }}ฟังก์ชันสาธารณะ __construct (int $ width = 10,int $ height = 10){$ this-> width = $ width;$ this-> height = $ height;}}
ฉันคิดว่ามันน่าจะเป็นช่วงเวลาที่สนุกสนานในการลองใช้แมโครชั้นเรียนโดยประกาศคุณสมบัติส่วนตัวด้วย getters สาธารณะ สำหรับเรื่องนี้ผมต้องติดตั้ง pre / class-accessors
(ผ่าน composer ต้อง
)
หลังจากนั้นฉันเปลี่ยนรหัสซ็อกเก็ตเพื่อให้สามารถสร้างฟาร์มใหม่ได้ตามต้องการ จาก app / Socket / GameSocket ก่อน
:
namespace App \ Socket;ใช้ Aerys \ Request;ใช้ Aerys \ Response;ใช้ Aerys \ Websocket;ใช้ Aerys \ Websocket \ Endpoint;ใช้ Aerys \ Websocket \ Message;ใช้ App \ Model \ FarmModel;class GameSocket ใช้ Websocket{private $ farms = [];ฟังก์ชั่นสาธารณะ onData (int client_d $,Message $ message){$ body = yield $ message;if ($ body === "new farm") {ฟาร์ม $ = new FarmModel ;$ payload = json_encode (["ฟาร์ม" => ["width" => $ farm-> width,"height" => ฟาร์ม -> ความสูง,]]);ให้ $ this-> endpoint-> send ($ payload, $ clientId);$ this-> farms [$ clientId] = $ farm;}}ฟังก์ชั่น public onClose (int $ clientId,int $ code, string $ เหตุผล){ไม่มีการตั้งค่า ($ this-> การเชื่อมต่อ [$ ClientID]);ไม่มีการตั้งค่า ($ this-> ฟาร์ม [$ ClientID]);}// .}
ฉันสังเกตเห็นว่า GameSocket คล้ายกับเกมก่อนหน้านี้
(ยกเว้นการกระจายเสียงก้องฉันกำลังตรวจหาฟาร์มใหม่
และส่งข้อความกลับมาเท่านั้น ไปยังลูกค้าที่ถาม
"บางทีนี่อาจเป็นช่วงเวลาที่เหมาะสมที่จะได้รับรหัสที่ใช้ร่วมกันน้อยลง ฉันจะเปลี่ยนชื่อ องค์ประกอบ jsx
ถึง ฟาร์ม jsx
"
จาก สินทรัพย์ / js / ฟาร์ม jsx
:
import ตอบสนองจาก "react"ฟาร์มระดับขยายตอบสนอง. ซ็อกเก็ต = WebSocket ใหม่ ("ws: // 127. 0 0. 1: 8080 / ws")นี้. เบ้า. addEventListener ("ข้อความ" นี้ OnMessage)// DEBUGนี้. เบ้า. addEventListener ("open", => {นี้. เบ้า. ส่ง ( "ใหม่ฟาร์ม")})}}ฟาร์มเริ่มต้นการส่งออก
ในความเป็นจริงสิ่งเดียวที่ฉันเปลี่ยนคือการส่ง ฟาร์มใหม่
แทน สวัสดีโลก
ทุกสิ่งทุกอย่างก็เหมือนกัน ฉันต้องเปลี่ยนแอป รหัส jsx
จาก สินทรัพย์ / js / app jsx
:
import ตอบสนองจาก "react"นำเข้า ReactDOM จาก "react-dom"นำเข้าฟาร์มจาก "./ ฟาร์ม"ReactDOM แสดงผล ( <ฟาร์ม /> ,เอกสาร. querySelector (". app"))
มันไกลจากที่ฉันต้องการ แต่ใช้การเปลี่ยนแปลงเหล่านี้ฉันสามารถมองเห็น accessors ชั้นเรียนในการดำเนินการเช่นเดียวกับต้นแบบแบบคำขอ / รูปแบบการตอบสนองสำหรับการโต้ตอบ WebSocket ในอนาคต ฉันเปิดคอนโซลและเห็น {"ฟาร์ม": {"กว้าง": 10 "สูง": 10}}
"ยิ่งใหญ่!"
จากนั้นฉันสร้างชั้น Patch
เพื่อแสดงแต่ละกระเบื้อง ฉันคิดว่านี่คือเหตุผลที่ตรรกะของเกมจะเกิดขึ้นมาก จาก แอพ / รุ่น / PatchModel ก่อน
:
namespace App \ Model;คลาส PatchModel{ส่วนตัว x{get {return $ this-> x; }}ส่วนตัว $ y{get {return $ this-> y; }}ฟังก์ชันสาธารณะ __construct (int $ x, int $ y){$ this-> x = $ x;$ this-> y = $ y;}}
ฉันจะต้องสร้างแพทช์ให้มากที่สุดเท่าที่มีช่องว่างในฟาร์มใหม่
ฉันสามารถทำเช่นนี้เป็นส่วนหนึ่งของ FarmModel
การก่อสร้าง จาก แอป / โมเดล / FarmModel ก่อน
:
namespace App \ Model;class FarmModel{ความกว้าง $ ส่วนตัว{ได้รับ {return $ this-> width; }}ความสูงส่วนตัว ${ได้รับ {return $ this-> height; }}แพทช์ส่วนตัว ${รับ {return $ this-> patches; }}ฟังก์ชันสาธารณะ __construct ($ width = 10, $ height = 10){$ this-> width = $ width;$ this-> height = $ height;$ this-> createPatches ;}createPatches ฟังก์ชันเอกชน {สำหรับ ($ i = 0; $ i <$ this-> width; $ i ++) {$ this-> patches [$ i] = [];สำหรับ ($ j = 0; $ j <$ this-> height; $ j ++) {$ this-> patches [$ i] [$ j] =PatchModel ใหม่ ($ i, $ j);}}}}
สำหรับแต่ละเซลล์ฉันสร้างวัตถุ PatchModel แบบใหม่ สิ่งเหล่านี้เริ่มต้นง่ายๆ แต่พวกเขาต้องการองค์ประกอบแบบสุ่ม - วิธีปลูกต้นไม้วัชพืชดอกไม้ .อย่างน้อยก็เริ่มต้นด้วย จาก แอพ / รุ่น / PatchModel ก่อน
:
ฟังก์ชันเริ่มต้นของฟังก์ชันสาธารณะ (int $ width, int $ height,array $ patches){if (! $ this-> started && random_int (0, 10)> 7) {$ this-> started = true;กลับจริง;}กลับเท็จ;}
ฉันคิดว่าฉันจะเริ่มต้นโดยการสุ่มเพิ่มแพทช์ นี้ไม่ได้เปลี่ยนสถานะภายนอกของแพทช์ แต่ก็ให้ฉันวิธีการทดสอบว่าพวกเขาเริ่มต้นโดยฟาร์ม จาก แอป / โมเดล / FarmModel. สำหรับผู้เริ่มต้นฉันแนะนำคำหลัก
async
โดยใช้มาโคร คุณเห็นแอมป์จัดการคำหลัก
โดยแก้คำสัญญา ประเด็นก็คือเมื่อแอมป์เห็นคำหลัก
คำหลักจะถือว่าสิ่งที่เกิดขึ้นคือ Coroutine (ในกรณีส่วนใหญ่)
createPatches
ฟังก์ชันปกติและเพิ่งกลับ Coroutine จากนั้น แต่ที่เป็นเช่นชิ้นส่วนทั่วไปของรหัสที่ฉันอาจรวมถึงการสร้างแมโครพิเศษสำหรับมัน ในเวลาเดียวกันฉันสามารถเปลี่ยนรหัสที่ฉันได้ทำในส่วนก่อนหน้า จาก ผู้ช่วย ก่อน
: async function mix ($ path) {$ manifest = yield Amp \ File \ get (."/ public / mix-manifest. json");$ manifest = json_decode ($ manifest, true);if (isset ($ manifest [$ path])) {return $ manifest [$ path];}โยนข้อยกเว้นใหม่ ("{$ path} ไม่พบ");}
ก่อนหน้านี้ฉันต้องสร้างเครื่องกำเนิดไฟฟ้าแล้วห่อมันใหม่ Coroutine
:
ใช้ Amp \ Coroutine;function mix ($ path) {$ generator = => {$ manifest = yield Amp \ File \ get (."/ public / mix-manifest. json");$ manifest = json_decode ($ manifest, true);if (isset ($ manifest [$ path])) {return $ manifest [$ path];}โยนข้อยกเว้นใหม่ ("{$ path} ไม่พบ");};กลับ Coroutine ใหม่ ($ generator );}
PatchModel
สำหรับแต่ละ x
และ y
ในกริด เริ่มแรก createPatches
จากนั้นผมเริ่มต้นลูปอื่นเพื่อเรียกเมธอด เริ่มต้น
ในแต่ละแพทช์ ฉันจะทำสิ่งเหล่านี้ในขั้นตอนเดียวกัน แต่ฉันต้องการเริ่ม วิธีการของฉันเพื่อให้สามารถตรวจสอบแพทช์โดยรอบ นั่นหมายความว่าฉันจะต้องสร้างสิ่งเหล่านี้ทั้งหมดก่อน
ฉันยังเปลี่ยน FarmModel
เพื่อยอมรับ onGrowth
closure ความคิดคือการที่ฉันสามารถเรียกการปิดตัวว่าแพทช์เติบโตขึ้น (แม้ในช่วงบูตสตาร์ท)
ทุกครั้งที่แพทช์เติบโตขึ้นฉันจะรีเซ็ตตัวแปร $ change
นี้ยืนยันว่าแพทช์จะเติบโตต่อไปจนกว่าจะผ่านฟาร์มทั้งไม่มีการเปลี่ยนแปลง ฉันยังเรียกการปิดบัญชี ในการปิดหน้าต่าง ฉันต้องการอนุญาตให้ onGrowth
เป็นการปิดปกติหรือแม้กระทั่งการคืน Coroutine
นั่นเป็นเหตุผลที่ฉันต้องทำ createPatches
a async
onGrowth
coroutines สิ่งที่ซับซ้อนบิต แต่ฉันเห็นว่ามันเป็นสิ่งสำคัญสำหรับการอนุญาตให้มีการกระทำ async อื่น ๆ เมื่อแพทช์เติบโตขึ้น บางทีภายหลังฉันต้องการส่งข้อความซ็อกเก็ตและฉันสามารถทำเช่นนั้นได้ถ้า ผลผลิต
ทำงานภายใน onGrowth
ฉันสามารถให้ผลผลิต onGrowth
ถ้า createPatches
เป็นฟังก์ชัน async
และเนื่องจาก createPatches
เป็นฟังก์ชัน async
ฉันจะต้องให้ผลผลิตภายใน GameSocket
"มันง่ายที่จะได้รับการปิดโดยทุกสิ่งที่จำเป็นต้องเรียนรู้เมื่อทำแอพพลิเคชัน PHP แรก async Semalt ให้เร็วเกินไป! "
รหัสสุดท้ายที่ฉันต้องเขียนเพื่อตรวจสอบว่าการทำงานทั้งหมดอยู่ใน GameSocket
จาก app / Socket / GameSocket ก่อน
:
if ($ body === "new farm") {$ patches = [];ฟาร์ม $ = FarmModel ใหม่ (10, 10,(PatchModel $ patch) ใช้ (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,]);});yield $ farm-> createPatches ;$ payload = json_encode (["ฟาร์ม" => ["width" => $ farm-> width,"height" => ฟาร์ม -> ความสูง,]"patches" => $ patches,]);ให้ $ this-> endpoint-> send ($ payload, $ clientId);$ this-> farms [$ clientId] = $ farm;}
นี่เป็นเพียงเล็กน้อยซับซ้อนกว่ารหัสก่อนหน้านี้ที่ฉันมี. หลังจากนั้นฉันต้องผ่านพัสดุของแพทช์ไปยังซ็อกเก็ตโหลด
"จะเกิดอะไรขึ้นถ้าฉันเริ่มซ่อมแพทช์แต่ละชิ้นเป็นสิ่งสกปรกที่แห้ง? แล้วฉันจะทำให้บางคนมีวัชพืชและอื่น ๆ มีต้นไม้ ."
ฉันตั้งค่าเกี่ยวกับการปรับแต่งแพทช์ จาก แอพ / รุ่น / PatchModel ก่อน
:
ส่วนตัว $ เริ่ม = เท็จ;ส่วนตัว $ เปียก {รับ {return $ this-> wet?: false; }};private $ type {รับ {return $ this-> type?: "dirt"; }};เริ่มต้นฟังก์ชันสาธารณะ (int $ width, int $ height,array $ patches){if ($ this-> started) {กลับเท็จ;}if (random_int (0, 100) <90) {กลับเท็จ;}$ this-> started = true;$ this-> type = "weed";กลับจริง;}
ฉันเปลี่ยนลำดับของตรรกะรอบเล็กน้อยออกก่อนถ้าแพทช์ได้เริ่มต้นแล้ว ฉันยังลดโอกาสของการเจริญเติบโต หากไม่มีการออกก่อนหน้านี้เกิดขึ้นชนิดของแพทช์จะถูกเปลี่ยนเป็นวัชพืช
ฉันสามารถใช้ประเภทนี้เป็นส่วนหนึ่งของ payload ข้อความซ็อกเก็ต จาก app / Socket / GameSocket ก่อน
:
$ farm = new FarmModel (10, 10,(PatchModel $ patch) ใช้ (& $ patches) {array_push ($ patches, ["x" => $ patch-> x,"y" => $ patch-> y,"wet" => $ patch-> เปียก,"type" => $ patch-> type,]);});
การแสดงผลฟาร์ม
ถึงเวลาแล้วที่จะต้องแสดงฟาร์มโดยใช้กระบวนการตอบสนองที่ฉันได้ตั้งไว้ก่อนหน้านี้ ฉันได้รับความกว้าง
และ
ของฟาร์มดังนั้นฉันจึงสามารถทำให้ทุกสิ่งสกปรกแห้ง (ยกเว้นกรณีที่ควรปลูกวัชพืช) จาก สินทรัพย์ / js / app jsx
:
import ตอบสนองจาก "react"ฟาร์มระดับขยายตอบสนอง ตัวแทน{คอนสตรัค {ซุปเปอร์ นี้. onMessage = นี้ OnMessage ผูก (นี้)นี้. state = {"ฟาร์ม": {"width": 0,"ความสูง": 0,}"แพทช์": [],};}componentWillMount {นี้. ซ็อกเก็ต = WebSocket ใหม่ ("ws: // 127. 0 0. 1: 8080 / ws")นี้. เบ้า. addEventListener ("ข้อความ" นี้ OnMessage)// DEBUGนี้. เบ้า. addEventListener ("open", => {นี้. เบ้า. ส่ง ( "ใหม่ฟาร์ม")})}OnMessage (จ){ให้ข้อมูล = JSON แยกวิเคราะห์ (e. ข้อมูล);if (ข้อมูลฟาร์ม) {นี้. setState ({"ฟาร์ม": ข้อมูลฟาร์ม})}ถ้า (ข้อมูลแพทช์) {นี้. setState ({"แพทช์": ข้อมูลแพทช์})}}componentWillUnmount {นี้. เบ้า. removeEventListener (this. onMessage)นี้. ซ็อกเก็ต = null}render {ปล่อยแถว = []ให้ฟาร์ม = นี้ สถานะ. ฟาร์มให้ statePatches = นี้ สถานะ. แพทช์สำหรับ (ปล่อย y = 0; y <ฟาร์มความสูง; y ++) {ปล่อยแพทช์ = []สำหรับ (ให้ x = 0; x <ฟาร์มความกว้าง x ++) {ให้ className = "patch"statePatches forEach ((patch) => {if (patch. x === x && patch. y === y) {className + = "" + patch ชนิดถ้า (patch เปียก) {className + = "" + เปียก}}})แพทช์ ผลักดัน ( )}แถว ผลักดัน ( {} แพทช์ )}กลับ ( {แถว} )}}ฟาร์มเริ่มต้นการส่งออก
ฉันลืมที่จะอธิบายถึงสิ่งที่เคยทำมาก่อนหน้านี้ ฟาร์ม
คอมโพเนนต์การตอบสนองเป็นวิธีที่แตกต่างกันในการสร้างอินเทอร์เฟซ. ฉันสามารถใช้วิธีเช่น componentWillMount
และ componentWillUnmount
เป็นวิธีที่จะนำไปสู่จุดข้อมูลอื่น ๆ (เช่น WebSockets) และเมื่อฉันได้รับการอัปเดตผ่านทาง WebSocket ฉันจะอัพเดตสถานะของคอมโพเนนต์ตราบเท่าที่ฉันได้ตั้งค่าสถานะเริ่มต้นไว้ในตัวสร้าง
ส่งผลให้มีชุดที่น่าเกลียดน่ากลัวแม้ว่าจะมีการทำงานของ div ฉันตั้งค่าเกี่ยวกับการเพิ่มสไตล์บางอย่าง จาก แอป / แอ็คชัน / โฮมแอ็พ ก่อน
:
namespace App \ Action;ใช้ Aerys \ Request;ใช้ Aerys \ Response;คลาสโฮม{ฟังก์ชั่นสาธารณะ __invoke (Request $ request,การตอบสนอง $){$ js = yield mix ("/ js / app. js");$ css = yield mix ("/ css / app Source . css");$ response-> จบ ("