domingo, 8 de noviembre de 2026

Juego de tanques echo con IA al 100 por 100;

https://sites.google.com/d/1RsRFkHVKLR1EGn8kdMQnYCDBIxhW9P4k/p/1-ATwBP7lGuRrIxInHOsTP3wBV4EP-2su/edit?pli=1 



codigo completo en html:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>3D Tank Warzone</title>
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- Three.js -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <!-- Google Fonts -->
    <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@500;700&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Rajdhani', sans-serif;
            user-select: none;
            -webkit-user-select: none;
            touch-action: none;
            overflow: hidden;
            background-color: #050508;
        }
        .font-orbitron {
            font-family: 'Orbitron', sans-serif;
        }
        /* Custom UI Glassmorphism */
        .glass-panel {
            background: rgba(13, 17, 23, 0.85);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(0, 255, 204, 0.25);
            box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.6);
        }
        .glow-text-cyan {
            text-shadow: 0 0 12px rgba(0, 255, 255, 0.8);
        }
        .glow-text-red {
            text-shadow: 0 0 12px rgba(255, 50, 50, 0.8);
        }
        .glow-box-cyan {
            box-shadow: 0 0 18px rgba(0, 255, 255, 0.4);
        }
        /* Virtual Joysticks for Touch */
        .joystick-zone {
            position: relative;
            width: 130px;
            height: 130px;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.08);
            border: 2px solid rgba(0, 255, 204, 0.4);
            touch-action: none;
        }
        .joystick-knob {
            position: absolute;
            top: 50%;
            left: 50%;
            width: 48px;
            height: 48px;
            margin-top: -24px;
            margin-left: -24px;
            border-radius: 50%;
            background: radial-gradient(circle, rgba(0,255,204,0.9) 0%, rgba(0,150,255,0.7) 100%);
            box-shadow: 0 0 12px rgba(0,255,204,0.6);
            pointer-events: none;
        }
    </style>
