跳至主要内容

Lua 腳本:範例與模式

本頁包含 FreeMinecraftModels 道具腳本的完整可運行範例,以及實用模式和最佳實踐。每個範例都附有詳細說明,解釋其功能和原因。

如果你剛接觸道具腳本,請從入門指南開始。完整的 API 詳情請參閱 Prop API


範例:無敵道具

本範例教你: 最簡單的實用腳本——取消傷害,使道具無法被破壞。

這是 FreeMinecraftModels 自帶的預製腳本。

完整腳本檔案(點擊展開)
return {
api_version = 1,

on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}

逐步講解

  1. 鉤子選擇 -- on_left_click 在玩家攻擊(左鍵點擊)道具時觸發。底層是道具的支撐盔甲架上的 EntityDamageByEntityEvent

  2. 事件守衛 -- context.event 在此鉤子中應該始終存在,但添加守衛檢查是一種好習慣。

  3. 取消 -- context.event.cancel() 取消傷害事件,防止盔甲架受到傷害和被摧毀。

用法

在你的道具 .yml 設定中新增:

isEnabled: true
scripts:
- invulnerable.lua

範例:互動式門

本範例教你: 右鍵切換狀態、播放和停止動畫,以及使用 context.state 追蹤門是開啟還是關閉狀態。

完整腳本檔案(點擊展開)
local OPEN_ANIMATION = "open"
local CLOSE_ANIMATION = "close"

return {
api_version = 1,

on_spawn = function(context)
context.state.is_open = false
end,

on_left_click = function(context)
-- Make the door invulnerable
if context.event then
context.event.cancel()
end
end,

on_right_click = function(context)
local loc = context.prop.current_location

if context.state.is_open then
-- Close the door
context.prop:stop_animation()
context.prop:play_animation(CLOSE_ANIMATION, true, false)
context.state.is_open = false

if loc then
context.world:play_sound(
"BLOCK_WOODEN_DOOR_CLOSE",
loc.x, loc.y, loc.z,
1.0, 1.0
)
end
else
-- Open the door
context.prop:stop_animation()
context.prop:play_animation(OPEN_ANIMATION, true, false)
context.state.is_open = true

if loc then
context.world:play_sound(
"BLOCK_WOODEN_DOOR_OPEN",
loc.x, loc.y, loc.z,
1.0, 1.0
)
end
end
end
}

逐步講解

  1. 檔案作用域常數 -- OPEN_ANIMATIONCLOSE_ANIMATION 定義在 return 表的上方。這樣可以方便地針對使用不同動畫名稱的不同模型檔案進行修改。

  2. 狀態初始化 -- on_spawn 設定 context.state.is_open = false。狀態在此道具實例的所有鉤子之間保持。

  3. 無敵 -- on_left_click 鉤子取消傷害,防止門被意外破壞。

  4. 切換邏輯 -- on_right_click 檢查 context.state.is_open,停止當前動畫,播放相應的動畫,翻轉狀態,並播放聲音。在 play_animation() 之前呼叫 stop_animation() 確保乾淨的過渡。

  5. 聲音回饋 -- context.world:play_sound() 在道具位置播放 Bukkit Sound 列舉名稱。聲音名稱必須使用 UPPER_CASE 列舉名。

動畫名稱

"open""close" 等動畫名稱必須與模型檔案中定義的一致。如果找不到動畫,play_animation() 回傳 false 且不會執行任何操作。請檢查你的模型檔案以確認準確的動畫名稱。


範例:帶粒子效果的接近觸發器

本範例教你: 區域建立、區域進入/離開事件監聽、粒子效果和清理。

完整腳本檔案(點擊展開)
local ZONE_RADIUS = 8
local PARTICLE_INTERVAL = 10 -- ticks between particle bursts

return {
api_version = 1,

on_spawn = function(context)
context.state.zone_handle = nil
context.state.particle_task = nil
context.state.players_in_zone = 0

local loc = context.prop.current_location
if loc == nil then return end

-- Create a sphere zone around the prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, ZONE_RADIUS)
context.state.zone_handle = handle

-- Watch for enter/leave
context.zones:watch(
handle,
function(player)
-- on_enter
context.state.players_in_zone = (context.state.players_in_zone or 0) + 1
end,
function(player)
-- on_leave
context.state.players_in_zone = math.max(0, (context.state.players_in_zone or 0) - 1)
end
)

-- Start a repeating particle effect at the zone boundary
context.state.particle_task = context.scheduler:run_repeating(0, PARTICLE_INTERVAL, function(tick_context)
local prop_loc = tick_context.prop.current_location
if prop_loc == nil then return end

-- Spawn particles in a ring at the zone boundary
for angle = 0, 350, 30 do
local rad = math.rad(angle)
local px = prop_loc.x + math.cos(rad) * ZONE_RADIUS
local pz = prop_loc.z + math.sin(rad) * ZONE_RADIUS

if (tick_context.state.players_in_zone or 0) > 0 then
-- Red particles when players are inside
tick_context.world:spawn_particle("DUST", px, prop_loc.y + 0.5, pz, 1, 0, 0, 0, 0)
else
-- Green particles when zone is empty
tick_context.world:spawn_particle("HAPPY_VILLAGER", px, prop_loc.y + 0.5, pz, 1, 0, 0, 0, 0)
end
end
end)
end,

