บทความ › พัฒนาเกม

Roblox EP5 เกมลาวาขึ้น: สร้างทั้งด่านด้วยโค้ด และทดสอบกฎเกมนอก Studio

เกมที่สองของซีรีส์ เกมเป็นรอบที่ลาวาค่อย ๆ ขึ้น ใครถึงยอดหอคอยได้แต้ม ทั้งด่านสร้างจากโค้ดด้วย Rojo และทดสอบกฎว่ากระโดดถึงทุกขั้นด้วย Lune

กลาง อ่าน 13 นาที 20 ตุลาคม 2569 ทำเกม Roblox เกมแรก · บทที่ 5/5

อ่านจบแล้วจะรู้
  • ด่านที่สร้างจากโค้ด แก้ตัวเลขที่เดียวแล้วทั้งด่านเปลี่ยนตาม และเก็บประวัติใน Git ได้
  • แยกกฎของเกมเป็นฟังก์ชันที่ไม่แตะวัตถุของ Roblox แล้วทดสอบนอก Studio ได้ว่ายุติธรรมไหม
  • เกมเป็นรอบมีโครงเดียวกันแทบทุกเกม รอ → เล่น → ตัดสิน → กลับไปรอ และเซิร์ฟเวอร์เป็นคนตัดสินเสมอ

EP1-4 เราวางพื้นและบันไดด้วยมือใน Studio แล้วเขียนโค้ดให้ชิ้นส่วนทำงาน บทนี้ทำเกมที่สอง ลาวาขึ้น เกมเป็นรอบที่เล่นกับเพื่อนได้ และเปลี่ยนวิธีทำ คือสร้างทั้งด่านจากโค้ด แล้วทดสอบกฎเกมนอก Studio ก่อนกด Play จริง วิธีนี้คือแบบที่ทีมทำเกมจริงใช้เมื่อเกมเริ่มใหญ่

บทนี้ยากขึ้นจากเดิม ถ้ายังไม่ได้อ่าน EP3 เรื่องเซิร์ฟเวอร์กับเครื่องผู้เล่น ควรอ่านก่อน

เกมที่จะได้

  1. ผู้เล่นเกิดบนเกาะห้องรอ นับถอยหลัง 15 วินาที
  2. ทุกคนถูกวาร์ปไปรอบฐานหอคอยบันไดวน 24 ขั้น
  3. 5 วินาทีต่อมา ลาวาเริ่มขึ้น ขึ้นเป็นระลอก ช้าสลับพุ่ง
  4. ใครเท้าแตะลาวาตาย กลับไปห้องรอ
  5. ครบ 75 วินาที ใครยืนบนยอดสีทองได้ Wins +1 แล้วเริ่มรอบใหม่

ระหว่างทางมีขั้นบันไดสองแบบพิเศษ ขั้นสีฟ้าขยับขึ้นลง และขั้นสีน้ำตาลที่สั่นแล้วถล่มเมื่อเหยียบ

ทำไมสร้างด่านด้วยโค้ด

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

เกมนี้เป็นรูปทรงซ้ำ ๆ ตามสูตร บันไดวนรอบเสา ระยะเท่ากัน จึงเหมาะกับโค้ดมาก ส่วนด่านที่ต้องจัดวางสวยทีละมุม ใช้มือใน Studio ดีกว่า หลายเกมใช้ผสมกัน

เครื่องมือสองตัว

  • Rojo เชื่อมไฟล์โค้ดในเครื่องกับ Studio เราเขียนโค้ดเป็นไฟล์ .luau ธรรมดา แล้วให้ Rojo สร้างไฟล์เกม .rbxlx ที่เปิดใน Studio ได้
  • Lune รันภาษา Luau นอก Roblox ใช้รันเทสต์กฎเกมในเครื่องเรา ไม่ต้องเปิด Studio

ทั้งสองตัวเป็นโอเพนซอร์ส โหลดจากหน้า Releases บน GitHub ของแต่ละโครงการ บทนี้ใช้ Rojo 7.7.1 และ Lune 0.11.0 บน Windows

ไฟล์ในโปรเจกต์ไปอยู่ตรงไหนใน Studio:

ไฟล์อยู่ใน Studio ที่ประเภท
src/shared/Config.luauReplicatedStorage › SharedModuleScript ตัวเลขทั้งเกม
src/shared/RoundLogic.luauReplicatedStorage › SharedModuleScript กฎของเกม
src/server/MapBuilder.luauServerScriptService › ServerModuleScript สร้างด่าน
src/server/Main.server.luauServerScriptService › ServerScript ลูปของเกม
src/client/StatusUI.client.luauStarterPlayerScripts › ClientLocalScript แถบสถานะบนจอ

Rojo ดูนามสกุลไฟล์ .server.luau เป็น Script, .client.luau เป็น LocalScript, ที่เหลือเป็น ModuleScript และจับคู่โฟลเดอร์กับที่ใน Studio จากไฟล์ default.project.json:

{
  "name": "lava-rising",
  "tree": {
    "$className": "DataModel",
    "ReplicatedStorage": { "Shared": { "$path": "src/shared" } },
    "ServerScriptService": { "Server": { "$path": "src/server" } },
    "StarterPlayer": {
      "StarterPlayerScripts": { "Client": { "$path": "src/client" } }
    }
  }
}

สร้างไฟล์เกมด้วยคำสั่งเดียว แล้วดับเบิลคลิกไฟล์ที่ได้เพื่อเปิดใน Studio:

rojo build -o build/lava-rising.rbxlx

ตัวเลขทั้งเกมอยู่ไฟล์เดียว

Config.luau เก็บทุกตัวเลขที่กำหนดหน้าตาและความยากของเกม ตัดมาบางส่วน:

local Config = {
	INTERMISSION = 15,   -- วินาทีที่รอในห้องรอ
	ROUND_TIME = 75,     -- ความยาวรอบ
	LAVA_DELAY = 5,      -- ลาวาเริ่มขยับหลังเริ่มรอบกี่วินาที

	TOWER_STEPS = 24,    -- จำนวนขั้นบันได
	STEP_RISE = 5,       -- ความสูงต่อขั้น (กระโดดปกติได้ราว 7)
	STEP_ANGLE = 30,     -- หมุนกี่องศาต่อขั้น
	TOWER_RADIUS = 22,   -- ระยะจากเสากลางถึงขั้นบันได
	PLATFORM_SIZE = 8,

	SURGE_WAVES = 3,     -- ลาวาขึ้นเป็นระลอก กี่ระลอกต่อรอบ
	SURGE_STRENGTH = 0.6 -- 0 = ขึ้นคงที่, 0.6 = เร็วช้าระหว่าง 0.4 ถึง 1.6 เท่า
}
return Config

อยากให้เกมยากขึ้น ลด ROUND_TIME อยากให้หอคอยสูงขึ้น เพิ่ม TOWER_STEPS ทั้งด่านเปลี่ยนตามเอง

กฎของเกม เขียนเป็นฟังก์ชันล้วน

จุดสำคัญของบทนี้ คือแยกกฎของเกมออกจากวัตถุใน Roblox RoundLogic.luau รับตัวเลขเข้าไป คืนตัวเลขออกมา ไม่แตะ Part ไม่แตะผู้เล่น เช่น ตำแหน่งขั้นบันไดแต่ละขั้น:

function RoundLogic.towerLayout(cfg)
	local points = {}
	for i = 1, cfg.TOWER_STEPS do
		local a = math.rad(i * cfg.STEP_ANGLE)
		table.insert(points, {
			x = cfg.TOWER_RADIUS * math.cos(a),
			y = i * cfg.STEP_RISE,
			z = cfg.TOWER_RADIUS * math.sin(a),
		})
	end
	return points
end

ขั้นที่ i หมุนไป i × 30 องศา สูงขึ้น i × 5 studs ได้บันไดวนรอบเสา ส่วน MapBuilder ค่อยเอาตำแหน่งพวกนี้ไปสร้าง Part จริง

กฎว่าใครโดนลาวาก็สั้นมาก ตำแหน่งที่วัดได้คือ HumanoidRootPart ซึ่งอยู่สูงกว่าเท้าราว 3 studs:

function RoundLogic.isBurned(rootY, lavaTop, cfg)
	return rootY - cfg.BURN_MARGIN <= lavaTop  -- BURN_MARGIN = 3
end
พุ่งพุ่งพุ่งยอด 125เริ่มขยับ 5 วิเวลา (วินาที)ความสูงลาวาจริง: เป็นระลอกถ้าขึ้นคงที่0255075
ขึ้นอย่างเดียว ช้าสลับพุ่ง 3 ระลอก และหยุดใต้ยอดพอดีตอนหมดเวลา

ระดับลาวาไม่ได้ขึ้นเป็นเส้นตรง แต่ขึ้นเป็นระลอก ช่วงช้าให้หายใจ ช่วงพุ่งให้ตื่นเต้น สูตรออกแบบให้ลาวาขึ้นอย่างเดียว ไม่ลง และถึงจุดสูงสุดพอดีตอนหมดเวลา ใต้ยอดหอคอย 4 studs คนบนยอดจึงรอดเสมอ

ทดสอบกฎนอก Studio

เพราะกฎไม่แตะ Roblox เราจึงรันมันด้วย Lune ได้ ไฟล์ tests/round_logic.luau ถามคำถามที่ตอบด้วยการคำนวณ (ชื่อเทสต์เป็นภาษาอังกฤษตามโค้ดจริง) เช่น

check("a step is jumpable (rise under 7)", Config.STEP_RISE < 7)

local gap = RoundLogic.stepGap(Config)
check("gap between steps is positive and short (0-6 studs)", gap > 0 and gap < 6)

-- แม้ช่วงลาวาพุ่งเร็วที่สุด ก็ต้องเหลือเวลาอย่างน้อย 1.5 วินาทีต่อขั้น
local secondsPerStep = Config.STEP_RISE / RoundLogic.lavaPeakSpeed(Config)
check("lava gives at least 1.5 s per step, even in a surge", secondsPerStep >= 1.5)

check("standing on the summit at the end is safe", not RoundLogic.isBurned(summitTop + 3, endY, Config))

รันในเครื่อง:

lune run tests/round_logic
lava
  ok  still during the head start
  ok  lava only ever goes up
  ...
climb time
  ok  lava gives at least 1.5 s per step, even in a surge
...
34 passed, 0 failed

ตัวอย่างการคำนวณ ช่องว่างระหว่างขั้น ระยะระหว่างจุดกลางของสองขั้นที่ห่างกัน 30 องศาบนวงรัศมี 22 คือ 2 × 22 × sin 15° ≈ 11.4 studs หักความกว้างขั้น 8 studs เหลือช่องว่างจริง ≈ 3.4 studs กระโดดข้ามสบาย

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

ทดสอบสิ่งที่คำนวณได้ด้วยโค้ด เก็บเวลาเล่นจริงไว้ตัดสินสิ่งที่วัดด้วยตัวเลขไม่ได้ คือสนุกไหม

ลูปของเกม: รอ เล่น ตัดสิน

ห้องรอนับถอยหลัง 15 วิเล่นรอบวาร์ป ลาวาขึ้น 75 วิตัดสินคนที่รอด Wins +1ทุกเฟรม: ขยับลาวา ตรวจใครโดนทั้งหมดนี้เซิร์ฟเวอร์เป็นคนทำ
while true do: รอ เล่น ตัดสิน แล้ววนใหม่

Main.server.luau เป็น Script ฝั่งเซิร์ฟเวอร์ตัวเดียวที่คุมทั้งเกม โครงทั้งหมดคือ

while true do
	intermission()  -- นับถอยหลังในห้องรอ
	playRound()     -- วาร์ป ลาวาขึ้น ตัดสิน
end

ใน playRound มีสามเรื่องที่ควรดู

1. จดว่าใครยังอยู่ ตอนวาร์ป ใส่ชื่อผู้เล่นลงตาราง alive และฟัง Died ของแต่ละคน

alive[player.Name] = true
humanoid.Died:Connect(function()
	alive[player.Name] = false
end)

2. ขยับลาวาและตรวจทุกเฟรม ใช้ RunService.Heartbeat ซึ่งทำงานทุกเฟรมบนเซิร์ฟเวอร์

