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

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

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

เริ่มต้น อ่าน 13 นาที 16 ตุลาคม 2569 ทำเกม Roblox เกมแรก · บทที่ 4/5

อ่านจบแล้วจะรู้
  • จุดเซฟของ obby คือ SpawnLocation หลายจุด แล้วให้เซิร์ฟเวอร์ตั้ง RespawnLocation ของผู้เล่นเป็นจุดล่าสุดที่ผ่าน
  • ของที่ทำงานเหมือนกันหลายชิ้น ให้ติด Tag แล้วคุมด้วย Script เดียว แก้ที่เดียวมีผลทั้งเกม
  • leaderstats คือโฟลเดอร์ชื่อพิเศษ ใส่ค่าไว้ข้างในแล้ว Roblox แสดงเป็นกระดานคะแนนให้เอง

สามตอนที่ผ่านมาเรามีชิ้นส่วนครบแล้ว: ฉากที่เดินกระโดดได้ (EP1) พื้นลาวาและพื้นที่หายไป (EP2) และความเข้าใจว่าโค้ดฝั่งไหนทำงานที่ไหน (EP3) ตอนนี้จะประกอบทั้งหมดเป็นเกม obby ที่เล่นได้จริง ตั้งแต่จุดเริ่มต้นถึงเส้นชัย

เกมที่จะได้

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

เตรียมฉาก

1. ล้างของเก่า ลบ Script ที่ใส่ไว้ในพื้นแต่ละชิ้นจาก EP2 ทิ้ง ไม่อย่างนั้นโค้ดจะทำงานซ้ำสองชุด

2. จุดเซฟ ใน Workspace สร้าง Folder ชื่อ Checkpoints (ชี้ที่ Workspace → + → Folder) แล้วใส่ SpawnLocation เข้าไป 5 จุด

  • แม่แบบ Baseplate มี SpawnLocation มาให้แล้วหนึ่งจุด ลากมันเข้าโฟลเดอร์ Checkpoints แล้วเปลี่ยนชื่อเป็น 1 นี่คือจุดเริ่มต้น
  • เพิ่มอีกสี่จุดจากแท็บ Model → Spawn ตั้งชื่อ 2, 3, 4, 5 วางไว้ต้นด่านถัดไปแต่ละด่าน
  • ทุกจุดตั้ง Duration เป็น 0 (ไม่ต้องมีฟองป้องกันตอนเกิด)
  • ห้ามมี SpawnLocation อยู่นอกโฟลเดอร์นี้ ไม่อย่างนั้นผู้เล่นอาจไปเกิดที่จุดนั้นแบบสุ่ม

ชื่อต้องเป็นตัวเลข เพราะโค้ดจะใช้ชื่อเป็นเลขด่าน

3. ลาวา วางพื้นลาวากี่ชิ้นก็ได้ ทุกชิ้นตั้ง Anchored แล้ว ติด Tag: เลือกชิ้นนั้น ใน Properties เลื่อนลงไปหาส่วน Tags กด + พิมพ์ Lava เลือกหลายชิ้นพร้อมกันแล้วติดทีเดียวก็ได้

4. พื้นที่หายไป ทำแบบเดียวกัน ติด Tag FadePad

5. เส้นชัย สร้าง Part ใน Workspace ชื่อ Finish ตั้ง Anchored ทำให้ดูเด่น เช่น สีทอง Material Neon

6. ค่าตั้ง ใช้ ModuleScript Config ใน ReplicatedStorage จาก EP3 ต้องมีค่า FADE_SECONDS และ GONE_SECONDS

ออกแบบด่านให้สนุก