on_left_click = function(context)
-- Make invulnerable
if context.event then
context.event.cancel()
end
end,

on_destroy = function(context)
-- Clean up the repeating task
if context.state.particle_task then
context.scheduler:cancel(context.state.particle_task)
context.state.particle_task = nil
end
-- Clean up the zone watch
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
context.state.zone_handle = nil
end
end
}

逐步講解

  1. 常數 -- ZONE_RADIUSPARTICLE_INTERVAL 位於檔案作用域,便於調整。

  2. 狀態初始化 -- on_spawn 在執行其他操作之前,將所有狀態欄位設定為 nil / 0

  3. 區域建立 -- context.zones:create_sphere() 建立以道具為中心的球形區域。回傳的控制代碼是一個數字 ID,用於後續參照該區域。

  4. 區域監聽 -- context.zones:watch() 註冊玩家進入和離開的回呼。回呼增減儲存在 context.state 中的計數器。

  5. 粒子迴圈 -- 重複任務每半秒在道具周圍以環形生成粒子。粒子類型根據區域內是否有玩家而變化。

  6. 清理 -- on_destroy 取消重複任務並取消區域監聽。雖然在道具被移除時兩者都會自動清理,但明確清理是最佳實踐。

粒子效能

每個 tick 生成大量粒子可能影響效能。使用合理的間隔(10-20 tick),並保持粒子數量較低。上面的範例使用 PARTICLE_INTERVAL = 10(每秒兩次),每環僅 12 個粒子。


範例:發聲道具

本範例教你: 互動時播放聲音、使用 state 和 scheduler 實現冷卻行為,以及防止連續快速互動。

完整腳本檔案(點擊展開)
local SOUND_NAME = "BLOCK_NOTE_BLOCK_HARP"
local COOLDOWN_TICKS = 40 -- 2 seconds between sounds

return {
api_version = 1,

on_spawn = function(context)
context.state.on_cooldown = false
end,

on_left_click = function(context)
-- Make invulnerable
if context.event then
context.event.cancel()
end
end,

on_right_click = function(context)
-- Prevent spam
if context.state.on_cooldown then
return
end

local loc = context.prop.current_location
if loc == nil then return end

-- Play the sound
context.world:play_sound(SOUND_NAME, loc.x, loc.y, loc.z, 1.0, 1.0)

-- Show some particles
context.world:spawn_particle("NOTE", loc.x, loc.y + 1.5, loc.z, 5, 0.3, 0.3, 0.3, 0)

-- Set cooldown
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}

逐步講解

  1. 冷卻模式 -- 由於 FMM 道具腳本沒有像 EliteMobs 那樣的內建 context.cooldowns API,此範例使用 context.state.on_cooldownscheduler:run_later() 實現簡單的冷卻。播放聲音時旗標設為 true,延遲任務在 COOLDOWN_TICKS 後將其重設。

  2. 聲音播放 -- context.world:play_sound() 接受 UPPER_CASE 的 Bukkit Sound 列舉名稱、座標、音量和音調。

  3. 粒子回饋 -- 聲音播放時,道具上方會出現音符粒子,提供視覺提示。

  4. 無敵 -- on_left_click 鉤子照常取消傷害。

  5. 排程器回呼上下文 -- run_later 回呼接收 later_context,一個新的上下文。我們使用 later_context.state(而不是 context.state)來重設冷卻旗標。由於狀態是共享的,兩者指向同一個表——但使用回呼的上下文參數是正確的習慣。

冷卻實作

FMM 道具腳本沒有 EliteMobs 的 context.cooldowns API。請使用此處展示的模式:在 context.state 中使用布林旗標,配合 scheduler:run_later() 進行重設。這讓你可以完全控制冷卻持續時間和行為。


範例:動畫氛圍道具

本範例教你: 生成時啟動循環動畫,配合基於 tick 的粒子發射器。

完整腳本檔案(點擊展開)
return {
api_version = 1,

on_spawn = function(context)
-- Start the idle animation immediately, looping
context.prop:play_animation("idle", false, true)

-- Emit ambient particles every 40 ticks (2 seconds)
context.state.ambient_task = context.scheduler:run_repeating(0, 40, function(tick_context)
local loc = tick_context.prop.current_location
if loc == nil then return end

tick_context.world:spawn_particle(
"ENCHANT",
loc.x, loc.y + 1, loc.z,
10, 0.5, 0.5, 0.5, 0.05
)
end)
end,

on_left_click = function(context)
if context.event then
context.event.cancel()
end
end,

on_destroy = function(context)
if context.state.ambient_task then
context.scheduler:cancel(context.state.ambient_task)
end
end
}

