Plant เป็นประสบการณ์อ้างอิงที่ผู้เล่นสามารถปลูกและรดน้ำเมล็ดพันธุ์ได้ เพื่อให้พวกเขาสามารถเก็บเกี่ยวและขายพืชที่เกิดขึ้นในภายหลัง

โครงการนี้มุ่งเน้นไปที่กรณีการใช้งานทั่วไปที่คุณอาจพบเมื่อพัฒนาประสบการณ์ใน Roblox ในกรณีที่มีความเหมาะสม คุณจะพบหมายเหตุเกี่ยวกับการแลกเปลี่ยน ข้อตกลง และทำไมเลือกวิธีการต่างๆ ทำให้คุณสามารถตัดสินใจได้ดีที่สุดสำหรับประสบการณ์ของคุณเอง
รับไฟล์
- ไปที่ หน้าเพจประสบการณ์ Plant.
- คลิกที่ปุ่ม ⋯ และ แก้ไขในสตูดิโอ.
กรณีการใช้งาน
Plant ครอบคลุมกรณีการใช้งานดังต่อไปนี้:
- ข้อมูลเซสชันและความยืดหยุ่นของข้อมูลผู้เล่น
- การจัดการมุมมอง UI
- การเชื่อมต่อระหว่างไคลเอนต์และเซิร์ฟเวอร์
- ประสบการณ์ผู้ใช้ครั้งแรก (FTUE)
- การซื้อเงินตราแข็งและอ่อน
นอกจากนี้ โครงการนี้ยังแก้ปัญหาที่เฉพาะเจาะจงซึ่งสามารถใช้งานได้กับประสบการณ์มากมาย รวมถึง:
- การปรับแต่งพื้นที่ในโลเคชั่นที่เกี่ยวข้องกับผู้เล่น
- การจัดการความเร็วในการเคลื่อนที่ของตัวละครผู้เล่น
- การสร้างวัตถุที่จะติดตามตัวละครไปทั่ว
- การตรวจจับว่าส่วนใดของโลกที่ตัวละครอยู่ในนั้น
โปรดทราบว่ามีการใช้งานบางกรณีในประสบการณ์นี้ที่เล็กเกินไป โดยเฉพาะอย่างยิ่ง ไม่สามารถแสดงวิธีการแก้ปัญหาสำหรับความท้าทายในการออกแบบที่น่าสนใจได้ ซึ่งไม่ได้รับการครอบคลุม
โครงสร้างของโครงการ
การตัดสินใจครั้งแรกเมื่อสร้างประสบการณ์คือการตัดสินใจว่าโครงสร้าง โครงการ จะเป็นอย่างไร ซึ่งส่วนใหญ่รวมถึงการวางตำแหน่งอินสแตนซ์เฉพาะลงใน โมเดลข้อมูล และการจัดระเบียบและพิธีกรรมทางเข้าของโค้ดจากทั้งไคลเอนต์และเซิร์ฟเวอร์
โมเดลข้อมูล
ตารางต่อไปนี้อธิบายถึงบริการคอนเทนเนอร์ที่มีอยู่ในโมเดลข้อมูลที่อินสแตนซ์ถูกวางอยู่ในนั้น
| บริการ | ประเภทของอินสแตนซ์ |
|---|---|
| Workspace | ประกอบไปด้วยโมเดลสถิติที่แสดงถึงโลก 3 มิติ โดยเฉพาะส่วนของโลกที่ไม่เป็นของผู้เล่นใดๆ คุณไม่จำเป็นต้องสร้าง แปรรูป หรือทำลายอินสแตนซ์เหล่านี้ในขณะรันไทม์ ดังนั้นจึงเป็นเรื่องที่ยอมรับได้ที่จะปล่อยให้พวกเขาอยู่ที่นี่ ยังมี Folder ที่ว่างเปล่า ซึ่งจะมีโมเดลฟาร์มของผู้เล่นเพิ่มเข้าไปในขณะรันไทม์ |
| Lighting | เอฟเฟกต์บรรยากาศและแสง |
| ReplicatedFirst | ประกอบด้วยส่วนเล็กที่สุดของอินสแตนซ์ที่จำเป็นในการแสดงหน้าจอการโหลดและเริ่มต้นประสบการณ์ ยิ่งมีอินสแตนซ์ใน ReplicatedFirst มากเท่าไร ระยะเวลาการรอให้มันถูกโอนก็จะนานขึ้นก่อนที่โค้ดใน ReplicatedFirst จะรัน
|
| ReplicatedStorage | ทำหน้าที่เป็นคอนเทนเนอร์เก็บข้อมูลสำหรับอินสแตนซ์ทั้งหมดที่ต้องการเข้าถึงจากทั้งไคลเอนต์และเซิร์ฟเวอร์
|
| ServerScriptService | ประกอบไปด้วย Script ที่ทำหน้าที่เป็นจุดเริ่มต้นสำหรับโค้ดฝั่งเซิร์ฟเวอร์ทั้งหมดในโครงการ |
| ServerStorage | ทำหน้าที่เป็นคอนเทนเนอร์เก็บข้อมูลสำหรับอินสแตนซ์ทั้งหมดที่ไม่จำเป็นต้องถูกโอนให้กับไคลเอนต์
|
| SoundService | ประกอบด้วยวัตถุ Sound ที่ใช้สำหรับเอฟเฟกต์เสียงภายในประสบการณ์ ภายใต้ SoundService วัตถุ Sound เหล่านี้ไม่มีที่ตั้งและไม่ได้ถูกจำลองในพื้นที่ 3 มิติ |
จุดเข้าถึง
ส่วนใหญ่โครงการจะจัดระเบียบโค้ดภายใน ModuleScripts ที่สามารถนำเข้าได้ทั่วทั้งฐานโค้ด ModuleScripts เป็นโค้ดที่นำกลับมาใช้ใหม่ได้ แต่ไม่สามารถรันด้วยตัวเองได้; ต้องนำเข้าโดย Script หรือ LocalScript โครงการ Roblox หลายแห่งจะมีจำนวน Script และ LocalScript ที่มากมาย ซึ่งแต่ละรายการเกี่ยวข้องกับพฤติกรรมหรือระบบเฉพาะในประสบการณ์ จึงต้องสร้างจุดเข้าถึงหลายจุด
สำหรับไมโครเกม Plant ได้ใช้วิธีที่แตกต่างออกไปโดยใช้แค่ LocalScript เดียวที่เป็นจุดเริ่มต้นสำหรับโค้ดของไคลเอนต์ทั้งหมด และ Script เดียวที่เป็นจุดเริ่มต้นสำหรับโค้ดบนเซิร์ฟเวอร์ วิธีที่ถูกต้องสำหรับโครงการของคุณขึ้นอยู่กับความต้องการของคุณ แต่การมีจุดเข้าถึงเพียงจุดเดียวจะให้การควบคุมที่ดีกว่าต่อการดำเนินการตามลำดับ
รายการต่อไปนี้เป็นการอธิบายการเปรียบเทียบระหว่างทั้งสองวิธี:
- Script เดียวและ LocalScript เดียวครอบคลุมโค้ดของเซิร์ฟเวอร์และไคลเอนต์ตามลำดับ
- การควบคุมอันดับของระบบต่างๆ ที่เริ่มต้นได้ดีกว่าเพราะทุกโค้ดจะถูกกำหนดไว้จากสคริปต์เดียว
- สามารถส่งวัตถุด้วยการอ้างอิงระหว่างระบบได้
สถาปัตยกรรมระบบในระดับสูง
ระบบชั้นยอดในโครงการมีรายละเอียดดังต่อไปนี้ ระบบบางระบบมีความซับซ้อนมากกว่าระบบอื่น และในหลายกรณีความสามารถของพวกมันจะถูกจำลองผ่านลำดับชั้นของคลาสอื่น ๆ

