Level 3-4 - Nodebox Deep Dive
In diesem Kapitel baust du Blöcke, die nicht wie ein voller Würfel aussehen. Stattdessen setzt du Formen aus mehreren kleinen Quadern zusammen. Damit entstehen Tische, Stühle, Lampen, Rohre, Geländer, Rahmen und viele weitere Spielobjekte.
-0.5 bis +0.5).u = 1/16).fixed, wallmounted, connected, leveled anwenden.paramtype2 (4dir, facedir, wallmounted) nutzen.selection_box und collision_box didaktisch klug gestalten.
Du solltest bereits wissen, wie ein Mod aufgebaut ist (mod.conf, init.lua),
wie man core.register_node verwendet und wie Texturen mit tiles eingebunden werden.
Ein normaler Block ist immer ein voller Würfel. Das reicht für Stein oder Erde, aber nicht für Möbel, Technik oder kleine Dekorationen. Nodebox löst genau dieses Problem: ein einzelner Node kann aus vielen Quadern bestehen.
Jeder Block besitzt einen lokalen Raum von -0.5 bis +0.5 in
x, y, z.
| Achse | Bedeutung | Negativ | Positiv |
|---|---|---|---|
x |
links/rechts | links | rechts |
y |
unten/oben | unten | oben |
z |
vorne/hinten | eine Richtung der Tiefe | Gegenrichtung der Tiefe |
Sehr praktisch ist der Raster-Trick:
local u = 1/16
-- wichtige Ankerpunkte
-- -8*u = -0.5
-- 8*u = 0.5
Dadurch kannst du wie in "Pixeln" planen und musst fast nie mit unlesbaren Dezimalzahlen arbeiten.
Jede Box besteht aus genau sechs Werten:
{x1, y1, z1, x2, y2, z2}
Regel: x1 <= x2, y1 <= y2, z1 <= z2.
Bei vertauschten Werten verschwinden Boxen oft oder werden falsch dargestellt.
Lies die Box {-2*u, -8*u, -2*u, 2*u, -6*u, 2*u}: Wie breit, wie hoch, an welcher Höhe?
Ohne drawtype = "nodebox" wird die Form nicht als Nodebox gezeichnet.
Die Grundform sieht so aus:
core.register_node("course:example", {
description = "Nodebox Beispiel",
tiles = {"default_wood.png"},
drawtype = "nodebox",
paramtype = "light",
sunlight_propagates = true,
node_box = {
type = "fixed",
fixed = {
{-8*u, 6*u, -8*u, 8*u, 8*u, 8*u},
},
},
})
| Typ | Wofür? | Beispiele |
|---|---|---|
fixed |
feste Form aus Quadern | Tisch, Stuhl, Rahmen, Maschine |
wallmounted |
Formen für Wand/Boden/Decke | Lampe, Kamera, Halter |
connected |
automatische Arme zu Nachbarn | Rohre, Zäune, Kabel |
leveled |
variable Höhe über param2 |
Tank, Messstand, Füllbalken |
fixed - Beispielelocal u = 1/16
core.register_node("course:table", {
description = "Kleiner Tisch",
tiles = {"default_wood.png"},
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "4dir",
groups = {choppy = 2, oddly_breakable_by_hand = 2},
node_box = {
type = "fixed",
fixed = {
-- Tischplatte
{-8*u, 6*u, -8*u, 8*u, 8*u, 8*u},
-- Beine
{-7*u, -8*u, -7*u, -5*u, 6*u, -5*u},
{ 5*u, -8*u, -7*u, 7*u, 6*u, -5*u},
{-7*u, -8*u, 5*u, -5*u, 6*u, 7*u},
{ 5*u, -8*u, 5*u, 7*u, 6*u, 7*u},
},
},
selection_box = {
type = "fixed",
fixed = {-8*u, -8*u, -8*u, 8*u, 8*u, 8*u},
},
collision_box = {
type = "fixed",
fixed = {-8*u, -8*u, -8*u, 8*u, 8*u, 8*u},
},
})
local u = 1/16
core.register_node("course:chair", {
description = "Holzstuhl",
tiles = {"default_wood.png"},
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "4dir",
groups = {choppy = 2, oddly_breakable_by_hand = 2},
node_box = {
type = "fixed",
fixed = {
-- Sitz
{-6*u, -1*u, -6*u, 6*u, 1*u, 6*u},
-- Lehne
{-6*u, 1*u, 4*u, 6*u, 10*u, 6*u},
-- Beine
{-6*u, -8*u, -6*u, -4*u, -1*u, -4*u},
{ 4*u, -8*u, -6*u, 6*u, -1*u, -4*u},
{-6*u, -8*u, 4*u, -4*u, -1*u, 6*u},
{ 4*u, -8*u, 4*u, 6*u, -1*u, 6*u},
},
},
collision_box = {
type = "fixed",
fixed = {-6*u, -8*u, -6*u, 6*u, 10*u, 6*u},
},
})
local u = 1/16
core.register_node("course:window_frame", {
description = "Fensterrahmen",
tiles = {"default_wood.png"},
drawtype = "nodebox",
paramtype = "light",
sunlight_propagates = true,
groups = {choppy = 2, oddly_breakable_by_hand = 2},
node_box = {
type = "fixed",
fixed = {
{-8*u, -8*u, -1*u, 8*u, -6*u, 1*u},
{-8*u, 6*u, -1*u, 8*u, 8*u, 1*u},
{-8*u, -8*u, -1*u, -6*u, 8*u, 1*u},
{ 6*u, -8*u, -1*u, 8*u, 8*u, 1*u},
{-1*u, -8*u, -1*u, 1*u, 8*u, 1*u},
},
},
})
paramtype2| Einstellung | Wirkung | Typischer Einsatz |
|---|---|---|
paramtype2 = "4dir" |
4 Richtungen um Y-Achse | Möbel, Konsolen, Terminals |
paramtype2 = "facedir" |
mehr Drehvarianten inkl. oben/unten | Maschinen, die gekippt sein können |
paramtype2 = "wallmounted" |
wand-/boden-/deckenbezogen | Lampen, Kameras, Halter |
on_place = core.rotate_node
wallmounted - Wandlampelocal u = 1/16
core.register_node("course:wall_lamp", {
description = "Wandlampe",
tiles = {"default_torch_on_floor.png"},
drawtype = "nodebox",
paramtype = "light",
light_source = 12,
paramtype2 = "wallmounted",
groups = {choppy = 2, oddly_breakable_by_hand = 3},
node_box = {
type = "wallmounted",
wall_top = {
{-2*u, 6*u, -2*u, 2*u, 8*u, 2*u},
},
wall_bottom = {
{-2*u, -8*u, -2*u, 2*u, -6*u, 2*u},
},
wall_side = {
{-2*u, -2*u, 6*u, 2*u, 2*u, 8*u},
},
},
selection_box = {
type = "wallmounted",
wall_top = {-2*u, 6*u, -2*u, 2*u, 8*u, 2*u},
wall_bottom = {-2*u, -8*u, -2*u, 2*u, -6*u, 2*u},
wall_side = {-2*u, -2*u, 6*u, 2*u, 2*u, 8*u},
},
})
Bei wallmounted definierst du drei Formen:
wall_top (Decke), wall_bottom (Boden), wall_side (Wand).
connected - Rohrsystemlocal u = 1/16
core.register_node("course:pipe", {
description = "Rohr",
tiles = {"default_steel_block.png"},
drawtype = "nodebox",
paramtype = "light",
groups = {cracky = 2, pipe = 1},
connects_to = {"group:pipe"},
node_box = {
type = "connected",
fixed = {
{-2*u, -2*u, -2*u, 2*u, 2*u, 2*u},
},
connect_left = {-8*u, -1*u, -1*u, -2*u, 1*u, 1*u},
connect_right = { 2*u, -1*u, -1*u, 8*u, 1*u, 1*u},
connect_front = {-1*u, -1*u, -8*u, 1*u, 1*u, -2*u},
connect_back = {-1*u, -1*u, 2*u, 1*u, 1*u, 8*u},
connect_top = {-1*u, 2*u, -1*u, 1*u, 8*u, 1*u},
connect_bottom = {-1*u, -8*u, -1*u, 1*u, -2*u, 1*u},
},
})
connects_to = {"group:pipe", "course:valve"}
Wichtig: Connected-Nodebox rotiert nicht wie ein normales fixed-Objekt.
Die Engine entscheidet automatisch anhand von connects_to, welche Arme sichtbar sind.
leveled - variabler Füllstandlocal u = 1/16
core.register_node("course:tank", {
description = "Tank (leveled)",
tiles = {"default_glass.png"},
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "leveled",
sunlight_propagates = true,
groups = {cracky = 3, oddly_breakable_by_hand = 2},
node_box = {
type = "leveled",
fixed = {-6*u, -8*u, -6*u, 6*u, 8*u, 6*u},
},
})
Kernidee: Bei paramtype2 = "leveled" speichert param2 den Level.
Das Thema ist fortgeschritten, aber perfekt für Mini-Projekte mit Rechtsklick-Interaktion.
selection_box steuert das Anklicken, collision_box das Anstoßen.
Beide müssen nicht identisch sein.
local u = 1/16
core.register_node("course:thin_sign", {
description = "Dünnes Schild",
tiles = {"default_wood.png"},
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "4dir",
node_box = {
type = "fixed",
fixed = {-8*u, -2*u, 7*u, 8*u, 6*u, 8*u},
},
selection_box = {
type = "fixed",
fixed = {-8*u, -3*u, 6*u, 8*u, 7*u, 8*u},
},
collision_box = {
type = "fixed",
fixed = {-8*u, -2*u, 7*u, 8*u, 6*u, 8*u},
},
})
-- falsch (x1 > x2 und z1 > z2)
{ 2*u, -8*u, 2*u, -2*u, -6*u, -2*u }
-- richtig
{-2*u, -8*u, -2*u, 2*u, -6*u, 2*u}
Wenn node_box vorhanden ist, aber drawtype = "nodebox" fehlt,
wirkt der Block oft wie ein normaler Vollblock.
Bei dünnen Formen fehlen oft paramtype = "light" und ggf.
sunlight_propagates = true. Dann sehen transparente Formen zu dunkel aus.
Spieler bleiben schnell hängen, wenn die Collision zu fein fragmentiert ist. Lösung: Kollisionsform vereinfachen, Auswahlform getrennt optimieren.
local u = 1/16 arbeiten.Kombiniere Tisch, Stuhl und Wandlampe zu einem kleinen Bau-Set. Optional gibst du jedem Node ein eigenes Crafting-Rezept.
core.register_craft({
output = "course:table",
recipe = {
{"default:wood", "default:wood", "default:wood"},
{"", "default:stick", ""},
{"", "default:stick", ""},
},
})
wallmounted (Gehäuse + Linse).connected-Node.connected-Node.Baue ein komplettes "Arcade-Eck": Automat, Hocker, Wandlampe, Kabel. Alle Objekte sollen sinnvoll anklickbar sein und keine nervigen Kollisionsfehler haben.
fixed): Sockel, Säule, Schale.fixed): Korpus, Bildschirm, Joystick.connected): Pfosten plus automatische Streben.connected + fixed): Technik-Look für Fabriken.-0.5 .. +0.5 in allen Achsen.u = 1/16.drawtype = "nodebox" nicht vergessen.selection_box und collision_box separat denken.connected und leveled werden Nodes dynamisch.