逐步講解

  1. 自動啟動動畫 -- on_spawn 立即播放循環 "idle" 動畫。blend 的 false 表示不與之前的動畫混合直接開始。loop 的 true 表示無限重複。

  2. 氛圍粒子 -- 重複任務每 2 秒在道具上方生成附魔台粒子,營造魔法氛圍效果。

  3. 清理 -- on_destroy 取消粒子任務。


最佳實踐

  • 從小鉤子開始並驗證。 寫一個只發送日誌訊息的 on_spawn。確認它觸發了,然後在此基礎上建構。

  • 保持輔助函式為區域變數。 在 return 表上方宣告輔助函式,如 local function toggle_door(context)。這使它們不會進入全域作用域。

  • on_spawn 中初始化所有狀態。 如果你在 on_right_click 中讀取 context.state.is_open 但從未在 on_spawn 中設定它,它將是 nil,你的比較可能會產生意外行為。

  • 完成後取消重複任務。 每個 run_repeating 都應在 on_destroy 中有對應的 cancel。洩漏的任務會浪費 CPU。

  • 使用排程器回呼的新上下文。 排程器回呼接收新的上下文參數。始終在回呼內使用該參數,而不是外部的 context

  • 保持 on_game_tick 輕量。 如果你定義了此鉤子,它每個伺服器 tick 都會執行(每秒 20 次)。將開銷大的工作放在基於狀態的冷卻檢查之後。

  • 預設使道具無敵。 除非你希望道具可以被破壞,否則在每個腳本中都包含 on_left_click 的傷害取消。

  • Bukkit 列舉使用 UPPER_CASE。 聲音名稱和粒子名稱必須使用 Bukkit 列舉常數格式(例如 "FLAME",而不是 "flame")。


常見新手錯誤

  • 在排程器回呼中使用外部 context 外部上下文捕獲的是鉤子執行時的快照。在回呼內部,始終使用回呼自身的參數。

  • 忘記取消重複任務。 如果你在 on_spawn 中啟動了 run_repeating 但從未取消它,任務會一直執行直到道具被移除。

  • 未在 on_spawn 中初始化狀態。 在設定之前讀取 context.state.x 會回傳 nil,這可能會無聲地破壞你的邏輯。

  • 錯誤的動畫名稱。 如果 play_animation("open") 回傳 false,說明動畫名稱與模型檔案中的不符。檢查模型以確認準確的名稱。

  • 聲音/粒子名稱使用小寫。 "flame" 不起作用——使用 "FLAME"。API 內部會將粒子轉換為 UPPER_CASE,但 Sound 列舉名稱必須精確匹配。

  • 忘記 api_version = 1 回傳的表必須包含此欄位,否則 FMM 將不會載入該腳本。

  • 在回傳的表中放置非鉤子函式。 輔助函式必須在 return 陳述式上方宣告。回傳表中只允許使用鉤子名稱(on_spawnon_right_click 等)作為鍵。


QC 檢查清單

在部署道具腳本之前,使用此檢查清單進行驗證:

  1. 檔案恰好回傳一個包含 api_version = 1 的表。
  2. 每個鉤子名稱與鉤子列表中的條目精確匹配。
  3. 在呼叫 cancel() 之前,透過 if context.event then 檢查了 context.event
  4. context.state 欄位在 on_spawn 中初始化。
  5. 每個 scheduler:run_repeating(...) 呼叫在 on_destroy 中有對應的 scheduler:cancel(...)
  6. 排程器回呼使用回呼自身的上下文參數,而不是外部的 context
  7. on_game_tick 鉤子將開銷大的工作放在檢查之後。
  8. 所有方法名稱存在於 Prop API 參考中——沒有編造的別名。
  9. 聲音和粒子名稱使用 UPPER_CASE 的 Bukkit 列舉名稱。
  10. 腳本不在鉤子或回呼中呼叫任何阻塞或長時間執行的操作。

AI 生成提示

如果你想讓 AI 可靠地生成道具腳本,請確保提示詞中包含:

  • 準確的鉤子名稱 -- 例如 on_right_click,而不是「當玩家點擊道具時」。
  • 來自模型檔案的動畫名稱 -- AI 無法猜測這些名稱;請提供它們。
  • Sound 列舉名稱 -- 例如 "BLOCK_NOTE_BLOCK_HARP",而不是「豎琴聲」。
  • Particle 列舉名稱 -- 例如 "FLAME",而不是「火焰粒子」。
  • 道具是否應該無敵 -- 如果是,請包含帶有 context.event.cancel()on_left_click
  • 只使用文件中記錄的方法名稱 -- 如果不在 Prop API 頁面上,它就不存在。

良好的提示詞範例

撰寫一個 FMM 道具腳本,右鍵點擊時播放 "activate" 動畫,使道具無敵,點擊時在道具位置生成 FLAME 粒子,播放 BLOCK_LEVER_CLICK 聲音,並使用 context.state 和 scheduler:run_later 實現點擊之間 2 秒的冷卻。


下一步

  • 入門指南 -- 檔案結構、鉤子、首個腳本講解、範本
  • Prop API -- 所有上下文表的完整 API 參考
  • 疑難排解 -- 常見問題、除錯技巧