แต่ละระบบเหล่านี้เป็น "ซิงเกิลตัน" นั่นหมายความว่ามันเป็นคลาสที่ไม่สามารถถูกสร้างอินสแตนซ์ได้ ซึ่งจะถูกเริ่มต้นโดยสคริปต์ start ที่เกี่ยวข้องจากทั้งฝั่งไคลเอนต์และเซิร์ฟเวอร์ คุณสามารถอ่านข้อมูลเพิ่มเติมเกี่ยวกับ รูปแบบซิงเกิลตัน ได้ในภายหลังในคู่มือนี้
เซิร์ฟเวอร์
ระบบที่มีการเชื่อมต่อกับเซิร์ฟเวอร์มีดังนี้
| ระบบ | คำอธิบาย |
|---|---|
| เครือข่าย |
|
| PlayerDataServer |
|
| ตลาด |
|
| CollisionGroupManager |
|
| FarmManagerServer |
|
| PlayerObjectsContainer |
|
| TagPlayers |
|
| FtueManagerServer |
|
| CharacterSpawner |
|
ไคลเอนต์
ระบบที่เชื่อมต่อกับไคลเอนต์มีดังนี้
| ระบบ | คำอธิบาย |
|---|---|
| เครือข่าย |
|
| PlayerDataClient |
|
| MarketClient |
|
| LocalWalkJumpManager |
|
| FarmManagerClient |
|
| UISetup |
|
| FtueManagerClient |
|
| CharacterSprint |
|
การสื่อสารระหว่างไคลเอนต์และเซิร์ฟเวอร์
ประสบการณ์ Roblox ส่วนใหญ่เกี่ยวข้องกับการสื่อสารบางอย่างระหว่างไคลเอนต์และเซิร์ฟเวอร์ ซึ่งอาจรวมถึงการที่ไคลเอนต์ขอให้เซิร์ฟเวอร์ทำการกระทำบางอย่าง และเซิร์ฟเวอร์ทำการสืบพันธุ์การอัปเดตไปยังไคลเอนต์
ในโครงการนี้ การสื่อสารระหว่างไคลเอนต์และเซิร์ฟเวอร์ถูกเก็บไว้ให้เป็นมาตรฐานทั่วไปที่สุดโดยการจำกัดการใช้วัตถุ RemoteEvent และ RemoteFunction เพื่อลดจำนวนกฎพิเศษที่ต้องติดตาม โครงการนี้ใช้วิธีการดังต่อไปนี้ตามลำดับของความนิยม:
- การสืบพันธุ์ผ่าน ระบบข้อมูลผู้เล่น.
- การสืบพันธุ์ผ่าน คุณสมบัติ.
- การสืบพันธุ์ผ่าน แท็ก.
- การส่งข้อความ โดยตรง ผ่านโมดูล Network.
การสืบพันธุ์ผ่านระบบข้อมูลผู้เล่น
ระบบข้อมูลผู้เล่น ช่วยให้สามารถเชื่อมโยงข้อมูลกับผู้เล่นซึ่งยั่งยืนระหว่างเซสชันบันทึก ระบบนี้มีการสืบพันธุ์จากไคลเอนต์ไปยังเซิร์ฟเวอร์และชุด API ที่สามารถใช้เพื่อค้นหาข้อมูลและสมัครรับข้อมูลการเปลี่ยนแปลง ทำให้เหมาะสำหรับการสืบพันธุ์การเปลี่ยนแปลงของสถานะผู้เล่นจากเซิร์ฟเวอร์ไปยังไคลเอนต์
ตัวอย่างเช่น แทนที่จะยิง UpdateCoins RemoteEvent เพื่อบอกไคลเอนต์ว่ามีเหรียญกี่เหรียญ คุณสามารถเรียกใช้คำสั่งต่อไปนี้และปล่อยให้ไคลเอนต์สมัครรับข้อมูลมันผ่านเหตุการณ์ PlayerDataClient.updated
PlayerDataServer.setValue(player, "coins", 5)
แน่นอนว่าวิธีนี้จะมีประโยชน์เพียงในการสืบพันธุ์จากเซิร์ฟเวอร์ไปยังไคลเอนต์และสำหรับค่าใดๆ ที่คุณต้องการให้ยั่งยืนระหว่างเซสชัน แต่พบว่ามีความหมายในกรณีที่น่าประหลาดใจในโครงการ เช่น:
- ขั้นตอน FTUE ปัจจุบัน
- อินเวนทอรีของผู้เล่น
- จำนวนเหรียญที่ผู้เล่นมี
- สถานะของฟาร์มของผู้เล่น
การสืบพันธุ์ผ่านคุณสมบัติ
ในสถานการณ์ที่เซิร์ฟเวอร์ต้องการที่จะสืบพันธุ์ค่าที่กำหนดเองให้กับไคลเอนต์ที่เฉพาะเจาะจงกับ Instance ที่กำหนด คุณสามารถใช้ คุณสมบัติได้ Roblox จะทำการสืบพันธุ์ค่าคุณสมบัติโดยอัตโนมัติ ดังนั้นคุณไม่จำเป็นต้องสร้างเส้นทางโค้ดใดๆ เพื่อทำการสืบพันธุ์ข้อมูลที่เกี่ยวข้องกับวัตถุ ข้อดีอีกประการคือการสืบพันธุ์นี้จะเกิดขึ้นพร้อมกับอินสแตนซ์เอง
นี่เป็นสิ่งที่มีประโยชน์โดยเฉพาะสำหรับอินสแตนซ์ที่ถูกสร้างขึ้นเมื่อไม่ใช้งาน เนื่องจากคุณสมบัติที่ตั้งไว้บนอินสแตนซ์ใหม่ก่อนที่จะถูกเป็นพ่อแม่ให้กับโมเดลข้อมูลจะทำการสืบพันธุ์ตามอาโทธมิกกับอินสแตนซ์เอง ซึ่งเป็นการหลีกเลี่ยงการเขียนโค้ดเพื่อ "รอ" ข้อมูลเพิ่มเติมที่ต้องถูกสืบพันธุ์ผ่าน RemoteEvent หรือ StringValue
คุณยังสามารถอ่านคุณสมบัติจากโมเดลข้อมูลได้โดยตรงจากทั้งไคลเอนต์หรือเซิร์ฟเวอร์ โดยใช้วิธีการ GetAttribute() และสมัครรับข้อมูลการเปลี่ยนแปลงด้วยวิธีการ GetAttributeChangedSignal() ในโครงการ Plant วิธีนี้ได้ใช้สำหรับการสืบพันธุ์สถานะปัจจุบันของพืชไปยังไคลเอนต์
การสืบพันธุ์ผ่านแท็ก
CollectionService ช่วยให้คุณสามารถระบุแท็กสตริงที่ใช้กับ Instance ได้ ซึ่งมีความเป็นประโยชน์ในการจัดประเภทอินสแตนซ์และสืบพันธุ์การจัดประเภทนั้นไปยังไคลเอนต์
ตัวอย่างเช่น แท็ก CanPlant จะถูกใช้กับเซิร์ฟเวอร์เพื่อตั้งสัญญาณให้ไคลเอนต์ทราบว่ากระถางที่กำหนดสามารถรับพืชได้
ส่งข้อความโดยตรงผ่านโมดูลเครือข่าย
ในสถานการณ์ที่ไม่มีตัวเลือกก่อนหน้านี้ใดๆ ที่สามารถใช้ได้ คุณสามารถใช้การเรียกเครือข่ายแบบกำหนดเองผ่านโมดูล Network นี่เป็นตัวเลือกเดียวในโครงการที่อนุญาตให้มีการสื่อสารจากไคลเอนต์ไปยังเซิร์ฟเวอร์ และเหมาะสำหรับการถ่ายทอดการขอจากไคลเอนต์และการตอบกลับจากเซิร์ฟเวอร์
Plant ใช้การเรียกเครือข่ายโดยตรงสำหรับคำขอจากไคลเอนต์หลากหลาย เช่น:
- รดน้ำพืช
- ปลูกเมล็ดพันธุ์
- ซื้อรายการ
ข้อเสียของวิธีนี้คือแต่ละข้อความแต่ละข้อความต้องการการกำหนดค่าที่กำหนดเองซึ่งอาจส่งผลให้ความซับซ้อนของโครงการเพิ่มขึ้น ถึงแม้จะหลีกเลี่ยงได้มากที่สุดโดยเฉพาะในการสื่อสารระหว่างเซิร์ฟเวอร์ไปยังไคลเอนต์
คลาสและซิงเกิลตัน
คลาสในโครงการ Plant เช่นเดียวกับอินสแตนซ์ใน Roblox สามารถถูกสร้างขึ้นและทำลายได้ รูปแบบคลาสของมันถูกสร้างแรงบันดาลใจจากแนวทาง Lua แบบประดับของ การเขียนโปรแกรมเชิงวัตถุ โดยมีการเปลี่ยนแปลงบางอย่างเพื่อรองรับการสนับสนุน การตรวจสอบประเภทเข้มงวด
การสร้างอินสแตนซ์
หลายคลาสในโครงการนี้เกี่ยวข้องกับหนึ่งหรือมากกว่าหนึ่ง Instances วัตถุของคลาสที่กำหนดจะถูกสร้างขึ้นโดยใช้วิธีการ new() ซึ่งสอดคล้องกับวิธีการที่มีการสร้างอินสแตนซ์ใน Roblox โดยใช้ Instance.new()
รูปแบบนี้มักถูกใช้สำหรับวัตถุซึ่งมีคลาสที่มีตัวแทนทางกายภาพในโมเดลข้อมูลและคลาสจะขยายความสามารถของมัน ตัวอย่างที่ดีคือ BeamBetween ซึ่งสร้างวัตถุ Beam ระหว่าง Attachment สองอันและรักษาให้การยึดเหล่านั้นอยู่ในทิศทางที่ให้ลำแสงหันขึ้นข้างบน อินสแตนซ์เหล่านี้สามารถถูกคัดลอกมาจากเวอร์ชันที่สร้างไว้ล่วงหน้าใน ReplicatedStorage หรือส่งมอบในการเรียก new() เป็นอาร์กิวเมนต์และเก็บในวัตถุใต้ self
อินสแตนซ์ที่เกี่ยวข้อง
ตามที่กล่าวไว้ข้างต้น หลายคลาสในโครงการนี้มีตัวแทนในโมเดลข้อมูล ซึ่งเป็นอินสแตนซ์ที่เชื่อมโยงกับคลาสและถูกปรับแต่งโดยมัน
แทนที่จะสร้างอินสแตนซ์เหล่านี้เมื่อมีการสร้างวัตถุของคลาส โค้ดจะเลือกใช้วิธีการ Clone() เพื่อสร้างสำเนาของ Instance ที่จัดเก็บไว้ภายใต้ ReplicatedStorage หรือ ServerStorage แม้ว่าจะสามารถทำการจัดระเบียบคุณสมบัติของอินสแตนซ์เหล่านี้และสร้างพวกเขาจากศูนย์ในฟังก์ชัน new() ของคลาส แต่นั่นจะทำให้การแก้ไขวัตถุเป็นเรื่องยากและทำให้ยากสำหรับผู้อ่านในการทำความเข้าใจ นอกจากนี้ การคัดลอกอินสแตนซ์มักจะเป็นการดำเนินการที่รวดเร็วกว่าในการสร้างอินสแตนซ์ใหม่และปรับแต่งคุณสมบัติของมันในระหว่างรันไทม์
การรวมกัน
แม้ว่าการสืบทอดจะเป็นไปได้ใน Luau โดยใช้ เมตาเทเบิล แต่โครงการนี้เลือกที่จะอนุญาตให้คลาสขยายซึ่งกันและกันผ่าน การรวมกัน เมื่อรวมคลาสกัน การ "เด็ก" จะถูกสร้างขึ้นในวิธีการ new() ของคลาสและจะถูกนำไปรวมเป็นสมาชิกภายใต้ self
สำหรับตัวอย่างในทางปฏิบัติ ดูที่คลาส CloseButton ซึ่งห่อหุ้มกับคลาส Button
การทำความสะอาด
คล้ายกับวิธีที่ Instance สามารถถูกทำลายด้วยวิธีการ Destroy() คลาสที่สามารถถูกสร้างอินสแตนซ์ก็สามารถถูกทำลายได้เช่นกัน วิธีการทำลายสำหรับคลาสในโครงการคือ destroy() โดยใช้ 'd' ตัวพิมพ์เล็กเพื่อให้สอดคล้องกับ camelCase ในความสอดคล้องของกระบวนการของโค้ดทั้งหมด และเพื่อแยกแยะระหว่างคลาสในโครงการและอินสแตนซ์ของ Roblox
บทบาทของวิธีการ destroy() คือทำลายอินสแตนซ์ใดๆ ที่สร้างโดยวัตถุ ตัดการเชื่อมต่อการเชื่อมต่อใดๆ และเรียกใช้ destroy() กับวัตถุเด็ก นี่เป็นสิ่งที่สำคัญมากสำหรับการเชื่อมต่อ เพราะอินสแตนซ์ที่มีการเชื่อมต่อที่เปิดใช้งานจะไม่ได้รับการทำความสะอาดโดยการเก็บขยะของ Luau แม้ว่าจะไม่มีการอ้างอิงไปยังอินสแตนซ์หรือการเชื่อมต่อกับอินสแตนซ์ก็ตาม
ซิงเกิลตัน
ซิงเกิลตัน ตามชื่อบอกคือ คลาสที่สามารถมีวัตถุมีแค่หนึ่งตัวเท่านั้น ซึ่งเป็นสิ่งที่เทียบเท่ากับ บริการ ของ Roblox แทนที่จะเก็บการอ้างอิงถึงวัตถุซิงเกิลตันและส่งผ่านในโค้ด Luau โครงการ Plant ใช้ประโยชน์จากความจริงที่ว่าการเรียก ModuleScript จะเก็บค่าที่ส่งกลับไว้ในแคช ซึ่งหมายความว่าการเรียก ModuleScript ซิงเกิลตันเดียวกันจากที่แตกต่างกันจะส่งคืนวัตถุที่เหมือนกันนั่นเอง ข้อยกเว้นเพียงอย่างเดียวของกฎนี้คือถ้าสภาพแวดล้อมที่แตกต่าง (ไคลเอนต์หรือเซิร์ฟเวอร์) เข้าถึง ModuleScript
ซิงเกิลตันมีการแยกแยะจากคลาสที่สามารถสร้างอินสแตนซ์ได้โดยไม่มีวิธีการ new() แต่จะมีการส่งคืนวัตถุพร้อมกับวิธีการและสถานะโดยตรงผ่าน ModuleScript เนื่องจากซิงเกิลตันไม่ถูกสร้างอินสแตนซ์ซึ่งจะไม่ใช้ไวยากรณ์ self และวิธีการจะถูกเรียกด้วยจุด (.) แทนการใช้เครื่องหมายสองจุด (:)
การตรวจสอบประเภทเข้มงวด
Luau รองรับการตรวจสอบประเภทแบบค่อยเป็นค่อยไป ซึ่งหมายความว่าคุณสามารถเพิ่มการกำหนดประเภทแบบเลือกได้ในบางหรือทั้งหมดของโค้ดในโครงการนี้การตรวจสอบประเภท 'เข้มงวด' ถูกใช้สำหรับทุกสคริปต์ ซึ่งเป็นตัวเลือกที่มีการตรวจสอบที่ไม่อนุญาตมากที่สุดสำหรับเครื่องมือ การวิเคราะห์สคริปต์ ของ Roblox และมีโอกาสตัดจับข้อผิดพลาดประเภทก่อนการรัน
รูปแบบการคลาสชนิด
แนวทางที่สร้างขึ้นในการสร้างคลาสใน Lua นั้น ได้รับการบันทึกไว้อย่างดี แต่ไม่เหมาะสำหรับการตรวจสอบประเภทที่เข้มงวดใน Luau การเข้าถึงประเภทของคลาสที่ง่ายที่สุดใน Luau คือวิธีการ typeof():
type ClassType = typeof(Class.new())
วิธีนี้ใช้ได้ แต่จะไม่เป็นประโยชน์เมื่อคลาสของคุณถูกเรียกใช้ด้วยค่าที่มีอยู่เฉพาะในระหว่างรันไทม์ เช่น Player นอกจากนี้ สมมติฐานที่ทำในไวยากรณ์คลาสที่เป็นอัตลักษณ์ Lua คือการประกาศวิธีการในคลาส self จะต้องเป็นอินสแตนซ์ที่เป็นของคลาสนั้น ๆ เสมอ ซึ่งไม่ใช่สมมติฐานที่เครื่องจักรตรวจสอบประเภทสามารถทำได้
เพื่อรองรับการตรวจสอบประเภทที่เข้มงวด โครงการ Plant ใช้โซลูชันที่แตกต่างจากไวยากรณ์คลาส Luau แบบอัตลักษณ์ในหลายกรณี บางประการอาจรู้สึกว่าไม่เป็นสัญชาตญาณ:
- การกำหนดของ self จะถูกทำซ้ำทั้งในคำประกาศประเภทและในตัวสร้าง นั่นจะเพิ่มภาระการบำรุงรักษาแต่จะมีการแจ้งเตือนหากการกำหนดทั้งสองนั้นหลุดออกจากกัน
- วิธีการของคลาสจะถูกประกาศด้วยจุด ดังนั้น self จะถูกประกาศชัดเจนว่าเป็นประเภท ClassType วิธีการยังคงถูกเรียกด้วยเครื่องหมายโคลอนตามปกติ
--!strict
local MyClass = {}
MyClass.__index = MyClass
export type ClassType = typeof(setmetatable(
{} :: {
property: number,
},
MyClass
))
function MyClass.new(property: number): ClassType
local self = {
property = property,
}
setmetatable(self, MyClass)
return self
end
function MyClass.addOne(self: ClassType)
self.property += 1
end
return MyClass
การแคสประเภทหลังการตรวจสอบเงื่อนไขเชิงตรรกะ
ในขณะที่เขียน ประเภทของค่าไม่ได้มีการแคบลงหลังจากการตรวจสอบเงื่อนไขเชิงตรรกะ ตัวอย่างเช่น หลังจากการตรวจสอบเงื่อนไขด้านล่าง ประเภทของ optionalParameter จะไม่ถูกแคบลงไปที่ number
--!strict
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
print(optionalParameter + 1)
end
เพื่อชดเชยสิ่งนี้ ตัวแปรใหม่จะถูกสร้างขึ้นหลังจากการตรวจสอบเหล่านี้โดยมีการแคสต์ประเภทของมันอย่างชัดเจน
--!strict
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
local parameter = optionalParameter :: number
print(parameter + 1)
end
การสำรวจโครงสร้างข้อมูลโมเดล
ในบางกรณี ฐานโค้ดต้องเดินทางผ่านลำดับชั้นของโมเดลข้อมูลที่เป็นต้นไม้ของวัตถุที่ถูกสร้างขึ้นเมื่อไม่ใช้งาน ซึ่งนำเสนอความท้าทายที่น่าสนใจสำหรับการตรวจสอบประเภท ในขณะนี้ยังไม่สามารถกำหนดลำดับชั้นของโมเดลข้อมูลแบบทั่วไปเป็นประเภทได้ โดยผลลัพธ์คือบางกรณีที่ข้อมูลประเภทเพียงอย่างเดียวที่มีให้สำหรับโครงสร้างโมเดลข้อมูลคือประเภทของอินสแตนซ์ระดับบนสุด
วิธีการลดปัญหานี้คือการแคสต์ไปที่ any และจากนั้น refine เช่น:
local function enableVendor(vendor: Model)
local zonePart: BasePart = (vendor :: any).ZonePart
end
ปัญหากับวิธีการนี้คือมันส่งผลกระทบต่อการอ่านที่ไม่สามารถไล่นำได้ ในทางกลับกัน โครงการใช้โมดูลทั่วไปที่เรียกว่า getInstance สำหรับสำรวจลำดับชั้นของโมเดลข้อมูลที่ยิงแคสต์ไปที่ any ภายใน
local function enableVendor(vendor: Model)
local zonePart: BasePart = getInstance(vendor, "ZonePart")
end
เมื่อความเข้าใจของเครื่องทดสอบประเภทเกี่ยวกับโมเดลข้อมูลพัฒนาขึ้น หลายๆ รูปแบบเช่นนี้อาจไม่จำเป็นอีกต่อไป
ส่วนติดต่อผู้ใช้
Plant รวมถึงส่วนติดต่อผู้ใช้ 2D ที่ซับซ้อนและง่าย ซึ่งรวมถึงสิ่งของในมุมมองแบบไม่โต้ตอบเช่นเคาน์เตอร์เหรียญและเมนูแบบโต้ตอบที่ซับซ้อนเหมือนร้านค้า
วิธีการ UI
คุณสามารถเปรียบเทียบ Roblox UI กับ DOM ของ HTML ได้อย่างหลวมๆ เนื่องจากมันเป็นลำดับชั้นของวัตถุที่อธิบายสิ่งที่ผู้ใช้ควรเห็น วิธีการในการสร้างและอัปเดต UI ของ Roblox จะถูกแบ่งออกเป็นประเภท เชิงบังคับ และ เชิงประกาศ
| วิธีการ | ข้อดีและข้อเสีย |
|---|---|
| เชิงบังคับ | ในวิธีการเชิงบังคับ UI จะถูกมองว่าเป็นลำดับชั้นอินสแตนซ์อื่นๆ ใน Roblox โครงสร้าง UI จะถูกสร้างขึ้นก่อนรันไทม์ใน Studio และเพิ่มลงในโมเดลข้อมูล โดยทั่วไปอยู่โดยตรงใน StarterGui จากนั้นเมื่อรันไทม์ โค้ดจะปรับแต่งชิ้นส่วนเฉพาะของ UI เพื่อสะท้อนสถานะที่ผู้สร้างต้องการ วิธีการนี้มีข้อดีบางประการ คุณสามารถสร้าง UI จากพื้นฐานใน Studio และเก็บไว้ในโมเดลข้อมูล นี่เป็นประสบการณ์การแก้ไขที่เรียบง่ายและมองเห็นได้ซึ่งสามารถเร่งการสร้าง UI ได้ เนื่องจากโค้ด UI แบบเชิงบังคับจะเน้นไปที่สิ่งที่ต้องเปลี่ยน ทำให้การเปลี่ยนแปลงง่ายๆ ของ UI ทำได้ง่าย ข้อเสียที่ชัดเจนคือ วิธีการเชิงบังคับต้องการให้สถานะถูกนำมาใช้เป็นการแปลงค่า ซึ่งตัวแทนที่ซับซ้อนไม่สามารถพบและจัดการได้อย่างง่ายดาย มักเกิดข้อผิดพลาดเมื่อพัฒนาข้อความ UI แบบเชิงบังคับ โดยเฉพาะเมื่อสถานะและ UI อาจตกตะกอนโดยการอัปเดตหลายๆ ตัวที่มีปฏิสัมพันธ์ในลำดับที่ไม่คาดคิด อีกหนึ่งความท้าทายในการใช้วิธีการเชิงบังคับคือมันยากที่จะแบ่งย่อย UI ออกเป็นส่วนประกอบที่มีความหมายซึ่งสามารถประกาศได้ครั้งเดียวและนำกลับมาใช้ใหม่ได้ เนื่องจากทั้งต้นไม้ UI จะถูกประกาศที่เวลาการแก้ไข รูปแบบทั่วไปอาจถูกทำซ้ำในหลายส่วนของโมเดลข้อมูล |
| เชิงประกาศ | ในวิธีการเชิงประกาศ สถานะที่ต้องการของอินสแตนซ์ UI จะถูกประกาศอย่างชัดเจน และการนำเสนอที่มีประสิทธิภาพของสถานะนี้จะถูกซ่อนอยู่ภายใต้ไลบรารี เช่น Roact หรือ Fusion ข้อดีของวิธีการนี้คือการนำเสนอสถานะกลายเป็นเรื่องง่าย คุณเพียงอธิบายว่าคุณต้องการให้ UI ของคุณมีลักษณะอย่างไร ซึ่งทำให้การระบุและแก้ไขข้อผิดพลาดง่ายขึ้นอย่างมีนัยสำคัญ ข้อเสียหลักคือการต้องประกาศทั้งต้นไม้ UI ในโค้ด ไลบรารีอย่าง Roact และ Fusion มีไวยากรณ์ที่จะทำให้สิ่งนี้ง่ายขึ้น แต่ก็ยังเป็นกระบวนการที่ใช้เวลานานและเป็นประสบการณ์การแก้ไขที่ไม่ใช่สัญชาตญาณในการประกอบ UI |
Plant ใช้แนวทาง เชิงบังคับ โดยมีแนวคิดว่าสิ่งต่าง ๆ โดยตรงจะให้ภาพรวมที่มีประสิทธิภาพมากขึ้นเกี่ยวกับวิธีการสร้างและปรับเปลี่ยน UI ใน Roblox ซึ่งจะไม่สามารถทำได้ด้วยแนวทางเชิงประกาศ โครงสร้างและตรรกะของ UI ที่ถูกทำซ้ำบ้างก็ถูกวางแผนเพื่อหลีกเลี่ยงกับดักที่พบมากในรูปแบบ UI แบบเชิงบังคับ
สถาปัตยกรรมระดับสูง