ด่านอุปสรรคเคล็ดลับ
1บันไดธรรมดาให้ผู้เล่นคุ้นกับการกระโดด
2แผ่นพื้นห่างกันเหนือลาวาระยะ 5–6 หน่วย ยังง่าย
3พื้นที่หายไปบังคับให้รีบ ไม่ยืนคิดนาน
4สะพานแคบกว้าง 1–2 หน่วย มีลาวาสองข้างทดสอบความแม่นยำ
5รวมทุกอย่าง ระยะกระโดดเกือบสุดด่านสุดท้ายควรยากที่สุด

กฎทองของ obby คือ ด่านต้องผ่านได้จริง ทดสอบเองทุกด่าน ถ้าต้องลองเกินสิบรอบถึงผ่าน ผู้เล่นส่วนใหญ่จะเลิกก่อน

แตะจุดเซฟ → Stage = เลขจุด12345เริ่ม★Finishลาวาเกิดใหม่ที่จุดล่าสุดWins +1 · กลับไปจุด 1
Stage เดินหน้าอย่างเดียว ตกลาวาไม่ต้องเริ่มใหม่ทั้งหมด

โค้ดทั้งเกม: Script เดียวใน ServerScriptService

ชี้ที่ ServerScriptService → + → Script ตั้งชื่อ ObbyServer แล้วใส่โค้ดนี้ทั้งหมด อธิบายทีละส่วนข้างล่าง

local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Config = require(ReplicatedStorage:WaitForChild("Config"))
local checkpoints = workspace:WaitForChild("Checkpoints")
local finish = workspace:WaitForChild("Finish")
local firstSpawn = checkpoints:WaitForChild("1")

-- ใครมาแตะ? คืนค่าผู้เล่น (หรือ nil ถ้าไม่ใช่ผู้เล่น) กับ Humanoid
local function playerFromHit(hit)
	local character = hit.Parent
	local humanoid = character and character:FindFirstChildWhichIsA("Humanoid")
	if not humanoid or humanoid.Health <= 0 then
		return nil, nil
	end
	return Players:GetPlayerFromCharacter(character), humanoid
end

-- 1) กระดานคะแนน: Stage กับ Wins
Players.PlayerAdded:Connect(function(player)
	local stats = Instance.new("Folder")
	stats.Name = "leaderstats"

	local stage = Instance.new("IntValue")
	stage.Name = "Stage"
	stage.Value = 1
	stage.Parent = stats

	local wins = Instance.new("IntValue")
	wins.Name = "Wins"
	wins.Parent = stats

	stats.Parent = player
	player.RespawnLocation = firstSpawn
end)

-- 2) จุดเซฟ: SpawnLocation ชื่อ 1, 2, 3 ... ในโฟลเดอร์ Checkpoints
for _, spawn in checkpoints:GetChildren() do
	local number = tonumber(spawn.Name)
	if spawn:IsA("SpawnLocation") and number then
		spawn.Touched:Connect(function(hit)
			local player = playerFromHit(hit)
			if not player then
				return
			end
			local stage = player.leaderstats.Stage
			if number > stage.Value then  -- เดินหน้าได้อย่างเดียว
				stage.Value = number
				player.RespawnLocation = spawn
			end
		end)
	end
end

-- 3) ลาวา: ทุกชิ้นที่ติด Tag "Lava"
local function setupLava(part)
	part.Touched:Connect(function(hit)
		local _, humanoid = playerFromHit(hit)
		if humanoid then
			humanoid.Health = 0
		end
	end)
end
for _, part in CollectionService:GetTagged("Lava") do
	setupLava(part)
end
CollectionService:GetInstanceAddedSignal("Lava"):Connect(setupLava)

-- 4) พื้นที่หายไป: ทุกชิ้นที่ติด Tag "FadePad"
local function setupFade(pad)
	local busy = false  -- แต่ละแผ่นมี busy ของตัวเอง
	pad.Touched:Connect(function(hit)
		if busy or not playerFromHit(hit) then
			return
		end
		busy = true
		for i = 1, 10 do
			pad.Transparency = i / 10
			task.wait(Config.FADE_SECONDS / 10)
		end
		pad.CanCollide = false
		task.wait(Config.GONE_SECONDS)
		pad.CanCollide = true
		pad.Transparency = 0
		busy = false
	end)
