โครงการอ้างอิงพืช

*เนื้อหานี้แปลโดยใช้ AI (เวอร์ชัน Beta) และอาจมีข้อผิดพลาด หากต้องการดูหน้านี้เป็นภาษาอังกฤษ ให้คลิกที่นี่

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

แบนเนอร์โครงการพืช

โครงการนี้มุ่งเน้นไปที่กรณีการใช้งานทั่วไปที่คุณอาจพบเมื่อพัฒนาเกมบน Roblox ซึ่งเมื่อมีความเหมาะสม คุณจะพบหมายเหตุเกี่ยวกับการแลกเปลี่ยน ข้อตกลง และเหตุผลของการเลือกการดำเนินการต่างๆ เพื่อให้คุณสามารถตัดสินใจได้ดีที่สุดสำหรับเกมของคุณเอง

รับไฟล์

  1. คลิกที่ปุ่ม และ แก้ไขใน Studio

กรณีการใช้งาน

Plant ครอบคลุมกรณีการใช้งานดังต่อไปนี้:

  • การเก็บข้อมูลเซสชันและข้อมูลผู้เล่น
  • การจัดการมุมมอง UI
  • การเชื่อมต่อระหว่างไคลเอนต์และเซิร์ฟเวอร์
  • ประสบการณ์ผู้ใช้ครั้งแรก (FTUE)
  • การซื้อสกุลเงินแข็งและอ่อน

นอกจากนี้ โครงการนี้ยังแก้ปัญหาชุดปัญหาที่แคบกว่าซึ่งใช้ได้กับหลายเกม รวมถึง:

  • การปรับแต่งพื้นที่ในสถานที่ที่เกี่ยวข้องกับผู้เล่น
  • การจัดการความเร็วในการเคลื่อนที่ของตัวละครผู้เล่น
  • การสร้างวัตถุที่ติดตามตัวละครไปรอบๆ
  • การตรวจจับว่าส่วนใดของโลกที่ตัวละครอยู่

โปรดทราบว่ามีกระบวนการใช้งานหลายอย่างในเกมนี้ที่เล็กเกินไป แคบเกินไป หรือไม่แสดงให้เห็นถึงการแก้ปัญหาที่น่าสนใจ; เหล่านี้จะไม่ได้รับการกล่าวถึง

โครงสร้างโครงการ

การตัดสินใจครั้งแรกเมื่อสร้างเกมคือการตัดสินใจว่าจะจัดโครงสร้าง โครงการ อย่างไร ซึ่งหลักๆ จะรวมถึงการวางตำแหน่งของอินสแตนซ์เฉพาะใน โมเดลข้อมูล และวิธีการจัดระเบียบและโครงสร้างจุดเริ่มต้นสำหรับทั้งโค้ดไคลเอนต์และเซิร์ฟเวอร์

โมเดลข้อมูล

ตารางต่อไปนี้อธิบายว่าบริการคอนเทนเนอร์ใดในโมเดลข้อมูลที่อินสแตนซ์ถูกวางไว้

บริการประเภทของอินสแตนซ์
Workspace

ประกอบด้วยโมเดลสถิตที่แสดงถึงโลก 3D โดยเฉพาะส่วนของโลกที่ไม่เป็นของผู้เล่นใดๆ คุณไม่จำเป็นต้องสร้าง แก้ไข หรือทำลายอินสแตนซ์เหล่านี้ในระหว่างการทำงาน ดังนั้นจึงสามารถทิ้งไว้ที่นี่ได้

นอกจากนี้ยังมี Folder ที่ว่างเปล่า ซึ่งจะมีโมเดลฟาร์มของผู้เล่นเพิ่มเข้ามาในระหว่างการทำงาน

Lighting

เอฟเฟกต์บรรยากาศและแสง

ReplicatedFirst