RunService.Heartbeat:Connect(function()
	lavaTop = RoundLogic.lavaHeight(os.clock() - started, Config)
	MapBuilder.setLava(lava, lavaTop)
	for _, player in Players:GetPlayers() do
		local root = if alive[player.Name] then rootOf(player) else nil
		if root and RoundLogic.isBurned(root.Position.Y, lavaTop, Config) then
			player.Character:FindFirstChildWhichIsA("Humanoid").Health = 0
		end
	end
end)

ทำไมไม่ใช้ Touched แบบลาวาใน EP2 เพราะลาวาก้อนนี้ถูกย้ายตำแหน่งทุกเฟรม และกินพื้นที่ทั้งสนาม การเทียบความสูงตรง ๆ แน่นอนกว่าและอ่านง่ายกว่า

บรรทัด if alive[player.Name] มาจากบั๊กจริงที่เจอก่อนส่งให้เล่น ลาวาขึ้นสูงกว่าระดับเกาะห้องรอ ถ้าตรวจทุกคนในเซิร์ฟเวอร์ คนที่รออยู่ในห้องรอจะตายไปด้วยทั้งที่ไม่ได้อยู่ใกล้ลาวาเลย

3. ตัดสินตอนจบ ใครยังอยู่ในตาราง alive ได้ Wins +1 แล้ววาร์ปกลับห้องรอ ทุกอย่างนี้เกิดบนเซิร์ฟเวอร์ เครื่องผู้เล่นแค่แสดงข้อความ ตามกติกาเดิมจาก EP3 ไม่เชื่อเครื่องผู้เล่น

แถบสถานะบนจอใช้วิธีง่ายที่สุด เซิร์ฟเวอร์เขียนข้อความลง StringValue ชื่อ Status ใน ReplicatedStorage และ LocalScript ฝั่งผู้เล่นแค่อ่านค่ามาแสดง ไม่ต้องใช้ RemoteEvent

ทำให้สนุกขึ้น

รุ่นแรกเล่นได้ แต่เรียบไป ปีนวนขึ้นไปเรื่อย ๆ รุ่นที่สองเพิ่มสามอย่าง

  • ลาวาเป็นระลอก มี 3 ระลอกต่อรอบ ช่วงพุ่งแถบสถานะเปลี่ยนเป็นสีแดง ขึ้นว่า LAVA SURGE!
  • ขั้นพิเศษ ตามแพตเทิร์น ปกติ › ขยับ › ปกติ › ถล่ม วนไปจนสุดหอคอย ขั้นที่ขยับใช้ TweenService ให้ลอยขึ้นลง ขั้นที่ถล่มจะสั่น 0.6 วินาทีแล้วหายไป 3 วินาที
  • มาตรวัดความสูงเหนือลาวา บนจอผู้เล่น และจอแดงขึ้นเรื่อย ๆ เมื่อลาวาใกล้ เป็นแค่การแสดงผลฝั่งผู้เล่น ไม่ได้ตัดสินอะไร

ความสนุกต้องไม่แลกกับความยุติธรรม จึงมีกฎเพิ่มในเทสต์ และเทสต์ทั้งหมดต้องผ่านก่อนสร้างไฟล์เกม

  • 3 ขั้นแรกเป็นขั้นปกติเสมอ ทุกคนได้เริ่มอย่างสบาย
  • ไม่มีขั้นพิเศษสองขั้นติดกัน
  • ขั้นสุดท้ายก่อนยอดเป็นขั้นปกติ
  • ขั้นที่ขยับ บวกระยะขยับแล้วยังสูงไม่ถึง 7 studs

ลองเล่นในเครื่อง

  1. สร้างไฟล์เกมด้วย rojo build แล้วดับเบิลคลิก build/lava-rising.rbxlx
  2. Studio เปิดขึ้นมาจะเห็นแค่ท้องฟ้า เป็นเรื่องปกติ
  3. กด Play (▶ ฟ้า มุมซ้ายบน) หรือ F5 เล่นคนเดียวได้ เพราะ MIN_PLAYERS = 1
  4. ลองหลายคน ไปที่ Test › Clients and Servers ตั้ง 2-3 คน แล้วกด Start
  5. เปิดหน้าต่าง Output จากเมนู Window ไว้ดู error

