iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Software Development

Roblox Studio AI 協作開發大全系列 第 14

第 14 章:任務系統

  • 分享至 

  • xImage
  •  

前面幾章已經讓 AI Adventure Island 有了場景、可蒐集的水晶、HUD、RemoteEvent,以及一套可以拿去問 Assistant 的除錯方法。接下來要把這些零件組成第一條真正的遊戲流程:玩家和 NPC 接任務,蒐集指定物品,回報任務,取得獎勵。

這一章的重點不是「寫很多劇情」。任務系統的核心是狀態。玩家現在是尚未接任務、正在進行、可以回報,還是已完成?只要狀態定義清楚,NPC 對話、HUD 文字、獎勵發放都會變得可控。

本章目標

完成本章後,你會得到第一條完整任務線:

  • 玩家靠近 Guide NPC 時出現互動提示。
  • 第一次互動會接下「蒐集 3 顆水晶」任務。
  • HUD 會顯示目前任務目標。
  • 玩家水晶不足時,Guide 會提示還需要更多水晶。
  • 玩家達成條件後,Guide 會完成任務並給予 Gold 獎勵。
  • 任務完成後不會重複給獎。

你也會學到三個新的設計觀念:

  • ProximityPromptService:集中管理世界中的互動 prompt。
  • ModuleScript:把任務資料從流程程式中抽出來。
  • 任務狀態機:用明確狀態描述玩家進度。

任務設計的玩家流程範例。

圖 14-1 任務系統要把目標、進度、回饋與獎勵串成清楚流程,程式實作前先確認玩家能理解下一步。

Context Pack

本章使用下列 Roblox 官方文件作為 context:

這些文件提供本章需要的四個基礎:

  • ProximityPrompt 可以放在 NPC、Part、Attachment 或 Model 下,讓玩家靠近後按鍵互動。
  • Roblox scripting 是 event-driven,任務流程應該回應互動事件,而不是一直用 loop 檢查。
  • ModuleScript 可以回傳一個 table,用來存放任務資料與共用設定。
  • leaderstats 可以用 IntValue 顯示玩家資料,例如 Crystals 與本章新增的 Gold

開場情境

目前島上已經有一個 NPC_QuestGiver_Placeholder,也有 3 顆可蒐集的水晶。這些元件目前只是存在於同一個場景裡,還沒有形成一個可理解的目標。

我們要把這些元件串成第一條任務:

Guide: Can you collect 3 crystals for me?
Player collects crystals.
Guide: Thank you. Here is 25 gold.

這看起來像對話,其實背後是四個狀態:

NotStarted -> InProgress -> ReadyToTurnIn -> Completed

不要用一個 HasQuest = true 就結束。任務流程通常至少要知道「還沒接」、「接了但未完成」、「可以回報」、「已完成」。如果只用布林值,後面加入多步驟任務、獎勵、重複提示時會很快失控。

第一個 Prompt

先請 Assistant 建立最小可玩的任務系統。

【Ch14 主任務|建立任務系統】
我們要繼續開發 Roblox Studio 專案「AI Adventure Island」。

現有專案背景:
- Workspace.AdventureIsland 裡有一個名為 NPC_QuestGiver_Placeholder 的 NPC。
- 玩家已經可以蒐集水晶。
- Server 程式已經會建立 leaderstats.Crystals。
- StarterGui.AdventureHUD 裡有可顯示任務目標與提示文字的 TextLabel。
- ReplicatedStorage.Remotes 已經存在。
- 目前先不要加入 DataStore。

請建立第一套任務系統:
1. 為 NPC_QuestGiver_Placeholder 新增或更新 ProximityPrompt。
2. 在 server 使用 ProximityPromptService 處理這個 prompt。
3. 在 ServerStorage 建立名為 QuestDefinitions 的 ModuleScript。
4. 定義一個名為 CollectCrystalsForGuide 的任務:
   - RequiredCrystals = 3
   - RewardGold = 25
   - ObjectiveText = "替嚮導蒐集 3 顆水晶"
   - ReadyText = "回去找嚮導"
   - CompletedText = "任務完成"
