iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Software Development

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

第 12 章:Client / Server 分工

  • 分享至 

  • xImage
  •  

本章目標

第 11 章完成了第一個 HUD。

玩家可以看到:

Crystals: 0
Objective: Collect 3 crystals

leaderstats.Crystals 改變時,AdventureHUDController 會更新畫面。

目前資料流是單向的:

Server state -> replicated Player leaderstats -> LocalScript read -> HUD display

這很適合顯示狀態。

但遊戲不會永遠只是顯示狀態。玩家會按按鈕、選道具、打開商店、使用技能、提交任務。

這些動作通常從 client 開始,最後必須由 server 判斷。

本章要建立第一個安全的 client/server request flow。

我們會在 HUD 裡新增一個簡單按鈕:

Guide

玩家按下後:

  1. LocalScript 呼叫 RemoteEvent:FireServer()
  2. Server 收到請求。
  3. Server 檢查玩家是否已收集至少 3 顆水晶。
  4. 如果通過,server 回傳提示文字給該玩家。
  5. LocalScript 顯示提示文字。

這不是完整任務系統。

這是一個最小 RemoteEvent 範例,用來建立最重要的觀念:

client 可以請求
server 必須驗證
client 不能自己決定權威狀態

RemoteEvent 從 client 傳送到 server 的流程圖。

圖 12-1 Client 透過 RemoteEvent 提出請求,server 驗證後才修改權威狀態,不能直接信任 client 傳入的結果。

Context Pack

本章依據以下 Roblox 官方文件撰寫:

官方 client-server 文件說明,Roblox 遊戲預設是 multiplayer,server 是維護遊戲狀態的 ultimate authority。

一個 Roblox server 同時連接多個玩家 client 的模型。

圖 12-2 每位玩家都有自己的 client,但共同連到同一個 server;共享狀態必須由 server 維護並同步給各 client。

官方 RemoteEvent 文件說明,RemoteEvent 可以跨 client-server boundary 做單向非同步通訊。

最常見的 client -> server 寫法是:

Client: RemoteEvent:FireServer(args)
Server: RemoteEvent.OnServerEvent:Connect(function(player, args))

注意 server callback 的第一個參數:

player

它是觸發 remote 的玩家。你不應該讓 client 自己傳「我是誰」當作權威資料。

安全文件也提醒:任何 client 能觸發 server action 的地方,都可能被濫用。Server 必須驗證 context、permission、type、value。

本章會把這句話落到最小實作。

開場情境

目前你的專案有:

StarterGui
  AdventureHUD
    MainFrame
      CrystalCountLabel
      ObjectiveLabel
    AdventureHUDController

ServerScriptService
  CrystalCollectionService

第 12 章要新增:

ReplicatedStorage
  Remotes
    RequestGuideHint

StarterGui
  AdventureHUD
    MainFrame
      GuideButton
      HintLabel
    AdventureHUDController

ServerScriptService
  GuideHintService

新增的角色分工如下:

GuideButton
  玩家按的 UI button

AdventureHUDController
  client LocalScript,偵測按鈕 Activated,FireServer 請求提示

RequestGuideHint
  RemoteEvent,放在 ReplicatedStorage,client/server 都看得到

GuideHintService
  server Script,驗證玩家 Crystals 數量,再 FireClient 回傳提示

HintLabel
  顯示 server 回傳的提示文字

這章不會讓按鈕直接改 Crystals.Value

這是刻意的。

如果 client 按鈕可以直接改水晶數,玩家就能在本機竄改,讓自己的資料不可信。

第一個 Prompt

請在 Assistant 輸入:

【Ch12 主任務|建立 Client/Server 流程】
我正在製作 Roblox Studio 專案 AI Adventure Island。

目前已有:
- StarterGui.AdventureHUD.MainFrame.CrystalCountLabel
- StarterGui.AdventureHUD.MainFrame.ObjectiveLabel
- StarterGui.AdventureHUD.AdventureHUDController LocalScript
- ServerScriptService.CrystalCollectionService
- 玩家有 leaderstats.Crystals

請幫我建立第一個 client/server RemoteEvent flow。

目標:
玩家在 HUD 按下 GuideButton 後,client 向 server 請求提示。
Server 必須檢查玩家是否已收集至少 3 顆水晶。
如果 Crystals >= 3,server 回傳 "Return to the Guide near the center path."
如果 Crystals < 3,server 回傳 "Collect 3 crystals before asking the Guide."

