🎮ArcadeLab

幽灵行者 网页复刻Demo

by BoldStar37
818 lines26.8 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>幽灵行者 网页复刻Demo</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            overflow: hidden;
            background: #101020;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        #game-container {
            width: 100vw;
            height: 100vh;
            position: relative;
        }
        #ui {
            position: absolute;
            bottom: 20px;
            left: 20px;
            z-index: 100;
            color: #fff;
            pointer-events: none;
        }
        .focus-bar-container {
            width: 180px;
            height: 180px;
            position: relative;
        }
        .focus-bar-bg {
            width: 100%;
            height: 100%;
            fill: none;
            stroke: #2a2a4e;
            stroke-width: 8;
        }
        .focus-bar {
            width: 100%;
            height: 100%;
            fill: none;
            stroke: #00d4ff;
            stroke-width: 8;
            stroke-linecap: round;
            transform: rotate(-90deg);
            transform-origin: center;
            transition: stroke-dashoffset 0.3s ease;
        }
        .focus-text {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            font-size: 14px;
            color: #00d4ff;
            text-align: center;
        }
        .dash-cooldown {
            position: absolute;
            bottom: 0;
            left: 200px;
            width: 20px;
            height: 100px;
            background: #2a2a4e;
            border-radius: 4px;
            overflow: hidden;
        }
        .dash-fill {
            width: 100%;
            height: 100%;
            background: #ff2a6d;
            transition: height 0.1s ease;
            transform-origin: bottom;
        }
        #controls-hint {
            position: absolute;
            top: 20px;
            right: 20px;
            color: rgba(255,255,255,0.85);
            font-size: 12px;
            line-height: 1.8;
            text-align: right;
            pointer-events: none;
        }
        #death-screen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(15,15,30,0.85);
            display: none;
            z-index: 200;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            color: #ff2a6d;
            font-size: 32px;
        }
        #death-screen p {
            font-size: 18px;
            color: #fff;
            margin-top: 20px;
        }
        #start-screen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(135deg, #101020 0%, #252545 100%);
            display: flex;
            z-index: 300;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            color: #fff;
        }
        #start-screen h1 {
            font-size: 64px;
            background: linear-gradient(90deg, #00d4ff, #ff2a6d);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            margin-bottom: 20px;
        }
        #start-screen p {
            font-size: 18px;
            color: rgba(255,255,255,0.85);
            margin-bottom: 40px;
        }
        #start-btn {
            padding: 15px 40px;
            font-size: 18px;
            background: linear-gradient(90deg, #00d4ff, #0099cc);
            border: none;
            border-radius: 8px;
            color: #fff;
            cursor: pointer;
            transition: transform 0.2s ease;
        }
        #start-btn:hover {
            transform: scale(1.05);
        }
        .neon-text {
            text-shadow: 0 0 10px #00d4ff, 0 0 20px #00d4ff, 0 0 30px #00d4ff;
        }
    </style>