5. 建立或更新 leaderstats,讓每位玩家都有初始值為 0 的 Gold。
6. 在 server 使用 GuideQuestState attribute 儲存每位玩家的任務狀態。
7. 只能使用以下狀態:
   - NotStarted
   - InProgress
   - ReadyToTurnIn
   - Completed
8. 在 ReplicatedStorage.Remotes 新增名為 QuestUpdate 的 RemoteEvent。
9. 當玩家和 Guide 交談時:
   - NotStarted:改成 InProgress,並將任務目標文字傳送到玩家的 HUD。
   - InProgress 且少於 3 顆水晶:傳送訊息,告訴玩家還需要幾顆水晶。
   - InProgress 且至少有 3 顆水晶:完成任務、給予 25 Gold、改成 Completed,並傳送任務完成文字。
   - Completed:傳送感謝訊息,但不要再次給予 Gold。
10. 更新現有的 AdventureHUD LocalScript,讓它監聽 QuestUpdate 並顯示訊息。

請維持簡單的 prototype 規模。不要重寫水晶蒐集程式,也不要信任 client 對任務是否完成的判斷。

這個 prompt 明確限制了 Assistant 的工作範圍。它不是叫 Assistant 「做一個任務系統」,而是告訴它:

  • 讀哪一些現有物件。
  • 新增哪一些物件。
  • 狀態有哪些。
  • 哪些事情只能在 server 做。
  • 哪些事情只是 HUD 顯示。
  • 這章不要做 DataStore。

這種 prompt 比較長,但對工程型任務很重要。當你要讓 AI 生成一套「會持續成長」的系統時,prompt 不能只描述畫面結果,也要描述資料歸屬與責任邊界。

Assistant 可能產生的結果

Assistant 可能會建立或修改這些物件:

ServerStorage
└── QuestDefinitions (ModuleScript)

ServerScriptService
├── PlayerStatsService 或既有 player stats script
└── QuestService (Script)

ReplicatedStorage
└── Remotes
    └── QuestUpdate (RemoteEvent)

Workspace
└── AdventureIsland
    └── NPCs
        └── NPC_QuestGiver_Placeholder
            └── GuideQuestPrompt (ProximityPrompt)

StarterGui
└── AdventureHUD
    └── AdventureHUDController (LocalScript)

QuestDefinitions 可能長得像這樣:

local QuestDefinitions = {
	CollectCrystalsForGuide = {
		RequiredCrystals = 3,
		RewardGold = 25,
		ObjectiveText = "Collect 3 crystals for the Guide",
		ReadyText = "Return to the Guide",
		CompletedText = "Quest complete",
	},
}

return QuestDefinitions

這是 ModuleScript 最適合初學者理解的用法:先不要把它做成複雜服務,只把「任務資料」集中放在一個 table 裡。

QuestService 的核心邏輯可能像這樣:

local Players = game:GetService("Players")
local ProximityPromptService = game:GetService("ProximityPromptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")

local remotes = ReplicatedStorage:WaitForChild("Remotes")
local questUpdate = remotes:WaitForChild("QuestUpdate")
local questDefinitions = require(ServerStorage:WaitForChild("QuestDefinitions"))

local QUEST_ID = "CollectCrystalsForGuide"
local QUEST_STATE_ATTRIBUTE = "GuideQuestState"

local function getQuestDefinition()
	return questDefinitions[QUEST_ID]
end

local function sendQuestMessage(player, message)
	questUpdate:FireClient(player, message)
end

local function getStat(player, statName)
	local leaderstats = player:FindFirstChild("leaderstats")
	if not leaderstats then
		return nil
	end

	return leaderstats:FindFirstChild(statName)
end

local function ensureQuestState(player)
	local state = player:GetAttribute(QUEST_STATE_ATTRIBUTE)
	if not state then
		player:SetAttribute(QUEST_STATE_ATTRIBUTE, "NotStarted")
		return "NotStarted"
	end

	return state
end

local function completeQuest(player, quest)
	local gold = getStat(player, "Gold")
	if gold then
		gold.Value += quest.RewardGold
	end

	player:SetAttribute(QUEST_STATE_ATTRIBUTE, "Completed")
	sendQuestMessage(player, quest.CompletedText .. " +" .. quest.RewardGold .. " Gold")
end