end
for _, pad in CollectionService:GetTagged("FadePad") do
	setupFade(pad)
end
CollectionService:GetInstanceAddedSignal("FadePad"):Connect(setupFade)

-- 5) เส้นชัย
local finishing = {}  -- กันนับซ้ำ แยกตามผู้เล่น

finish.Touched:Connect(function(hit)
	local player = playerFromHit(hit)
	if not player or finishing[player] then
		return
	end
	finishing[player] = true

	player.leaderstats.Wins.Value += 1
	player.leaderstats.Stage.Value = 1
	player.RespawnLocation = firstSpawn

	task.wait(1)
	local character = player.Character
	if character then
		character:PivotTo(firstSpawn.CFrame + Vector3.new(0, 4, 0))
	end
	finishing[player] = nil
end)

Players.PlayerRemoving:Connect(function(player)
	finishing[player] = nil
end)

อธิบายทีละส่วน

playerFromHit ทุกส่วนต้องถามคำถามเดียวกันว่า “ใครมาแตะ” จึงเขียนเป็นฟังก์ชันเดียวแล้วเรียกใช้ซ้ำ มันเช็กสามอย่าง: มี Humanoid ไหม, ยังไม่ตายใช่ไหม, และเป็นผู้เล่นจริงหรือแค่ตัวละคร NPC (GetPlayerFromCharacter คืน nil ถ้าไม่ใช่ผู้เล่น) ฟังก์ชันใน Luau คืนค่าได้หลายค่า จึงส่งทั้งผู้เล่นและ Humanoid กลับมา

กระดานคะแนน โฟลเดอร์ที่ชื่อ leaderstats (ตัวเล็กทั้งหมด ต้องสะกดตรงเป๊ะ) ที่อยู่ใต้ผู้เล่น Roblox จะเอาค่าข้างในไปแสดงเป็นกระดานมุมขวาบนให้เอง ค่าข้างในต้องเป็นวัตถุชนิดค่า เช่น IntValue (จำนวนเต็ม) ตั้งชื่อว่าอะไร กระดานก็แสดงหัวคอลัมน์ตามนั้น สังเกตว่าเราใส่ค่าทั้งหมดก่อน แล้วค่อยตั้ง stats.Parent = player เป็นบรรทัดสุดท้าย กระดานจะได้เห็นครบทีเดียว

จุดเซฟ player.RespawnLocation คือจุดที่ผู้เล่นจะเกิดใหม่เมื่อตาย แค่ตั้งค่านี้ Roblox จัดการการเกิดใหม่ให้เอง เงื่อนไข number > stage.Value ทำให้เดินหน้าได้อย่างเดียว ถ้าผู้เล่นเดินย้อนไปเหยียบจุดเก่า ด่านจะไม่ถอยกลับ ส่วน tonumber(spawn.Name) แปลงชื่อ "3" เป็นเลข 3 ถ้าตั้งชื่อเป็นอย่างอื่น ได้ nil แล้วจุดนั้นถูกข้าม

Tag และ CollectionService แทนที่จะมี Script ในลาวาทุกชิ้น เราติดป้ายชื่อ Lava ไว้ แล้ว CollectionService:GetTagged("Lava") คืนรายการทุกชิ้นที่มีป้ายนี้ ส่วน GetInstanceAddedSignal ดักชิ้นที่ถูกเพิ่มทีหลังระหว่างเกม ผลคือวางลาวาเพิ่มอีกร้อยชิ้นก็ไม่ต้องแก้โค้ดเลย แค่ติด Tag

นี่คือนิสัยของนักพัฒนาที่ควรติดตัวตั้งแต่ต้น: ของที่ทำงานเหมือนกัน ต้องมีโค้ดชุดเดียว