ประกอบด้วยชุดอินสแตนซ์ที่เล็กที่สุดที่จำเป็นในการแสดงหน้าจอโหลดและเริ่มเกม ยิ่งมีอินสแตนซ์มากขึ้นใน ReplicatedFirst เวลาที่รอให้พวกเขาทำซ้ำก่อนที่โค้ดใน ReplicatedFirst จะทำงานก็จะยิ่งนานขึ้น

  • ในโฟลเดอร์ Instances มี GUI หน้าจอโหลด
  • ในโฟลเดอร์ Source มีโค้ดหน้าจอโหลดและโค้ดที่จำเป็นในการรอให้เกมโหลดเสร็จ โค้ด start LocalScript เป็นจุดเริ่มต้นสำหรับโค้ดฝั่งไคลเอนต์ทั้งหมดในโครงการ
ReplicatedStorage

ทำหน้าที่เป็นคอนเทนเนอร์เก็บข้อมูลสำหรับอินสแตนซ์ทั้งหมดที่ต้องการการเข้าถึงทั้งจากไคลเอนต์และเซิร์ฟเวอร์

  • ในโฟลเดอร์ Dependencies มีไลบรารีของบุคคลที่สามที่ใช้ในโครงการ
  • ในโฟลเดอร์ Instances มีอินสแตนซ์ที่สร้างไว้ล่วงหน้าหลายประเภท
  • ในโฟลเดอร์ Source มีโค้ดทั้งหมดที่ ไม่ จำเป็นสำหรับกระบวนการโหลดที่ต้องเข้าถึงได้จากทั้งไคลเอนต์และเซิร์ฟเวอร์
ServerScriptService

ประกอบด้วย Script ที่ทำหน้าที่เป็นจุดเริ่มต้นสำหรับโค้ดฝั่งเซิร์ฟเวอร์ทั้งหมดในโครงการ

ServerStorage

ทำหน้าที่เป็นคอนเทนเนอร์เก็บข้อมูลสำหรับอินสแตนซ์ทั้งหมดที่ไม่จำเป็นต้องทำซ้ำไปยังไคลเอนต์

  • ในโฟลเดอร์ Instances มีโมเดล Farm ที่เป็นแม่แบบ คัดลอกนี้จะถูกวางใน Workspace เมื่อผู้เล่นเข้าร่วมเกม ซึ่งจะถูกทำซ้ำไปยังผู้เล่นทุกคน
  • ในโฟลเดอร์ Source มีโค้ดทั้งหมดที่เป็นเอกสิทธิ์ของเซิร์ฟเวอร์
SoundService

ประกอบด้วยวัตถุ Sound ที่ใช้สำหรับเอฟเฟกต์เสียงในเกม ภายใต้ SoundService วัตถุ Sound เหล่านี้ไม่มีตำแหน่งและไม่ได้จำลองในพื้นที่ 3D

จุดเริ่มต้น

โครงการส่วนใหญ่จะจัดระเบียบโค้ดภายใน ModuleScripts ที่สามารถนำเข้าได้ทั่วทั้งฐานข้อมูลโค้ด ModuleScripts เป็นแบบใช้ซ้ำได้ แต่จะไม่ทำงานด้วยตัวเอง; ต้องนำเข้าโดย Script หรือ LocalScript โครงการ Roblox หลายโครงการจะมีจำนวน Script และ LocalScript ที่มาก ซึ่งแต่ละอันเกี่ยวข้องกับพฤติกรรมหรือระบบเฉพาะในเกม สร้างจุดเริ่มต้นหลายจุด

สำหรับไมโครเกม Plant จะมีการใช้แนวทางที่แตกต่างออกไปผ่าน LocalScript เดียวที่เป็นจุดเริ่มต้นสำหรับโค้ดไคลเอนต์ทั้งหมด และ Script เดียวที่เป็นจุดเริ่มต้นสำหรับโค้ดเซิร์ฟเวอร์ทั้งหมด แนวทางที่ถูกต้องสำหรับโครงการของคุณขึ้นอยู่กับความต้องการของคุณ แต่จุดเริ่มต้นเดียวจะให้การควบคุมที่มากขึ้นเกี่ยวกับลำดับที่ระบบต่างๆ ถูกดำเนินการ

