Zum Hauptinhalt springen

Erstelle eigene Spiele in Lua/Luau mittels Roblox (Studio)

Inhaltsverzeichnis

Roblox Studio

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.Workspace

Math
#

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)	
end

Workspace
#

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)
end

Luau
#

Roblox’s eigene Lua -Variante.

  1. Script erstellen
  • im Explorer-Panel + neben dem Objekt (z.B. ServerScriptService) betätigen
  • Object auswählen oder suchen → Script (serverseitig) oder LocalScript (clientseitig)
  1. 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)
    -- ...
end

Konstanten → 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 werden

Typisches 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 nicht

Wie 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)  -- 42

Weg 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)  -- 100

Weg 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
end

Generisch (ü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)
end

Vergleich 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

💡 continue gibt es in Luau seit 2022: continue springt zum nächsten Durchlauf ohne break.

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 weg

Das 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)  -- 10

GetChildren 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
end

If
#

Grundstruktur
#

if bedingung then
    -- code
elseif andereBedingung then
    -- code
else
    -- code
end

Vergleichsoperatoren
#

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 gleich

logische Operatoren
#

if x > 0 and x < 10 then    -- und
if x < 0 or x > 10 then     -- oder
if not x then                -- nicht

Truthy / 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 falsy

Kurzform 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ürzer

Ternä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
end

Parts 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 sichtbar

Property 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
end

Objekte 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 werden

Vorteile 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
#