Dein eigenes OGame: Baue ein textbasiertes Idle-Game im Browser
Erinnerst du dich an OGame? Minen bauen, die pro Sekunde Ressourcen generieren, dann die Erträge in bessere Gebäude investieren – Suchtfaktor pur. Das Beste daran: Die Spielmechanik dahinter ist eigentlich sehr simpel. Kein Canvas, kein WebGL – nur cleveres State Management und ein Timer.
In diesem Tutorial bauen wir den soliden Grundstein für genau so ein Spiel. Am Ende hast du ein lauffähiges Idle-Game mit einer Währung (Credits), drei Gebäuden und einem persistenten Spielstand via localStorage.
🎮 Das Spielkonzept
| Feature | Details |
|---|---|
| Währung | Credits (1 Typ) |
| Gebäude | 3 Stück: Metallmine, Solarkraftwerk, Forschungslabor |
| Mechanik | Jedes Gebäude generiert Credits pro Sekunde. Upgrades erhöhen den Ertrag, kosten aber exponentiell mehr. |
| Persistenz | Spielstand wird in localStorage gespeichert und beim nächsten Besuch geladen. |
🛠️ Los geht's: Projekt aufsetzen
mkdir my-idle-game && cd my-idle-game
touch index.html
Das gesamte Spiel besteht aus einer einzigen Datei. Das macht es perfekt für Anfänger und erlaubt es, den Code in einem Tutorial kompakt zu halten.
Der komplette Code
my-idle-game/index.html:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Idle Colony</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Courier New', monospace;
background: #0a0e17;
color: #c8d6e5;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
#game {
width: 100%;
max-width: 500px;
padding: 2rem;
}
h1 {
color: #f0a500;
text-align: center;
margin-bottom: 0.25rem;
letter-spacing: 2px;
}
.subtitle {
text-align: center;
color: #576574;
font-size: 0.8rem;
margin-bottom: 2rem;
}
.resource-bar {
background: #141a2a;
border: 1px solid #f0a500;
border-radius: 8px;
padding: 1rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.resource-bar span { font-size: 0.85rem; color: #576574; }
.resource-bar strong { color: #f0a500; font-size: 1.4rem; }
.per-sec { font-size: 0.75rem; color: #10ac84; }
.building {
background: #141a2a;
border: 1px solid #222f3e;
border-radius: 8px;
padding: 1.25rem;
margin-bottom: 1rem;
transition: border-color 0.3s;
}
.building:hover { border-color: #f0a500; }
.building-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.building-name { font-weight: bold; color: #dfe6e9; }
.building-level { color: #f0a500; font-size: 0.85rem; }
.building-info {
font-size: 0.8rem;
color: #576574;
margin-bottom: 0.75rem;
}
.building-stats {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
color: #576574;
margin-bottom: 0.75rem;
}
button {
width: 100%;
padding: 0.6rem;
background: #f0a500;
color: #0a0e17;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-family: inherit;
font-size: 0.85rem;
transition: all 0.2s;
}
button:hover {
background: #e58e26;
transform: translateY(-1px);
}
button:disabled {
background: #222f3e;
color: #576574;
cursor: not-allowed;
transform: none;
}
.reset {
margin-top: 2rem;
background: transparent;
border: 1px solid #576574;
color: #576574;
font-size: 0.75rem;
}
.reset:hover {
border-color: #ee5253;
color: #ee5253;
background: transparent;
}
</style>
</head>
<body>
<div id="game">
<h1>⛏️ IDLE COLONY</h1>
<p class="subtitle">Textbasiertes Ressourcen-Management</p>
<div class="resource-bar">
<div>
<span>Credits</span><br>
<strong id="credits">0</strong>
</div>
<div class="per-sec">+<span id="cps">0</span> / Sek.</div>
</div>
<div id="buildings"></div>
<button class="reset" onclick="resetGame()">Spielstand zurücksetzen</button>
</div>
<script>
// === GAME CONFIG ===
const BUILDINGS = [
{
id: 'mine',
name: '⛏️ Metallmine',
baseCost: 10,
baseOutput: 1,
costMultiplier: 1.5,
desc: 'Gräbt Erz und wandelt es in Credits um.'
},
{ id: 'solar', name: '☀️ Solarkraftwerk', baseCost: 50, baseOutput: 4, costMultiplier: 1.6, desc: 'Erzeugt Energie, die als Credits verkauft wird.' },
{ id: 'lab', name: '🔬 Forschungslabor', baseCost: 200, baseOutput: 15, costMultiplier: 1.8, desc: 'Entwickelt Technologien für hohe Gewinne.' },
];
// === GAME STATE ===
let state = loadGame();
function defaultState() {
const levels = {};
BUILDINGS.forEach(b => levels[b.id] = 0);
return { credits: 0, levels };
}
function loadGame() {
try {
const saved = JSON.parse(localStorage.getItem('idleColony'));
if (saved && saved.levels) return saved;
} catch(e) {}
return defaultState();
}
function saveGame() {
localStorage.setItem('idleColony', JSON.stringify(state));
}
// === GAME LOGIC ===
function getCost(building) {
return Math.floor(building.baseCost * Math.pow(building.costMultiplier, state.levels[building.id]));
}
function getOutput(building) {
return building.baseOutput * state.levels[building.id];
}
function getTotalCPS() {
return BUILDINGS.reduce((sum, b) => sum + getOutput(b), 0);
}
function buyBuilding(buildingId) {
const building = BUILDINGS.find(b => b.id === buildingId);
const cost = getCost(building);
if (state.credits >= cost) {
state.credits -= cost;
state.levels[building.id]++;
saveGame();
render();
}
}
function resetGame() {
if (confirm('Spielstand wirklich löschen?')) {
state = defaultState();
saveGame();
render();
}
}
// === RENDERING ===
function render() {
document.getElementById('credits').textContent = Math.floor(state.credits).toLocaleString('de-DE');
document.getElementById('cps').textContent = getTotalCPS();
const container = document.getElementById('buildings');
container.innerHTML = BUILDINGS.map(b => {
const level = state.levels[b.id];
const cost = getCost(b);
const output = getOutput(b);
const canBuy = state.credits >= cost;
return `
<div class="building">
<div class="building-header">
<span class="building-name">${b.name}</span>
<span class="building-level">Lv. ${level}</span>
</div>
<div class="building-info">${b.desc}</div>
<div class="building-stats">
<span>Ertrag: +${output} /Sek.</span>
<span>Nächstes Lv.: +${b.baseOutput} /Sek.</span>
</div>
<button onclick="buyBuilding('${b.id}')" ${canBuy ? '' : 'disabled'}>
Upgrade → ${cost.toLocaleString('de-DE')} Credits
</button>
</div>`;
}).join('');
}
// === GAME LOOP (1x pro Sekunde) ===
setInterval(() => {
state.credits += getTotalCPS();
saveGame();
render();
}, 1000);
// Initial Render
render();
</script>
</body>
</html>
🧠 Die Kernmechanik erklärt
Der Game Loop
Das Herzstück ist ein simpler setInterval der jede Sekunde läuft:
- Berechne den gesamten Ertrag aller Gebäude (
getTotalCPS). - Addiere den Ertrag auf die Kredite.
- Speichere den State und rendere die UI neu.
Exponentielle Kosten
Das ist der Trick, der das Spiel "balanciert". Die Formel für die Gebäudekosten lautet:
Kosten = Basiskosten × Multiplikator ^ Level
Eine Metallmine kostet z.B.:
- Level 1:
10 × 1.5^0 = 10 Credits - Level 5:
10 × 1.5^4 = 50 Credits - Level 10:
10 × 1.5^9 = 384 Credits
Das sorgt dafür, dass der Spieler immer wieder vor der Entscheidung steht: "Upgrade ich meine billige Mine, oder spare ich auf das teure Labor?"
Persistenz
Der gesamte Spielstand (Credits + alle Gebäude-Level) wird bei jeder Änderung automatisch in localStorage geschrieben. Beim Laden der Seite wird er restauriert. So kann der Spieler jederzeit den Browser schließen und weiterspielen.
🚀 Sofort ausprobieren mit Node.js & Docker
Wir nutzen bewusst Express statt eines statischen Webservers. So kannst du später problemlos eine API für Leaderboards, serverseitige Spielstände oder Multiplayer-Funktionen ergänzen.
my-idle-game/package.json:
{
"name": "my-idle-game",
"version": "1.0.0",
"scripts": { "start": "node server.js" },
"dependencies": { "express": "^4.18.2" }
}
my-idle-game/server.js:
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Idle Colony läuft auf http://localhost:${PORT}`));
Verschiebe dein Spiel in einen public/-Ordner:
mkdir public
mv index.html public/
npm install
my-idle-game/docker-compose.yml:
version: '3.8'
services:
app:
image: node:18-alpine
working_dir: /app
volumes:
- .:/app
ports:
- "3000:3000"
command: sh -c "npm install && npm start"
docker compose up -d
Öffne http://localhost:3000 im Browser – und fange an zu minen! ⛏️
⚠️ 📸 SCREENSHOT ANFRAGE: Hier einen Screenshot des laufenden Spiels einfügen, am besten mit einigen bereits aufgelevelten Gebäuden und einem sichtbaren Credits-Zähler.
📈 Erweiterungs-Ideen
Du hast jetzt den Grundbaustein. Hier ist, wie man es weiter ausbauen kann:
| Feature | Schwierigkeit | Beschreibung |
|---|---|---|
| Offline-Erträge | ⭐⭐ | Beim Laden die vergangene Zeit berechnen und Credits gutschreiben |
| Mehrere Ressourcen | ⭐⭐⭐ | Metall, Energie, Forschungspunkte – jedes Gebäude produziert etwas anderes |
| Achievements | ⭐⭐ | "Erste Mine gebaut", "1.000.000 Credits erreicht" |
| Prestige-System | ⭐⭐⭐⭐ | Alles zurücksetzen für einen permanenten Bonus |
Fazit
Mit weniger als 200 Zeilen Code hast du ein funktionsfähiges Idle-Game gebaut, das die gleichen Kernmechaniken nutzt wie OGame, Cookie Clicker oder Factorio. Die Schönheit liegt in der Einfachheit: Ein Timer, ein State-Objekt und eine Render-Funktion – mehr braucht es nicht, um Suchtpotenzial zu erzeugen.
Viel Spaß beim Minen! ⛏️
Login