รายการต่อไปนี้อธิบายถึงการแลกเปลี่ยนของทั้งสองแนวทาง:

  • Script เดียวและ LocalScript เดียวครอบคลุมโค้ดเซิร์ฟเวอร์และไคลเอนต์ตามลำดับ
  • การควบคุมที่มากขึ้นเกี่ยวกับลำดับที่ระบบต่างๆ เริ่มต้นเพราะโค้ดทั้งหมดถูกเริ่มต้นจากสคริปต์เดียว
  • สามารถส่งผ่านวัตถุโดยอ้างอิงระหว่างระบบได้

สถาปัตยกรรมระบบระดับสูง

ระบบระดับสูงในโครงการมีรายละเอียดด้านล่าง บางระบบเหล่านี้ซับซ้อนกว่าระบบอื่นๆ และในหลายกรณีฟังก์ชันการทำงานของพวกมันถูกทำให้เป็นนามธรรมข้ามลำดับชั้นของคลาสอื่นๆ

แผนภาพสถาปัตยกรรมระบบโครงการพืช

แต่ละระบบเหล่านี้เป็น "ซิงเกิลตัน" ซึ่งหมายความว่าเป็นคลาสที่ไม่สามารถสร้างอินสแตนซ์ได้ซึ่งจะถูกเริ่มต้นโดยสคริปต์ start ที่เกี่ยวข้องของไคลเอนต์หรือเซิร์ฟเวอร์ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ รูปแบบซิงเกิลตัน ได้ในภายหลังในคู่มือนี้

เซิร์ฟเวอร์

ระบบต่อไปนี้เกี่ยวข้องกับเซิร์ฟเวอร์

ระบบคำอธิบาย
เครือข่าย
  • สร้างอินสแตนซ์ RemoteEvent และ RemoteFunction ทั้งหมด
  • เปิดเผยวิธีการสำหรับการส่งและฟังข้อความจากไคลเอนต์
  • การตรวจสอบประเภทสำหรับอาร์กิวเมนต์ที่ได้รับจากไคลเอนต์ในระหว่างการทำงาน
PlayerDataServer
  • บันทึกและโหลดข้อมูลผู้เล่นที่คงอยู่โดยใช้ DataStoreService
  • เก็บข้อมูลผู้เล่นในหน่วยความจำและทำซ้ำการเปลี่ยนแปลงไปยังไคลเอนต์
  • เปิดเผยสัญญาณและวิธีการสำหรับการสมัครสมาชิก การสอบถาม และการอัปเดตข้อมูลผู้เล่น
ตลาด
  • จัดการธุรกรรมสกุลเงินอ่อนจากไคลเอนต์
  • เปิดเผยวิธีการขายพืชที่เก็บเกี่ยว
CollisionGroupManager
  • กำหนดโมเดลตัวละครผู้เล่นให้กับ กลุ่มการชน
  • กำหนดค่ากลุ่มการชนเพื่อให้ตัวละครผู้เล่นไม่ชนกับรถเข็นพืช
FarmManagerServer
  • สร้างโมเดลฟาร์มของผู้เล่นใหม่จากข้อมูลผู้เล่นเมื่อพวกเขาเข้าร่วมเกม
  • ลบโมเดลฟาร์มเมื่อผู้เล่นออก
  • อัปเดตข้อมูลผู้เล่นเมื่อฟาร์มของผู้เล่นมีการเปลี่ยนแปลง
  • เปิดเผยวิธีการเข้าถึงคลาส Farm ที่เกี่ยวข้องกับผู้เล่นที่กำหนด