請完成:
1. 在 ReplicatedStorage 建立 Folder,名稱為 Remotes。
2. 在 Remotes 裡建立 RemoteEvent,名稱為 RequestGuideHint。
3. 在 StarterGui.AdventureHUD.MainFrame 裡建立 TextButton,名稱為 GuideButton。
   - Text = "Guide"
   - 放在 HUD 內,不要擋住 CrystalCountLabel 和 ObjectiveLabel。
4. 在 MainFrame 裡建立 TextLabel,名稱為 HintLabel。
   - 初始文字為空字串或 "Hint: --"
5. 修改 AdventureHUDController LocalScript:
   - 取得 ReplicatedStorage.Remotes.RequestGuideHint。
   - GuideButton.Activated 時呼叫 RequestGuideHint:FireServer()。
   - 監聽 RequestGuideHint.OnClientEvent。
   - 收到 server 回傳文字時,更新 HintLabel.Text。
6. 建立 ServerScriptService.GuideHintService server Script:
   - 取得同一個 RemoteEvent。
   - 使用 OnServerEvent 接收 player。
   - 從 player.leaderstats.Crystals 讀取數值。
   - 由 server 判斷要回傳哪個提示。
   - 使用 FireClient(player, message) 回傳給該玩家。
7. 不要讓 client 直接修改 Crystals.Value。
8. 不要建立完整任務系統、DataStore、商店或背包。

完成後請:
- 列出 Explorer 最終階層。
- 說明 client 做了什麼。
- 說明 server 做了什麼。
- 說明 server 做了哪些驗證。
- 說明 Playtest 時如何確認 client 不能自己決定獎勵或狀態。

這個 prompt 比前幾章更重要。

因為 RemoteEvent 是 AI 很容易寫壞的地方。

如果 prompt 只說:

讓 Guide button 顯示提示。

Assistant 可能把所有邏輯都寫在 LocalScript 裡。

那雖然能顯示文字,但它沒有教會你 server authority。

本章 prompt 明確要求:

client 發請求
server 驗證
server 回覆
client 顯示

Assistant 可能產生的結果

你希望 Explorer 接近:

ReplicatedStorage
  Remotes
    RequestGuideHint

StarterGui
  AdventureHUD
    MainFrame
      CrystalCountLabel
      ObjectiveLabel
      GuideButton
      HintLabel
    AdventureHUDController

ServerScriptService
  CrystalCollectionService
  GuideHintService

AdventureHUDController 可能新增:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remotes = ReplicatedStorage:WaitForChild("Remotes")
local requestGuideHint = remotes:WaitForChild("RequestGuideHint")

local guideButton = mainFrame:WaitForChild("GuideButton")
local hintLabel = mainFrame:WaitForChild("HintLabel")

guideButton.Activated:Connect(function()
	requestGuideHint:FireServer()
end)

requestGuideHint.OnClientEvent:Connect(function(message)
	hintLabel.Text = "Hint: " .. message
end)

GuideHintService 可能是:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remotes = ReplicatedStorage:WaitForChild("Remotes")
local requestGuideHint = remotes:WaitForChild("RequestGuideHint")

local REQUIRED_CRYSTALS = 3

local function getCrystalCount(player)
	local leaderstats = player:FindFirstChild("leaderstats")
	if not leaderstats then
		return 0
	end

	local crystals = leaderstats:FindFirstChild("Crystals")
	if not crystals then
		return 0
	end

	return crystals.Value
end

requestGuideHint.OnServerEvent:Connect(function(player)
	local crystalCount = getCrystalCount(player)

	if crystalCount >= REQUIRED_CRYSTALS then
		requestGuideHint:FireClient(player, "Return to the Guide near the center path.")
	else
		requestGuideHint:FireClient(player, "Collect 3 crystals before asking the Guide.")
	end
end)

這段範例還很小。

但它已經包含 client/server 分工的核心:

LocalScript:
  UI input -> FireServer
  OnClientEvent -> update text

Server Script:
  OnServerEvent(player) -> read server state -> decide -> FireClient

如果 Assistant 產生的程式讓 client 傳入 crystal count,例如:

requestGuideHint:FireServer(crystals.Value)

你要提高警覺。

client 可以傳一個數字給 server,但 server 不應該相信它。

本章的 server 應該自己讀:

player.leaderstats.Crystals

而不是相信 client 說「我有 3 顆」。

元件拆解

Client / Server

Roblox runtime 裡,server 和 client 都有自己的工作。