ถ้าเปิดไฟล์จาก Studio แทนการดับเบิลคลิก ไปที่ File › Open from File

ข้อผิดพลาดที่เจอบ่อย

  • ตรวจลาวากับผู้เล่นทุกคนในเซิร์ฟเวอร์ คนในห้องรอตายไปด้วย
  • ไม่ยกเลิก Connect ตอนจบรอบ รอบถัดไปมีตัวตรวจลาวาซ้อนสองชุด แก้ด้วยการเก็บ connection ไว้ในตารางแล้ว Disconnect ทั้งหมดตอนจบ
  • ตั้งลาวาให้ขึ้นถึงยอด ไม่มีใครชนะได้เลย ต้องหยุดใต้ยอดเสมอ
  • แก้ตัวเลขใน Config แล้วไม่รันเทสต์ ได้ด่านที่กระโดดไม่ถึงโดยไม่รู้ตัว
  • ใส่ LocalScript ไว้ผิดที่ ตามที่เล่าใน EP3 มันจะไม่ทำงานเลยโดยไม่มี error

ตอนหน้า

ตอนนี้ Wins หายทุกครั้งที่ออกจากเกม EP6 จะบันทึกข้อมูลผู้เล่นข้ามรอบด้วย DataStore พร้อมวิธีจัดการตอนบันทึกล้มเหลว และการ Publish เกมขึ้น Roblox ซึ่งต้องทำก่อนใช้ DataStore ได้

คำถามที่พบบ่อย

ต้องใช้ Rojo ไหม ทำใน Studio อย่างเดียวได้ไหม

ได้ โค้ดทุกไฟล์ในบทนี้วางเป็น Script และ ModuleScript ใน Studio ตามตารางในบทได้เลย Rojo ช่วยตอนโปรเจกต์ใหญ่ขึ้น เพราะเขียนโค้ดในเครื่องมือที่ถนัด ใช้ Git และให้ AI ช่วยแก้ไฟล์ได้

ทำไมเปิดไฟล์เกมแล้วเห็นแค่ท้องฟ้าว่าง

เพราะด่านถูกสร้างตอนเซิร์ฟเวอร์เริ่มทำงาน กด Play แล้วฉากจะขึ้นมา เป็นข้อแลกเปลี่ยนของการสร้างด่านด้วยโค้ด

Lune คืออะไร ปลอดภัยไหม

Lune เป็นโปรแกรมรันภาษา Luau นอก Roblox เป็นโอเพนซอร์ส โหลดจาก GitHub ของโครงการ ใช้รันเทสต์กฎเกมในเครื่องเราเอง ไม่ได้ส่งอะไรขึ้น Roblox

คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ

อ่านต่อ

#เกมเริ่มต้น

Roblox EP1 เริ่มทำเกมแรก: ติดตั้ง Roblox Studio และสร้างฉากแรกที่กด Play ได้

ทำเกม Roblox ตั้งแต่ศูนย์ ติดตั้ง Roblox Studio รู้จักหน้าจอ Explorer และ Properties วางพื้น ทำบันได กด Play ทดสอบ และเข้าใจว่าเกมหนึ่งเกมประกอบด้วยอะไรบ้าง

อ่าน 6 นาที
#เกมเริ่มต้น

Roblox EP4 ประกอบ obby ให้ครบเกม: จุดเซฟ กระดานด่าน ลาวาทั้งเกม และเส้นชัย

รวมทุกอย่างเป็นเกม obby ที่เล่นได้จริง จุดเซฟด้วย SpawnLocation กระดานคะแนน leaderstats ลาวาทั้งเกมคุมด้วย Script เดียวผ่าน Tag และเส้นชัยที่นับครั้งชนะ พร้อมโค้ดเต็ม

อ่าน 13 นาที
#เกมเริ่มต้น

Roblox EP3 เซิร์ฟเวอร์กับเครื่องผู้เล่น: โค้ดแต่ละแบบทำงานที่ไหน

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

อ่าน 7 นาที