</head>
<body>
    <div id="game-container">
        <div id="start-screen">
            <h1>GHOSTRUNNER</h1>
            <p>赛博朋克高速跑酷动作游戏 网页复刻Demo</p>
            <button id="start-btn">点击开始游戏</button>
        </div>
        <div id="death-screen">
            <h2>你已死亡</h2>
            <p>按 R 键从检查点重生</p>
        </div>
        <div id="ui">
            <div class="focus-bar-container">
                <svg viewBox="0 0 100 100">
                    <polygon class="focus-bar-bg" points="50,0 93.3,25 93.3,75 50,100 6.7,75 6.7,25" />
                    <polygon class="focus-bar" points="50,0 93.3,25 93.3,75 50,100 6.7,75 6.7,25" stroke-dasharray="300" stroke-dashoffset="0" />
                </svg>
                <div class="focus-text">专注力</div>
                <div class="dash-cooldown">
                    <div class="dash-fill"></div>
                </div>
            </div>
        </div>
        <div id="controls-hint">
            WASD - 移动<br>
            空格 - 跳跃<br>
            Ctrl - 滑铲<br>
            Shift - 冲刺/子弹时间<br>
            鼠标左键 - 攻击<br>
            R - 重生
        </div>
    </div>

    <script type="importmap">
    {
        "imports": {
            "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
            "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
        }
    }
    </script>

    <script type="module">
        import * as THREE from 'three';
        import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';

        // 基础设置
        let scene, camera, renderer, controls;
        let playerVelocity = new THREE.Vector3();
        let playerPosition = new THREE.Vector3(0, 2, 0);
        let canJump = false;
        let isSliding = false;
        let slideTimer = 0;
        let isDashing = false;
        let dashCooldown = 0;
        let isSlowMotion = false;
        let focus = 100;
        let maxFocus = 100;
        let isDead = false;
        let spawnPoint = new THREE.Vector3(0, 2, 0);
        let enemies = [];
        let bullets = [];
        let clock = new THREE.Clock();
        let timeScale = 1;

        // 输入状态
        const keys = {
            forward: false,
            backward: false,
            left: false,
            right: false,
            jump: false,
            slide: false,
            dash: false,
            attack: false
        };

        // 初始化
        async function init() {
            // 场景 整体提亮
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x121228);
            scene.fog = new THREE.Fog(0x121228, 15, 100);

            // 相机
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.copy(playerPosition);

            // 渲染器
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            renderer.setPixelRatio(window.devicePixelRatio);
            document.getElementById('game-container').appendChild(renderer.domElement);

            // 控制器
            controls = new PointerLockControls(camera, document.body);

            // 环境光 大幅提亮
            const ambientLight = new THREE.AmbientLight(0x606090, 0.8);
            scene.add(ambientLight);

            // 主方向光 增强亮度
            const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
            dirLight.position.set(10, 20, 10);
            dirLight.castShadow = true;
            dirLight.shadow.mapSize.width = 2048;
            dirLight.shadow.mapSize.height = 2048;
            scene.add(dirLight);

            // 霓虹灯光 增强强度
            const neonBlue = new THREE.PointLight(0x00d4ff, 3.5, 25);
            neonBlue.position.set(-10, 5, 0);
            scene.add(neonBlue);

            const neonPink = new THREE.PointLight(0xff2a6d, 3.5, 25);
            neonPink.position.set(10, 5, 0);
            scene.add(neonPink);

            const neonPurple = new THREE.PointLight(0x9d4edd, 3.5, 25);
            neonPurple.position.set(0, 5, 20);
            scene.add(neonPurple);

            // 创建场景
            createLevel();

            // 创建玩家武器
            createWeapon();

            // 创建敌人
            createEnemies();

            // 事件监听
            setupEventListeners();

            // 窗口resize
            window.addEventListener('resize', onWindowResize);

            // 开始动画
            animate();
        }

        function createLevel() {
            // 地面 提亮材质
            const groundGeo = new THREE.PlaneGeometry(100, 100);
            const groundMat = new THREE.MeshStandardMaterial({ 
                color: 0x222244,
                metalness: 0.8,
                roughness: 0.2
            });
            const ground = new THREE.Mesh(groundGeo, groundMat);
            ground.rotation.x = -Math.PI / 2;
            ground.receiveShadow = true;
            scene.add(ground);

            // 墙面 提亮材质
            const wallMat = new THREE.MeshStandardMaterial({
                color: 0x203058,
                metalness: 0.5,
                roughness: 0.5
            });

            // 左墙
            const leftWall = new THREE.Mesh(new THREE.BoxGeometry(1, 10, 100), wallMat);
            leftWall.position.set(-15, 5, 0);
            leftWall.receiveShadow = true;
            scene.add(leftWall);

            // 右墙
            const rightWall = new THREE.Mesh(new THREE.BoxGeometry(1, 10, 100), wallMat);
            rightWall.position.set(15, 5, 0);
            rightWall.receiveShadow = true;
            scene.add(rightWall);

            // 平台1
            const platform1 = new THREE.Mesh(new THREE.BoxGeometry(8, 1, 8), wallMat);
            platform1.position.set(0, 4, 10);
            platform1.castShadow = true;
            platform1.receiveShadow = true;
            scene.add(platform1);

            // 平台2
            const platform2 = new THREE.Mesh(new THREE.BoxGeometry(8, 1, 8), wallMat);
            platform2.position.set(-8, 6, 20);
            platform2.castShadow = true;
            platform2.receiveShadow = true;
            scene.add(platform2);

            // 平台3
            const platform3 = new THREE.Mesh(new THREE.BoxGeometry(8, 1, 8), wallMat);
            platform3.position.set(8, 8, 30);
            platform3.castShadow = true;
            platform3.receiveShadow = true;
            scene.add(platform3);

            // 霓虹牌
            const neonSignGeo = new THREE.PlaneGeometry(4, 2);
            const neonSignMat = new THREE.MeshStandardMaterial({
                color: 0x00d4ff,
                emissive: 0x00d4ff,
                emissiveIntensity: 2.5,
                metalness: 1,
                roughness: 0
            });
            const neonSign = new THREE.Mesh(neonSignGeo, neonSignMat);
            neonSign.position.set(-14, 7, 10);
            neonSign.rotation.y = Math.PI / 2;
            scene.add(neonSign);

            const neonSign2 = new THREE.Mesh(neonSignGeo, new THREE.MeshStandardMaterial({
                color: 0xff2a6d,
                emissive: 0xff2a6d,
                emissiveIntensity: 2.5,
                metalness: 1,
                roughness: 0
            }));
            neonSign2.position.set(14, 7, 20);
            neonSign2.rotation.y = -Math.PI / 2;
            scene.add(neonSign2);
        }

        function createWeapon() {
            // 刀
            const knifeGeo = new THREE.BoxGeometry(0.1, 0.05, 1.5);
            const knifeMat = new THREE.MeshStandardMaterial({
                color: 0x00d4ff,
                emissive: 0x00d4ff,
                emissiveIntensity: 0.8,
                metalness: 1,
                roughness: 0
            });
            const knife = new THREE.Mesh(knifeGeo, knifeMat);
            knife.position.set(0.3, -0.2, -0.5);
            knife.rotation.x = -0.2;
            camera.add(knife);
        }

        function createEnemies() {
            // 手枪兵
            const enemyGeo = new THREE.CapsuleGeometry(0.3, 1.4, 4, 8);
            const enemyMat = new THREE.MeshStandardMaterial({
                color: 0xe94560,
                metalness: 0.5,
                roughness: 0.5
            });

            // 敌人1
            const enemy1 = new THREE.Mesh(enemyGeo, enemyMat);
            enemy1.position.set(0, 1.7, 15);
            enemy1.castShadow = true;
            scene.add(enemy1);
            enemies.push({
                mesh: enemy1,
                health: 1,
                shootCooldown: 0,
                spawnPoint: enemy1.position.clone()
            });

            // 敌人2
            const enemy2 = new THREE.Mesh(enemyGeo, enemyMat);
            enemy2.position.set(-8, 2.7, 25);
            enemy2.castShadow = true;
            scene.add(enemy2);
            enemies.push({
                mesh: enemy2,
                health: 1,
                shootCooldown: 0,
                spawnPoint: enemy2.position.clone()
            });
        }

        function setupEventListeners() {
            // 键盘事件
            document.addEventListener('keydown', onKeyDown);
            document.addEventListener('keyup', onKeyUp);

            // 鼠标事件
            document.addEventListener('mousedown', onMouseDown);
            document.addEventListener('mouseup', onMouseUp);

            // 开始按钮
            document.getElementById('start-btn').addEventListener('click', () => {
                document.getElementById('start-screen').style.display = 'none';
                controls.lock();
            });

            // 控制锁定事件
            controls.addEventListener('lock', () => {
                // 锁定成功
            });

            controls.addEventListener('unlock', () => {
                if(!isDead) {
                    // 可以加暂停?
                }
            });
        }

        function onKeyDown(event) {
            if(isDead) {
                if(event.code === 'KeyR') {
                    respawn();
                }
                return;
            }

            switch(event.code) {
                case 'KeyW': keys.forward = true; break;
                case 'KeyS': keys.backward = true; break;
                case 'KeyA': keys.left = true; break;
                case 'KeyD': keys.right = true; break;
                case 'Space': keys.jump = true; break;
                case 'ControlLeft': 
                    if(!isSliding && canJump) { // 只有在地面才能滑铲
                        isSliding = true;
                        slideTimer = 0.8;
                    }
                    keys.slide = true; 
                    break;
                case 'ShiftLeft': keys.dash = true; break;
                case 'KeyR': 
                    if(isDead) respawn();
                    break;
            }
        }

        function onKeyUp(event) {
            switch(event.code) {
                case 'KeyW': keys.forward = false; break;
                case 'KeyS': keys.backward = false; break;
                case 'KeyA': keys.left = false; break;
                case 'KeyD': keys.right = false; break;
                case 'Space': keys.jump = false; break;
                case 'ControlLeft': keys.slide = false; break;
                case 'ShiftLeft': 
                    keys.dash = false;
                    if(isSlowMotion) {
                        isSlowMotion = false;
                        timeScale = 1;
                    }
                    break;
            }
        }

        function onMouseDown(event) {
            if(event.button === 0) { // 左键
                keys.attack = true;
                attack();
            }
        }

        function onMouseUp(event) {
            if(event.button === 0) {
                keys.attack = false;
            }
        }

        function attack() {
            // 检测前方敌人
            const direction = new THREE.Vector3();
            camera.getWorldDirection(direction);
            
            enemies.forEach((enemy, index) => {
                const distance = camera.position.distanceTo(enemy.mesh.position);
                if(distance < 2) { // 攻击范围
                    // 击杀敌人
                    scene.remove(enemy.mesh);
                    enemies.splice(index, 1);
                    
                    // 击杀特效:镜头震动
                    shakeCamera(0.1, 0.1);
                    
                    // 溅血效果
                    createBloodSplash(enemy.mesh.position);
                }
            });
        }

        function shakeCamera(intensity, duration) {
            const startPos = camera.position.clone();
            const startTime = clock.getElapsedTime();
            
            function shake() {
                const elapsed = clock.getElapsedTime() - startTime;
                if(elapsed < duration) {
                    camera.position.x = startPos.x + (Math.random() - 0.5) * intensity;
                    camera.position.y = startPos.y + (Math.random() - 0.5) * intensity;
                    requestAnimationFrame(shake);
                } else {
                    camera.position.copy(startPos);
                }
            }
            shake();
        }

        function createBloodSplash(position) {
            const bloodGeo = new THREE.SphereGeometry(0.3, 8, 8);
            const bloodMat = new THREE.MeshBasicMaterial({ color: 0xff2a6d });
            for(let i=0; i<10; i++) {
                const particle = new THREE.Mesh(bloodGeo, bloodMat);
                particle.position.copy(position);
                particle.position.x += (Math.random() - 0.5) * 1;
                particle.position.y += Math.random() * 1;
                particle.position.z += (Math.random() - 0.5) * 1;
                particle.velocity = new THREE.Vector3(
                    (Math.random() - 0.5) * 2,
                    Math.random() * 3,
                    (Math.random() - 0.5) * 2
                );
                scene.add(particle);
                
                // 粒子消失
                setTimeout(() => {
                    scene.remove(particle);
                }, 500);
            }
        }

        function respawn() {
            playerPosition.copy(spawnPoint);
            camera.position.copy(spawnPoint);
            playerVelocity.set(0,0,0);
            isDead = false;
            isSliding = false;
            isDashing = false;
            isSlowMotion = false;
            timeScale = 1;
            focus = maxFocus;
            dashCooldown = 0;
            
            // 重置敌人和子弹
            bullets.forEach(bullet => scene.remove(bullet.mesh));
            bullets = [];
            
            // 如果敌人被杀死了,重新生成
            if(enemies.length < 2) {
                createEnemies();
            }
            
            document.getElementById('death-screen').style.display = 'none';
            controls.lock();
        }

        function die() {
            isDead = true;
            timeScale = 1;
            document.getElementById('death-screen').style.display = 'flex';
            controls.unlock();
        }

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

        function updatePlayer(delta) {
            if(isDead) return;

            delta *= timeScale;

            // 速度
            let speed = 8;
            let jumpForce = 10;
            let gravity = -25;

            // 滑铲
            if(isSliding) {
                slideTimer -= delta;
                speed = 15; // 滑铲加速
                camera.position.y = playerPosition.y - 0.8; // 降低高度
                
                if(slideTimer <= 0) {
                    isSliding = false;
                }
            }

            // 冲刺
            if(keys.dash && dashCooldown <= 0) {
                if(canJump) { // 地面冲刺
                    isDashing = true;
                    speed = 20;
                    dashCooldown = 1;
                    setTimeout(() => { isDashing = false; }, 0.2);
                } else if(focus > 20) { // 空中子弹时间
                    isSlowMotion = true;
                    timeScale = 0.2;
                    focus -= delta * 50;
                }
            }

            // 冲刺冷却
            if(dashCooldown > 0) {
                dashCooldown -= delta;
            }

            // 专注力恢复
            if(!isSlowMotion && focus < maxFocus) {
                focus += delta * 10;
                if(focus > maxFocus) focus = maxFocus;
            }

            // 更新UI
            updateUI();

            // 移动方向
            const moveDirection = new THREE.Vector3();
            if(keys.forward) moveDirection.z -= 1;
            if(keys.backward) moveDirection.z += 1;
            if(keys.left) moveDirection.x -= 1;
            if(keys.right) moveDirection.x += 1;

            moveDirection.normalize();
            moveDirection.applyQuaternion(camera.quaternion);
            moveDirection.y = 0;

            // 应用速度
            playerVelocity.x = moveDirection.x * speed;
            playerVelocity.z = moveDirection.z * speed;

            // 重力
            playerVelocity.y += gravity * delta;

            // 跳跃
            if(keys.jump && canJump) {
                playerVelocity.y = jumpForce;
                canJump = false;
            }

            // 更新位置
            playerPosition.x += playerVelocity.x * delta;
            playerPosition.y += playerVelocity.y * delta;
            playerPosition.z += playerVelocity.z * delta;

            // 地面碰撞
            if(playerPosition.y < 2) {
                playerPosition.y = 2;
                playerVelocity.y = 0;
                canJump = true;
            }

            // 墙碰撞
            if(playerPosition.x < -14) playerPosition.x = -14;
            if(playerPosition.x > 14) playerPosition.x = 14;

            // 掉落死亡
            if(playerPosition.y < -10) {
                die();
            }

            // 更新相机位置
            if(!isSliding) {
                camera.position.y = playerPosition.y;
            }
            camera.position.x = playerPosition.x;
            camera.position.z = playerPosition.z;
        }

        function updateEnemies(delta) {
            if(isDead) return;

            delta *= timeScale;

            enemies.forEach(enemy => {
                const distance = camera.position.distanceTo(enemy.mesh.position);
                
                // 看向玩家
                enemy.mesh.lookAt(camera.position);

                // 射击
                if(distance < 30 && enemy.shootCooldown <= 0) {
                    shoot(enemy);
                    enemy.shootCooldown = 1.5;
                }

                if(enemy.shootCooldown > 0) {
                    enemy.shootCooldown -= delta;
                }
            });
        }

        function shoot(enemy) {
            // 创建子弹
            const bulletGeo = new THREE.SphereGeometry(0.1, 8, 8);
            const bulletMat = new THREE.MeshBasicMaterial({ color: 0xff2a6d });
            const bullet = new THREE.Mesh(bulletGeo, bulletMat);
            
            bullet.position.copy(enemy.mesh.position);
            
            // 子弹方向
            const direction = new THREE.Vector3();
            direction.subVectors(camera.position, enemy.mesh.position).normalize();
            
            scene.add(bullet);
            bullets.push({
                mesh: bullet,
                velocity: direction.multiplyScalar(20)
            });
        }

        function updateBullets(delta) {
            if(isDead) return;

            delta *= timeScale;

            for(let i = bullets.length - 1; i >= 0; i--) {
                const bullet = bullets[i];
                
                // 更新子弹位置
                bullet.mesh.position.x += bullet.velocity.x * delta;
                bullet.mesh.position.y += bullet.velocity.y * delta;
                bullet.mesh.position.z += bullet.velocity.z * delta;

                // 检测是否击中玩家
                const distance = bullet.mesh.position.distanceTo(camera.position);
                if(distance < 0.5) {
                    // 玩家死亡
                    die();
                    scene.remove(bullet.mesh);
                    bullets.splice(i, 1);
                    continue;
                }

                // 超出范围移除
                if(bullet.mesh.position.z > 100 || bullet.mesh.position.z < -100) {
                    scene.remove(bullet.mesh);
                    bullets.splice(i, 1);
                }
            }
        }

        function updateUI() {
            // 更新专注力槽
            const focusPercent = focus / maxFocus;
            const dashPercent = Math.max(0, 1 - dashCooldown);
            
            const focusBar = document.querySelector('.focus-bar');
            const dashFill = document.querySelector('.dash-fill');
            
            // 六边形周长约300
            focusBar.style.strokeDashoffset = 300 * (1 - focusPercent);
            dashFill.style.height = (dashPercent * 100) + '%';
        }

        function animate() {
            requestAnimationFrame(animate);

            const delta = clock.getDelta();

            if(!isDead) {
                updatePlayer(delta);
                updateEnemies(delta);
                updateBullets(delta);
            }

            renderer.render(scene, camera);
        }

        // 启动
        init();
    </script>
</body>
</html>

Game Source: 幽灵行者 网页复刻Demo

Creator: BoldStar37

Libraries: three

Complexity: complex (818 lines, 26.8 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: demo-boldstar37" to link back to the original. Then publish at arcadelab.ai/publish.