可以用這個分法:

Server
  管理權威遊戲狀態
  驗證請求
  修改玩家資料
  同步結果

Client
  顯示畫面
  接收本地輸入
  播放本地回饋
  向 server 發出請求

第 10 章的 CrystalCollectionService 是 server。

第 11 章的 AdventureHUDController 是 client。

第 12 章把它們連起來,但不是讓它們混在一起。

這是你開始讀 Roblox 程式時最重要的架構線。

如果看到 AI 把所有東西都放進一個 Script,先問:

這段邏輯應該跑在 server 還是 client?

ReplicatedStorage

RemoteEvent 要放在 client 和 server 都能看見的位置。

本章使用:

ReplicatedStorage
  Remotes
    RequestGuideHint

ReplicatedStorage 的用途是放 client 和 server 都需要存取的共享物件。

RemoteEvent 放這裡很常見,因為:

  • LocalScript 可以 WaitForChild() 找到它。
  • Server Script 也可以 WaitForChild() 找到它。
  • 它本身是通訊物件,不是 server-only 邏輯。

但注意:

RemoteEvent 放在 ReplicatedStorage 不代表它安全。

任何 client 都能看見並呼叫它。

安全性來自 server 的驗證,不是藏起 remote。

RemoteEvent

RemoteEvent 是非同步、單向的跨界通訊。

本章用到兩個方向。

Client -> Server:

requestGuideHint:FireServer()

Server 接收:

requestGuideHint.OnServerEvent:Connect(function(player)
	-- validate and respond
end)

Server -> Client:

requestGuideHint:FireClient(player, message)

Client 接收:

requestGuideHint.OnClientEvent:Connect(function(message)
	hintLabel.Text = "Hint: " .. message
end)

注意 OnServerEvent 的第一個參數是 player

這是 Roblox 自動提供的觸發者。

所以你不需要 client 送:

requestGuideHint:FireServer(player)

也不應該相信 client 自己傳的 player。

Server 驗證

本章 server 驗證很簡單:

local crystalCount = getCrystalCount(player)

if crystalCount >= REQUIRED_CRYSTALS then
	-- allowed message
else
	-- not enough message
end

但這個小檢查就是安全邊界。

client 只能說:

我按了 Guide button。

server 需要自己判斷:

這位玩家真的有 3 顆水晶嗎?

不要讓 client 說:

我有 3 顆水晶,所以請給我下一步。

未來做商店時,也是一樣:

client: 我想買 itemId = "SpeedBoots"
server: 檢查 item 是否存在、價格是否正確、玩家錢是否足夠、購買頻率是否合理

未來做技能時:

client: 我按了技能鍵
server: 檢查冷卻、距離、狀態、目標是否合法

這就是 server authority。

不要相信 Client

安全文件有一句核心精神:

Every piece of data sent from a client must be validated by the server.

本章的 RemoteEvent 沒有 client 參數,所以風險很小。

但你仍然要知道:client 端程式、UI、按鈕、LocalScript 都不是權威來源。

玩家可能:

  • 重複呼叫 RemoteEvent。
  • 用錯誤型別呼叫 RemoteEvent。
  • 傳入極大字串或複雜 table。
  • 嘗試假裝自己有足夠資源。
  • 嘗試要求 server 修改不該修改的狀態。

本章先不寫完整 rate limiter。

但你要開始在 prompt 中要求:

server must validate
do not trust client-provided values
keep client request minimal

這三句之後會一直用。

Playtest

RemoteEvent 流程是否正確

進入 Play mode。

測試一:未收集 3 顆水晶

1. 直接按 GuideButton。
2. HintLabel 應顯示 Collect 3 crystals before asking the Guide.
3. Crystals.Value 不應改變。

測試二:收集 3 顆水晶後

1. 收集 Crystal_01 到 Crystal_03。
2. 確認 HUD 顯示 Crystals: 3。
3. 按 GuideButton。
4. HintLabel 應顯示 Return to the Guide near the center path.

測試三:檢查 server/client Output

可以請 Assistant 在 prototype 中加入少量 print()

[Client] Requesting guide hint
[Server] PlayerName requested guide hint with 3 crystals

但不要把 Output 當 UI。

Output 只是開發驗證。

測試四:確認 client 沒有直接改狀態

檢查 AdventureHUDController

不應該出現 Crystals.Value += 1
不應該出現 leaderstats 內數值被 LocalScript 修改