PlayerObjectsContainer
  • สร้างวัตถุต่างๆ ที่เกี่ยวข้องกับอายุการใช้งานของผู้เล่นและให้วิธีการในการดึงข้อมูลเหล่านี้
TagPlayers
  • เพิ่มแท็ก CollectionService ให้กับวัตถุผู้เล่นและตัวละครทั้งหมด
FtueManagerServer
  • ในระหว่าง FTUE จะดำเนินการแต่ละขั้นตอนและรอให้เสร็จสิ้น
CharacterSpawner
  • ทำให้ตัวละครเกิดใหม่เมื่อพวกเขาตาย โปรดทราบว่า Players.CharacterAutoLoads ถูกปิดใช้งานเพื่อให้การเกิดใหม่ถูกระงับจนกว่าข้อมูลของผู้เล่นจะโหลดเสร็จ

ไคลเอนต์

ระบบต่อไปนี้เกี่ยวข้องกับไคลเอนต์

ระบบคำอธิบาย
เครือข่าย
  • รอให้เซิร์ฟเวอร์สร้างอินสแตนซ์ RemoteEvent และ RemoteFunction ทั้งหมด
  • เปิดเผยวิธีการสำหรับการส่งและฟังข้อความไปและกลับจากเซิร์ฟเวอร์
  • บังคับการตรวจสอบประเภทพารามิเตอร์ในระหว่างการทำงาน
  • รัน pcall() บนฟังก์ชันระยะไกล
PlayerDataClient
  • เก็บข้อมูลของผู้เล่นในหน่วยความจำ
  • เปิดเผยวิธีการและสัญญาณสำหรับการสอบถามและสมัครสมาชิกการเปลี่ยนแปลงในข้อมูลผู้เล่น
MarketClient
  • เปิดเผยวิธีการขอให้เซิร์ฟเวอร์ซื้อรายการสำหรับสกุลเงินอ่อน
LocalWalkJumpManager
  • เปิดเผยวิธีการในการปรับเปลี่ยน WalkSpeed หรือ JumpHeight ของตัวละครผ่านตัวคูณเพื่อหลีกเลี่ยงความขัดแย้งเมื่อปรับเปลี่ยนค่าจากหลายที่
FarmManagerClient
  • ฟังการใช้แท็ก CollectionService ที่เฉพาะเจาะจงกับอินสแตนซ์และสร้าง "ส่วนประกอบ" ที่เพิ่มพฤติกรรมให้กับอินสแตนซ์เหล่านี้ "ส่วนประกอบ" หมายถึงคลาสที่ถูกสร้างเมื่อแท็ก CollectionService ถูกเพิ่มไปยังอินสแตนซ์และถูกทำลายเมื่อมันถูกลบ; เหล่านี้ใช้สำหรับการกระตุ้น CTA ในฟาร์มและคลาสต่างๆ ที่สื่อสารสถานะฟาร์มไปยังผู้เล่น
UISetup
  • เริ่มต้นเลเยอร์ UI ทั้งหมด
  • กำหนดค่าบางเลเยอร์ให้มองเห็นได้เฉพาะในส่วนทางกายภาพของโลก
  • เชื่อมต่อเอฟเฟกต์กล้องพิเศษเมื่อเมนูถูกเปิดใช้งาน
FtueManagerClient
  • กำหนดค่า FTUE ขั้นตอนบนไคลเอนต์
CharacterSprint
  • ใช้ LocalWalkJumpManager เพื่อเพิ่ม WalkSpeed เมื่อผู้เล่นตัวละครอยู่นอกฟาร์มของพวกเขา

การสื่อสารระหว่างไคลเอนต์และเซิร์ฟเวอร์

เกม Roblox ส่วนใหญ่เกี่ยวข้องกับการสื่อสารระหว่างไคลเอนต์และเซิร์ฟเวอร์ ซึ่งอาจรวมถึงการที่ไคลเอนต์ขอให้เซิร์ฟเวอร์ดำเนินการบางอย่างและเซิร์ฟเวอร์ทำซ้ำการอัปเดตไปยังไคลเอนต์