เลเยอร์และส่วนประกอบ
ใน Plant โครงสร้าง UI ทั้งหมดจะเป็น Layer หรือ Component.
- Layer ถูกกำหนดเป็นซิงเกิลตันกลุ่มบนสุดที่ห่อหุ้มโครงสร้าง UI ที่ถูกสร้างล่วงหน้าใน ReplicatedStorage เลเยอร์อาจประกอบด้วยส่วนประกอบจำนวนหนึ่ง หรืออาจมีตรรกะของตนเองอยู่ภายใน ตัวอย่างของเลเยอร์คือเมนูอินเวนทอรีหรือจำนวนเหรียญที่ชี้อยู่ในมุมไอคอน
- Component เป็นส่วนประกอบ UI ที่สามารถนำกลับมาใช้ใหม่ได้ เมื่อมีการสร้างวัตถุส่วนประกอบใหม่ มันจะสร้างสำเนาคลอดของแม่แบบที่ถูกสร้างล่วงหน้าจาก ReplicatedStorage ส่วนประกอบอาจมีการรวมรวมส่วนประกอบอื่นในตัวมันเอง ตัวอย่างของส่วนประกอบคือคลาสปุ่มทั่วไปหรือแนวคิดของรายการของรายการ
การจัดการมุมมอง
ปัญหาการจัดการ UI ที่พบบ่อยคือการจัดการมุมมอง โครงการนี้มีช่วงเมนูและเคาน์เตอร์แบบ HUD ที่หลากหลาย บางประเภทฟังการป้อนข้อมูลจากผู้ใช้ และการจัดการอย่างระมัดระวังว่าสิ่งเหล่านี้จะมองเห็นได้หรือเปิดใช้งานเมื่อใดนั้นจึงมีความจำเป็น
Plant เข้าหารปัญหานี้ด้วยระบบ UIHandler ซึ่งจัดการว่าเลเยอร์ UI จะต้องปรากฏหรือไม่ปรากฏ ทุกเลเยอร์ UI ในประสบการณ์จะถูกจัดประเภทเป็น HUD หรือ Menu และสถานะการแสดงของพวกเขาถูกจัดการด้วยกฎดังนี้
- สถานะเปิดใช้งานของเลเยอร์ Menu และ HUD สามารถเปลี่ยนแปลงได้
- เลเยอร์ HUD ที่เปิดใช้งานจะแสดงเพียงเมื่อไม่มีเลเยอร์ Menu ที่เปิดใช้งาน
- เลเยอร์ Menu ที่เปิดใช้งานจะถูกเก็บไว้ในสแต็ค และเลเยอร์ Menu เพียงหนึ่งเลเยอร์จะปรากฏขึ้นในแต่ละครั้ง เมื่อมีการเปิดเลเยอร์ Menu มันจะถูกแทรกเข้ามาข้างหน้าในสแต็คและแสดง เมื่อเลเยอร์ Menu ถูกปิด จะถูกลบออกจากสแต็คและเลเยอร์ Menu ที่เปิดใช้งานถัดไปในคิวจะถูกแสดง
วิธีนี้เป็นวิธีที่เข้าใจได้ง่ายเพราะมันอนุญาตให้มีการออกแบบเมนูให้มีประวัติ หากเมนูหนึ่งถูกเปิดจากเมนูอีกอันหนึ่ง การปิดเมนูใหม่จะทำให้เมนูเก่าเปิดขึ้นมาอีกครั้ง
ซิงเกิลตันเลเยอร์ UI จะลงทะเบียนตัวเองกับ UIHandler และจะได้รับสัญญาณที่ส่งเมื่อสถานะการมองเห็นของมันควรมีการเปลี่ยนแปลง
การอ่านเพิ่มเติม
จากภาพรวมที่ละเอียดเกี่ยวกับโครงการ Plant คุณอาจต้องการสำรวจแนวทางดังต่อไปนี้ซึ่งมีความลึกซึ้งเกี่ยวกับแนวคิดและหัวข้อที่เกี่ยวข้อง
- แบบจำลองไคลเอนต์-เซิร์ฟเวอร์ — ภาพรวมของแบบจำลองไคลเอนต์-เซิร์ฟเวอร์ใน Roblox
- เหตุการณ์และการเรียกกลับทางไกล — ทั้งหมดเกี่ยวกับเหตุการณ์เครือข่ายทางไกลและการเรียกกลับสำหรับการสื่อสารข้ามพรมแดนระหว่างไคลเอนต์และเซิร์ฟเวอร์
- UI — รายละเอียดเกี่ยวกับวัตถุส่วนติดต่อผู้ใช้และการออกแบบใน Roblox