
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.
Studio #
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 den Großteil der Objekte (Part, Baseplate, SpawnLocation, …) einer Welt (Experience).
Wird in Roblox Studio mit großem “W” geschrieben, in Scripts nutzt man aber häufig workspace.
Properties #
Erlaubt das Anzeigen und Anpassen der Eigenschaften (Attribute / Parameter) eines Objekts (aus Explorer).
Part #
Part hinzufügen und manipulieren: Select, Move, Scale, Rotate, …
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 |
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 "AddPoints"
-- CLIENT schickt Anfrage:
local event = game.ReplicatedStorage.AddPoints
event:FireServer(10)
-- SERVER empfängt:
event.OnServerEvent:Connect(function(player, points)
print(player.Name .. " bekommt " .. points .. " 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.
ScriptA und ScriptB 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. Auch hier ist der Wert nicht persistent und das Timing entscheidend.
-- ModuleScript "Daten" in ReplicatedStorage:
local Daten = {}
Daten.punkte = 0
return Daten-- ScriptA:
local Daten = require(game.ReplicatedStorage.Daten)
Daten.punkte = 100-- ScriptB:
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.
-- ScriptA feuert ein Event:
local event = game.ServerScriptService.MeinEvent
event:Fire("Hallo Script B!")-- ScriptB hört zu:
local event = game.ServerScriptService.MeinEvent
event.Event:Connect(function(message)
print(message) -- "Hallo Script B!"
end)Luau #
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
Grundlegender Syntax #
Kommentar #
-- Zeile
print("Hello") -- Inline
--[[
Block
]]Variablen #
local name = "Spieler"
local punkte = 0Bedingungen #
if punkte > 10 then
print("Gut gemacht!")
endSchleifen #
for i = 1, 5 do
print(i)
endFunktionen #
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 |
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 #
der Syntax für ungleich ~= ist sehr ungewöhnlich.
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, ebenfalls ungewöhnlich:
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"Kommentar #
-- Zeile
print("Hello") -- Inline
--[[
Block
]]Klassen #
Game #
tbd
game.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 workspace, 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)
endDatentypen #
Vector3 #
Vector3.new(X, Y, Z)Koordinatensystem in Roblox #
Y (oben/unten)
↑
│
│
└──────→ X (links/rechts)
╱
╱
↙ Z (vor/zurück)| Wert | Richtung | Positiv | Negativ |
|---|---|---|---|
X |
Links/Rechts | Rechts | Links |
Y |
Oben/Unten | Oben | Unten |
Z |
Vor/Zurück | Rückwärts | Vorwärts |
Vector3.new(10, 0, 0) -- 10 Studs nach rechts
Vector3.new(0, 10, 0) -- 10 Studs nach oben
Vector3.new(0, 0, 10) -- 10 Studs rückwärts
Vector3.new(0, 0, -10) -- 10 Studs vorwärtsPraktische Beispiele #
-- Part auf dem Boden, mittig:
part.Position = Vector3.new(0, 0, 0)
-- Part 10 Studs in der Luft:
part.Position = Vector3.new(0, 10, 0)
-- Part Größe (Breite, Höhe, Tiefe):
part.Size = Vector3.new(4, 1, 4) -- flache Plattform
-- Spieler nach oben schleudern:
humanoidRootPart.Velocity = Vector3.new(0, 50, 0)💡 Tipp: In Roblox Studio kannst du unter View → Camera Orientation die Achsen einblenden – hilfreich um sich im 3D-Raum zu orientieren.
Enums #
FromValue #
workspace.Part.Shape = Enum.PartType:FromValue( math.random(0, 4) ) Patterns #
Explosion #
Explode als ModuleScript in ReplicatedStorage:
local Explode = {}
function Explode.boom(otherPart)
local explosion = Instance.new("Explosion")
explosion.Position = otherPart.Position
-- explosion.BlastRadius = 10
-- explosion.BlastPressure = 100000
explosion.Parent = workspace
end
function Explode.boomCrater(otherPart)
local explosion = Instance.new("Explosion")
explosion.Position = otherPart.Position
-- explosion.BlastRadius = 10
-- explosion.BlastPressure = 100000
explosion.ExplosionType = Enum.ExplosionType.Craters -- nur auf Terrain (Gras, Erde, etc.)
explosion.Parent = workspace
end
return ExplodeFolgendes Script an ein Part oder Ähnliches hängen, ggf. auch mit :GetChildren() oder :GetDescendants() in Verbindung mit Folder oder Tag arbeiten.
local Explode = require(game.ReplicatedStorage.Explode)
local part = script.Parent
part.touched:Connect(Explode.boom) --boomCraterSpieler 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
endCharacter wird noch oben geschleudert #
local players = game:GetService("Players"):GetChildren()
for _, player in players do
player.Character:FindFirstChild("HumanoidRootPart").Velocity = Vector3.new(0, 500, 0)
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 werden übernommen
- auch Childs (Skripte, Welds, Meshes…) werden kopiert
- schneller
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.
Tag #
In Studio Tag (einmalig) erstellen und an beliebige Objekte anhängen.
local CollectionService = game:GetService("CollectionService")
local taggedItems = CollectionService:GetTagged("AnyTag")
for _, item in taggedItems do
if item:IsA("Part") then -- auf bestimmte Klasse filtern
-- beliebige Aktion, z.B. item.touched:Connect(AnyFunction)
end
endTag an ein Partbinden:
local CollectionService = game:GetService("CollectionService")
local part = workspace.Part
CollectionService:AddTag(part, "AnyTag") Tag entfernen:
local CollectionService = game:GetService("CollectionService")
local part = workspace.Part
CollectionService:RemoveTag(part, "AnyTag")Events:
-- Wenn ein neues Objekt den Tag bekommt:
local CollectionService = game:GetService("CollectionService")
CollectionService:GetInstanceAddedSignal("AnyTag"):Connect(function(part)
print(part.Name .. " hat jetzt Tag AnyTag!")
end)
-- Wenn ein Objekt den Tag verliert:
CollectionService:GetInstanceRemovedSignal("AnyTag"):Connect(function(part)
print(part.Name .. " hat Tag AnyTag verloren!")
end)funktioniert nicht mit Tags die zur Laufzeit über Studio gesetzt werden, allerdings mit Tags die mittels Developer Console gesetzt werden:
local CollectionService = game:GetService("CollectionService")
CollectionService:AddTag(workspace.AnyPart, "AnyTag")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