ในโครงการนี้ การสื่อสารระหว่างไคลเอนต์และเซิร์ฟเวอร์จะถูกเก็บให้เป็นทั่วไปที่สุดเท่าที่จะเป็นไปได้โดยการจำกัดการใช้วัตถุ RemoteEvent และ RemoteFunction เพื่อลดจำนวนกฎพิเศษที่ต้องติดตาม โครงการนี้ใช้วิธีการต่อไปนี้ตามลำดับความชอบ:

การทำซ้ำผ่านระบบข้อมูลผู้เล่น

ระบบข้อมูลผู้เล่น ช่วยให้ข้อมูลสามารถเชื่อมโยงกับผู้เล่นซึ่งคงอยู่ระหว่างเซสชันการบันทึก ระบบนี้ให้การทำซ้ำจากไคลเอนต์ไปยังเซิร์ฟเวอร์และชุด API ที่สามารถใช้ในการสอบถามข้อมูลและสมัครสมาชิกการเปลี่ยนแปลง ทำให้เหมาะสำหรับการทำซ้ำการเปลี่ยนแปลงสถานะของผู้เล่นจากเซิร์ฟเวอร์ไปยังไคลเอนต์

ตัวอย่างเช่น แทนที่จะเรียกใช้ UpdateCoins RemoteEvent ที่กำหนดเองเพื่อบอกไคลเอนต์ว่ามีเหรียญกี่เหรียญ คุณสามารถเรียกใช้สิ่งต่อไปนี้และให้ไคลเอนต์สมัครสมาชิกผ่านเหตุการณ์ PlayerDataClient.updated

PlayerDataServer.setValue(player, "coins", 5)

แน่นอนว่านี่มีประโยชน์เฉพาะสำหรับการทำซ้ำจากเซิร์ฟเวอร์ไปยังไคลเอนต์และสำหรับค่าที่คุณต้องการให้คงอยู่ระหว่างเซสชัน แต่สิ่งนี้ใช้ได้กับกรณีจำนวนมากในโครงการ รวมถึง:

  • ขั้นตอน FTUE ปัจจุบัน
  • สินค้าคงคลังของผู้เล่น
  • จำนวนเหรียญที่ผู้เล่นมี
  • สถานะของฟาร์มของผู้เล่น

การทำซ้ำผ่านแอตทริบิวต์

ในสถานการณ์ที่เซิร์ฟเวอร์ต้องทำซ้ำค่าที่กำหนดเองไปยังไคลเอนต์ซึ่งเฉพาะเจาะจงกับ Instance ที่กำหนด คุณสามารถใช้ แอตทริบิวต์ Roblox จะทำการทำซ้ำค่าแอตทริบิวต์โดยอัตโนมัติ ดังนั้นคุณไม่จำเป็นต้องรักษาเส้นทางโค้ดใดๆ เพื่อทำซ้ำสถานะที่เกี่ยวข้องกับวัตถุ ข้อดีอีกประการหนึ่งคือการทำซ้ำนี้เกิดขึ้นพร้อมกับอินสแตนซ์เอง

สิ่งนี้มีประโยชน์โดยเฉพาะสำหรับอินสแตนซ์ที่สร้างขึ้นในระหว่างการทำงาน เนื่องจากแอตทริบิวต์ที่ตั้งค่าในอินสแตนซ์ใหม่ก่อนที่จะถูกจัดวางในโมเดลข้อมูลจะทำการทำซ้ำพร้อมกันกับอินสแตนซ์เอง สิ่งนี้หลีกเลี่ยงความจำเป็นในการเขียนโค้ดเพื่อ "รอ" ข้อมูลเพิ่มเติมที่จะถูกทำซ้ำผ่าน RemoteEvent หรือ StringValue

