Lua-Scripting: Beispiele & Muster
Diese Seite enthält vollständige, funktionsfähige Beispiele von FreeMinecraftModels-Prop-Skripten sowie praktische Muster und bewährte Vorgehensweisen. Jedes Beispiel enthält eine Erklärung, die beschreibt, was es tut und warum.
Wenn du neu im Prop-Scripting bist, beginne mit Erste Schritte. Für vollständige API-Details siehe die Prop-API.
Beispiel: Unverwundbares Prop
Was dieses Beispiel lehrt: Das einfachste nützliche Skript -- Schaden abbrechen, damit das Prop nicht zerstört werden kann.
Dies ist das vorgefertigte Skript, das mit FreeMinecraftModels ausgeliefert wird.
Vollständige Skriptdatei (zum Aufklappen klicken)
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
Erklärung
-
Hook-Wahl --
on_left_clickwird ausgelöst, wenn ein Spieler das Prop schlägt (Linksklick). Intern handelt es sich um einEntityDamageByEntityEventauf dem zugrunde liegenden Armor Stand des Props. -
Event-Prüfung --
context.eventsollte in diesem Hook immer vorhanden sein, aber die Prüfung ist eine gute Praxis. -
Abbruch --
context.event.cancel()bricht das Schadensereignis ab, wodurch der Armor Stand keinen Schaden nimmt und nicht zerstört wird.
Verwendung
Füge Folgendes zur .yml-Konfiguration deines Props hinzu:
isEnabled: true
scripts:
- invulnerable.lua
Beispiel: Interaktive Tür
Was dieses Beispiel lehrt: Zustand bei Rechtsklick umschalten, Animationen abspielen und stoppen sowie context.state verwenden, um zu verfolgen, ob die Tür offen oder geschlossen ist.
Vollständige Skriptdatei (zum Aufklappen klicken)
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
}
Erklärung
-
Konstanten auf Dateiebene --
OPEN_ANIMATIONundCLOSE_ANIMATIONwerden oberhalb der Return-Tabelle definiert. So lassen sie sich leicht für verschiedene Modelldateien mit unterschiedlichen Animationsnamen anpassen. -
Zustandsinitialisierung --
on_spawnsetztcontext.state.is_open = false. Der Zustand bleibt über alle Hooks für diese Prop-Instanz bestehen. -
Unverwundbarkeit -- Der
on_left_click-Hook bricht den Schaden ab, damit die Tür nicht versehentlich zerstört werden kann. -
Umschaltlogik --
on_right_clickprüftcontext.state.is_open, stoppt die aktuelle Animation, spielt die entsprechende Animation ab, schaltet den Zustand um und gibt einen Sound aus. Derstop_animation()-Aufruf vorplay_animation()gewährleistet saubere Übergänge. -
Sound-Rückmeldung --
context.world:play_sound()spielt den Bukkit-Sound-Enum-Namen an der Position des Props ab. Sound-Namen müssen UPPER_CASE-Enum-Namen sein.
Animationsnamen wie "open" und "close" müssen mit den in der Modelldatei definierten übereinstimmen. Wenn die Animation nicht gefunden wird, gibt play_animation() false zurück und es passiert nichts. Überprüfe deine Modelldatei auf die genauen Animationsnamen.
Beispiel: Näherungsauslöser mit Partikeln
Was dieses Beispiel lehrt: Zonen-Erstellung, Zonenüberwachung für Betreten-/Verlassen-Ereignisse, Partikeleffekte und Bereinigung.
Vollständige Skriptdatei (zum Aufklappen klicken)
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
}
Erklärung
-
Konstanten --
ZONE_RADIUSundPARTICLE_INTERVALbefinden sich auf Dateiebene zur einfachen Anpassung. -
Zustandsinitialisierung --
on_spawnsetzt alle Zustandsfelder aufnil/0, bevor irgendetwas anderes geschieht. -
Zonen-Erstellung --
context.zones:create_sphere()erstellt eine Kugelzone um das Prop. Der zurückgegebene Handle ist eine numerische ID, um diese Zone später zu referenzieren. -
Zonenüberwachung --
context.zones:watch()registriert Callbacks für das Betreten und Verlassen durch Spieler. Die Callbacks erhöhen und verringern einen Zähler, der incontext.stategespeichert ist. -
Partikelschleife -- Eine wiederkehrende Aufgabe erzeugt alle halbe Sekunde Partikel in einem Ring um das Prop. Der Partikeltyp ändert sich je nachdem, ob sich Spieler in der Zone befinden.
-
Bereinigung --
on_destroybricht die wiederkehrende Aufgabe ab und entfernt die Zonenüberwachung. Obwohl beides beim Entfernen des Props automatisch bereinigt wird, ist eine explizite Bereinigung eine bewährte Vorgehensweise.
Das Erzeugen vieler Partikel bei jedem Tick kann die Leistung beeinträchtigen. Verwende ein angemessenes Intervall (10-20 Ticks) und halte die Partikelanzahl niedrig. Das obige Beispiel verwendet PARTICLE_INTERVAL = 10 (zweimal pro Sekunde) mit nur 12 Partikeln pro Ring.
Beispiel: Sound-erzeugendes Prop
Was dieses Beispiel lehrt: Sounds bei Interaktion abspielen, Cooldown-ähnliches Verhalten mit State und Scheduler sowie die Verhinderung von Schnellklick-Interaktionen.
Vollständige Skriptdatei (zum Aufklappen klicken)
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
}
Erklärung
-
Cooldown-Muster -- Da FMM-Prop-Skripte keine eingebaute
context.cooldowns-API wie EliteMobs haben, implementiert das Beispiel einen einfachen Cooldown mitcontext.state.on_cooldownundscheduler:run_later(). Das Flag wird auftruegesetzt, wenn der Sound abgespielt wird, und eine verzögerte Aufgabe setzt es nachCOOLDOWN_TICKSzurück. -
Sound-Wiedergabe --
context.world:play_sound()nimmt den Bukkit-Sound-Enum-Namen in UPPER_CASE, Koordinaten, Lautstärke und Tonhöhe entgegen. -
Partikel-Rückmeldung -- Notenpartikel erscheinen über dem Prop, wenn der Sound abgespielt wird, als visueller Hinweis.
-
Unverwundbarkeit -- Der
on_left_click-Hook bricht den Schaden wie gewohnt ab. -
Scheduler-Callback-Kontext -- Der
run_later-Callback erhältlater_context, einen frischen Kontext. Wir verwendenlater_context.state(nichtcontext.state), um das Cooldown-Flag zurückzusetzen. Da der Zustand geteilt wird, zeigen beide auf dieselbe Tabelle -- aber die Verwendung des Callback-Kontextparameters ist die korrekte Gewohnheit.
FMM-Prop-Skripte haben nicht die context.cooldowns-API von EliteMobs. Verwende das hier gezeigte Muster: ein boolesches Flag in context.state kombiniert mit scheduler:run_later() zum Zurücksetzen. Dies gibt dir volle Kontrolle über Cooldown-Dauer und -Verhalten.
Beispiel: Animiertes Umgebungs-Prop
Was dieses Beispiel lehrt: Eine Schleifenanimation beim Spawnen starten, mit einem Tick-basierten Partikelemitter.
Vollständige Skriptdatei (zum Aufklappen klicken)
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
}
Erklärung
-
Automatischer Animationsstart --
on_spawnspielt sofort eine Schleifenanimation"idle"ab. Dasfalsefür Blend bedeutet, dass sie ohne Überblendung einer vorherigen Animation startet. Dastruefür Loop bedeutet, dass sie sich endlos wiederholt. -
Umgebungspartikel -- Eine wiederkehrende Aufgabe erzeugt alle 2 Sekunden Verzauberungstisch-Partikel über dem Prop und schafft einen magischen Umgebungseffekt.
-
Bereinigung --
on_destroybricht die Partikelaufgabe ab.
Bewährte Vorgehensweisen
-
Mit einem kleinen Hook beginnen und überprüfen. Schreibe einen einzelnen
on_spawn, der eine Log-Nachricht sendet. Bestätige, dass er ausgelöst wird. Dann baue darauf auf. -
Hilfsfunktionen lokal halten. Deklariere Helfer wie
local function toggle_door(context)oberhalb der Return-Tabelle. So bleiben sie außerhalb des globalen Gültigkeitsbereichs. -
Alle Zustände in
on_spawninitialisieren. Wenn ducontext.state.is_openinon_right_clickliest, aber nie inon_spawnsetzt, wird esnilsein und deine Vergleiche könnten sich unerwartet verhalten. -
Wiederkehrende Aufgaben abbrechen, wenn sie nicht mehr benötigt werden. Jedes
run_repeatingsollte ein passendescancelinon_destroyhaben. Nicht abgebrochene Aufgaben verschwenden CPU. -
Frische Scheduler-Callback-Kontexte verwenden. Scheduler-Callbacks erhalten einen frischen Kontextparameter. Verwende diesen Parameter immer innerhalb des Callbacks, nicht den äußeren
context. -
on_game_tickleichtgewichtig halten. Wenn du diesen Hook definierst, wird er jeden Server-Tick ausgeführt (20 Mal pro Sekunde). Schütze aufwendige Arbeit hinter einer zustandsbasierten Cooldown-Prüfung. -
Props standardmäßig unverwundbar machen. Sofern du nicht möchtest, dass das Prop zerstörbar ist, füge die
on_left_click-Schadensabbruch-Logik in jedes Skript ein. -
UPPER_CASE für Bukkit-Enums verwenden. Sound-Namen und Partikel-Namen müssen das Bukkit-Enum-Konstantenformat verwenden (z. B.
"FLAME", nicht"flame").
Häufige Anfängerfehler
-
Den äußeren
contextinnerhalb eines Scheduler-Callbacks verwenden. Der äußere Kontext erfasst einen Schnappschuss zum Zeitpunkt, als der Hook ausgeführt wurde. Innerhalb von Callbacks immer den eigenen Parameter des Callbacks verwenden. -
Vergessen, wiederkehrende Aufgaben abzubrechen. Wenn du ein
run_repeatinginon_spawnstartest, aber nie abbrichst, läuft die Aufgabe, bis das Prop entfernt wird. -
Zustand nicht in
on_spawninitialisieren. Das Lesen voncontext.state.xvor dem Setzen gibtnilzurück, was deine Logik stillschweigend brechen kann. -
Falsche Animationsnamen. Wenn
play_animation("open")falsezurückgibt, stimmt der Animationsname nicht mit dem in der Modelldatei überein. Überprüfe das Modell auf die genauen Namen. -
Sound-/Partikelnamen in Kleinbuchstaben.
"flame"funktioniert nicht -- verwende"FLAME". Die API konvertiert Partikel intern in UPPER_CASE, aber Sound-Enum-Namen müssen exakt sein. -
api_version = 1vergessen. Die zurückgegebene Tabelle muss dieses Feld enthalten, sonst lädt FMM das Skript nicht. -
Funktionen in der zurückgegebenen Tabelle platzieren, die keine Hooks sind. Hilfsfunktionen müssen oberhalb der
return-Anweisung deklariert werden. Nur Hook-Namen (on_spawn,on_right_clickusw.) sind als Schlüssel in der zurückgegebenen Tabelle erlaubt.
QC-Checkliste
Verwende diese Checkliste, um ein Prop-Skript vor dem Einsatz zu überprüfen:
- Die Datei gibt genau eine Tabelle mit
api_version = 1zurück. - Jeder Hook-Name stimmt genau mit einem Eintrag in der Hook-Liste überein.
context.eventwird mitif context.event thengeprüft, bevorcancel()aufgerufen wird.context.state-Felder werden inon_spawninitialisiert.- Jeder
scheduler:run_repeating(...)-Aufruf hat ein passendesscheduler:cancel(...)inon_destroy. - Scheduler-Callbacks verwenden den eigenen Kontextparameter des Callbacks, nicht den äußeren
context. on_game_tick-Hooks schützen aufwendige Arbeit hinter einer Prüfung.- Alle Methodennamen existieren in der Prop-API-Referenz -- keine erfundenen Aliase.
- Sound- und Partikelnamen verwenden UPPER_CASE-Bukkit-Enum-Namen.
- Das Skript ruft keine blockierenden oder lang laufenden Operationen innerhalb eines Hooks oder Callbacks auf.
Tipps zur KI-Generierung
Wenn du möchtest, dass eine KI zuverlässig Prop-Skripte generiert, stelle sicher, dass der Prompt Folgendes enthält:
- Exakter Hook-Name -- z. B.
on_right_click, nicht "wenn der Spieler das Prop klickt". - Animationsnamen aus der Modelldatei -- die KI kann diese nicht erraten; gib sie an.
- Sound-Enum-Namen -- z. B.
"BLOCK_NOTE_BLOCK_HARP", nicht "Harfenklang". - Partikel-Enum-Namen -- z. B.
"FLAME", nicht "Feuerpartikel". - Ob das Prop unverwundbar sein soll -- wenn ja,
on_left_clickmitcontext.event.cancel()einbeziehen. - Nur dokumentierte Methodennamen verwenden -- wenn es nicht auf der Prop-API-Seite steht, existiert es nicht.
Gutes Prompt-Beispiel
Schreibe ein FMM-Prop-Skript, das die Animation "activate" bei Rechtsklick abspielt, das Prop unverwundbar macht, FLAME-Partikel an der Prop-Position beim Klick erzeugt, den BLOCK_LEVER_CLICK-Sound abspielt und einen 2-Sekunden-Cooldown zwischen Klicks mit context.state und scheduler:run_later hat.
Nächste Schritte
- Erste Schritte -- Dateistruktur, Hooks, erste Skript-Erklärung, Vorlagen
- Prop-API -- vollständige API-Referenz für alle Context-Tabellen
- Fehlerbehebung -- häufige Probleme, Debugging-Tipps