local function handleGuideQuest(player)
	local quest = getQuestDefinition()
	local state = ensureQuestState(player)
	local crystals = getStat(player, "Crystals")

	if state == "NotStarted" then
		player:SetAttribute(QUEST_STATE_ATTRIBUTE, "InProgress")
		sendQuestMessage(player, quest.ObjectiveText)
		return
	end

	if state == "InProgress" then
		local crystalCount = crystals and crystals.Value or 0
		if crystalCount >= quest.RequiredCrystals then
			completeQuest(player, quest)
		else
			local remaining = quest.RequiredCrystals - crystalCount
			sendQuestMessage(player, "You still need " .. remaining .. " more crystals.")
		end
		return
	end

	if state == "ReadyToTurnIn" then
		completeQuest(player, quest)
		return
	end

	if state == "Completed" then
		sendQuestMessage(player, "Thanks again for helping the island.")
	end
end

Players.PlayerAdded:Connect(function(player)
	ensureQuestState(player)
end)

ProximityPromptService.PromptTriggered:Connect(function(promptObject, player)
	if promptObject.Name ~= "GuideQuestPrompt" then
		return
	end

	handleGuideQuest(player)
end)

這段程式還不是最終架構,但已經比「把所有東西寫在 NPC 底下的一個 Script」更清楚。它把四件事拆開了:

  • 任務資料在 QuestDefinitions
  • 任務流程在 QuestService
  • 玩家數值在 leaderstats
  • HUD 顯示透過 QuestUpdate 通知 client。

如果 Assistant 沒有自動替玩家新增 Gold,可以用修正 prompt 補上:

請更新現有的玩家數值設定,讓每位玩家都有 leaderstats.Gold,型別為 IntValue,初始值為 0。
不要移除 leaderstats.Crystals,也不要替它重新命名。
這項邏輯必須保留在 server。

HUD 的 LocalScript 則只需要顯示 server 傳來的文字:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remotes = ReplicatedStorage:WaitForChild("Remotes")
local questUpdate = remotes:WaitForChild("QuestUpdate")

local screenGui = script.Parent
local objectiveLabel = screenGui:WaitForChild("ObjectiveLabel")
local hintLabel = screenGui:WaitForChild("HintLabel")

questUpdate.OnClientEvent:Connect(function(message)
	objectiveLabel.Text = message
	hintLabel.Text = message
end)

這裡要注意一件事:HUD 只顯示結果,不決定結果。玩家有沒有完成任務、要不要拿獎勵,必須由 server 判斷。

元件拆解

ProximityPrompt

ProximityPrompt 是任務的入口。玩家靠近 Guide,畫面出現按鍵提示,按下後觸發互動事件。

Explorer 中的 ProximityPrompt 放在 Attachment 下方。

圖 14-2 把 ProximityPrompt 放在 Guide 模型的 Attachment 下,可精準控制提示出現的位置;同時保留清楚名稱方便 QuestService 尋找。

建議讓 Assistant 設定這些屬性:

Name = GuideQuestPrompt
ObjectText = Guide
ActionText = Talk
KeyboardKeyCode = E
HoldDuration = 0
MaxActivationDistance = 10
RequiresLineOfSight = false

ObjectText 告訴玩家互動對象是誰,ActionText 告訴玩家會做什麼。MaxActivationDistance 太大會讓提示很吵,太小則玩家找不到互動點。對初版 NPC 來說,10 studs 是容易測試的距離。

ProximityPromptService

前面第 8 章可以直接在單一物件上接 prompt.Triggered。本章開始改用 ProximityPromptService.PromptTriggered,原因是任務系統會長大。

現在只有 Guide,後面會有商店、門、傳送點、寶箱。如果每個 prompt 都放自己的 Script,Explorer 會變得很分散。集中到 QuestService 或互動管理服務,可以讓你清楚看到「玩家按下 prompt 後,server 做了什麼」。

ModuleScript

QuestDefinitions 不執行任務流程,只描述任務資料。

RequiredCrystals = 3
RewardGold = 25
ObjectiveText = "Collect 3 crystals for the Guide"

這種資料不應該散落在多個 if statement 裡。當你之後要把需求改成 5 顆水晶,或把獎勵改成 50 Gold,只要改一個地方。

Quest State

本章使用玩家 attribute 保存任務狀態:

player:SetAttribute("GuideQuestState", "InProgress")

Attribute 適合保存簡單狀態字串。這裡先不用 DataStore,因為我們還在建立玩法原型;關閉 Play Solo 後進度消失是可以接受的。等任務流程穩定後,再進入資料保存。

狀態要用清楚的字串,而不是模糊的變數名稱:

NotStarted
InProgress
ReadyToTurnIn
Completed

ReadyToTurnIn 在這個最小版本中可以不一定被長時間保存,因為玩家和 Guide 對話時,如果水晶已足夠就直接完成。不過保留這個狀態名稱很有價值。後面如果你想讓 HUD 在玩家蒐集到第 3 顆水晶時立刻改成「Return to the Guide」,就可以把狀態改為 ReadyToTurnIn

RemoteEvent

QuestUpdate 是 server 對 client 的通知管道。

questUpdate:FireClient(player, "Collect 3 crystals for the Guide")

這不是讓 client 回報任務完成的通道。client 不能說「我完成了,請給我獎勵」。client 只能接收 server 已經判斷好的任務文字。

這和第 12 章的原則一致:client 可以請求,server 必須驗證。

leaderstats.Gold

第 10 章已經建立 leaderstats.Crystals。本章新增 Gold,讓任務完成後有明確回饋。

Gold 也是 IntValue

local gold = Instance.new("IntValue")
gold.Name = "Gold"
gold.Value = 0
gold.Parent = leaderstats

這個數值會在 Roblox 預設 leaderboard 上顯示。後面的商店章節可以直接沿用它,讓任務獎勵接上購買流程。

Playtest

進入 Play Solo 後,依照下面順序測試。

第一輪:尚未蒐集水晶

  1. 走到 Guide NPC 附近。
  2. 確認畫面出現 Talk prompt。
  3. 按下 E。
  4. HUD 應顯示 Collect 3 crystals for the Guide
  5. 再次按 E。
  6. HUD 應顯示還需要幾顆水晶。

第二輪:蒐集水晶

  1. 蒐集 1 顆水晶。
  2. 回去找 Guide。
  3. HUD 應顯示還需要 2 顆。
  4. 蒐集到 3 顆。
  5. 回去找 Guide。
  6. HUD 應顯示任務完成。
  7. leaderboard 的 Gold 應增加 25。

第三輪:防止重複領獎

  1. 完成任務後再次按 Guide prompt。
  2. HUD 可以顯示感謝文字。
  3. Gold 不應再次增加。

這一輪很重要。任務系統最常見的 bug 不是不能完成,而是完成後可以反覆領獎。

修正 Prompt

如果測試時發現重複給獎,使用這個 prompt:

【Ch14 修正 1|阻止任務重複獎勵】
Guide 任務目前可以重複完成,而且每次都會給予 Gold。

預期結果:
- GuideQuestState 變成 Completed 後,再和 Guide 交談只會顯示感謝訊息。
- Gold 不應再次增加。

目前行為:
- 任務完成後,Gold 仍會再次增加。

請只檢查 QuestService。
請加入防護條件:如果 GuideQuestState 已經是 Completed,completeQuest 就不能再次給予獎勵。
不要修改 QuestDefinitions、水晶蒐集功能或 HUD。

如果 prompt 沒有出現,使用這個 prompt:

【Ch14 修正 2|顯示 Guide 任務提示】
在 Play Solo 中,Guide NPC 沒有顯示 ProximityPrompt。

預期結果:
- NPC_QuestGiver_Placeholder 應該有一個名為 GuideQuestPrompt 的 ProximityPrompt。
- 這個 prompt 的 ObjectText 應顯示「嚮導」,ActionText 應顯示「交談」。

請檢查:
- prompt 的 parent 是否為 BasePart、Attachment,或已設定 PrimaryPart 的 Model。
- MaxActivationDistance 是否合理。
- Enabled 是否為 true。

只修正 prompt 的放置位置與 properties。

如果 HUD 沒有更新,使用這個 prompt:

【Ch14 修正 3|同步任務 HUD 訊息】
QuestService 已經輸出訊息或觸發 QuestUpdate,但 AdventureHUD 沒有顯示任務訊息。

預期結果:
- QuestUpdate.OnClientEvent 會更新 ObjectiveLabel 與 HintLabel。