คุณยังสามารถอ่านแอตทริบิวต์โดยตรงจากโมเดลข้อมูลได้จากทั้งไคลเอนต์หรือเซิร์ฟเวอร์ โดยใช้วิธี GetAttribute() และสมัครสมาชิกการเปลี่ยนแปลงด้วยวิธี GetAttributeChangedSignal() ในโครงการ Plant วิธีการนี้ถูกใช้สำหรับการทำซ้ำสถานะปัจจุบันของพืชไปยังไคลเอนต์

การทำซ้ำผ่านแท็ก

CollectionService ช่วยให้คุณสามารถใช้แท็กสตริงกับ Instance ได้ สิ่งนี้มีประโยชน์สำหรับการจัดประเภทอินสแตนซ์และทำซ้ำการจัดประเภทนั้นไปยังไคลเอนต์

ตัวอย่างเช่น แท็ก CanPlant ถูกใช้ในเซิร์ฟเวอร์เพื่อบ่งบอกไปยังไคลเอนต์ว่ากระถางที่กำหนดสามารถรับพืชได้

การส่งข้อความโดยตรงผ่านโมดูลเครือข่าย

ในสถานการณ์ที่ไม่มีตัวเลือกก่อนหน้านี้ใช้ได้ คุณสามารถใช้การเรียกเครือข่ายที่กำหนดเองผ่านโมดูล เครือข่าย นี่เป็นตัวเลือกเดียวในโครงการที่อนุญาตให้มีการสื่อสารจากไคลเอนต์ไปยังเซิร์ฟเวอร์และจึงมีประโยชน์ที่สุดสำหรับการส่งคำขอจากไคลเอนต์และรับการตอบสนองจากเซิร์ฟเวอร์

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 ในโครงการ Plant ใช้ประโยชน์จากความจริงที่ว่าการเรียกใช้ ModuleScript จะเก็บค่าที่ส่งกลับไว้ในแคช ซึ่งหมายความว่าการเรียกใช้ ModuleScript ซิงเกิลตันเดียวกันจากสถานที่ต่างๆ จะให้วัตถุที่ส่งกลับเดียวกันอย่างสม่ำเสมอ ข้อยกเว้นเพียงอย่างเดียวสำหรับกฎนี้คือถ้าสภาพแวดล้อมที่แตกต่างกัน (ไคลเอนต์หรือเซิร์ฟเวอร์) เข้าถึง ModuleScript

ซิงเกิลตันจะแยกออกจากคลาสที่สามารถสร้างอินสแตนซ์ได้โดยข้อเท็จจริงที่ว่าพวกเขาไม่มีวิธี new() แทนที่จะเป็นเช่นนั้น วัตถุพร้อมกับวิธีการและสถานะของมันจะถูกส่งกลับโดยตรงผ่าน ModuleScript เนื่องจากซิงเกิลตันไม่ได้ถูกสร้างขึ้น จึงไม่มีการใช้ไวยากรณ์ self และวิธีการจะถูกเรียกด้วยจุด (.) แทนที่จะเป็นโคลอน (:)

การตรวจสอบประเภทที่เข้มงวด

Luau สนับสนุนการพิมพ์แบบค่อยเป็นค่อยไป ซึ่งหมายความว่าคุณสามารถเพิ่มการกำหนดประเภทที่เลือกได้ในบางส่วนหรือทั้งหมดของโค้ดของคุณ ในโครงการนี้ การตรวจสอบประเภทที่เข้มงวดจะถูกใช้สำหรับทุกสคริปต์ นี่เป็นตัวเลือกที่น้อยที่สุดสำหรับเครื่องมือ Script Analysis ของ Roblox และดังนั้นจึงมีแนวโน้มที่จะจับข้อผิดพลาดประเภทก่อนการทำงาน

ไวยากรณ์คลาสที่มีประเภท

