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

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

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

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

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

รับไฟล์

  1. คลิกที่ปุ่ม และ แก้ไขในสตูดิโอ.

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

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

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

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

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

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

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

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

โมเดลข้อมูล

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

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

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

ยังมี 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 เหล่านี้ไม่มีที่ตั้งและไม่ได้ถูกจำลองในพื้นที่ 3 มิติ

จุดเข้าถึง

ส่วนใหญ่โครงการจะจัดระเบียบโค้ดภายใน 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 จะถูกใช้กับเซิร์ฟเวอร์เพื่อตั้งสัญญาณให้ไคลเอนต์ทราบว่ากระถางที่กำหนดสามารถรับพืชได้

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

ในสถานการณ์ที่ไม่มีตัวเลือกก่อนหน้านี้ใดๆ ที่สามารถใช้ได้ คุณสามารถใช้การเรียกเครือข่ายแบบกำหนดเองผ่านโมดูล 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 แบบเชิงบังคับ

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

แผนภาพสถาปัตยกรรม 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 เป็นส่วนหนึ่งของเครื่องหมายการค้าที่จดทะเบียน และไม่ได้จดทะเบียนของเราในสหรัฐฯ และประเทศอื่นๆ