</head>
<body class="w-screen h-screen m-0 p-0 overflow-hidden bg-black text-white select-none">

    <!-- 3D Render Canvas Container -->
    <div id="canvas-container" class="w-full h-full absolute inset-0 z-0"></div>

    <!-- HUD Overlay -->
    <div id="hud-overlay" class="absolute inset-0 z-10 pointer-events-none flex flex-col justify-between p-3 md:p-6 hidden">
        <!-- Top Bar Header -->
        <div class="flex justify-between items-start w-full">
            <!-- Health & Reload Bar -->
            <div class="glass-panel p-3 md:p-4 rounded-xl w-56 md:w-80 pointer-events-auto">
                <div class="flex justify-between items-center mb-1">
                    <span class="font-orbitron text-[10px] md:text-xs font-bold text-cyan-400">BLINDAJE DEL TANQUE</span>
                    <span id="health-text" class="font-orbitron text-[10px] md:text-xs font-bold text-emerald-400">100 HP</span>
                </div>
                <!-- Health Bar Track -->
                <div class="w-full bg-gray-900 h-2.5 md:h-3 rounded-full overflow-hidden border border-gray-700 mb-2">
                    <div id="health-bar" class="bg-gradient-to-r from-emerald-500 to-cyan-400 h-full w-full transition-all duration-200"></div>
                </div>

                <div class="flex justify-between items-center mb-1">
                    <span class="font-orbitron text-[10px] md:text-xs text-gray-400">RECARGA CAÑÓN</span>
                    <span id="reload-text" class="font-orbitron text-[10px] md:text-xs text-emerald-400">LISTO</span>
                </div>
                <!-- Reload Bar Track -->
                <div class="w-full bg-gray-900 h-1.5 rounded-full overflow-hidden border border-gray-800">
                    <div id="reload-bar" class="bg-cyan-400 h-full w-full transition-all duration-75"></div>
                </div>
            </div>

            <!-- Score & Waves -->
            <div class="glass-panel px-4 py-2 md:px-6 md:py-3 rounded-xl flex items-center space-x-3 md:space-x-6 text-center pointer-events-auto">
                <div>
                    <div class="text-[9px] md:text-xs text-gray-400 font-orbitron uppercase">OLEADA</div>
                    <div id="hud-wave" class="text-xl md:text-2xl font-black font-orbitron text-cyan-400 glow-text-cyan">1</div>
                </div>
                <div class="h-6 md:h-8 w-px bg-cyan-500/30"></div>
                <div>
                    <div class="text-[9px] md:text-xs text-gray-400 font-orbitron uppercase">BAJAS</div>
                    <div id="hud-kills" class="text-xl md:text-2xl font-black font-orbitron text-amber-400">0</div>
                </div>
                <div class="h-6 md:h-8 w-px bg-cyan-500/30"></div>
                <div>
                    <div class="text-[9px] md:text-xs text-gray-400 font-orbitron uppercase">PUNTOS</div>
                    <div id="hud-score" class="text-xl md:text-2xl font-black font-orbitron text-white">0</div>
                </div>
            </div>

            <!-- Radar Minimap -->
            <div class="glass-panel p-1.5 rounded-xl relative w-28 h-28 md:w-36 md:h-36 flex items-center justify-center border-cyan-500/40 pointer-events-auto">
                <canvas id="minimap-canvas" width="130" height="130" class="rounded-lg w-full h-full"></canvas>
                <div class="absolute text-[8px] md:text-[9px] font-orbitron text-cyan-400/80 top-1 left-2">RADAR</div>
            </div>
        </div>

        <!-- Crosshair / Aiming Center Pointer -->
        <div id="crosshair" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none">
            <div class="w-8 h-8 border border-cyan-400/60 rounded-full flex items-center justify-center">
                <div class="w-1.5 h-1.5 bg-cyan-400 rounded-full glow-box-cyan"></div>
            </div>
        </div>

        <!-- Touch Controls (Mobile / On-screen Controls) -->
        <div id="touch-controls" class="w-full flex justify-between items-end pb-2 px-2 pointer-events-auto md:hidden">
            <!-- Left Joystick: Move -->
            <div id="joystick-left" class="joystick-zone">
                <div id="knob-left" class="joystick-knob"></div>
            </div>

            <!-- Fire Button -->
            <button id="btn-fire" class="w-16 h-16 rounded-full bg-gradient-to-tr from-red-600 to-amber-500 border-2 border-red-300 font-orbitron font-black text-white text-xs active:scale-95 shadow-lg shadow-red-600/50 flex items-center justify-center">
                FUEGO
            </button>

            <!-- Right Joystick: Aim Turret -->
            <div id="joystick-right" class="joystick-zone">
                <div id="knob-right" class="joystick-knob"></div>
            </div>
        </div>
    </div>

    <!-- Start / Main Menu Overlay -->
    <div id="start-menu" class="absolute inset-0 z-20 flex items-center justify-center bg-black/85 backdrop-blur-md p-4">
        <div class="glass-panel p-6 md:p-10 rounded-2xl max-w-lg w-full text-center border-cyan-500/40 glow-box-cyan">
            <h1 class="font-orbitron text-3xl md:text-5xl font-black text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 via-teal-300 to-emerald-400 mb-3 glow-text-cyan">
                TANK WARZONE 3D
            </h1>
            <p class="text-gray-300 text-xs md:text-sm mb-6">¡Destruye los tanques enemigos con proyectiles 3D, sobrevive a las oleadas y domina el terreno!</p>
           
            <!-- Controls Guide -->
            <div class="bg-gray-900/90 rounded-xl p-4 mb-6 text-left border border-gray-800 text-xs md:text-sm space-y-2">
                <div class="font-orbitron font-bold text-cyan-400 mb-1 uppercase">Controles de Combate</div>
                <div class="flex justify-between"><span class="text-gray-400">Mover / Girar Tanque:</span> <span class="text-white font-mono">Teclas WASD / Flechas</span></div>
                <div class="flex justify-between"><span class="text-gray-400">Apuntar Torreta (360°):</span> <span class="text-white font-mono">Movimiento del Ratón</span></div>
                <div class="flex justify-between"><span class="text-gray-400">Disparar Cañón:</span> <span class="text-white font-mono">Clic Izquierdo / Espacio</span></div>
                <div class="flex justify-between"><span class="text-gray-400">Dispositivos Táctiles:</span> <span class="text-white font-mono">Joysticks Virtuales</span></div>
            </div>

            <button id="btn-start" class="w-full py-3.5 rounded-xl bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-400 hover:to-blue-500 font-orbitron font-black text-base md:text-lg text-white shadow-lg shadow-cyan-500/30 transition-all transform hover:scale-[1.02] active:scale-95">
                INICIAR BATALLA
            </button>
        </div>
    </div>

    <!-- Game Over Screen Overlay -->
    <div id="gameover-menu" class="absolute inset-0 z-20 flex items-center justify-center bg-black/90 backdrop-blur-md p-4 hidden">
        <div class="glass-panel p-6 md:p-8 rounded-2xl max-w-md w-full text-center border-red-500/40 shadow-red-900/40">
            <h2 class="font-orbitron text-3xl md:text-4xl font-black text-red-500 mb-2 glow-text-red">TANQUE DESTRUIDO</h2>
            <p class="text-gray-400 text-xs md:text-sm mb-6">¡Tu blindaje ha cedido ante los impactos enemigos!</p>

            <div class="bg-gray-900/90 rounded-xl p-4 mb-6 border border-gray-800 space-y-3">
                <div class="flex justify-between items-center text-xs md:text-sm">
                    <span class="text-gray-400 font-orbitron">PUNTUACIÓN TOTAL</span>
                    <span id="final-score" class="font-orbitron font-bold text-lg md:text-xl text-white">0</span>
                </div>
                <div class="flex justify-between items-center text-xs md:text-sm">
                    <span class="text-gray-400 font-orbitron">TANQUES DESTRUIDOS</span>
                    <span id="final-kills" class="font-orbitron font-bold text-lg md:text-xl text-amber-400">0</span>
                </div>
                <div class="flex justify-between items-center text-xs md:text-sm">
                    <span class="text-gray-400 font-orbitron">OLEADAS SURCADAS</span>
                    <span id="final-wave" class="font-orbitron font-bold text-lg md:text-xl text-cyan-400">0</span>
                </div>
            </div>

            <button id="btn-restart" class="w-full py-3.5 rounded-xl bg-gradient-to-r from-red-600 to-amber-600 hover:from-red-500 hover:to-amber-500 font-orbitron font-black text-base md:text-lg text-white shadow-lg shadow-red-600/30 transition-all transform hover:scale-[1.02] active:scale-95">
                REPLEGARSE Y REINTENTAR
            </button>
        </div>
    </div>

    <!-- Game Over Screen Overlay -->
    <div id="gameover-menu" class="absolute inset-0 z-20 flex items-center justify-center bg-black/90 backdrop-blur-md p-4 hidden">
        <div class="glass-panel p-8 md:p-10 rounded-2xl max-w-md w-full text-center border-red-500/40 shadow-red-900/30">
            <h2 class="font-orbitron text-4xl font-black text-red-500 mb-2 glow-text-red">TANK DESTROYED</h2>
            <p class="text-gray-400 text-sm mb-6">Your armored hull has been compromised!</p>

            <div class="bg-gray-900/80 rounded-xl p-4 mb-6 border border-gray-800 space-y-3">
                <div class="flex justify-between items-center text-sm">
                    <span class="text-gray-400 font-orbitron">TOTAL SCORE</span>
                    <span id="final-score" class="font-orbitron font-bold text-xl text-white">0</span>
                </div>
                <div class="flex justify-between items-center text-sm">
                    <span class="text-gray-400 font-orbitron">TANKS DESTROYED</span>
                    <span id="final-kills" class="font-orbitron font-bold text-xl text-amber-400">0</span>
                </div>
                <div class="flex justify-between items-center text-sm">
                    <span class="text-gray-400 font-orbitron">WAVES SURVIVED</span>
                    <span id="final-wave" class="font-orbitron font-bold text-xl text-cyan-400">0</span>
                </div>
            </div>

            <button id="btn-restart" class="w-full py-4 rounded-xl bg-gradient-to-r from-red-600 to-amber-600 hover:from-red-500 hover:to-amber-500 font-orbitron font-black text-lg text-white shadow-lg shadow-red-600/30 transition-all transform hover:scale-[1.02] active:scale-95">
                REDEPLOY TANK
            </button>
        </div>
    </div>

    <!-- JavaScript Game Logic -->
    <script>
        /**
         * Tank Warzone 3D - Main Engine & Game Logic
         */

        // Global Game State
        const state = {
            active: false,
            score: 0,
            kills: 0,
            wave: 1,
            playerHealth: 100,
            maxPlayerHealth: 100,
            canShoot: true,
            reloadTime: 0.8, // Seconds
            reloadProgress: 1,
            lastFrameTime: performance.now()
        };

        // Inputs Setup
        const keys = { w: false, a: false, s: false, d: false, space: false };
        const mouse = { x: 0, y: 0, raycastPoint: new THREE.Vector3() };
        const touchInput = {
            left: { active: false, x: 0, y: 0 },
            right: { active: false, x: 0, y: 0 }
        };

        // Three.js Core Components
        let scene, camera, renderer, clock;
        let playerTank, playerBody, playerTurret, playerBarrel;
        const enemies = [];
        const projectiles = [];
        const particles = [];
        const obstacles = [];
        const wrecks = [];
        const mapBounds = 110;

        // Sound Effects (Web Audio API Synthesizer)
        class SoundFX {
            constructor() {
                this.ctx = null;
            }
            init() {
                if (!this.ctx) {
                    this.ctx = new (window.AudioContext || window.webkitAudioContext)();
                }
            }
            playCannon() {
                if (!this.ctx) return;
                const osc = this.ctx.createOscillator();
                const gain = this.ctx.createGain();
                const now = this.ctx.currentTime;
               
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(150, now);
                osc.frequency.exponentialRampToValueAtTime(30, now + 0.35);

                gain.gain.setValueAtTime(0.8, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.35);

                osc.connect(gain);
                gain.connect(this.ctx.destination);
                osc.start(now);
                osc.stop(now + 0.35);
            }
            playExplosion() {
                if (!this.ctx) return;
                const now = this.ctx.currentTime;

                // Create noise for crunch
                const bufferSize = this.ctx.sampleRate * 0.5;
                const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
                const data = buffer.getChannelData(0);
                for (let i = 0; i < bufferSize; i++) {
                    data[i] = Math.random() * 2 - 1;
                }

                const noise = this.ctx.createBufferSource();
                noise.buffer = buffer;

                const filter = this.ctx.createBiquadFilter();
                filter.type = 'lowpass';
                filter.frequency.setValueAtTime(800, now);
                filter.frequency.linearRampToValueAtTime(50, now + 0.5);

                const gain = this.ctx.createGain();
                gain.gain.setValueAtTime(1.0, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.5);

                noise.connect(filter);
                filter.connect(gain);
                gain.connect(this.ctx.destination);
                noise.start(now);
            }
            playHit() {
                if (!this.ctx) return;
                const osc = this.ctx.createOscillator();
                const gain = this.ctx.createGain();
                const now = this.ctx.currentTime;

                osc.type = 'square';
                osc.frequency.setValueAtTime(300, now);
                osc.frequency.exponentialRampToValueAtTime(80, now + 0.15);

                gain.gain.setValueAtTime(0.4, now);
                gain.gain.exponentialRampToValueAtTime(0.01, now + 0.15);

                osc.connect(gain);
                gain.connect(this.ctx.destination);
                osc.start(now);
                osc.stop(now + 0.15);
            }
        }
        const sfx = new SoundFX();

        // Initialize 3D Engine
        function initEngine() {
            const container = document.getElementById('canvas-container');

            // Scene setup with atmospheric fog
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x0a0e17);
            scene.fog = new THREE.FogExp2(0x0a0e17, 0.008);

            // Camera setup
            camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);

            // Renderer setup
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            container.appendChild(renderer.domElement);

            clock = new THREE.Clock();

            // Lighting Setup
            const ambientLight = new THREE.AmbientLight(0x99ccff, 0.4);
            scene.add(ambientLight);

            const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
            dirLight.position.set(60, 100, 40);
            dirLight.castShadow = true;
            dirLight.shadow.mapSize.width = 2048;
            dirLight.shadow.mapSize.height = 2048;
            dirLight.shadow.camera.near = 10;
            dirLight.shadow.camera.far = 300;
            const d = 100;
            dirLight.shadow.camera.left = -d;
            dirLight.shadow.camera.right = d;
            dirLight.shadow.camera.top = d;
            dirLight.shadow.camera.bottom = -d;
            scene.add(dirLight);

            // Grid Ground & Environment
            buildEnvironment();

            // Create Player Tank
            playerTank = createTankModel(0x00f0ff, true);
            playerBody = playerTank.getObjectByName("body");
            playerTurret = playerTank.getObjectByName("turret");
            playerBarrel = playerTank.getObjectByName("barrel");
            scene.add(playerTank);
            playerTank.position.set(0, 0, 0);

            // Window Resize Listener
            window.addEventListener('resize', onWindowResize);

            // Event Listeners for Controls
            setupInputListeners();
        }

        // Build Environment (Ground Grid, Rocks, Barriers)
        function buildEnvironment() {
            // Textured Ground Plane
            const groundGeo = new THREE.PlaneGeometry(260, 260, 64, 64);
            const groundMat = new THREE.MeshStandardMaterial({
                color: 0x121824,
                roughness: 0.8,
                metalness: 0.2,
            });
            const ground = new THREE.Mesh(groundGeo, groundMat);
            ground.rotation.x = -Math.PI / 2;
            ground.receiveShadow = true;
            scene.add(ground);

            // Floor Grid Helper (Sci-fi Aesthetic)
            const gridHelper = new THREE.GridHelper(260, 52, 0x00e5ff, 0x1d2c4d);
            gridHelper.position.y = 0.05;
            scene.add(gridHelper);

            // Outer Arena Barriers
            const wallMat = new THREE.MeshStandardMaterial({ color: 0x0f172a, roughness: 0.5 });
            const borderThickness = 4;
            const borderHeight = 6;

            const wallGeomH = new THREE.BoxGeometry(260, borderHeight, borderThickness);
            const wallGeomV = new THREE.BoxGeometry(borderThickness, borderHeight, 260);

            const nWall = new THREE.Mesh(wallGeomH, wallMat);
            nWall.position.set(0, borderHeight/2, -130);
            scene.add(nWall); obstacles.push(nWall);

            const sWall = new THREE.Mesh(wallGeomH, wallMat);
            sWall.position.set(0, borderHeight/2, 130);
            scene.add(sWall); obstacles.push(sWall);

            const eWall = new THREE.Mesh(wallGeomV, wallMat);
            eWall.position.set(130, borderHeight/2, 0);
            scene.add(eWall); obstacles.push(eWall);

            const wWall = new THREE.Mesh(wallGeomV, wallMat);
            wWall.position.set(-130, borderHeight/2, 0);
            scene.add(wWall); obstacles.push(wWall);

            // Scatter Low-Poly Obstacles/Bunkers
            const boxGeo = new THREE.BoxGeometry(1, 1, 1);
            const obsMat = new THREE.MeshStandardMaterial({ color: 0x26334d, roughness: 0.6 });

            const seedPositions = [
                {x: -30, z: -30, w: 12, h: 6, d: 12},
                {x: 30, z: 30, w: 10, h: 5, d: 14},
                {x: -40, z: 40, w: 14, h: 8, d: 8},
                {x: 40, z: -40, w: 8, h: 6, d: 16},
                {x: 0, z: 60, w: 16, h: 7, d: 10},
                {x: 0, z: -60, w: 14, h: 6, d: 14},
                {x: -70, z: 0, w: 10, h: 7, d: 20},
                {x: 70, z: 0, w: 12, h: 6, d: 18}
            ];

            seedPositions.forEach(p => {
                const obs = new THREE.Mesh(boxGeo, obsMat);
                obs.scale.set(p.w, p.h, p.d);
                obs.position.set(p.x, p.h / 2, p.z);
                obs.castShadow = true;
                obs.receiveShadow = true;
                scene.add(obs);
                obs.radius = Math.max(p.w, p.d) / 2;
                obstacles.push(obs);
            });
        }

        // Generate Detailed 3D Tank Model
        function createTankModel(primaryColor, isPlayer = false) {
            const tankGroup = new THREE.Group();

            const bodyMat = new THREE.MeshStandardMaterial({ color: primaryColor, roughness: 0.4, metalness: 0.5 });
            const darkMat = new THREE.MeshStandardMaterial({ color: 0x151d2a, roughness: 0.8 });
            const barrelMat = new THREE.MeshStandardMaterial({ color: 0x334155, metalness: 0.8, roughness: 0.3 });
            const glowMat = new THREE.MeshBasicMaterial({ color: isPlayer ? 0x00f0ff : 0xff3333 });

            // Hull Base
            const bodyGeo = new THREE.BoxGeometry(4.2, 1.4, 6);
            const body = new THREE.Mesh(bodyGeo, bodyMat);
            body.position.y = 1.1;
            body.castShadow = true;
            body.receiveShadow = true;
            body.name = "body";
            tankGroup.add(body);

            // Tracks Left & Right
            const trackGeo = new THREE.BoxGeometry(0.9, 1.2, 6.4);
            const trackLeft = new THREE.Mesh(trackGeo, darkMat);
            trackLeft.position.set(-2.4, 0.8, 0);
            trackLeft.castShadow = true;
            tankGroup.add(trackLeft);

            const trackRight = new THREE.Mesh(trackGeo, darkMat);
            trackRight.position.set(2.4, 0.8, 0);
            trackRight.castShadow = true;
            tankGroup.add(trackRight);

            // Turret Group
            const turretGroup = new THREE.Group();
            turretGroup.position.set(0, 2.0, -0.2);
            turretGroup.name = "turret";

            const turretGeo = new THREE.BoxGeometry(2.8, 1.1, 3.4);
            const turret = new THREE.Mesh(turretGeo, bodyMat);
            turret.position.set(0, 0, 0);
            turret.castShadow = true;
            turretGroup.add(turret);

            // Turret Glow Strip
            const visorGeo = new THREE.BoxGeometry(2.0, 0.25, 0.2);
            const visor = new THREE.Mesh(visorGeo, glowMat);
            visor.position.set(0, 0.2, -1.75);
            turretGroup.add(visor);

            // Barrel Group
            const barrelGroup = new THREE.Group();
            barrelGroup.position.set(0, 0, -1.6);
            barrelGroup.name = "barrel";

            const barrelGeo = new THREE.CylinderGeometry(0.22, 0.25, 4.2, 16);
            barrelGeo.rotateX(-Math.PI / 2);
            const barrel = new THREE.Mesh(barrelGeo, barrelMat);
            barrel.position.set(0, 0, -2.1);
            barrel.castShadow = true;
            barrelGroup.add(barrel);

            // Cannon Muzzle Tip
            const tipGeo = new THREE.CylinderGeometry(0.3, 0.3, 0.6, 16);
            tipGeo.rotateX(-Math.PI / 2);
            const tip = new THREE.Mesh(tipGeo, darkMat);
            tip.position.set(0, 0, -4.2);
            barrelGroup.add(tip);

            turretGroup.add(barrelGroup);
            tankGroup.add(turretGroup);

            return tankGroup;
        }

        // Spawn Enemies in Waves
        function spawnWave(waveNum) {
            const enemyCount = 2 + waveNum * 2;
            state.wave = waveNum;
            document.getElementById('hud-wave').innerText = state.wave;

            for (let i = 0; i < enemyCount; i++) {
                // Random position away from player
                let px, pz;
                do {
                    px = (Math.random() - 0.5) * 200;
                    pz = (Math.random() - 0.5) * 200;
                } while (Math.hypot(px - playerTank.position.x, pz - playerTank.position.z) < 40);

                const enemyMesh = createTankModel(0xe11d48, false);
                enemyMesh.position.set(px, 0, pz);

                const enemyObj = {
                    mesh: enemyMesh,
                    turret: enemyMesh.getObjectByName("turret"),
                    barrel: enemyMesh.getObjectByName("barrel"),
                    health: 60 + waveNum * 15,
                    maxHealth: 60 + waveNum * 15,
                    reloadTimer: Math.random() * 2,
                    speed: 8 + Math.min(waveNum * 0.5, 6),
                    radius: 3.2
                };

                // Add 3D Health Bar
                const hpBarGroup = new THREE.Group();
                const hpBgGeo = new THREE.PlaneGeometry(3.5, 0.4);
                const hpBgMat = new THREE.MeshBasicMaterial({ color: 0x000000, side: THREE.DoubleSide });
                const hpBg = new THREE.Mesh(hpBgGeo, hpBgMat);

                const hpFgGeo = new THREE.PlaneGeometry(3.4, 0.3);
                const hpFgMat = new THREE.MeshBasicMaterial({ color: 0xff2244, side: THREE.DoubleSide });
                const hpFg = new THREE.Mesh(hpFgGeo, hpFgMat);
                hpFg.position.z = 0.01;
                hpFg.name = "hpFg";

                hpBarGroup.add(hpBg);
                hpBarGroup.add(hpFg);
                hpBarGroup.position.set(0, 4.2, 0);
                enemyMesh.add(hpBarGroup);
                enemyObj.hpBarGroup = hpBarGroup;
                enemyObj.hpFg = hpFg;

                enemies.push(enemyObj);
                scene.add(enemyMesh);
            }
        }

        // Fire Cannon Shell
        function fireShell(owner, originPos, direction, isPlayer = false) {
            sfx.playCannon();

            // Recoil Animation
            const barrel = owner.barrel;
            if (barrel) {
                barrel.position.z = -1.0;
            }

            // Shell Mesh
            const shellGeo = new THREE.SphereGeometry(0.35, 8, 8);
            const shellMat = new THREE.MeshBasicMaterial({
                color: isPlayer ? 0x00ffff : 0xff5500
            });
            const shell = new THREE.Mesh(shellGeo, shellMat);
            shell.position.copy(originPos);

            // Light flash at muzzle
            const flash = new THREE.PointLight(isPlayer ? 0x00ffff : 0xffaa00, 3, 10);
            flash.position.copy(originPos);
            scene.add(flash);
            setTimeout(() => scene.remove(flash), 60);

            projectiles.push({
                mesh: shell,
                velocity: direction.clone().multiplyScalar(isPlayer ? 75 : 55),
                isPlayer: isPlayer,
                life: 2.5
            });

            scene.add(shell);
        }

        // Spawn Explosion Effect & Debris
        function createExplosion(position, color = 0xff6600, scale = 1.0) {
            sfx.playExplosion();

            const particleCount = Math.floor(25 * scale);
            for (let i = 0; i < particleCount; i++) {
                const pGeo = new THREE.BoxGeometry(0.3 * scale, 0.3 * scale, 0.3 * scale);
                const pMat = new THREE.MeshBasicMaterial({
                    color: Math.random() > 0.5 ? color : 0xffd700
                });
                const p = new THREE.Mesh(pGeo, pMat);
                p.position.copy(position);

                const velocity = new THREE.Vector3(
                    (Math.random() - 0.5) * 20 * scale,
                    (Math.random() * 15 + 5) * scale,
                    (Math.random() - 0.5) * 20 * scale
                );

                particles.push({
                    mesh: p,
                    velocity: velocity,
                    life: 0.6 + Math.random() * 0.4,
                    maxLife: 1.0
                });

                scene.add(p);
            }

            // Flash sphere
            const flashGeo = new THREE.SphereGeometry(2 * scale, 16, 16);
            const flashMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.9 });
            const flash = new THREE.Mesh(flashGeo, flashMat);
            flash.position.copy(position);
            scene.add(flash);

            let opacity = 0.9;
            const fadeInterval = setInterval(() => {
                opacity -= 0.15;
                if (opacity <= 0) {
                    scene.remove(flash);
                    clearInterval(fadeInterval);
                } else {
                    flash.material.opacity = opacity;
                }
            }, 30);
        }

        // Spawn Charred Tank Wreckage
        function spawnWreckage(position, rotation) {
            const wreckMat = new THREE.MeshStandardMaterial({ color: 0x1c1917, roughness: 0.9 });
            const wreckGeo = new THREE.BoxGeometry(4.2, 1.2, 5.8);
            const wreck = new THREE.Mesh(wreckGeo, wreckMat);
            wreck.position.copy(position);
            wreck.position.y = 0.6;
            wreck.rotation.copy(rotation);
            scene.add(wreck);
            wrecks.push(wreck);

            // Emit persistent smoke for 5 seconds
            const smokeInterval = setInterval(() => {
                if (Math.random() < 0.6) {
                    const sGeo = new THREE.SphereGeometry(0.5 + Math.random() * 0.5, 6, 6);
                    const sMat = new THREE.MeshBasicMaterial({ color: 0x444444, transparent: true, opacity: 0.4 });
                    const smoke = new THREE.Mesh(sGeo, sMat);
                    smoke.position.copy(position).add(new THREE.Vector3((Math.random()-0.5)*2, 1, (Math.random()-0.5)*2));
                    scene.add(smoke);

                    particles.push({
                        mesh: smoke,
                        velocity: new THREE.Vector3(0, 2 + Math.random()*2, 0),
                        life: 1.2,
                        maxLife: 1.2,
                        isSmoke: true
                    });
                }
            }, 200);

            setTimeout(() => clearInterval(smokeInterval), 6000);
        }

        // Setup Player Controls / Input Listeners
        function setupInputListeners() {
            // Keyboard
            window.addEventListener('keydown', (e) => {
                if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.w = true;
                if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.s = true;
                if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.a = true;
                if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.d = true;
                if (e.code === 'Space') {
                    if (state.active && state.canShoot) triggerPlayerShoot();
                }
            });

            window.addEventListener('keyup', (e) => {
                if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.w = false;
                if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.s = false;
                if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.a = false;
                if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.d = false;
            });

            // Mouse Aiming via Raycasting onto ground plane
            const raycaster = new THREE.Raycaster();
            const groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);

            window.addEventListener('mousemove', (e) => {
                mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
                mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;

                raycaster.setFromCamera(mouse, camera);
                raycaster.ray.intersectPlane(groundPlane, mouse.raycastPoint);
            });

            window.addEventListener('mousedown', (e) => {
                if (e.button === 0 && state.active && state.canShoot) {
                    sfx.init();
                    triggerPlayerShoot();
                }
            });

            // Mobile Touch Joystick Handlers
            setupTouchJoystick('joystick-left', 'knob-left', (dx, dy) => {
                touchInput.left.x = dx;
                touchInput.left.y = dy;
            });

            setupTouchJoystick('joystick-right', 'knob-right', (dx, dy) => {
                touchInput.right.x = dx;
                touchInput.right.y = dy;
            });

            document.getElementById('btn-fire').addEventListener('touchstart', (e) => {
                e.preventDefault();
                sfx.init();
                if (state.active && state.canShoot) triggerPlayerShoot();
            });

            // Menu Buttons
            document.getElementById('btn-start').addEventListener('click', () => {
                sfx.init();
                startGame();
            });
            document.getElementById('btn-restart').addEventListener('click', () => {
                sfx.init();
                restartGame();
            });
        }

        // Touch Joystick Helper Logic
        function setupTouchJoystick(zoneId, knobId, onChange) {
            const zone = document.getElementById(zoneId);
            const knob = document.getElementById(knobId);
            let active = false;
            let touchId = null;
            let startX = 0, startY = 0;

            zone.addEventListener('touchstart', (e) => {
                e.preventDefault();
                const touch = e.changedTouches[0];
                touchId = touch.identifier;
                active = true;
                const rect = zone.getBoundingClientRect();
                startX = rect.left + rect.width / 2;
                startY = rect.top + rect.height / 2;
            });

            window.addEventListener('touchmove', (e) => {
                if (!active) return;
                for (let i = 0; i < e.changedTouches.length; i++) {
                    const touch = e.changedTouches[i];
                    if (touch.identifier === touchId) {
                        let dx = touch.clientX - startX;
                        let dy = touch.clientY - startY;
                        const dist = Math.hypot(dx, dy);
                        const maxDist = 45;

                        if (dist > maxDist) {
                            dx = (dx / dist) * maxDist;
                            dy = (dy / dist) * maxDist;
                        }

                        knob.style.transform = `translate(${dx}px, ${dy}px)`;
                        onChange(dx / maxDist, dy / maxDist);
                    }
                }
            });

            const endHandler = (e) => {
                if (!active) return;
                for (let i = 0; i < e.changedTouches.length; i++) {
                    if (e.changedTouches[i].identifier === touchId) {
                        active = false;
                        knob.style.transform = `translate(0px, 0px)`;
                        onChange(0, 0);
                    }
                }
            };

            window.addEventListener('touchend', endHandler);
            window.addEventListener('touchcancel', endHandler);
        }

        // Trigger Player Shot Action
        function triggerPlayerShoot() {
            if (!state.canShoot || !state.active) return;

            state.canShoot = false;
            state.reloadProgress = 0;

            // Get Barrel tip position
            const muzzleWorldPos = new THREE.Vector3();
            playerBarrel.getWorldPosition(muzzleWorldPos);

            // Get Barrel direction vector
            const dir = new THREE.Vector3();
            playerTurret.getWorldDirection(dir);
            dir.negate(); // Barrel points back along Z in local space

            fireShell({ barrel: playerBarrel }, muzzleWorldPos, dir, true);
        }

        // Start / Reset Game Session
        function startGame() {
            document.getElementById('start-menu').classList.add('hidden');
            document.getElementById('gameover-menu').classList.add('hidden');
            document.getElementById('hud-overlay').classList.remove('hidden');

            state.active = true;
            state.score = 0;
            state.kills = 0;
            state.playerHealth = 100;
            state.wave = 1;
            updateHUD();

            // Clear old enemies, projectiles, wrecks
            enemies.forEach(e => scene.remove(e.mesh));
            enemies.length = 0;

            projectiles.forEach(p => scene.remove(p.mesh));
            projectiles.length = 0;

            wrecks.forEach(w => scene.remove(w));
            wrecks.length = 0;

            playerTank.position.set(0, 0, 0);
            playerTank.rotation.set(0, 0, 0);

            spawnWave(1);
        }

        function restartGame() {
            startGame();
        }

        function triggerGameOver() {
            state.active = false;
            createExplosion(playerTank.position, 0x00f0ff, 2.5);

            document.getElementById('hud-overlay').classList.add('hidden');
            document.getElementById('gameover-menu').classList.remove('hidden');
            document.getElementById('final-score').innerText = state.score;
            document.getElementById('final-kills').innerText = state.kills;
            document.getElementById('final-wave').innerText = state.wave;
        }

        function updateHUD() {
            const hpPct = Math.max(0, (state.playerHealth / state.maxPlayerHealth) * 100);
            document.getElementById('health-bar').style.width = hpPct + '%';
            document.getElementById('health-text').innerText = `${Math.ceil(state.playerHealth)} HP`;

            document.getElementById('hud-score').innerText = state.score;
            document.getElementById('hud-kills').innerText = state.kills;

            // Color coding low health
            if (hpPct < 30) {
                document.getElementById('health-bar').className = "bg-red-600 h-full w-full transition-all duration-200";
            } else if (hpPct < 60) {
                document.getElementById('health-bar').className = "bg-amber-500 h-full w-full transition-all duration-200";
            } else {
                document.getElementById('health-bar').className = "bg-gradient-to-r from-emerald-500 to-cyan-400 h-full w-full transition-all duration-200";
            }
        }

        // Render Dynamic Minimap Radar
        function updateMinimap() {
            const canvas = document.getElementById('minimap-canvas');
            const ctx = canvas.getContext('2d');
            const w = canvas.width;
            const h = canvas.height;
            const scale = w / 260; // Map bounds scale ratio

            ctx.clearRect(0, 0, w, h);

            // Radar background sweep lines
            ctx.fillStyle = 'rgba(10, 16, 26, 0.9)';
            ctx.fillRect(0, 0, w, h);

            ctx.strokeStyle = 'rgba(0, 255, 204, 0.15)';
            ctx.lineWidth = 1;
            ctx.beginPath();
            ctx.arc(w/2, h/2, w/3, 0, Math.PI*2);
            ctx.stroke();

            // Draw Obstacles
            ctx.fillStyle = 'rgba(255, 255, 255, 0.15)';
            obstacles.forEach(obs => {
                const ox = (obs.position.x + 130) * scale;
                const oz = (obs.position.z + 130) * scale;
                ctx.fillRect(ox - 3, oz - 3, 6, 6);
            });

            // Draw Enemies (Red Dots)
            ctx.fillStyle = '#ff3355';
            enemies.forEach(e => {
                const ex = (e.mesh.position.x + 130) * scale;
                const ez = (e.mesh.position.z + 130) * scale;
                ctx.beginPath();
                ctx.arc(ex, ez, 3, 0, Math.PI*2);
                ctx.fill();
            });

            // Draw Player (Cyan Icon)
            if (state.playerHealth > 0) {
                const px = (playerTank.position.x + 130) * scale;
                const pz = (playerTank.position.z + 130) * scale;
                ctx.fillStyle = '#00f0ff';
                ctx.beginPath();
                ctx.arc(px, pz, 4, 0, Math.PI*2);
                ctx.fill();
            }
        }

        // Window Resize Handler
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        /**
         * Main Frame Animation & Update Loop
         */
        function animate() {
            requestAnimationFrame(animate);

            const dt = Math.min(clock.getDelta(), 0.1);

            if (state.active) {
                updatePlayer(dt);
                updateEnemies(dt);
                updateProjectiles(dt);
                updateParticles(dt);
                updateCamera();
                updateMinimap();

                // Reload Bar Progress
                if (!state.canShoot) {
                    state.reloadProgress += dt / state.reloadTime;
                    if (state.reloadProgress >= 1) {
                        state.reloadProgress = 1;
                        state.canShoot = true;
                        document.getElementById('reload-text').innerText = "READY";
                        document.getElementById('reload-text').className = "font-orbitron text-xs text-emerald-400";
                    } else {
                        document.getElementById('reload-text').innerText = "RELOADING";
                        document.getElementById('reload-text').className = "font-orbitron text-xs text-amber-400";
                    }
                    document.getElementById('reload-bar').style.width = (state.reloadProgress * 100) + '%';
                }

                // Check Wave Completion
                if (enemies.length === 0) {
                    state.wave++;
                    state.playerHealth = Math.min(state.maxPlayerHealth, state.playerHealth + 30); // Repair bonus
                    updateHUD();
                    spawnWave(state.wave);
                }
            } else {
                // Menu idle camera rotation
                const time = Date.now() * 0.0003;
                camera.position.x = Math.sin(time) * 40;
                camera.position.z = Math.cos(time) * 40;
                camera.position.y = 25;
                camera.lookAt(0, 0, 0);
            }

            renderer.render(scene, camera);
        }

        // Update Player Tank Physics & Controls
        function updatePlayer(dt) {
            const moveSpeed = 16.0;
            const turnSpeed = 2.4;

            // Combine Keyboard & Touch Input for Movement
            let moveDir = 0;
            let turnDir = 0;

            if (keys.w) moveDir += 1;
            if (keys.s) moveDir -= 1;
            if (keys.a) turnDir += 1;
            if (keys.d) turnDir -= 1;

            if (touchInput.left.y !== 0) moveDir = -touchInput.left.y;
            if (touchInput.left.x !== 0) turnDir = -touchInput.left.x;

            // Hull Rotation
            playerTank.rotation.y += turnDir * turnSpeed * dt;

            // Forward/Backward Motion
            if (moveDir !== 0) {
                const forward = new THREE.Vector3(0, 0, -1).applyAxisAngle(new THREE.Vector3(0, 1, 0), playerTank.rotation.y);
                const nextPos = playerTank.position.clone().add(forward.multiplyScalar(moveDir * moveSpeed * dt));

                // Collision Detection with Obstacles & Boundaries
                let collide = false;
                if (Math.abs(nextPos.x) > mapBounds || Math.abs(nextPos.z) > mapBounds) collide = true;

                for (let obs of obstacles) {
                    if (nextPos.distanceTo(obs.position) < (obs.radius + 2.5)) {
                        collide = true;
                        break;
                    }
                }

                if (!collide) {
                    playerTank.position.copy(nextPos);
                }
            }

            // Turret Aiming (Mouse Raycast or Touch Right Joystick)
            if (touchInput.right.x !== 0 || touchInput.right.y !== 0) {
                // Touch aiming
                const targetAngle = Math.atan2(-touchInput.right.x, -touchInput.right.y);
                playerTurret.rotation.y = targetAngle - playerTank.rotation.y;
            } else {
                // Mouse aiming
                const targetPoint = mouse.raycastPoint.clone();
                const turretWorldPos = new THREE.Vector3();
                playerTurret.getWorldPosition(turretWorldPos);

                const dx = targetPoint.x - turretWorldPos.x;
                const dz = targetPoint.z - turretWorldPos.z;
                const targetWorldAngle = Math.atan2(dx, dz) + Math.PI;

                playerTurret.rotation.y = targetWorldAngle - playerTank.rotation.y;
            }

            // Smooth Barrel Recoil Recovery
            if (playerBarrel.position.z < 0) {
                playerBarrel.position.z = Math.min(0, playerBarrel.position.z + dt * 4.0);
            }
        }

        // Update Enemy Tanks AI Logic
        function updateEnemies(dt) {
            enemies.forEach((enemy, index) => {
                const eMesh = enemy.mesh;
                const distToPlayer = eMesh.position.distanceTo(playerTank.position);

                // Move towards player if far, stay in firing range
                if (distToPlayer > 18 && distToPlayer < 120) {
                    const dir = new THREE.Vector3().subVectors(playerTank.position, eMesh.position).normalize();
                    const targetAngle = Math.atan2(dir.x, dir.z) + Math.PI;

                    // Smooth rotation towards player
                    let diff = targetAngle - eMesh.rotation.y;
                    // Normalize angle
                    diff = Math.atan2(Math.sin(diff), Math.cos(diff));
                    eMesh.rotation.y += Math.sign(diff) * Math.min(Math.abs(diff), dt * 1.8);

                    // Move forward
                    const forward = new THREE.Vector3(0, 0, -1).applyAxisAngle(new THREE.Vector3(0, 1, 0), eMesh.rotation.y);
                    const nextPos = eMesh.position.clone().add(forward.multiplyScalar(enemy.speed * dt));

                    // Avoid obstacles & bounds
                    let collide = false;
                    if (Math.abs(nextPos.x) > mapBounds || Math.abs(nextPos.z) > mapBounds) collide = true;

                    for (let obs of obstacles) {
                        if (nextPos.distanceTo(obs.position) < (obs.radius + enemy.radius)) {
                            collide = true; break;
                        }
                    }

                    if (!collide) {
                        eMesh.position.copy(nextPos);
                    }
                }

                // Turret auto-aim at player
                const eTurretPos = new THREE.Vector3();
                enemy.turret.getWorldPosition(eTurretPos);
                const aimDir = new THREE.Vector3().subVectors(playerTank.position, eTurretPos);
                const aimAngle = Math.atan2(aimDir.x, aimDir.z) + Math.PI;
                enemy.turret.rotation.y = aimAngle - eMesh.rotation.y;

                // Firing AI
                enemy.reloadTimer -= dt;
                if (distToPlayer < 80 && enemy.reloadTimer <= 0) {
                    enemy.reloadTimer = 2.2 + Math.random() * 1.5;
                    const muzzlePos = new THREE.Vector3();
                    enemy.barrel.getWorldPosition(muzzlePos);

                    const fireVector = aimDir.clone().normalize();
                    fireShell(enemy, muzzlePos, fireVector, false);
                }

                // Barrel Recoil Recovery
                if (enemy.barrel.position.z < 0) {
                    enemy.barrel.position.z = Math.min(0, enemy.barrel.position.z + dt * 4.0);
                }

                // Billboard Health Bar to face camera
                enemy.hpBarGroup.quaternion.copy(camera.quaternion);
            });
        }

        // Update Projectiles & Hit Detection
        function updateProjectiles(dt) {
            for (let i = projectiles.length - 1; i >= 0; i--) {
                const p = projectiles[i];
                p.life -= dt;

                const nextPos = p.mesh.position.clone().addScaledVector(p.velocity, dt);

                // Collision with Map Obstacles / Walls
                let hitObstacle = false;
                if (Math.abs(nextPos.x) > mapBounds || Math.abs(nextPos.z) > mapBounds) hitObstacle = true;

                if (!hitObstacle) {
                    for (let obs of obstacles) {
                        if (nextPos.distanceTo(obs.position) < obs.radius) {
                            hitObstacle = true;
                            break;
                        }
                    }
                }

                if (hitObstacle) {
                    createExplosion(p.mesh.position, 0xffaa00, 0.7);
                    scene.remove(p.mesh);
                    projectiles.splice(i, 1);
                    continue;
                }

                // Hit Detection: Player Shell hitting Enemy Tank
                if (p.isPlayer) {
                    let hitEnemyIndex = -1;
                    for (let eIdx = 0; eIdx < enemies.length; eIdx++) {
                        const enemy = enemies[eIdx];
                        if (nextPos.distanceTo(enemy.mesh.position) < enemy.radius) {
                            hitEnemyIndex = eIdx;
                            break;
                        }
                    }

                    if (hitEnemyIndex !== -1) {
                        const enemy = enemies[hitEnemyIndex];
                        sfx.playHit();
                        enemy.health -= 35;
                        createExplosion(nextPos, 0xffaa00, 0.9);

                        // Update Enemy 3D HP bar scale
                        const hpPct = Math.max(0, enemy.health / enemy.maxHealth);
                        enemy.hpFg.scale.set(hpPct, 1, 1);

                        // Check Enemy Destruction
                        if (enemy.health <= 0) {
                            createExplosion(enemy.mesh.position, 0xff3300, 1.8);
                            spawnWreckage(enemy.mesh.position, enemy.mesh.rotation);

                            scene.remove(enemy.mesh);
                            enemies.splice(hitEnemyIndex, 1);

                            state.kills++;
                            state.score += 150;
                            updateHUD();
                        }

                        scene.remove(p.mesh);
                        projectiles.splice(i, 1);
                        continue;
                    }
                }
                // Hit Detection: Enemy Shell hitting Player Tank
                else {
                    if (nextPos.distanceTo(playerTank.position) < 2.8) {
                        sfx.playHit();
                        state.playerHealth -= 15;
                        createExplosion(nextPos, 0xff0033, 1.2);
                        updateHUD();

                        if (state.playerHealth <= 0) {
                            triggerGameOver();
                        }

                        scene.remove(p.mesh);
                        projectiles.splice(i, 1);
                        continue;
                    }
                }

                // Lifespan expiration
                if (p.life <= 0) {
                    scene.remove(p.mesh);
                    projectiles.splice(i, 1);
                } else {
                    p.mesh.position.copy(nextPos);
                }
            }
        }

        // Update Explosion Particle Physics
        function updateParticles(dt) {
            for (let i = particles.length - 1; i >= 0; i--) {
                const p = particles[i];
                p.life -= dt;

                p.mesh.position.addScaledVector(p.velocity, dt);

                if (!p.isSmoke) {
                    p.velocity.y -= 25 * dt; // Gravity effect
                }

                const opacity = p.life / p.maxLife;
                p.mesh.scale.setScalar(opacity);

                if (p.life <= 0) {
                    scene.remove(p.mesh);
                    particles.splice(i, 1);
                }
            }
        }

        // Smooth Camera Follow Logic
        function updateCamera() {
            const cameraOffset = new THREE.Vector3(0, 22, 26); // Third-person height and distance
            const targetCamPos = playerTank.position.clone().add(cameraOffset);

            camera.position.lerp(targetCamPos, 0.1);
            camera.lookAt(playerTank.position.x, playerTank.position.y + 1.5, playerTank.position.z);
        }

        // Bootstrapping Engine on Page Load
        window.onload = () => {
            initEngine();
            animate();
        };

    </script>
</body>
</html>

No hay comentarios:

Publicar un comentario