แนวทางที่จัดตั้งขึ้นในการสร้างคลาสใน Lua ได้รับการ บันทึกไว้อย่างดี อย่างไรก็ตามมันไม่เหมาะสมกับการพิมพ์ที่เข้มงวดของ Luau ใน Luau วิธีที่ง่ายที่สุดในการรับประเภทของคลาสคือวิธี typeof():

type ClassType = typeof(Class.new())

วิธีนี้ใช้ได้ แต่ไม่ค่อยมีประโยชน์เมื่อคลาสของคุณถูกเริ่มต้นด้วยค่าที่มีอยู่เฉพาะในระหว่างการทำงาน เช่น Player นอกจากนี้ สมมติฐานที่ทำในไวยากรณ์คลาส Lua ที่เป็นอัตลักษณ์คือการประกาศวิธีการในคลาส self จะต้องเป็นอินสแตนซ์ของคลาสนั้นเสมอ; นี่ไม่ใช่สมมติฐานที่เครื่องมือการตรวจสอบประเภทสามารถทำได้

เพื่อสนับสนุนการตรวจสอบประเภทที่เข้มงวด โครงการ Plant ใช้โซลูชันที่แตกต่างจากไวยากรณ์คลาส Lua ที่เป็นอัตลักษณ์ในหลายๆ ด้าน ซึ่งบางอย่างอาจรู้สึกไม่เป็นธรรมชาติ:

  • การกำหนด 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

การเดินทางในลำดับชั้นของ DataModel

ในบางกรณี ฐานข้อมูลโค้ดจำเป็นต้องเดินทางในลำดับชั้นของโมเดลข้อมูลของต้นไม้ของวัตถุที่ถูกสร้างขึ้นในระหว่างการทำงาน สิ่งนี้นำเสนอความท้าทายที่น่าสนใจสำหรับการตรวจสอบประเภท ณ เวลาที่เขียน ไม่สามารถกำหนดลำดับชั้นของโมเดลข้อมูลทั่วไปเป็นประเภทได้ ดังนั้นจึงมีกรณีที่ข้อมูลประเภทเดียวที่มีอยู่สำหรับโครงสร้างโมเดลข้อมูลคือประเภทของอินสแตนซ์ระดับสูงสุด

แนวทางหนึ่งในการแก้ปัญหานี้คือการแคสต์เป็น any และจากนั้นปรับปรุง ตัวอย่างเช่น:

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 กับ HTML DOM ได้อย่างหลวมๆ เพราะมันเป็นลำดับชั้นของวัตถุที่อธิบายว่าสิ่งที่ผู้ใช้ควรเห็นคืออะไร แนวทางในการสร้างและอัปเดต UI ของ Roblox จะแบ่งออกเป็น แนวทางเชิงบังคับ และ แนวทางเชิงประกาศ

แนวทางข้อดีและข้อเสีย
เชิงบังคับ

ในแนวทางเชิงบังคับ UI จะถูกจัดการเหมือนกับลำดับชั้นของอินสแตนซ์อื่นๆ บน Roblox โครงสร้าง UI จะถูกสร้างขึ้นก่อนการทำงานใน Studio และเพิ่มลงในโมเดลข้อมูล โดยทั่วไปจะอยู่ใน StarterGui โดยตรง จากนั้นในระหว่างการทำงาน โค้ดจะจัดการชิ้นส่วนเฉพาะของ UI เพื่อสะท้อนสถานะที่ผู้สร้างต้องการ

แนวทางนี้มีข้อดีบางประการ คุณสามารถสร้าง UI จากศูนย์ใน Studio และเก็บไว้ในโมเดลข้อมูล นี่เป็นประสบการณ์การแก้ไขที่ง่ายและมองเห็นได้ซึ่งสามารถเร่งการสร้าง UI ได้ เนื่องจากโค้ด UI เชิงบังคับจะเกี่ยวข้องเฉพาะกับสิ่งที่ต้องเปลี่ยนแปลง ทำให้การเปลี่ยนแปลง UI ที่ง่ายทำได้ง่าย

