🎮ArcadeLab

Football Stars

by AtomicFox16
398 lines25.3 KB🛠️ Three.js (3D graphics)
▶ Play


<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Football Stars</title>
    <style>
        body { margin: 0; padding: 0; background-color: #04090f; overflow: hidden; font-family: 'Segoe UI', Arial, sans-serif; user-select: none; touch-action: none; color: white; }
        #canvas3d { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }
        #score-board { position: absolute; top: 15px; left: 20px; background: linear-gradient(90deg, rgba(10,25,60,0.95) 0%, rgba(18,50,110,0.95) 100%); padding: 8px 24px; border-left: 5px solid #ffcc00; border-radius: 0 6px 6px 0; font-weight: 900; min-width: 280px; justify-content: space-between; display: flex; align-items: center; box-shadow: 0 12px 30px rgba(0,0,0,0.6); z-index: 2; border: 1px solid rgba(255,255,255,0.05); }
        .score-display { background: #020612; padding: 4px 16px; border-radius: 4px; font-size: 22px; color: #ffcc00; font-family: monospace; border: 1px solid rgba(255,204,0,0.2); }
        #match-time { color: #8a99ad; font-size: 14px; font-weight: 700; }
        #tournament-stage-text { position: absolute; top: 75px; left: 20px; font-size: 12px; font-weight: 800; background: rgba(0,0,0,0.6); padding: 4px 12px; border-radius: 4px; letter-spacing: 1px; color: #ffcc00; z-index: 2; }
        .control-zone { position: absolute; bottom: 30px; width: 100%; display: flex; justify-content: space-between; padding: 0 40px; box-sizing: border-box; pointer-events: none; z-index: 2; }
        .joystick-container { width: 125px; height: 125px; background: rgba(4,10,30,0.45); border: 2px solid rgba(255,204,0,0.25); border-radius: 50%; position: relative; pointer-events: auto; backdrop-filter: blur(12px); }
        #joystick-knob { width: 56px; height: 56px; background: radial-gradient(circle, #ffffff 0%, #9cb2c9 100%); border-radius: 50%; position: absolute; top: 32px; left: 32px; box-shadow: 0 8px 25px rgba(0,0,0,0.6); }
        .action-buttons { display: flex; gap: 18px; align-items: flex-end; pointer-events: auto; }
        .btn-fifa { width: 78px; height: 78px; border-radius: 50%; color: white; font-weight: 800; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 11px; border: 2px solid rgba(255,255,255,0.25); text-transform: uppercase; cursor: pointer; }
        .btn-shoot { background: linear-gradient(135deg, #ff9f00 0%, #cc5200 100%); }
        .btn-pass { background: linear-gradient(135deg, #00A3FF 0%, #0044cc 100%); width: 70px; height: 70px; }
        .btn-fifa:active { transform: scale(0.88); }
        .screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at center, #0c2040 0%, #030715 100%); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 10; overflow-y: auto; padding: 20px; box-sizing: border-box; }
        .menu-title { font-size: 36px; font-weight: 900; margin-bottom: 25px; letter-spacing: 3px; background: linear-gradient(90deg, #fff, #ffcc00, #fff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-transform: uppercase; }
        .selection-box { background: rgba(5,15,40,0.85); border: 1px solid #ffcc00; padding: 25px; border-radius: 12px; text-align: center; box-shadow: 0 20px 45px rgba(0,0,0,0.6); }
        select { background: #061026; color: white; border: 1px solid #ffcc00; padding: 12px 30px; font-size: 16px; border-radius: 6px; outline: none; font-weight: bold; cursor: pointer; }
        .btn-gold { background: linear-gradient(90deg, #ffcc00 0%, #ff9900 100%); border: none; color: #050b14; font-weight: 900; padding: 16px 50px; font-size: 16px; border-radius: 6px; box-shadow: 0 10px 30px rgba(255,204,0,0.3); cursor: pointer; text-transform: uppercase; margin-top: 20px; }
        table { border-collapse: collapse; margin: 15px 0; width: 340px; text-align: left; background: rgba(255,255,255,0.05); }
        th, td { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.1); font-size: 14px; }
        th { background: rgba(255,204,0,0.2); color: #ffcc00; }
        .highlight-row { background: rgba(0,229,255,0.15); font-weight: bold; }
        #goal-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255,204,0,0.25); display: flex; align-items: center; justify-content: center; z-index: 5; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; backdrop-filter: blur(2px); }
        .goal-text { font-size: 70px; font-weight: 900; text-shadow: 0 0 30px #ffcc00; font-style: italic; color: white; }
        #loading-screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at center, #0c2040 0%, #030715 100%); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 99; transition: opacity 0.5s ease; }
        .bar-container { width: 70%; max-width: 400px; height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; overflow: hidden; position: relative; margin-top: 20px; }
        #progress-bar { width: 0%; height: 100%; background: linear-gradient(90deg, #ffcc00 0%, #ff9900 100%); box-shadow: 0 0 12px #ffcc00; }
    </style>
    <script src="https://cloudflare.com"></script>
</head>
<body>

    <div id="loading-screen" style="display:none;">
        <div style="color: #ffcc00; font-weight: 900; letter-spacing: 5px;">EA SPORTS</div>
        <div style="font-size: 28px; font-weight: 900; text-transform: uppercase;">Football Stars</div>
        <div class="bar-container"><div id="progress-bar"></div></div>
        <div id="progress-text" style="font-size: 18px; font-weight: 800; color: #ffcc00; margin-top: 15px; font-family: monospace;">0%</div>
        <div id="loading-info" style="font-size: 11px; color: #8a99ad; margin-top: 5px; text-transform: uppercase; letter-spacing: 1px;">Stadion-Texturen werden gerendert...</div>
    </div>

    <div id="menu-team" class="screen">
        <div style="color: #ffcc00; font-weight: 900; letter-spacing: 4px; margin-bottom: 5px;">Football Stars</div>
        <div class="menu-title">Wähle dein Land</div>
        <div class="selection-box">
            <select id="select-country"></select><br>
            <button class="btn-gold" onclick="setupTournament()">Turnier starten</button>
        </div>
    </div>

    <div id="menu-hub" class="screen" style="display:none;">
        <div class="menu-title" id="hub-stage-title">Gruppenphase</div>
        <div class="selection-box" style="min-width: 360px;">
            <div id="hub-content"></div>
            <button class="btn-gold" id="btn-next-match" onclick="triggerLoadingScreen()">Nächstes Match spielen</button>
        </div>
    </div>

    <div id="menu-penalty" class="screen" style="display:none;">
        <div class="menu-title">Elfmeterschiessen</div>
        <div class="selection-box">
            <h3 id="penalty-teams" style="color:#ffcc00; font-size:24px;">0 : 0</h3>
            <p id="penalty-info">Nutze den Joystick nach links/rechts zum Zielen und drücke SCHUSS!</p>
            <button class="btn-gold" onclick="executePenaltyRound()">Schießen / Halten</button>
        </div>
    </div>

    <div id="goal-overlay"><div class="goal-text" id="gt">TOR!</div></div>

    <div id="score-board" style="display:none;">
        <span id="name-p" class="team-badge">GER</span>
        <span class="score-display"><span id="p-score">0</span> : <span id="e-score">0</span></span>
        <span id="name-e" class="team-badge">BRA</span>
        <span id="match-time">00:00</span>
    </div>
    <div id="tournament-stage-text" style="display:none;">GRUPPENPHASE</div>

    <div id="canvas3d"></div>
    
    <div class="control-zone" id="controls" style="display:none;">
        <div class="joystick-container" id="joystick"><div id="joystick-knob"></div></div>
        <div class="action-buttons">
            <div class="btn-fifa btn-pass" id="passBtn">Pass</div>
            <div class="btn-fifa btn-shoot" id="shootBtn">Schuss</div>
        </div>
    </div>

    <script>
        let scene, camera, renderer, gameRunning = false, penaltyMode = false;
        let pitchW = 60, pitchH = 40;
        let scorePlayer = 0, scoreEnemy = 0, matchMinutes = 0, matchSeconds = 0;
        let userTeamKey = "", tournamentStage = "GROUP", currentOpponent = "", groupStageData = [], koBracket = {};
        
        const player = { x: -15, z: 0, vx: 0, vz: 0, speed: 0.24, radius: 0.8, mesh: null };
        const enemy = { x: 15, z: 0, speed: 0.14, radius: 0.8, mesh: null };
        const referee = { x: 0, z: -6, speed: 0.12, radius: 0.7, mesh: null };
        const ball = { x: 0, y: 0.4, z: 0, vx: 0, vz: 0, radius: 0.4, mesh: null, friction: 0.976, owner: null };

        const TEAMS = {
            GER: { short: "GER", flag: "🇩🇪", name: "Deutschland", color: 0xffffff, pantColor: 0x111111, speed: 0.24 },
            BRA: { short: "BRA", flag: "🇧🇷", name: "Brasilien", color: 0xf1c40f, pantColor: 0x004d98, speed: 0.26 },
            FRA: { short: "FRA", flag: "🇫🇷", name: "Frankreich", color: 0x0055a5, pantColor: 0xffffff, speed: 0.25 },
            ARG: { short: "ARG", flag: "🇦🇷", name: "Argentinien", color: 0x74acdf, pantColor: 0xffffff, speed: 0.25 },
            ESP: { short: "ESP", flag: "🇪🇸", name: "Spanien", color: 0xc0392b, pantColor: 0xf1c40f, speed: 0.23 },
            POR: { short: "POR", flag: "🇵🇹", name: "Portugal", color: 0xb31919, pantColor: 0x008000, speed: 0.24 }
        };

        const selectC = document.getElementById("select-country");
        Object.keys(TEAMS).forEach(k => selectC.innerHTML += `<option value="${k}">${TEAMS[k].flag} ${TEAMS[k].name}</option>`);

        function triggerLoadingScreen() {

document.getElementById("menu-hub").style.display = "none";
const screen = document.getElementById("loading-screen");
const bar = document.getElementById("progress-bar");
const txt = document.getElementById("progress-text");
const info = document.getElementById("loading-info");
screen.style.display = "flex"; screen.style.opacity = "1";
let start = null;
const duration = 2000; // Genau 2 Sekunden Ladezeit
function loadAnim(ts) {
if(!start) start = ts;
let elapsed = ts - start;
let progress = Math.min(elapsed / duration, 1);
let pPercent = Math.floor(progress * 100);
bar.style.width = pPercent + "%";
txt.innerText = pPercent + "%";
if (pPercent < 50) {
info.innerText = "Stadion-Texturen werden gerendert...";
} else if (pPercent < 90) {
info.innerText = "Kader-Synchronisation: Server-Check...";
} else {
info.innerText = "Match wird gestartet...";
}
if (progress < 1) {
requestAnimationFrame(loadAnim);
} else {
setTimeout(() => {
screen.style.opacity = "0";
setTimeout(() => { screen.style.display = "none"; playNextMatch(); }, 500);
}, 200);
}
}
requestAnimationFrame(loadAnim);
}
function setupTournament() {
userTeamKey = selectC.value; tournamentStage = "GROUP";
groupStageData = Object.keys(TEAMS).map(k => ({ key: k, name: TEAMS[k].name, flag: TEAMS[k].flag, pts: 0, gd: 0, matchesPlayed: 0 }));
document.getElementById("menu-team").style.display = "none";
init3D();
updateTournamentHub();
}
function updateTournamentHub() {
document.getElementById("menu-hub").style.display = "flex";
const content = document.getElementById("hub-content");
if (tournamentStage === "GROUP") {
document.getElementById("hub-stage-title").innerText = "🏆 WM - Gruppenphase";
groupStageData.sort((a,b) => b.pts - a.pts || b.gd - a.gd);
let html = <table><tr><th>Pos</th><th>Team</th><th>Sp</th><th>Diff</th><th>Pkt</th></tr>;
groupStageData.forEach((t, i) => {
let c = t.key === userTeamKey ? "class='highlight-row'" : "";
html += <tr ${c}><td>${i+1}</td><td>${t.flag} ${t.key}</td><td>${t.matchesPlayed}</td><td>${t.gd}</td><td>${t.pts}</td></tr>;
});
html += </table>;
let me = groupStageData.find(t => t.key === userTeamKey);
if (me.matchesPlayed >= 3) {
groupStageData.sort((a,b) => b.pts - a.pts || b.gd - a.gd);
tournamentStage = "HALBFINALE";
content.innerHTML = "Gruppenphase beendet! Du stehst in den K.o.-Spielen!";
document.getElementById("btn-next-match").innerText = "Zum Halbfinale";
return;
}
let myIdx = groupStageData.findIndex(t => t.key === userTeamKey);
currentOpponent = groupStageData[(myIdx + me.matchesPlayed + 1) % groupStageData.length].key;
html += <p>Nächstes Spiel: <b>Dein Team</b> vs <b>${TEAMS[currentOpponent].flag} ${TEAMS[currentOpponent].name}</b></p>;
content.innerHTML = html;
} else if(tournamentStage === "HALBFINALE" || tournamentStage === "FINALE") {
document.getElementById("hub-stage-title").innerText = 🏁 WM - ${tournamentStage};
content.innerHTML = <h3>K.o.-Runde</h3><p>${TEAMS[userTeamKey].flag} ${userTeamKey} vs ${TEAMS[currentOpponent].flag} ${currentOpponent}</p>;
document.getElementById("btn-next-match").innerText = "Match starten";
} else if(tournamentStage === "FINISHED") {
document.getElementById("hub-stage-title").innerText = "🎉 WELTMEISTER!";
content.innerHTML = <h2>🏆 CHAMPION! 🏆</h2><p>Du hast den goldenen Pokal gewonnen!</p>;
document.getElementById("btn-next-match").innerText = "Neues Turnier";
document.getElementById("btn-next-match").onclick = () => location.reload();
}
}
function playNextMatch() {
document.getElementById("score-board").style.display = "flex";
document.getElementById("tournament-stage-text").style.display = "block";
document.getElementById("tournament-stage-text").innerText = tournamentStage;
document.getElementById("controls").style.display = "flex";
document.getElementById("name-p").innerText = userTeamKey;
document.getElementById("name-e").innerText = currentOpponent;
updatePlayerColors();
scorePlayer = 0; scoreEnemy = 0; matchMinutes = 0; matchSeconds = 0;
document.getElementById("p-score").innerText = "0"; document.getElementById("e-score").innerText = "0";
penaltyMode = false;
resetPositions();
gameRunning = true;
}
function updatePlayerColors() {
if (player.mesh && player.mesh.children) player.mesh.children.material.color.setHex(TEAMS[userTeamKey].color);
if (player.mesh && player.mesh.children) player.mesh.children.material.color.setHex(TEAMS[userTeamKey].pantColor);
if (enemy.mesh && enemy.mesh.children) enemy.mesh.children.material.color.setHex(TEAMS[currentOpponent].color);
if (enemy.mesh && enemy.mesh.children) enemy.mesh.children.material.color.setHex(TEAMS[currentOpponent].pantColor);
}
function finishMatch() {
gameRunning = false;
document.getElementById("score-board").style.display = "none";
document.getElementById("tournament-stage-text").style.display = "none";
document.getElementById("controls").style.display = "none";
if (tournamentStage === "GROUP") {
let me = groupStageData.find(t => t.key === userTeamKey);
let opp = groupStageData.find(t => t.key === currentOpponent);
me.matchesPlayed++; opp.matchesPlayed++;
me.gd += (scorePlayer - scoreEnemy); opp.gd += (scoreEnemy - scorePlayer);
if(scorePlayer > scoreEnemy) me.pts += 3; else if(scorePlayer < scoreEnemy) opp.pts += 3; else { me.pts+=1; opp.pts+=1; }
groupStageData.forEach(t => {
if(t.key !== userTeamKey && t.key !== currentOpponent) {
t.matchesPlayed++; t.pts += Math.random() > 0.5 ? 3 : 1;
}
});
updateTournamentHub();
} else {
if (scorePlayer === scoreEnemy) { startPenaltyShootout(); }
else if(scorePlayer > scoreEnemy) {
if(tournamentStage === "HALBFINALE") {
tournamentStage = "FINALE";
let other = Object.keys(TEAMS).filter(k => k !== userTeamKey && k !== currentOpponent);
currentOpponent = other[Math.floor(Math.random()*other.length)];
} else tournamentStage = "FINISHED";
updateTournamentHub();
} else { alert("Du bist ausgeschieden!"); location.reload(); }
}
}
let penaltyState = { pScores: 0, eScores: 0, rounds: 0, turn: true };
function startPenaltyShootout() {
penaltyMode = true; gameRunning = false;
document.getElementById("menu-penalty").style.display = "flex";
penaltyState = { pScores: 0, eScores: 0, rounds: 0, turn: true };
camera.position.set(-18, 5, 0); camera.lookAt(pitchW/2, 1, 0);
updatePenaltyScreen();
}
function updatePenaltyScreen() {
document.getElementById("penalty-teams").innerText = ${userTeamKey} ${penaltyState.pScores} : ${penaltyState.eScores} ${currentOpponent};
document.getElementById("penalty-info").innerText = penaltyState.turn ? "Drücke Joystick nach links/rechts & drücke Schießen!" : "Gegner schießt! Drücke Halten.";
}
function executePenaltyRound() {
let userTarget = moveZ < -0.2 ? "left" : (moveZ > 0.2 ? "right" : "center");
if(penaltyState.turn) {
let gkDive = Math.random() > 0.5 ? "left" : "right";
if(userTarget !== gkDive) { penaltyState.pScores++; alert("TOR!"); } else alert("GEHALTEN!");
} else {
let aiShoot = Math.random() > 0.5 ? "left" : "right";
let userDive = Math.random() > 0.5 ? "left" : "right";
if(aiShoot !== userDive) { penaltyState.eScores++; alert("Gegner trifft."); } else alert("PARADE!");
penaltyState.rounds++;
}
penaltyState.turn = !penaltyState.turn; updatePenaltyScreen();
if(penaltyState.rounds >= 3 && penaltyState.pScores !== penaltyState.eScores) {
document.getElementById("menu-penalty").style.display = "none";
if(penaltyState.pScores > penaltyState.eScores) { scorePlayer++; finishMatch(); }
else { alert("Verloren im Elfmeterschießen!"); location.reload(); }
}
}
function init3D() {
const container = document.getElementById('canvas3d');
scene = new THREE.Scene(); scene.background = new THREE.Color(0x0a1424);
scene.fog = new THREE.FogExp2(0x0a1424, 0.015);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
let dLight = new THREE.DirectionalLight(0xffffff, 0.8); dLight.position.set(20, 40, 20); dLight.castShadow = true; scene.add(dLight);
createField(); createStadium();
player.mesh = create3DPlayer(); enemy.mesh = create3DPlayer(); referee.mesh = create3DReferee();
ball.mesh = new THREE.Mesh(new THREE.SphereGeometry(ball.radius, 16, 16), new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.3 }));
ball.mesh.castShadow = true; scene.add(ball.mesh);
}
function createField() {
const field = new THREE.Mesh(new THREE.PlaneGeometry(pitchW, pitchH), new THREE.MeshStandardMaterial({ color: 0x1e6b25, roughness: 0.6 }));
field.rotation.x = -Math.PI / 2; field.receiveShadow = true; scene.add(field);
let line = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.01, pitchH), new THREE.MeshBasicMaterial({ color: 0xffffff })); scene.add(line);
let goalMat = new THREE.MeshStandardMaterial({ color: 0xffffff });
const buildGoal = (x) => {
let g = new THREE.Group();
let p1 = new THREE.Mesh(new THREE.CylinderGeometry(0.1,0.1,4), goalMat); p1.position.set(0,2,-4);
let p2 = new THREE.Mesh(new THREE.CylinderGeometry(0.1,0.1,4), goalMat); p2.position.set(0,2,4);
let b = new THREE.Mesh(new THREE.CylinderGeometry(0.1,0.1,8), goalMat); b.rotation.x = Math.PI/2; b.position.set(0,4,0);
g.add(p1,p2,b); g.position.x = x; scene.add(g);
};
buildGoal(-pitchW/2); buildGoal(pitchW/2);
}
function createStadium() {
let m = new THREE.MeshStandardMaterial({ color: 0x2c3e50 });
const s = (w,h,d,x,z,r) => { let b = new THREE.Mesh(new THREE.BoxGeometry(w,h,d), m); b.position.set(x,h/2,z); b.rotation.y = r; scene.add(b); };
s(pitchW, 8, 5, 0, -pitchH/2 - 4, 0); s(pitchW, 8, 5, 0, pitchH/2 + 4, 0);
s(pitchH, 8, 5, -pitchW/2 - 4, 0, Math.PI/2); s(pitchH, 8, 5, pitchW/2 + 4, 0, Math.PI/2);
}
function create3DPlayer() {
let g = new THREE.Group();
let shirt = new THREE.Mesh(new THREE.CylinderGeometry(0.8,0.8,1.0), new THREE.MeshStandardMaterial()); shirt.position.y = 1.3; g.add(shirt);
let pants = new THREE.Mesh(new THREE.CylinderGeometry(0.8,0.8,0.8), new THREE.MeshStandardMaterial()); pants.position.y = 0.4; g.add(pants);
let head = new THREE.Mesh(new THREE.SphereGeometry(0.4), new THREE.MeshStandardMaterial({ color: 0xffdbac })); head.position.y = 2.1; g.add(head);
scene.add(g); return g;
}
function create3DReferee() {
let g = new THREE.Group();
let shirt = new THREE.Mesh(new THREE.CylinderGeometry(0.7,0.7,1.0), new THREE.MeshStandardMaterial({ color: 0xccff00 })); shirt.position.y = 1.3; g.add(shirt);
let pants = new THREE.Mesh(new THREE.CylinderGeometry(0.7,0.7,0.8), new THREE.MeshStandardMaterial({ color: 0x111111 })); pants.position.y = 0.4; g.add(pants);
let head = new THREE.Mesh(new THREE.SphereGeometry(0.38), new THREE.MeshStandardMaterial({ color: 0xffdbac })); head.position.y = 2.0; g.add(head);
scene.add(g); return g;
}
function resetPositions() {
player.x = -15; player.z = 0; player.vx = 0; player.vz = 0; enemy.x = 15; enemy.z = 0; referee.x = 0; referee.z = -6;
ball.x = 0; ball.y = 0.4; ball.z = 0; ball.vx = 0; ball.vy = 0; ball.vz = 0; ball.owner = null;
}
const joystick = document.getElementById("joystick"), knob = document.getElementById("joystick-knob");
let joystickActive = false, joyStartX = 0, joyStartY = 0, moveX = 0, moveZ = 0;
joystick.addEventListener("touchstart", (e) => {
if(!gameRunning && !penaltyMode) return; joystickActive = true; const rect = joystick.getBoundingClientRect();
joyStartX = rect.left + rect.width / 2; joyStartY = rect.top + rect.height / 2;
});
window.addEventListener("touchmove", (e) => {
if (!joystickActive) return;
let dx = e.touches[0].clientX - joyStartX, dy = e.touches[0].clientY - joyStartY;
let dist = Math.min(Math.sqrt(dx * dx + dy * dy), 40), angle = Math.atan2(dy, dx);
knob.style.transform = translate(${Math.cos(angle) * dist}px, ${Math.sin(angle) * dist}px);
moveX = Math.cos(angle) * (dist / 40); moveZ = Math.sin(angle) * (dist / 40);
});
window.addEventListener("touchend", () => { joystickActive = false; knob.style.transform = "translate(0px, 0px)"; moveX = 0; moveZ = 0; });
document.getElementById("passBtn").addEventListener("touchstart", () => {
if (ball.owner === "player") { ball.owner = null; ball.vx = 0.85; ball.vz = (Math.random() - 0.5) * 0.2; }
});
document.getElementById("shootBtn").addEventListener("touchstart", () => {
if (ball.owner === "player") {
ball.owner = null; let a = Math.atan2(0 - ball.z, pitchW/2 - ball.x);
ball.vx = Math.cos(a) * 1.3; ball.vz = Math.sin(a) * 1.3;
}
});
function updatePhysics() {
if(!gameRunning) return;
matchSeconds += 2; if(matchSeconds >= 60) { matchSeconds = 0; matchMinutes++; }
document.getElementById("match-time").innerText = ${String(matchMinutes).padStart(2,'0')}:00;
if (matchMinutes >= 3) { finishMatch(); return; }
player.x += moveX * player.speed; player.z += moveZ * player.speed;
player.x = Math.max(-pitchW/2+1, Math.min(pitchW/2-1, player.x)); player.z = Math.max(-pitchH/2+1, Math.min(pitchH/2-1, player.z));
if (player.mesh) player.mesh.position.set(player.x, 0, player.z);
if((moveX !== 0 || moveZ !== 0) && player.mesh) player.mesh.rotation.y = -Math.atan2(moveZ, moveX) + Math.PI/2;
let edx = ball.x - enemy.x, edz = ball.z - enemy.z, edist = Math.sqrt(edx * edx + edz * edz);
if(edist > 1) {
let ex = (edx/edist) * enemy.speed; let ez = (edz/edist) * enemy.speed;
enemy.x += ex; enemy.z += ez;
if (enemy.mesh) {
enemy.mesh.position.set(enemy.x, 0, enemy.z);
enemy.mesh.rotation.y = -Math.atan2(ez, ex) + Math.PI/2;
}
}
let rdx = ball.x - referee.x, rdz = (ball.z - 5) - referee.z, rdist = Math.sqrt(rdx * rdx + rdz * rdz);
if(rdist > 3) {
referee.x += (rdx/rdist)*referee.speed; referee.z += (rdz/rdist)*referee.speed;
if (referee.mesh) {
referee.mesh.position.set(referee.x, 0, referee.z);
referee.mesh.rotation.y = -Math.atan2(rdz, rdx) + Math.PI/2;
}
}
if (ball.owner === "player" && player.mesh) { ball.x = player.x + Math.sin(player.mesh.rotation.y) * 1.2; ball.z = player.z + Math.cos(player.mesh.rotation.y) * 1.2; }
else if (ball.owner === "enemy" && enemy.mesh) {
ball.x = enemy.x + Math.sin(enemy.mesh.rotation.y) * 1.2; ball.z = enemy.z + Math.cos(enemy.mesh.rotation.y) * 1.2;
if(ball.x < -8) { ball.owner = null; ball.vx = -1.1; ball.vz = (Math.random() - 0.5) * 0.3; }
} else { ball.x += ball.vx; ball.z += ball.vz; ball.vx *= ball.friction; ball.vz *= ball.friction; }
if (ball.mesh) ball.mesh.position.set(ball.x, ball.y, ball.z);
let pd = Math.sqrt((ball.x-player.x)**2 + (ball.z-player.z)**2); if(!ball.owner && pd < 1.3) ball.owner = "player";
let ed = Math.sqrt((ball.x-enemy.x)**2 + (ball.z-enemy.z)**2); if(!ball.owner && ed < 1.3) ball.owner = "enemy";
if (ball.x < -pitchW/2 && Math.abs(ball.z) < 4) { scoreEnemy++; document.getElementById("e-score").innerText = scoreEnemy; document.getElementById("gt").innerText="TOR FÜR GEGNER!"; document.getElementById("goal-overlay").style.opacity="1"; setTimeout(()=>document.getElementById("goal-overlay").style.opacity="0", 1500); resetPositions(); }
else if (ball.x > pitchW/2 && Math.abs(ball.z) < 4) { scorePlayer++; document.getElementById("p-score").innerText = scorePlayer; document.getElementById("gt").innerText="TOR FÜR DICH!"; document.getElementById("goal-overlay").style.opacity="1"; setTimeout(()=>document.getElementById("goal-overlay").style.opacity="0", 1500); resetPositions(); }
if (Math.abs(ball.z) > pitchH/2) { ball.vz *= -0.8; ball.owner = null; }
if (Math.abs(ball.x) > pitchW/2) { ball.vx *= -0.8; ball.owner = null; }
camera.position.set(ball.x - 12, 18, ball.z + 18); camera.lookAt(ball.x, 0, ball.z);
}
function animate() { requestAnimationFrame(animate); updatePhysics(); if(renderer) renderer.render(scene, camera); }
window.onload = () => { animate(); };
window.addEventListener('resize', () => { if(camera) { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } });



Game Source: Football Stars

Creator: AtomicFox16

Libraries: three

Complexity: complex (398 lines, 25.3 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: mini-fu-ball-game-atomicfox16" to link back to the original. Then publish at arcadelab.ai/publish.