🎮ArcadeLab

لعبة الشوتر FPS - الجوال

by FrozenRider22
910 lines32.4 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="ar">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>لعبة الشوتر FPS - الجوال</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
        body { 
            overflow: hidden; 
            font-family: Arial, sans-serif; 
            background: #000;
            touch-action: none;
            user-select: none;
            -webkit-user-select: none;
        }
        
        #topBar {
            position: fixed; top: 15px; left: 15px; right: 15px;
            display: flex; justify-content: space-between;
            z-index: 10; pointer-events: none;
        }
        
        #score, #timer, #health {
            color: white; font-size: 16px; font-weight: bold;
            text-shadow: 2px 2px 6px black;
            background: rgba(0,0,0,0.5);
            padding: 8px 12px;
            border-radius: 20px;
        }
        
        #health { color: #ff5252; }
        
        /* منطقة النظر - تغطي الشاشة كاملة */
        #lookArea {
            position: fixed; top: 0; left: 0;
            width: 100%; height: 100%;
            z-index: 15;
            touch-action: none;
        }
        
        /* عصا التحكم - فوق منطقة النظر */
        #joystick {
            position: fixed; bottom: 40px; left: 30px;
            width: 130px; height: 130px;
            background: rgba(255,255,255,0.15);
            border: 2px solid rgba(255,255,255,0.4);
            border-radius: 50%;
            z-index: 25;
            touch-action: none;
        }
        
        #stick {
            position: absolute;
            width: 60px; height: 60px;
            background: rgba(255,255,255,0.7);
            border: 2px solid white;
            border-radius: 50%;
            top: 33px; left: 33px;
            transition: transform 0.05s ease-out;
            box-shadow: 0 4px 10px rgba(0,0,0,0.4);
            pointer-events: none;
        }
        
        #crosshair {
            position: fixed; top: 50%; left: 50%;
            transform: translate(-50%, -50%);
            width: 40px; height: 40px;
            z-index: 12;
            pointer-events: none;
        }
        
        #crosshair .line {
            position: absolute;
            background: rgba(255, 50, 50, 0.9);
            box-shadow: 0 0 4px rgba(0,0,0,0.8);
        }
        
        #crosshair .h1 { top: 50%; left: 0; width: 12px; height: 2px; transform: translateY(-50%); }
        #crosshair .h2 { top: 50%; right: 0; width: 12px; height: 2px; transform: translateY(-50%); }
        #crosshair .v1 { left: 50%; top: 0; width: 2px; height: 12px; transform: translateX(-50%); }
        #crosshair .v2 { left: 50%; bottom: 0; width: 2px; height: 12px; transform: translateX(-50%); }
        #crosshair .dot { top: 50%; left: 50%; width: 3px; height: 3px; transform: translate(-50%, -50%); border-radius: 50%; }
        
        /* زر إطلاق النار */
        #fireBtn {
            position: fixed; bottom: 50px; right: 40px;
            width: 110px; height: 110px;
            background: radial-gradient(circle, #ff5252, #b71c1c);
            border: 4px solid rgba(255,255,255,0.6);
            border-radius: 50%;
            color: white;
            font-size: 22px;
            font-weight: bold;
            z-index: 30;
            touch-action: none;
            box-shadow: 0 6px 20px rgba(255,0,0,0.5), inset 0 0 20px rgba(255,255,255,0.2);
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        #fireBtn:active {
            transform: scale(0.92);
            background: radial-gradient(circle, #ff8a8a, #d32f2f);
        }
        
        /* زر إعادة التلقيم */
        #reloadBtn {
            position: fixed; bottom: 180px; right: 60px;
            width: 70px; height: 70px;
            background: rgba(50,50,50,0.85);
            border: 3px solid rgba(255,255,255,0.5);
            border-radius: 50%;
            color: white;
            font-size: 14px;
            font-weight: bold;
            z-index: 30;
            touch-action: none;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
        }
        
        #reloadBtn:active { transform: scale(0.92); }
        
        /* عداد الذخيرة */
        #ammo {
            position: fixed; bottom: 100px; right: 170px;
            color: white; font-size: 20px; font-weight: bold;
            text-shadow: 2px 2px 6px black;
            background: rgba(0,0,0,0.5);
            padding: 6px 14px;
            border-radius: 10px;
            z-index: 20;
            pointer-events: none;
        }
        
        #hitFlash {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: radial-gradient(circle, transparent 40%, rgba(255,0,0,0.6) 100%);
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.3s;
            z-index: 30;
        }
        
        #muzzleFlash {
            position: fixed; top: 50%; left: 50%;
            transform: translate(-50%, -50%);
            width: 200px; height: 200px;
            background: radial-gradient(circle, rgba(255,220,0,0.8), transparent 70%);
            pointer-events: none;
            opacity: 0;
            z-index: 30;
        }
        
        #startScreen, #gameOver {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            color: white;
            z-index: 200;
            text-align: center;
            padding: 20px;
        }
        
        #startScreen {
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
        }
        
        #gameOver {
            background: rgba(0,0,0,0.9);
            display: none;
        }
        
        #startScreen h1 {
            font-size: 38px;
            margin-bottom: 15px;
            color: #ff5252;
            text-shadow: 0 0 20px rgba(255,82,82,0.7);
        }
        
        #startScreen p {
            font-size: 15px;
            margin-bottom: 25px;
            opacity: 0.9;
            line-height: 1.8;
            max-width: 420px;
        }
        
        #startScreen .controls {
            background: rgba(255,255,255,0.1);
            padding: 18px;
            border-radius: 15px;
            margin-bottom: 25px;
            text-align: right;
            font-size: 14px;
            line-height: 1.9;
        }
        
        #startBtn, #restartBtn {
            padding: 18px 55px;
            font-size: 22px;
            font-weight: bold;
            background: linear-gradient(135deg, #ff5252, #b71c1c);
            color: white;
            border: none;
            border-radius: 50px;
            box-shadow: 0 8px 30px rgba(255,82,82,0.6);
            cursor: pointer;
            touch-action: manipulation;
        }
        
        #startBtn:active, #restartBtn:active { transform: scale(0.95); }
        
        #gameOver h1 {
            font-size: 44px;
            margin-bottom: 20px;
            color: #ff5252;
            text-shadow: 0 0 30px rgba(255,82,82,0.8);
        }
        
        #gameOver p { font-size: 20px; margin-bottom: 12px; }
        
        #gameOver .finalScore {
            font-size: 60px;
            font-weight: bold;
            color: #ffeb3b;
            margin: 15px 0 10px;
            text-shadow: 3px 3px 15px black;
        }
        
        #gameOver .kills {
            font-size: 18px;
            color: #4CAF50;
            margin-bottom: 25px;
        }
        
        #hint {
            position: fixed;
            top: 55%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: rgba(255,255,255,0.85);
            font-size: 13px;
            text-align: center;
            background: rgba(0,0,0,0.6);
            padding: 12px 20px;
            border-radius: 12px;
            z-index: 11;
            pointer-events: none;
            transition: opacity 1s;
        }
    </style>