ข้อเสียที่น่าสังเกตคือ เนื่องจากแนวทาง UI เชิงบังคับต้องการให้สถานะถูกนำไปใช้ด้วยตนเองในรูปแบบของการเปลี่ยนแปลง การแสดงผลที่ซับซ้อนของสถานะอาจกลายเป็นเรื่องยากที่จะค้นหาและแก้ไขข้อบกพร่อง มักจะเกิดข้อผิดพลาดเมื่อพัฒนาโค้ด UI เชิงบังคับ โดยเฉพาะเมื่อสถานะและ UI กลายเป็นไม่ตรงกันเนื่องจากการอัปเดตหลายครั้งที่มีปฏิสัมพันธ์ในลำดับที่ไม่คาดคิด

อีกความท้าทายหนึ่งของแนวทางเชิงบังคับคือมันยากที่จะแบ่ง UI ออกเป็นส่วนประกอบที่มีความหมายซึ่งสามารถประกาศได้ครั้งเดียวและนำกลับมาใช้ใหม่ได้ เนื่องจากต้นไม้ UI ทั้งหมดถูกประกาศในเวลาการแก้ไข รูปแบบทั่วไปอาจถูกทำซ้ำในหลายส่วนของโมเดลข้อมูล

เชิงประกาศ

ในแนวทางเชิงประกาศ สถานะที่ต้องการของอินสแตนซ์ UI จะถูกประกาศอย่างชัดเจน และการนำไปใช้สถานะนี้อย่างมีประสิทธิภาพจะถูกทำให้เป็นนามธรรมโดยไลบรารี เช่น Roact หรือ Fusion

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

ข้อเสียหลักคือการต้องประกาศต้นไม้ UI ทั้งหมดในโค้ด ไลบรารีอย่าง Roact และ Fusion มีไวยากรณ์เพื่อทำให้สิ่งนี้ง่ายขึ้น แต่ก็ยังเป็นกระบวนการที่ใช้เวลานานและประสบการณ์การแก้ไขที่ไม่เป็นธรรมชาติเมื่อประกอบ UI

Plant ใช้แนวทาง เชิงบังคับ โดยมีแนวคิดว่าการแสดงการเปลี่ยนแปลงโดยตรงจะให้ภาพรวมที่มีประสิทธิภาพมากขึ้นเกี่ยวกับวิธีการสร้างและจัดการ UI บน Roblox ซึ่งจะไม่สามารถทำได้ด้วยแนวทางเชิงประกาศ โครงสร้างและตรรกะ UI ที่ซ้ำซ้อนบางส่วนยังถูกทำให้เป็นนามธรรมใน ส่วนประกอบ ที่ใช้ซ้ำได้เพื่อหลีกเลี่ยงกับดักทั่วไปในรูปแบบ 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
  • Luau — รายละเอียดเกี่ยวกับ Luau ภาษาเขียนโปรแกรมที่สร้างโดย Roblox ซึ่งสืบทอดมาจาก Lua 5.1
  • เหตุการณ์ระยะไกลและการเรียกกลับ — เกี่ยวกับเหตุการณ์เครือข่ายระยะไกลและการเรียกกลับสำหรับการสื่อสารข้ามพรมแดนไคลเอนต์-เซิร์ฟเวอร์
  • UI — รายละเอียดเกี่ยวกับวัตถุและการออกแบบส่วนติดต่อผู้ใช้ใน Roblox
©2026 Roblox Corporation. Roblox, โลโก้ Roblox และ Powering Imagination เป็นส่วนหนึ่งของเครื่องหมายการค้าที่จดทะเบียน และไม่ได้จดทะเบียนของเราในสหรัฐฯ และประเทศอื่นๆ