busy ของแต่ละแผ่น ใน setupFade ตัวแปร busy ถูกสร้างใหม่ทุกครั้งที่เรียกฟังก์ชัน แต่ละแผ่นจึงมี busy เป็นของตัวเอง แผ่นหนึ่งกำลังจางหายไม่ทำให้แผ่นอื่นหยุดทำงาน ถ้าย้าย local busy = false ออกไปไว้นอกฟังก์ชัน ทุกแผ่นจะใช้ตัวเดียวกัน แล้วเหยียบแผ่นหนึ่งแผ่นอื่นจะเฉยหมด

เส้นชัย ใช้ตาราง finishing เป็น debounce แยกตามผู้เล่น ถ้าใช้ตัวแปรเดียวแบบ EP2 คนหนึ่งเข้าเส้นชัย อีกคนที่มาถึงพร้อมกันจะไม่ถูกนับ ส่วน character:PivotTo(...) ย้ายตัวละครทั้งตัวไปที่ตำแหน่งใหม่ เราย้ายไปเหนือจุดเริ่มต้น 4 หน่วย จะได้ไม่จมพื้น และ PlayerRemoving ลบข้อมูลของคนที่ออกจากเกม ไม่ให้ตารางโตไปเรื่อย ๆ

ทดสอบ

  1. กด Play ดูมุมขวาบน ต้องเห็นกระดานที่มี Stage = 1 และ Wins = 0
  2. เดินไปแตะจุดเซฟ 2 Stage ต้องเปลี่ยนเป็น 2
  3. กระโดดลงลาวา ต้องเกิดใหม่ที่จุด 2 ไม่ใช่จุดเริ่มต้น
  4. ไปให้ถึง Finish Wins ต้องเป็น 1 แล้วถูกส่งกลับจุดเริ่มต้น
  5. ทดสอบแบบ 2 คน (Test → Clients and Servers) ให้สองคนเข้าเส้นชัยพร้อมกัน ทั้งคู่ต้องได้ +1

ถ้าเกิดผิดจุด ให้เช็กว่ามี SpawnLocation หลงอยู่นอกโฟลเดอร์ไหม ถ้าลาวาไม่ทำงาน เช็กว่าติด Tag สะกด Lava ตรงตัวพิมพ์เล็กใหญ่ไหม และดู Output ว่ามีตัวแดงหรือเปล่า

ลองเองใน 10 นาที

  1. เพิ่มด่านที่ 6 แค่วาง SpawnLocation ชื่อ 6 ในโฟลเดอร์ โค้ดไม่ต้องแก้เลย
  2. เพิ่มลาวาอีก 10 ชิ้นด้วยการติด Tag อย่างเดียว
  3. แก้ GONE_SECONDS ใน Config เป็น 1 แล้วเล่นด่าน 3 ใหม่ ความยากเปลี่ยนทั้งเกมจากบรรทัดเดียว

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

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

ตอนหน้า

ได้เกมเล่นได้แล้ว แต่ออกจากเกมเมื่อไหร่ ด่านและจำนวนครั้งที่ชนะหายหมด EP5 ทำเกมที่สอง ลาวาขึ้น ที่สร้างทั้งด่านด้วยโค้ดและทดสอบกฎเกมนอก Studio แล้ว EP6 จะบันทึกข้อมูลผู้เล่นข้ามรอบด้วย DataStore

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

ออกจากเกมแล้วเข้าใหม่ ด่านที่ผ่านหายไหม

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

ทำไมเกิดใหม่แล้วยังมีฟองป้องกันรอบตัว

SpawnLocation มีค่า Duration คือเวลาที่ได้ ForceField หลังเกิด ค่าเริ่มต้น 10 วินาที ใน obby ไม่จำเป็น ตั้งเป็น 0 ได้

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

อ่านต่อ

#เกมกลาง

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

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

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

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

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

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

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

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

อ่าน 7 นาที