</head>
<body>

    <div id="startScreen">
        <h1>🔫 لعبة الشوتر FPS</h1>
        <div class="controls">
            🕹️ <b>العصا اليسرى:</b> للحركة<br>
            👆 <b>اسحب في أي مكان:</b> للنظر<br>
            🔴 <b>الزر الأحمر:</b> إطلاق النار<br>
            🔄 <b>زر RLD:</b> إعادة التلقيم<br>
            🎯 <b>الهدف:</b> اقتل أكبر عدد من الأعداء!
        </div>
        <p>⏱ لديك 60 ثانية — كل عدو يعطيك 10 نقاط</p>
        <button id="startBtn">ابدأ اللعب</button>
    </div>

    <div id="topBar">
        <div id="timer">⏱ 60</div>
        <div id="score">النقاط: 0</div>
        <div id="health">❤️ 100</div>
    </div>

    <div id="crosshair">
        <div class="line h1"></div>
        <div class="line h2"></div>
        <div class="line v1"></div>
        <div class="line v2"></div>
        <div class="line dot"></div>
    </div>

    <div id="hint">اسحب في أي مكان للنظر · اضغط الأحمر لإطلاق النار</div>

    <div id="ammo">30 / 30</div>

    <!-- منطقة النظر تغطي كل الشاشة (تحت الأزرار) -->
    <div id="lookArea"></div>

    <div id="joystick">
        <div id="stick"></div>
    </div>

    <button id="fireBtn">🔥</button>
    <button id="reloadBtn">RLD</button>

    <div id="hitFlash"></div>
    <div id="muzzleFlash"></div>

    <div id="gameOver">
        <h1>💀 انتهت اللعبة</h1>
        <p>نتيجتك النهائية</p>
        <div class="finalScore" id="finalScore">0</div>
        <div class="kills" id="killsText">الأعداء المقتولون: 0</div>
        <button id="restartBtn">العب مرة أخرى</button>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        // ===== الإعداد الأساسي =====
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x1a1a2e);
        scene.fog = new THREE.Fog(0x1a1a2e, 20, 60);

        const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
        scene.add(camera);

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        document.body.appendChild(renderer.domElement);

        // ===== الإضاءة =====
        const light = new THREE.DirectionalLight(0xffffff, 1);
        light.position.set(10, 20, 10);
        scene.add(light);
        scene.add(new THREE.AmbientLight(0x667799, 0.9));
        
        const fillLight = new THREE.PointLight(0x3366ff, 0.5, 50);
        fillLight.position.set(0, 5, 0);
        scene.add(fillLight);

        // ===== الأرضية =====
        const floor = new THREE.Mesh(
            new THREE.PlaneGeometry(60, 60),
            new THREE.MeshStandardMaterial({ color: 0x2c3e50, roughness: 0.9 })
        );
        floor.rotation.x = -Math.PI / 2;
        scene.add(floor);

        const grid = new THREE.GridHelper(60, 60, 0x4a6fa5, 0x2c3e50);
        grid.position.y = 0.02;
        scene.add(grid);

        // ===== الجدران =====
        const wallMat = new THREE.MeshStandardMaterial({ 
            color: 0x34495e, transparent: true, opacity: 0.5, side: THREE.DoubleSide 
        });
        const wallHeight = 4;
        const wallSize = 40;
        
        const wallPositions = [
            { pos: [0, wallHeight/2, -wallSize/2], rot: [0, 0, 0] },
            { pos: [0, wallHeight/2, wallSize/2], rot: [0, 0, 0] },
            { pos: [-wallSize/2, wallHeight/2, 0], rot: [0, Math.PI/2, 0] },
            { pos: [wallSize/2, wallHeight/2, 0], rot: [0, Math.PI/2, 0] }
        ];
        
        wallPositions.forEach(w => {
            const wall = new THREE.Mesh(new THREE.PlaneGeometry(wallSize, wallHeight), wallMat);
            wall.position.set(w.pos[0], w.pos[1], w.pos[2]);
            wall.rotation.set(w.rot[0], w.rot[1], w.rot[2]);
            scene.add(wall);
        });

        // ===== اللاعب =====
        const player = {
            position: new THREE.Vector3(0, 1.6, 0),
            yaw: 0,
            pitch: 0,
            health: 100
        };

        // ===== السلاح =====
        const weaponGroup = new THREE.Group();
        
        const weaponBody = new THREE.Mesh(
            new THREE.BoxGeometry(0.2, 0.2, 0.7),
            new THREE.MeshStandardMaterial({ color: 0x1a1a1a, metalness: 0.8, roughness: 0.3 })
        );
        weaponBody.position.set(0.35, -0.3, -0.7);
        weaponGroup.add(weaponBody);
        
        const weaponBarrel = new THREE.Mesh(
            new THREE.CylinderGeometry(0.06, 0.06, 0.5, 12),
            new THREE.MeshStandardMaterial({ color: 0x333333, metalness: 0.9, roughness: 0.2 })
        );
        weaponBarrel.rotation.x = Math.PI / 2;
        weaponBarrel.position.set(0.35, -0.3, -1.2);
        weaponGroup.add(weaponBarrel);
        
        const weaponGrip = new THREE.Mesh(
            new THREE.BoxGeometry(0.15, 0.3, 0.15),
            new THREE.MeshStandardMaterial({ color: 0x0a0a0a })
        );
        weaponGrip.position.set(0.35, -0.5, -0.5);
        weaponGroup.add(weaponGrip);
        
        camera.add(weaponGroup);

        // ===== الأعداء =====
        const enemies = [];
        const enemyColors = [0xff1744, 0xff6d00, 0xd500f9, 0x00e5ff];
        const MAX_ENEMIES = 8;
        const FIELD_LIMIT = 18;

        function createEnemy() {
            const color = enemyColors[Math.floor(Math.random() * enemyColors.length)];
            const enemy = new THREE.Group();
            
            // الجسم (أسطوانة قاعدتها على الأرض)
            const body = new THREE.Mesh(
                new THREE.CylinderGeometry(0.4, 0.4, 1.6, 8),
                new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.3 })
            );
            body.position.y = 0.8; // نصف الارتفاع → القاعدة على الأرض
            enemy.add(body);
            
            // الرأس
            const head = new THREE.Mesh(
                new THREE.SphereGeometry(0.35, 12, 12),
                new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: color, emissiveIntensity: 0.5 })
            );
            head.position.y = 1.95; // فوق الجسم مباشرة
            enemy.add(head);
            
            // العيون (في الجهة الأمامية +Z)
            const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
            const eyeL = new THREE.Mesh(new THREE.SphereGeometry(0.06, 6, 6), eyeMat);
            eyeL.position.set(-0.12, 2.0, 0.3);
            enemy.add(eyeL);
            
            const eyeR = new THREE.Mesh(new THREE.SphereGeometry(0.06, 6, 6), eyeMat);
            eyeR.position.set(0.12, 2.0, 0.3);
            enemy.add(eyeR);
            
            // موقع عشوائي بعيد عن اللاعب
            let x, z, attempts = 0;
            do {
                x = (Math.random() - 0.5) * (FIELD_LIMIT * 2 - 4);
                z = (Math.random() - 0.5) * (FIELD_LIMIT * 2 - 4);
                attempts++;
            } while (Math.sqrt((x - player.position.x)**2 + (z - player.position.z)**2) < 8 && attempts < 30);
            
            enemy.position.set(x, 0, z);
            enemy.userData = {
                color: color,
                speed: 0.02 + Math.random() * 0.02,
                lastShot: 0,
                shootCooldown: 1500 + Math.random() * 1500
            };
            
            scene.add(enemy);
            enemies.push(enemy);
        }

        function refillEnemies() {
            while (enemies.length < MAX_ENEMIES) {
                createEnemy();
            }
        }

        // ===== متغيرات اللعبة =====
        let score = 0;
        let kills = 0;
        let gameActive = false;
        let timeLeft = 60;
        const moveSpeed = 0.12;
        
        const MAX_AMMO = 30;
        let ammo = MAX_AMMO;
        let isReloading = false;

        // ===== إدخال الحركة =====
        const moveInput = { x: 0, y: 0 };
        const keys = {};
        window.addEventListener('keydown', e => keys[e.key.toLowerCase()] = true);
        window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);

        // ===== عصا التحكم (تُلتقط أحداثها قبل lookArea بفضل z-index) =====
        const joystick = document.getElementById('joystick');
        const stick = document.getElementById('stick');
        let joystickActive = false;
        let joystickCenter = { x: 0, y: 0 };
        const maxDist = 45;

        function getJoystickCenter() {
            const rect = joystick.getBoundingClientRect();
            return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
        }

        function handleJoystickStart(e) {
            e.preventDefault();
            e.stopPropagation();
            joystickActive = true;
            joystickCenter = getJoystickCenter();
            handleJoystickMove(e);
        }

        function handleJoystickMove(e) {
            if (!joystickActive) return;
            e.preventDefault();
            e.stopPropagation();
            
            const touch = e.touches ? e.touches[0] : e;
            let dx = touch.clientX - joystickCenter.x;
            let dy = touch.clientY - joystickCenter.y;
            
            const dist = Math.sqrt(dx*dx + dy*dy);
            const clampedDist = Math.min(dist, maxDist);
            const angle = Math.atan2(dy, dx);
            
            stick.style.transform = `translate(${Math.cos(angle) * clampedDist}px, ${Math.sin(angle) * clampedDist}px)`;
            
            moveInput.x = dx / maxDist;
            moveInput.y = dy / maxDist;
            
            const mag = Math.sqrt(moveInput.x**2 + moveInput.y**2);
            if (mag > 1) {
                moveInput.x /= mag;
                moveInput.y /= mag;
            }
        }

        function handleJoystickEnd(e) {
            e.preventDefault();
            e.stopPropagation();
            joystickActive = false;
            stick.style.transform = 'translate(0, 0)';
            moveInput.x = 0;
            moveInput.y = 0;
        }

        joystick.addEventListener('touchstart', handleJoystickStart, { passive: false });
        joystick.addEventListener('touchmove', handleJoystickMove, { passive: false });
        joystick.addEventListener('touchend', handleJoystickEnd, { passive: false });
        joystick.addEventListener('touchcancel', handleJoystickEnd, { passive: false });
        joystick.addEventListener('mousedown', handleJoystickStart);
        joystick.addEventListener('mousemove', handleJoystickMove);
        joystick.addEventListener('mouseup', handleJoystickEnd);

        // ===== منطقة النظر - تغطي كل الشاشة (ما عدا الأزرار) =====
        const lookArea = document.getElementById('lookArea');
        let lookingActive = false;
        let lastLookX = 0, lastLookY = 0;
        // ✅ حساسية منخفضة (كانت 0.005 - الآن 0.0025)
        const lookSensitivity = 0.0025;

        function handleLookStart(e) {
            // تجاهل اللمس إذا كان على الأزرار (احتياطي)
            if (e.target.closest('#joystick') || 
                e.target.closest('#fireBtn') || 
                e.target.closest('#reloadBtn')) return;
            
            e.preventDefault();
            lookingActive = true;
            const touch = e.touches ? e.touches[0] : e;
            lastLookX = touch.clientX;
            lastLookY = touch.clientY;
            
            const hint = document.getElementById('hint');
            if (hint) hint.style.opacity = '0';
        }

        function handleLookMove(e) {
            if (!lookingActive) return;
            e.preventDefault();
            
            const touch = e.touches ? e.touches[0] : e;
            const dx = touch.clientX - lastLookX;
            const dy = touch.clientY - lastLookY;
            
            player.yaw -= dx * lookSensitivity;
            player.pitch -= dy * lookSensitivity;
            player.pitch = Math.max(-Math.PI/2 + 0.1, Math.min(Math.PI/2 - 0.1, player.pitch));
            
            lastLookX = touch.clientX;
            lastLookY = touch.clientY;
        }

        function handleLookEnd(e) {
            e.preventDefault();
            lookingActive = false;
        }

        lookArea.addEventListener('touchstart', handleLookStart, { passive: false });
        lookArea.addEventListener('touchmove', handleLookMove, { passive: false });
        lookArea.addEventListener('touchend', handleLookEnd, { passive: false });
        lookArea.addEventListener('touchcancel', handleLookEnd, { passive: false });
        
        // ماوس للكمبيوتر
        lookArea.addEventListener('mousedown', handleLookStart);
        window.addEventListener('mousemove', handleLookMove);
        window.addEventListener('mouseup', handleLookEnd);

        // ===== إطلاق النار =====
        const fireBtn = document.getElementById('fireBtn');
        const reloadBtn = document.getElementById('reloadBtn');
        const muzzleFlash = document.getElementById('muzzleFlash');
        
        function shoot() {
            if (!gameActive || isReloading) return;
            if (ammo <= 0) {
                reload();
                return;
            }
            
            ammo--;
            updateAmmoDisplay();
            
            muzzleFlash.style.opacity = '1';
            setTimeout(() => muzzleFlash.style.opacity = '0', 60);
            
            weaponGroup.position.z = 0.1;
            setTimeout(() => weaponGroup.position.z = 0, 60);
            
            const raycaster = new THREE.Raycaster();
            const screenCenter = new THREE.Vector2(0, 0);
            raycaster.setFromCamera(screenCenter, camera);
            
            let closestEnemy = null;
            let closestDist = Infinity;
            
            enemies.forEach(enemy => {
                // مركز الهدف: منتصف الجسم (y=1)
                const enemyCenter = new THREE.Vector3(enemy.position.x, 1, enemy.position.z);
                const ray = raycaster.ray;
                
                const toEnemy = new THREE.Vector3().subVectors(enemyCenter, ray.origin);
                const proj = toEnemy.dot(ray.direction);
                
                if (proj > 0 && proj < 50) {
                    const closest = ray.origin.clone().add(ray.direction.clone().multiplyScalar(proj));
                    const distToRay = closest.distanceTo(enemyCenter);
                    
                    const hitRadius = 0.9 + proj * 0.02;
                    
                    if (distToRay < hitRadius && proj < closestDist) {
                        closestDist = proj;
                        closestEnemy = enemy;
                    }
                }
            });
            
            if (closestEnemy) {
                killEnemy(closestEnemy);
            }
        }
        
        function killEnemy(enemy) {
            const idx = enemies.indexOf(enemy);
            if (idx === -1) return;
            
            enemies.splice(idx, 1);
            scene.remove(enemy);
            
            score += 10;
            kills++;
            updateUI();
            
            setTimeout(() => {
                if (gameActive) refillEnemies();
            }, 500);
        }
        
        function reload() {
            if (isReloading || ammo === MAX_AMMO) return;
            isReloading = true;
            updateAmmoDisplay();
            
            setTimeout(() => {
                ammo = MAX_AMMO;
                isReloading = false;
                updateAmmoDisplay();
            }, 1000);
        }
        
        function updateAmmoDisplay() {
            const el = document.getElementById('ammo');
            if (isReloading) {
                el.textContent = '... جاري التلقيم';
                el.style.color = '#ffeb3b';
            } else {
                el.textContent = ammo + ' / ' + MAX_AMMO;
                el.style.color = ammo <= 5 ? '#ff5252' : 'white';
            }
        }
        
        // أزرار الإطلاق - pointerdown يعمل على الجوال والكمبيوتر
        fireBtn.addEventListener('pointerdown', (e) => { 
            e.preventDefault(); 
            e.stopPropagation();
            shoot(); 
        });
        reloadBtn.addEventListener('pointerdown', (e) => { 
            e.preventDefault(); 
            e.stopPropagation();
            reload(); 
        });

        // ===== بدء اللعبة =====
        function startGame() {
            score = 0;
            kills = 0;
            timeLeft = 60;
            ammo = MAX_AMMO;
            isReloading = false;
            player.health = 100;
            gameActive = true;
            
            enemies.forEach(e => scene.remove(e));
            enemies.length = 0;
            
            player.position.set(0, 1.6, 0);
            player.yaw = 0;
            player.pitch = 0;
            
            refillEnemies();
            updateUI();
            updateAmmoDisplay();
            
            document.getElementById('startScreen').style.display = 'none';
            document.getElementById('gameOver').style.display = 'none';
            
            const hint = document.getElementById('hint');
            hint.style.opacity = '1';
            setTimeout(() => hint.style.opacity = '0', 4000);
            
            lastTime = performance.now();
            animate();
        }

        function updateUI() {
            document.getElementById('score').textContent = 'النقاط: ' + score;
            document.getElementById('timer').textContent = '⏱ ' + Math.max(0, Math.ceil(timeLeft));
            document.getElementById('health').textContent = '❤️ ' + Math.max(0, player.health);
        }

        // ===== حلقة اللعبة =====
        let lastTime = performance.now();
        let bobTime = 0;

        function animate() {
            if (!gameActive) return;
            requestAnimationFrame(animate);

            const now = performance.now();
            const delta = Math.min((now - lastTime) / 1000, 0.1);
            lastTime = now;

            // كيبورد للكمبيوتر
            if (!joystickActive) {
                let x = 0, y = 0;
                if (keys['a']) x -= 1;
                if (keys['d']) x += 1;
                if (keys['w']) y -= 1;
                if (keys['s']) y += 1;
                moveInput.x = x;
                moveInput.y = y;
                
                if (keys['q']) player.yaw += 2 * delta;
                if (keys['e']) player.yaw -= 2 * delta;
            }

            // ===== حركة اللاعب =====
            const forward = new THREE.Vector3(-Math.sin(player.yaw), 0, -Math.cos(player.yaw));
            const right = new THREE.Vector3(Math.cos(player.yaw), 0, -Math.sin(player.yaw));

            const movement = new THREE.Vector3();
            movement.addScaledVector(forward, -moveInput.y);
            movement.addScaledVector(right, moveInput.x);

            if (movement.length() > 1) movement.normalize();

            player.position.x += movement.x * moveSpeed * 60 * delta;
            player.position.z += movement.z * moveSpeed * 60 * delta;

            player.position.x = Math.max(-FIELD_LIMIT, Math.min(FIELD_LIMIT, player.position.x));
            player.position.z = Math.max(-FIELD_LIMIT, Math.min(FIELD_LIMIT, player.position.z));

            // اهتزاز خفيف
            if (movement.length() > 0.1) {
                bobTime += delta * 10;
            } else {
                bobTime += delta * 3;
            }
            const bobAmount = movement.length() * 0.05;
            const bobY = Math.sin(bobTime) * bobAmount;

            // ===== تحديث الكاميرا =====
            camera.position.x = player.position.x;
            camera.position.y = player.position.y + bobY;
            camera.position.z = player.position.z;

            const lookDir = new THREE.Vector3(
                -Math.sin(player.yaw) * Math.cos(player.pitch),
                Math.sin(player.pitch),
                -Math.cos(player.yaw) * Math.cos(player.pitch)
            );
            camera.lookAt(
                camera.position.x + lookDir.x,
                camera.position.y + lookDir.y,
                camera.position.z + lookDir.z
            );

            // ===== حركة الأعداء (بدون طفو وبدون ميلان) =====
            enemies.forEach(enemy => {
                const ud = enemy.userData;
                
                // ✅ دوران حول المحور Y فقط (لا ميلان)
                const dx = player.position.x - enemy.position.x;
                const dz = player.position.z - enemy.position.z;
                enemy.rotation.y = Math.atan2(dx, dz);
                
                const distToPlayer = Math.sqrt(dx*dx + dz*dz);
                
                // ✅ الاقتراب من اللاعب (على الأرض، بدون طفو)
                if (distToPlayer > 3) {
                    const invDist = 1 / distToPlayer;
                    enemy.position.x += dx * invDist * ud.speed;
                    enemy.position.z += dz * invDist * ud.speed;
                }
                
                // هجوم اللاعب
                if (distToPlayer < 3 && now - ud.lastShot > ud.shootCooldown) {
                    ud.lastShot = now;
                    player.health -= 10;
                    
                    const hitFlash = document.getElementById('hitFlash');
                    hitFlash.style.opacity = '1';
                    setTimeout(() => hitFlash.style.opacity = '0', 200);
                    
                    if (player.health <= 0) {
                        player.health = 0;
                        updateUI();
                        endGame();
                        return;
                    }
                    updateUI();
                }
            });

            if (!gameActive) return;

            // إعادة ملء الأعداء
            if (enemies.length < MAX_ENEMIES) {
                refillEnemies();
            }

            // المؤقت
            timeLeft -= delta;
            updateUI();
            
            if (timeLeft <= 0) {
                endGame();
                return;
            }

            renderer.render(scene, camera);
        }

        function endGame() {
            gameActive = false;
            document.getElementById('finalScore').textContent = score;
            document.getElementById('killsText').textContent = 'الأعداء المقتولون: ' + kills;
            document.getElementById('gameOver').style.display = 'flex';
        }

        // ===== ربط أزرار البدء =====
        const startBtn = document.getElementById('startBtn');
        const restartBtn = document.getElementById('restartBtn');
        
        startBtn.addEventListener('click', startGame);
        restartBtn.addEventListener('click', startGame);
        
        startBtn.addEventListener('touchend', (e) => {
            e.preventDefault();
            startGame();
        }, { passive: false });
        restartBtn.addEventListener('touchend', (e) => {
            e.preventDefault();
            startGame();
        }, { passive: false });

        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        // منع التمرير الافتراضي
        document.addEventListener('touchmove', (e) => {
            if (e.target.closest('#joystick') || 
                e.target.closest('#lookArea') || 
                e.target.closest('#fireBtn') || 
                e.target.closest('#reloadBtn')) return;
            e.preventDefault();
        }, { passive: false });

        console.log('✅ اللعبة جاهزة - اضغط ابدأ اللعب');
    </script>
</body>
</html>

Game Source: لعبة الشوتر FPS - الجوال

Creator: FrozenRider22

Libraries: three

Complexity: complex (910 lines, 32.4 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: fps-frozenrider22" to link back to the original. Then publish at arcadelab.ai/publish.