Level 3-4 - Nodebox Deep Dive

Nodebox: Nicht-vollblockige Formen in Luanti mit Lua

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.

Lernziele

1. Voraussetzungen

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.

2. Warum Nodebox?

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.

3. Koordinatensystem im Block

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

3.1 Das 1/16-Raster

Sehr praktisch ist der Raster-Trick:

Raster-Grundlage
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.

3.2 Eine Box lesen

Jede Box besteht aus genau sechs Werten:

Box-Format
{x1, y1, z1, x2, y2, z2}

Regel: x1 <= x2, y1 <= y2, z1 <= z2. Bei vertauschten Werten verschwinden Boxen oft oder werden falsch dargestellt.

Mikro-Aufgabe

Lies die Box {-2*u, -8*u, -2*u, 2*u, -6*u, 2*u}: Wie breit, wie hoch, an welcher Höhe?

4. Nodebox aktivieren

Ohne drawtype = "nodebox" wird die Form nicht als Nodebox gezeichnet. Die Grundform sieht so aus:

Grundgerüst für Nodebox-Nodes
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},
        },
    },
})

5. Nodebox-Typen im Überblick

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

6. Typ fixed - Beispiele

6.1 Beispiel: Tisch

course:table
local 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},
    },
})

6.2 Beispiel: Stuhl

course:chair
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},
    },
})

6.3 Beispiel: Fensterrahmen (lichtdurchlässig)

course:window_frame
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},
        },
    },
})
Aufgaben zu fixed
  1. Mache die Tischplatte dicker (z.B. 3u statt 2u).
  2. Baue Armlehnen in den Stuhl ein (links/rechts je ein Quader).
  3. Erweitere den Rahmen um eine horizontale Mittelstrebe.
  4. Baue eine Bank mit 2 Beinen und langer Sitzfläche.

7. Rotation mit 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
Rotation beim Platzieren erzwingen
on_place = core.rotate_node

8. Typ wallmounted - Wandlampe

course:wall_lamp
local 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).

9. Typ connected - Rohrsystem

course:pipe
local 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},
    },
})
Verbindung zu Ventil erlauben
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.

10. Typ leveled - variabler Füllstand

course:tank
local 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.

11. Selection-Box vs Collision-Box

selection_box steuert das Anklicken, collision_box das Anstoßen. Beide müssen nicht identisch sein.

Dünnes Schild mit großzügiger Auswahlbox
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},
    },
})

12. Debugging - häufige Fehler

12.1 Vertauschte Koordinaten

Falsch vs richtig
-- 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}

12.2 drawtype vergessen

Wenn node_box vorhanden ist, aber drawtype = "nodebox" fehlt, wirkt der Block oft wie ein normaler Vollblock.

12.3 Licht wirkt falsch

Bei dünnen Formen fehlen oft paramtype = "light" und ggf. sunlight_propagates = true. Dann sehen transparente Formen zu dunkel aus.

12.4 Kollision nervt

Spieler bleiben schnell hängen, wenn die Collision zu fein fragmentiert ist. Lösung: Kollisionsform vereinfachen, Auswahlform getrennt optimieren.

13. Werkzeugkasten für sauberes Design

  1. Auf Papier im 16x16-Raster skizzieren (Top-View + Seitenansicht).
  2. Pro Bauteil Quadergruppen planen (Sockel, Beine, Streben, Deko).
  3. Immer mit local u = 1/16 arbeiten.
  4. Früh mit einfacher Collision testen, danach Details erhöhen.

14. Mini-Projekt: Möbel-Set "Cafe"

Kombiniere Tisch, Stuhl und Wandlampe zu einem kleinen Bau-Set. Optional gibst du jedem Node ein eigenes Crafting-Rezept.

Beispiel-Rezept für Tisch
core.register_craft({
    output = "course:table",
    recipe = {
        {"default:wood", "default:wood", "default:wood"},
        {"", "default:stick", ""},
        {"", "default:stick", ""},
    },
})
Team-Aufteilung für Unterricht

15. Viele Übungen (leicht bis Boss-Fight)

Level 1 - leicht

  1. Baue einen Hocker mit Sitzplatte und 4 Beinen.
  2. Baue ein Regal mit 2 Seitenwänden und 3 Böden.
  3. Verwandle den Tisch in eine lange Werkbank.

Level 2 - mittel

  1. Wandkamera mit wallmounted (Gehäuse + Linse).
  2. Dünne Kabelvariante als connected-Node.
  3. Großrohrvariante als zweite connected-Node.
  4. Fensterrahmen mit 2 vertikalen und 1 horizontalen Streben.

Level 3 - anspruchsvoll

  1. Ventil-Block bauen und mit Rohren verbindbar machen.
  2. Tank-Node mit Rechtsklick zum Erhöhen des Levels erweitern.
  3. Smart-Lampe: Rechtsklick schaltet zwischen hell/dunkel (zwei Node-Varianten).

Boss-Fight

Baue ein komplettes "Arcade-Eck": Automat, Hocker, Wandlampe, Kabel. Alle Objekte sollen sinnvoll anklickbar sein und keine nervigen Kollisionsfehler haben.

16. Kreativ-Ideen für eigene Projekte

17. Zusammenfassung

18. Weiterführende Seiten