
Intro #
Roblox Studio ist die Entwicklungsumgebung, mit der alle Spiele auf der Roblox-Plattform gebaut werden. Hier lernst du, wie du eigene Spiele erstellst - von der 3D-Welt bis zur Spielmechanik.
Die Programmiersprache ist Luau, eine Weiterentwicklung von Lua, die speziell für Roblox optimiert wurde. Im Vergleich zu anderen Sprachen ist Lua schlank und gut lesbar - ein guter Einstieg ins Programmieren.
Steuerung #
| Taste(n-Kombination) | Funktion |
|---|---|
| W A S D | vor, links, zurück, rechts |
| E Q | |
| rechte Maustaste | |
| Mausrad | Zoom |
| CTRL+D | duplizieren |
| F2 | umbenennen |
| F5 | ausführen |
| Shift+F5 | beenden |
| CTRL+S | speichern |
| F9 | Developer Console |
Explorer #
Workspace #
Beinhaltet alle Objekte (Part, Baseplate, SpawnLocation, …) der Welt (Experience). Wird in Roblox Studio mit großem “W” geschrieben, laut Dokumentation soll ein kleines “W” verwendet werden.
Properties #
Erlaubt das Anzeigen und Anpassen der Eigenschaften (Attribute / Parameter) eines Objekts (aus Explorer).
Part (Studio) #
Part hinzufügen und manipulieren: Select, Move, Scale, Rotate, …
Klassen #
Game #
tbd
Workspace #
siehe Workspace
Instance #
Objekte (Instanzen) erstellen und manipulieren.
Part #
local newPart = Instance.new("Part")
newPart.Name = "NewPart“
newPart.Parent = game.WorkspaceMath #
random #
while true do
local r, g, b = math.random(0, 255), math.random(0, 255), math.random(0, 255)
print(r, g, b)
workspace.Part.Color = Color3.fromRGB(r, g, b)
task.wait(0.1)
endWorkspace #
auch game.Workspace, game:GetService("Workspace")
https://create.roblox.com/docs/de-de/reference/engine/classes/Workspace
Je nach [[#Die wichtigsten Speicherorte|Kontext]] (Speicherorte / Ordner) können bestimmte Children erreicht werden.
for i, child in workspace:GetChildren() do
print(i, child.Name, child.ClassName)
endLuau #
Roblox’s eigene Lua -Variante.
- Script erstellen
- im
Explorer-Panel + neben dem Objekt (z.B.ServerScriptService) betätigen - Object auswählen oder suchen →
Script(serverseitig) oderLocalScript(clientseitig)
- Script öffnen
- Doppelklick auf das Script → der Code-Editor öffnet sich
Script-Typen #
| Typ | Wo | Wofür |
|---|---|---|
Script |
ServerScriptService | Spiellogik, die alle sehen |
LocalScript |
StarterPlayerScripts / StarterGui | Nur für den eigenen Spieler |
ModuleScript |
Überall | Wiederverwendbarer Code |
Grundlegende Syntax (Lua) #
-- Variablen
local name = "Spieler"
local punkte = 0
-- Bedingungen
if punkte > 10 then
print("Gut gemacht!")
end
-- Schleifen
for i = 1, 5 do
print(i)
end
-- Funktionen
local function sagHallo(name)
print("Hallo, " .. name)
end
sagHallo("Welt")Wichtige Roblox-Objekte #
-- Spieler finden
local Players = game:GetService("Players")
-- Ein Part erstellen
local part = Instance.new("Part")
part.Parent = workspace
part.Position = Vector3.new(0, 10, 0)
-- Events nutzen
Players.PlayerAdded:Connect(function(player)
print(player.Name .. " ist beigetreten!")
end)Namenskonventionen #
Variablen & Funktionen → camelCase
#
local playerName = "Max"
local totalScore = 0
local function getUserData(playerId)
-- ...
endKonstanten → SCREAMING_SNAKE_CASE
#
local MAX_PLAYERS = 10
local SPAWN_POSITION = Vector3.new(0, 5, 0)
local GAME_VERSION = "1.0.0"Klassen / Module / Services → PascalCase
#
local PlayerService = {}
local InventoryManager = {}
-- Roblox Services (bereits so benannt)
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")Roblox-Objekte (Instanzen) → PascalCase
#
local MyPart = Instance.new("Part")
local SpawnFolder = workspace:FindFirstChild("Spawns")Private Felder in Modulen → _underscore Präfix
#
local MyModule = {}
local _privateData = {} -- nur intern genutzt
function MyModule.publicFunction()
-- ...
end
return MyModuleÜbersicht #
| Typ | Konvention | Beispiel |
|---|---|---|
| Lokale Variable | camelCase |
playerHealth |
| Funktion | camelCase |
calculateDamage() |
| Konstante | UPPER_SNAKE_CASE |
MAX_SPEED |
| Klasse / Modul | PascalCase |
WeaponSystem |
| Roblox-Instanz | PascalCase |
BasePart |
| Privates Feld | _camelCase |
_internalState |
wichtige Speicherorte #
| Ordner | Script-Typ | Läuft auf | Sichtbar für |
|---|---|---|---|
ServerScriptService |
Script |
Server | Alle Spieler |
StarterPlayerScripts |
LocalScript |
Client | Nur dieser Spieler |
StarterGui |
LocalScript |
Client | Nur dieser Spieler |
StarterCharacterScripts |
LocalScript |
Client | Nur dieser Spieler |
ReplicatedStorage |
ModuleScript |
Beide | Server & Client |
ServerStorage |
ModuleScript |
Server | Nur Server |
Server vs. Client #
-- SERVER (ServerScriptService)
-- ✅ Kann Spielerdaten speichern (DataStore)
-- ✅ Verwaltet Spiellogik (Punkte, Leben)
-- ✅ Sicher – Spieler können es nicht manipulieren
-- ❌ Kein Zugriff auf GUI des Spielers
-- CLIENT (StarterPlayerScripts / StarterGui)
-- ✅ Kann GUI steuern
-- ✅ Reagiert auf Tasteneingaben (UserInputService)
-- ✅ Flüssigere Animationen & Effekte
-- ❌ Unsicher – kann von Spielern manipuliert werdenTypisches Beispiel #
-- ❌ FALSCH: LocalScript in ServerScriptService
-- Läuft gar nicht!
-- ❌ FALSCH: Script in StarterGui
-- Läuft, aber kann nicht auf GUI zugreifen
-- ✅ RICHTIG: Punkte auf Server verwalten
-- ServerScriptService/Script:
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
end)
-- ✅ RICHTIG: Tastendruck auf Client erkennen
-- StarterPlayerScripts/LocalScript:
local UIS = game:GetService("UserInputService")
UIS.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.E then
print("E gedrückt!")
end
end)Kommunikation zwischen Server & Client #
Da Server und Client getrennt sind, braucht man RemoteEvents:
-- In ReplicatedStorage: ein RemoteEvent namens "GivePunkte"
-- CLIENT schickt Anfrage:
local event = game.ReplicatedStorage.GivePunkte
event:FireServer(10)
-- SERVER empfängt:
event.OnServerEvent:Connect(function(player, punkte)
print(player.Name .. " möchte " .. punkte .. " Punkte")
end)💡 Faustregel: Spiellogik & Datenspeicherung → Server. GUI & Input → Client. Geteilter Code → ReplicatedStorage.
Mehrere Scripts im gleichen Ordner #
Scripts laufen parallel und unabhängig. Alle Scripts in einem Ordner starten gleichzeitig beim Spielstart – es gibt keine festgelegte Reihenfolge.
-- Script A
print("Ich bin Script A") -- kann vor oder nach B erscheinen!
-- Script B
print("Ich bin Script B") -- Reihenfolge nicht garantiert!Teilen sie sich Variablen? #
-- ❌ NEIN – jedes Script hat seinen eigenen Speicher
-- Script A:
local punkte = 100 -- nur in Script A sichtbar
-- Script B:
print(punkte) -- Fehler! punkte existiert hier nichtWie kommunizieren Scripts miteinander? #
Weg 1: Über ein Objekt in der Welt
MeinOrdner (Folder) und Wert (IntValue) zuvor unterhalb Workspace anlegen. Wert ist nicht persistent, fällt auf Ursprungswert zurück. Je nach Timing wird Ursprungswert verwenden.
Script unter ServerScriptService.
-- Script A schreibt:
workspace.MeinOrdner.Wert.Value = 42
-- Script B liest:
print(workspace.MeinOrdner.Wert.Value) -- 42Weg 2: ModuleScript (empfohlen)
ModuleScript Daten in ReplicatedStorage. ScriptA und ScriptB unter ServerScriptService.
-- ModuleScript "Daten" in ReplicatedStorage:
local Daten = {}
Daten.punkte = 0
return Daten
-- Script A:
local Daten = require(game.ReplicatedStorage.Daten)
Daten.punkte = 100
-- Script B:
local Daten = require(game.ReplicatedStorage.Daten)
print(Daten.punkte) -- 100Weg 3: BindableEvents (für Server-zu-Server)
ScriptA und ScriptB als Script unter ServerScriptService. MeinEvent als BindableEvent unter ServerScriptService.
-- Script A feuert ein Event:
task.wait(3)
print("Script A")
local event = game.ServerScriptService.MeinEvent
event:Fire("Hallo Script B!")
-- Script B hört zu:
print("Script B")
local event = game.ServerScriptService.MeinEvent
event.Event:Connect(function(nachricht)
print(nachricht) -- "Hallo Script B!"
end)Wann macht es Sinn, mehrere Scripts zu haben?
| Situation | Empfehlung |
|---|---|
| Viele unabhängige Systeme | separate Scripts sinnvoll |
| Scripts teilen viele Daten | ModuleScript nutzen |
| Ein Script wird sehr lang | aufteilen & Module nutzen |
| Gleiche Logik mehrfach | ModuleScript + require() |
💡 Faustregel: Lieber wenige, gut organisierte Scripts mit ModuleScripts für geteilten Code – als viele kleine Scripts die schwer zu überblicken sind.
For #
Numerisch #
-- for i = start, ende, schritt do
for i = 1, 10 do
print(i) -- 1, 2, 3 ... 10
end
for i = 1, 10, 2 do
print(i) -- 1, 3, 5, 7, 9
end
for i = 10, 1, -1 do
print(i) -- 10, 9, 8 ... 1
endGenerisch (über Tabellen) #
-- ipairs: Array, mit Index, stoppt bei nil
for index, value in ipairs(meinArray) do
print(index, value)
end
-- pairs: alle Einträge, auch gemischte Keys
for key, value in pairs(meineDictionary) do
print(key, value)
end
-- Modern (Luau): ohne ipairs/pairs
for value in meinArray do
print(value)
endVergleich ipairs vs pairs
#
local t = {
"Apfel", -- [1]
"Banane", -- [2]
name = "Obst", -- String-Key
"Kirsche", -- [3]
}
for i, v in ipairs(t) do
print(i, v) -- 1 Apfel, 2 Banane, 3 Kirsche
end -- ⚠️ name="Obst" wird ignoriert!
for k, v in pairs(t) do
print(k, v) -- alles, auch name="Obst"
end -- ⚠️ Reihenfolge nicht garantiert!Schleife abbrechen #
for i = 1, 100 do
if i == 5 then
break -- Schleife sofort beenden
end
print(i) -- 1, 2, 3, 4
end💡
continuegibt es in Luau seit 2022:continuespringt zum nächsten Durchlauf ohnebreak.
Lebensdauer von Variabeln #
Nur innerhalb des Blocks — danach sind sie weg:
for i = 1, 5 do
local x = i * 2 -- x existiert nur hier
print(x) -- funktioniert
end
print(x) -- nil! x existiert nicht mehr
print(i) -- nil! auch i ist wegDas gilt für alle Blöcke in Lua — for, while, if, do...end. Sobald der Block endet, werden die lokalen Variablen freigegeben.
Soll der Wert erhalten bleiben, muss die Variable vorher deklarieren:
local result
for i = 1, 5 do
result = i * 2 -- schreibt in die äußere Variable
end
print(result) -- 10GetChildren vs GetDescendant #
-- nur Part aus gleicher Ebene
for _, child in script.Parent:GetChildren() do
if child:IsA("Part") then
print(child.Name)
end
end
-- Part und Sub/PartSub aus tieferen Ebenen
for _, child in script.Parent:GetDescendants() do
if child:IsA("Part") then
print(child.Name)
end
endIf #
Grundstruktur #
if bedingung then
-- code
elseif andereBedingung then
-- code
else
-- code
endVergleichsoperatoren #
if x == 10 then -- gleich
if x ~= 10 then -- ungleich (nicht != wie in anderen Sprachen!)
if x > 10 then -- größer
if x < 10 then -- kleiner
if x >= 10 then -- größer gleich
if x <= 10 then -- kleiner gleichlogische Operatoren #
if x > 0 and x < 10 then -- und
if x < 0 or x > 10 then -- oder
if not x then -- nichtTruthy / Falsy #
In Luau gilt nur false und nil als falsy – alles andere ist truthy:
if 0 then print("wahr") end -- ✅ 0 ist truthy! Sehr ungewöhnlich!
if "" then print("wahr") end -- ✅ leerer String ist truthy! Sehr ungewöhnlich!
if nil then print("wahr") end -- ❌ nil ist falsy
if false then print("wahr") end -- ❌ false ist falsyKurzform für nil-Check #
local part = workspace:FindFirstChild("MyPart")
-- Beide sind equivalent:
if part ~= nil then print("gefunden") end
if part then print("gefunden") end -- kürzerTernärer Operator #
local x = if y == 10 then "ja" else "nein"Komentar #
-- Zeile
print("Hello") -- Inline
--[[
Block
]]Enums #
FromValue #
workspace.Part.Shape = Enum.PartType:FromValue( math.random(0, 4) ) Patterns #
Spieler kollidiert mit Part #
-- ServerScriptService/Script
local COOLDOWN_SECONDS = 0.1
local players = game:GetService("Players")
local debounces = {}
for i, child in workspace:GetDescendants() do
if child:IsA("Part") and child.Name:match("Coin") then
child.Touched:Connect(function(hit)
-- nur HumanoidRootPart reagiert (einmal pro Spieler, nicht jedes Körperteil)
if hit.Name ~= "HumanoidRootPart" then return end
local character = hit.Parent
local player = players:GetPlayerFromCharacter(character)
if debounces[player] then return end
debounces[player] = true
print(player.Name .. " hat " .. child.Name .." berührt!")
task.wait(COOLDOWN_SECONDS)
debounces[player] = false
end)
end
endParts beim Spielstart generieren #
→ ServerScriptService
-- ServerScriptService/Script
-- Läuft einmal beim Start, erstellt Parts für alle Spieler sichtbar
local RunService = game:GetService("RunService")
local coins = {}
for i = 1, 20 do
local coin = Instance.new("Part")
coin.Position = Vector3.new(math.random(-50, 50), 1, math.random(-50, 50))
coin.Parent = workspace
coin.Name = "Coin"..i
coin.Shape = Enum.PartType.Cylinder
coin.Size = Vector3.new(0.2, 1, 1)
coin.BrickColor = BrickColor.new("Gold")
coin.Transparency = 0.5
coin.Anchored = true
coin:SetAttribute("Value", 10)
table.insert(coins, coin)
end
RunService.Heartbeat:Connect(function(deltaTime)
for _, coin in coins do
coin.CFrame = coin.CFrame * CFrame.Angles(0, math.rad(90) * deltaTime, 0)
end
end)Parts zur Laufzeit spawnen (z.B. Projektile) #
→ ServerScriptService
-- Reaktion auf Spieleraktion, spawnt Part dynamisch
remoteEvent.OnServerEvent:Connect(function(player)
local projectile = Instance.new("Part")
projectile.Parent = workspace
end)Part nur für einen Spieler #
→ StarterPlayerScripts
-- LocalScript – nur dieser Spieler sieht die Parts
local part = Instance.new("Part")
part.Parent = workspace -- nur lokal sichtbarProperty via key, value #
local Part = game.Selection:Get()[1]
local changes = {
BrickColor = BrickColor.new("Bright red"),
Transparency = 1,
Material = Enum.Material.Neon,
Anchored = true,
}
for property, value in pairs(changes) do
Part[property] = value
endObjekte klonen #
Alternative zu Instance.new() oder Instance.fromExisting(). Vorlage einmal erstellen (z.B. in ServerStorage) und klonen.
local ServerStorage = game:GetService("ServerStorage")
local template = ServerStorage.MyPartTemplate -- vorkonfiguriertes Part
local clone = template:Clone()
clone.Position = Vector3.new(0, 5, 0)
clone.Parent = game.Workspace -- sollte zuletzt gesetzt werdenVorteile gegenüber Instance.new():
- alle Properties schon gesetzt — kein table, keine Schleife
- auch Childs (Skripte, Welds, Meshes…) werden kopiert
- schneller und weniger fehleranfällig
Für wiederkehrende gleichartige Objekte (Münzen, Gegner, Plattformen) ist Clone der Standard in Roblox. Für einmalige oder sehr unterschiedliche Objekte bleibt Instance.new() sinnvoll.
Nützliche Ressourcen #
- Offizielle Doku: create.roblox.com/docs
- Roblox Education: education.roblox.com
- DevForum: devforum.roblox.com
- https://create.roblox.com/docs/de-de
- https://create.roblox.com/docs/de-de/reference/engine/classes