Roblox EP4 ประกอบ obby ให้ครบเกม: จุดเซฟ กระดานด่าน ลาวาทั้งเกม และเส้นชัย
รวมทุกอย่างเป็นเกม obby ที่เล่นได้จริง จุดเซฟด้วย SpawnLocation กระดานคะแนน leaderstats ลาวาทั้งเกมคุมด้วย Script เดียวผ่าน Tag และเส้นชัยที่นับครั้งชนะ พร้อมโค้ดเต็ม
- จุดเซฟของ 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 คือ ด่านต้องผ่านได้จริง ทดสอบเองทุกด่าน ถ้าต้องลองเกินสิบรอบถึงผ่าน ผู้เล่นส่วนใหญ่จะเลิกก่อน
โค้ดทั้งเกม: 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 ลบข้อมูลของคนที่ออกจากเกม ไม่ให้ตารางโตไปเรื่อย ๆ
ทดสอบ
- กด Play ดูมุมขวาบน ต้องเห็นกระดานที่มี Stage = 1 และ Wins = 0
- เดินไปแตะจุดเซฟ 2 Stage ต้องเปลี่ยนเป็น 2
- กระโดดลงลาวา ต้องเกิดใหม่ที่จุด 2 ไม่ใช่จุดเริ่มต้น
- ไปให้ถึง
FinishWins ต้องเป็น 1 แล้วถูกส่งกลับจุดเริ่มต้น - ทดสอบแบบ 2 คน (Test → Clients and Servers) ให้สองคนเข้าเส้นชัยพร้อมกัน ทั้งคู่ต้องได้ +1
ถ้าเกิดผิดจุด ให้เช็กว่ามี SpawnLocation หลงอยู่นอกโฟลเดอร์ไหม ถ้าลาวาไม่ทำงาน เช็กว่าติด Tag สะกด Lava ตรงตัวพิมพ์เล็กใหญ่ไหม และดู Output ว่ามีตัวแดงหรือเปล่า
ลองเองใน 10 นาที
- เพิ่มด่านที่ 6 แค่วาง SpawnLocation ชื่อ
6ในโฟลเดอร์ โค้ดไม่ต้องแก้เลย - เพิ่มลาวาอีก 10 ชิ้นด้วยการติด Tag อย่างเดียว
- แก้
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 ได้
คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