Lua 腳本:範例與模式
本頁包含 FreeMinecraftModels 道具腳本的完整可運行範例,以及實用模式和最佳實踐。每個範例都附有詳細說明,解釋其功能和原因。
如果你剛接觸道具腳本,請從入門指南開始。完整的 API 詳情請參閱 Prop API。
範例:無敵道具
本範例教你: 最簡單的實用腳本——取消傷害,使道具無法被破壞。
這是 FreeMinecraftModels 自帶的預製腳本。
完整腳本檔案(點擊展開)
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
逐步講解
-
鉤子選擇 --
on_left_click在玩家攻擊(左鍵點擊)道具時觸發。底層是道具的支撐盔甲架上的EntityDamageByEntityEvent。 -
事件守衛 --
context.event在此鉤子中應該始終存在,但添加守衛檢查是一種好習慣。 -
取消 --
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
}
逐步講解
-
檔案作用域常數 --
OPEN_ANIMATION和CLOSE_ANIMATION定義在 return 表的上方。這樣可以方便地針對使用不同動畫名稱的不同模型檔案進行修改。 -
狀態初始化 --
on_spawn設定context.state.is_open = false。狀態在此道具實例的所有鉤子之間保持。 -
無敵 --
on_left_click鉤子取消傷害,防止門被意外破壞。 -
切換邏輯 --
on_right_click檢查context.state.is_open,停止當前動畫,播放相應的動畫,翻轉狀態,並播放聲音。在play_animation()之前呼叫stop_animation()確保乾淨的過渡。 -
聲音回饋 --
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
}
逐步講解
-
常數 --
ZONE_RADIUS和PARTICLE_INTERVAL位於檔案作用域,便於調整。 -
狀態初始化 --
on_spawn在執行其他操作之前,將所有狀態欄位設定為nil/0。 -
區域建立 --
context.zones:create_sphere()建立以道具為中心的球形區域。回傳的控制代碼是一個數字 ID,用於後續參照該區域。 -
區域監聽 --
context.zones:watch()註冊玩家進入和離開的回呼。回呼增減儲存在context.state中的計數器。 -
粒子迴圈 -- 重複任務每半秒在道具周圍以環形生成粒子。粒子類型根據區域內是否有玩家而變化。
-
清理 --
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
}
逐步講解
-
冷卻模式 -- 由於 FMM 道具腳本沒有像 EliteMobs 那樣的內建
context.cooldownsAPI,此範例使用context.state.on_cooldown和scheduler:run_later()實現簡單的冷卻。播放聲音時旗標設為true,延遲任務在COOLDOWN_TICKS後將其重設。 -
聲音播放 --
context.world:play_sound()接受 UPPER_CASE 的 Bukkit Sound 列舉名稱、座標、音量和音調。 -
粒子回饋 -- 聲音播放時,道具上方會出現音符粒子,提供視覺提示。
-
無敵 --
on_left_click鉤子照常取消傷害。 -
排程器回呼上下文 --
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
}
逐步講解
-
自動啟動動畫 --
on_spawn立即播放循環"idle"動畫。blend 的false表示不與之前的動畫混合直接開始。loop 的true表示無限重複。 -
氛圍粒子 -- 重複任務每 2 秒在道具上方生成附魔台粒子,營造魔法氛圍效果。
-
清理 --
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_spawn、on_right_click等)作為鍵。
QC 檢查清單
在部署道具腳本之前,使用此檢查清單進行驗證:
- 檔案恰好回傳一個包含
api_version = 1的表。 - 每個鉤子名稱與鉤子列表中的條目精確匹配。
- 在呼叫
cancel()之前,透過if context.event then檢查了context.event。 context.state欄位在on_spawn中初始化。- 每個
scheduler:run_repeating(...)呼叫在on_destroy中有對應的scheduler:cancel(...)。 - 排程器回呼使用回呼自身的上下文參數,而不是外部的
context。 on_game_tick鉤子將開銷大的工作放在檢查之後。- 所有方法名稱存在於 Prop API 參考中——沒有編造的別名。
- 聲音和粒子名稱使用 UPPER_CASE 的 Bukkit 列舉名稱。
- 腳本不在鉤子或回呼中呼叫任何阻塞或長時間執行的操作。
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 秒的冷卻。