如果 LocalScript 自己改 Crystals.Value,本章就失敗了。

修正 Prompt

如果按鈕沒反應:

【Ch12 修正 1|修復 GuideButton 請求】
Playtest 時按 GuideButton 沒有任何反應。

請檢查:
1. GuideButton 是否是 TextButton。
2. AdventureHUDController 是否有 guideButton.Activated:Connect。
3. RequestGuideHint 是否存在於 ReplicatedStorage.Remotes。
4. LocalScript 是否能取得 RemoteEvent。
5. ServerScriptService.GuideHintService 是否有 OnServerEvent listener。
6. 不要讓 client 直接改 Crystals.Value。

如果出現 remote discarded:

【Ch12 修正 2|修復 RemoteEvent 監聽】
Output 顯示 Remote event invocation discarded。

請檢查 RequestGuideHint:
1. 是否有 server OnServerEvent listener。
2. 是否有 client OnClientEvent listener 用來接收回覆。
3. RemoteEvent 是否 client/server 都使用同一個 ReplicatedStorage.Remotes.RequestGuideHint。
4. 不要建立第二個同名 RemoteEvent。

如果 Assistant 讓 client 傳水晶數:

【Ch12 修正 3|移除 Client 權威數值】
目前 LocalScript 會把 crystals.Value 傳給 server。

請修正成更安全的版本:
1. Client 只呼叫 RequestGuideHint:FireServer()。
2. Server 在 OnServerEvent(player) 中自己讀 player.leaderstats.Crystals。
3. Server 根據自己讀到的數值回傳提示。
4. 不要相信 client 傳入的 crystal count。

如果 Assistant 做太多系統:

【Ch12 修正 4|簡化 RemoteEvent 範例】
這章只需要最小 client/server RemoteEvent 範例。

請移除或不要建立:
- 完整任務系統
- NPC 對話樹
- DataStore
- 商店
- 背包
- 多個 RemoteEvent

請保留:
- ReplicatedStorage.Remotes.RequestGuideHint
- HUD GuideButton
- HUD HintLabel
- AdventureHUDController client FireServer / OnClientEvent
- GuideHintService server OnServerEvent / FireClient
- server 驗證 Crystals >= 3

工程整理

完成本章後,整理 Explorer:

ReplicatedStorage
  Remotes
    RequestGuideHint

StarterGui
  AdventureHUD
    MainFrame
      CrystalCountLabel
      ObjectiveLabel
      GuideButton
      HintLabel
    AdventureHUDController

ServerScriptService
  CrystalCollectionService
  GuideHintService

記錄 RemoteEvent 表:

RemoteEvent: RequestGuideHint
Location: ReplicatedStorage.Remotes
Direction:
  - Client -> Server: request guide hint
  - Server -> Client: hint message
Client sends:
  - no trusted data
Server validates:
  - player.leaderstats.Crystals >= 3
Server changes state:
  - none in this chapter
Client displays:
  - HintLabel.Text

這份表會在後面非常重要。

每新增一個 RemoteEvent,都應該寫清楚:

  • 誰發起?
  • 誰接收?
  • client 傳了什麼?
  • server 驗證什麼?
  • server 改了什麼狀態?
  • client 只負責顯示什麼?

如果你做不到這張表,代表 RemoteEvent 還沒設計清楚。

本章總結

這一章,你完成了第一個 client/server RemoteEvent flow。

你學到:

  • Roblox 遊戲預設是 client-server runtime。
  • Server 是遊戲狀態的權威來源。
  • Client 適合處理 UI、輸入與本地回饋。
  • RemoteEvent 是跨 client/server boundary 的通道,不是信任來源。
  • ReplicatedStorage 適合放 client/server 都要看到的 RemoteEvent。
  • FireServer() 讓 client 請求 server。
  • OnServerEvent 的第一個參數是觸發 remote 的 Player
  • FireClient() 讓 server 回覆指定 client。
  • Server 必須自己讀取和驗證玩家狀態,不應相信 client 傳來的權威資料。

到這裡,AI Adventure Island 已經有:

  • server 管理的水晶數。
  • client 顯示的 HUD。
  • client/server 之間的第一個安全請求流程。

下一章會暫停新增功能,專門建立 AI 協作除錯能力:如何把 Output、錯誤訊息、目前 Script、預期行為整理成 Assistant 能真正修得動的 prompt。


關於 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 點額度。名額有限,歡迎把握機會!


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

尚未有邦友留言

立即登入留言