請只檢查:
- ReplicatedStorage.Remotes.QuestUpdate
- StarterGui.AdventureHUD.AdventureHUDController
- AdventureHUD 底下的 label 名稱

不要修改 server 的任務狀態邏輯。

修正 prompt 的重點是限制範圍。任務系統牽涉 server、client、NPC、HUD、玩家資料。如果你只說「任務壞了,幫我修」,Assistant 很可能重寫太多東西。

工程整理

完成本章後,Explorer 應該維持這種分工:

ServerStorage
└── QuestDefinitions

ServerScriptService
├── PlayerStatsService
├── CrystalCollectionService
├── GuideHintService
└── QuestService

ReplicatedStorage
└── Remotes
    ├── RequestGuideHint
    └── QuestUpdate

StarterGui
└── AdventureHUD
    └── AdventureHUDController

如果 Assistant 把 QuestDefinitions 放在 ReplicatedStorage,也不是一定錯。若 client 也需要讀取任務名稱、描述、圖示,放在 ReplicatedStorage 可以共享。但本章的任務判斷都在 server,為了降低初學者誤把 client 當權威的機率,先放 ServerStorage 比較清楚。

整理時請檢查:

  • QuestService 不應該放在 NPC 下面。
  • HUD LocalScript 不應該直接加 Gold。
  • client 不應該設定 GuideQuestState
  • QuestDefinitions 不應該 require QuestService
  • QuestService 可以 require QuestDefinitions

這些規則看起來嚴格,但它們能避免專案在第 20 章之後變成一團互相依賴的腳本。

本章總結

本章把前面學過的互動、事件、玩家資料與 HUD 串成第一條完整任務線。

你建立了:

  • 一個 Guide NPC prompt。
  • 一個 QuestDefinitions ModuleScript。
  • 一個 server-side QuestService
  • 一個 QuestUpdate RemoteEvent。
  • 一個可發放的 Gold 獎勵。

更重要的是,你開始用狀態機理解遊戲流程。

NotStarted -> InProgress -> ReadyToTurnIn -> Completed

只要這條線清楚,任務就不再只是「NPC 說一句話」。它會變成一個能被測試、能被修正、能被擴充的系統。

下一章會繼續沿著這條任務線前進:把任務從一次性流程擴充成可以控制關卡進度的系統,例如解鎖新區域、開門、觸發下一段探索路線。

關於 Wolke

嗨!我是 Wolke,曾任 Google Developer Expert(GDE,2019–2023)LINE API Expert

我熱衷於研究 AI Agent、n8n 自動化工作流與全端開發架構,致力於將 AI 技術轉化為真正能落地的生產力工具。

如果你喜歡這篇文章,歡迎透過以下方式與我交流:

📚 技術著作
《實用的 Gemini API 開發點子書》:帶你運用 Gemini App、Google AI Studio、Gemini CLI 與 Antigravity IDE,打造 AI Agent 與實用產品。

📝 技術部落格
歡迎追蹤我的 Medium,我會持續分享 Agentic Automation、架構設計與實際開發的踩坑心得。

🎤 技術講座與合作
我持續受邀至技術社群及研討會,分享 AI Agent、自動化工作流、DevOps 與全端開發實戰。

我曾於 DevOpsDays Taipei 2026 主講「不再只是寫腳本!讓 AI 代理人成為你的 SRE 最佳夥伴」工作坊。

如果你的企業、社群或學校正在尋找相關主題講者,歡迎私訊聯繫,洽談講座與工作坊合作!

🎮 我的 Roblox 遊戲

🎁 免費贈送 OpenAI 或 Claude AI 額度

為了鼓勵大家實際動手打造自己的 Roblox 體驗,我每個月會開放:

  • 10 個名額
  • 每人 50 點 AI 額度
  • 名額送完為止

參加方式:

  1. 訂閱本系列文章。
  2. 分享任一篇系列文章。
  3. 私訊分享截圖及你的 AI 帳號 Email。

確認完成後,我會邀請你加入並設定 50 點額度。名額有限,歡迎把握機會!


上一篇
第 13 章:Prompt 修 Bug:從錯誤訊息回到 Assistant
系列文
Roblox Studio AI 協作開發大全14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言