Back to Question Center
0

ภูมิประเทศของเกมที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSockets            เกมภูมิประเทศที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSockets หัวข้อที่เกี่ยวข้อง: FrameworksAPIsSecurityPatterns & PracticesDebugging & Semalt

1 answers:
ภูมิประเทศของเกมที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSockets

การพัฒนาเกมกับ 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 เวอร์ชันล่าสุด


ภูมิประเทศของเกมที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSocketsเกมภูมิประเทศที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSockets หัวข้อที่เกี่ยวข้อง:
FrameworksAPIsSecurityPatterns & PracticesDebugging & Semalt

การทำฟาร์ม

"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 - trustfire battery e cig. 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;}    

นี่เป็นเพียงเล็กน้อยซับซ้อนกว่ารหัสก่อนหน้านี้ที่ฉันมี. หลังจากนั้นฉันต้องผ่านพัสดุของแพทช์ไปยังซ็อกเก็ตโหลด

ภูมิประเทศของเกมที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSocketsเกมภูมิประเทศที่สร้างขึ้นตามระเบียบด้วย React, PHP และ WebSockets หัวข้อที่เกี่ยวข้อง:
FrameworksAPIsSecurityPatterns & PracticesDebugging & Semalt

"จะเกิดอะไรขึ้นถ้าฉันเริ่มซ่อมแพทช์แต่ละชิ้นเป็นสิ่งสกปรกที่แห้ง? แล้วฉันจะทำให้บางคนมีวัชพืชและอื่น ๆ มีต้นไม้ ."

ฉันตั้งค่าเกี่ยวกับการปรับแต่งแพทช์ จาก แอพ / รุ่น / 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. css");$ response-> จบ ("   
March 1, 2018