🎮ArcadeLab

幽灵行者 - 完美可玩版

by AtomicShark71
211 lines7.9 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>幽灵行者 - 完美可玩版</title>
    <style>
        *{margin:0;padding:0;box-sizing:border-box}
        body{overflow:hidden;background:#1a1a35;font-family:Arial}
        #game{width:100vw;height:100vh;position:relative}
        #crosshair{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:8px;height:8px;border:2px solid #00d4ff;border-radius:50%;z-index:50;opacity:.8}
        #start{position:absolute;inset:0;background:#0f0f23;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;z-index:999}
        #start h1{font-size:50px;color:#00d4ff;margin-bottom:20px}
        #start button{padding:15px 30px;font-size:18px;background:#00d4ff;color:white;border:none;border-radius:8px;cursor:pointer}
        #death{position:absolute;inset:0;background:rgba(20,20,40,.95);display:none;flex-direction:column;align-items:center;justify-content:center;color:#fff}
    </style>
</head>
<body>
    <div id="game">
        <div id="start">
            <h1>GHOSTRUNNER</h1>
            <button id="startBtn">开始游戏</button>
        </div>
        <div id="death">
            <h2>你已死亡</h2>
            <p>按 R 重生</p>
        </div>
        <div id="crosshair"></div>
    </div>

    <script type="module">
        import * as THREE from 'https://unpkg.com/three@0.160.0/build/three.module.js';
        import { PointerLockControls } from 'https://unpkg.com/three@0.160.0/examples/jsm/controls/PointerLockControls.js';

        let scene, camera, renderer, controls;
        let playerPos = new THREE.Vector3(0,2,0);
        let velocity = new THREE.Vector3();
        let canJump = false;
        let isDead = false;
        let hookActive = false;
        let hookTarget = null;

        const keys = {w:false,a:false,s:false,d:false,space:false,shift:false};
        const startBtn = document.getElementById('startBtn');
        const startScreen = document.getElementById('start');

        startBtn.addEventListener('click', () => {
            startScreen.style.display = 'none';
            controls.lock();
        });

        function init(){
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x252545);
            scene.fog = new THREE.Fog(0x252545, 20, 100);

            camera = new THREE.PerspectiveCamera(75, innerWidth/innerHeight, 0.1, 1000);
            camera.position.copy(playerPos);

            renderer = new THREE.WebGLRenderer({antialias:true});
            renderer.setSize(innerWidth, innerHeight);
            document.getElementById('game').appendChild(renderer.domElement);

            controls = new PointerLockControls(camera, document.body);

            scene.add(new THREE.AmbientLight(0x8090ff, 2.0));
            const light = new THREE.DirectionalLight(0xffffff, 2);
            light.position.set(10,30,10);
            scene.add(light);

            createWorld();
            createHand();
            createHookPoints();
            setupControls();
            animate();
        }

        function createWorld(){
            const ground = new THREE.Mesh(
                new THREE.PlaneGeometry(100,100),
                new THREE.MeshStandardMaterial({color:0x333355, metalness:0.7})
            );
            ground.rotation.x = -Math.PI/2;
            scene.add(ground);
        }

        function createHand(){
            const group = new THREE.Group();
            camera.add(group);

            const arm = new THREE.Mesh(
                new THREE.CapsuleGeometry(0.15,0.8),
                new THREE.MeshStandardMaterial({color:0x2a2a4a, metalness:0.9})
            );
            arm.position.set(0.4,-0.2,-0.2);
            group.add(arm);

            const hand = new THREE.Mesh(
                new THREE.BoxGeometry(0.25,0.2,0.25),
                new THREE.MeshStandardMaterial({color:0x35356a, emissive:0x00d4ff, emissiveIntensity:0.3})
            );
            hand.position.set(0.45,-0.25,-0.5);
            group.add(hand);

            const blade = new THREE.Mesh(
                new THREE.BoxGeometry(0.04,0.02,1.8),
                new THREE.MeshStandardMaterial({color:0x00d4ff, emissive:0x00d4ff, emissiveIntensity:2})
            );
            blade.position.set(0.45,-0.25,-1.5);
            group.add(blade);
        }

        let hookPoints = [];
        function createHookPoints(){
            const pos = [[0,8,20],[0,10,40],[0,12,60],[0,14,80]];
            pos.forEach(p => {
                const h = new THREE.Mesh(
                    new THREE.SphereGeometry(0.2),
                    new THREE.MeshBasicMaterial({color:0x00d4ff})
                );
                h.position.set(p[0],p[1],p[2]);
                scene.add(h);
                hookPoints.push(h);
            });
        }

        function setupControls(){
            document.addEventListener('keydown', e => {
                if(isDead && e.key === 'r') location.reload();
                switch(e.key.toLowerCase()){
                    case 'w': keys.w=true; break;
                    case 'a': keys.a=true; break;
                    case 's': keys.s=true; break;
                    case 'd': keys.d=true; break;
                    case ' ': keys.space=true; break;
                    case 'shift': keys.shift=true; break;
                }
            });

            document.addEventListener('keyup', e => {
                switch(e.key.toLowerCase()){
                    case 'w': keys.w=false; break;
                    case 'a': keys.a=false; break;
                    case 's': keys.s=false; break;
                    case 'd': keys.d=false; break;
                    case ' ': keys.space=false; break;
                    case 'shift': keys.shift=false; break;
                }
            });

            document.addEventListener('mousedown', e => {
                if(e.button === 2) fireHook();
            });

            document.addEventListener('contextmenu', e => e.preventDefault());
        }

        function fireHook(){
            let closest = null;
            let minDist = 999;
            hookPoints.forEach(h => {
                const d = camera.position.distanceTo(h.position);
                if(d < 70 && d < minDist) { closest = h; minDist = d; }
            });
            if(closest) { hookTarget = closest; hookActive = true; }
        }

        function updatePlayer(){
            if(isDead) return;
            let speed = keys.shift ? 14 : 9;
            const move = new THREE.Vector3();

            if(keys.w) move.z -= 1;
            if(keys.s) move.z += 1;
            if(keys.a) move.x -= 1;
            if(keys.d) move.x += 1;
            move.normalize();
            move.applyQuaternion(camera.quaternion);
            move.y = 0;

            velocity.x = move.x * speed;
            velocity.z = move.z * speed;
            velocity.y -= 9.8 * 0.015;

            if(keys.space && canJump) { velocity.y = 6; canJump = false; }
            if(hookActive && hookTarget){
                const dir = new THREE.Vector3().subVectors(hookTarget.position, playerPos).normalize();
                playerPos.addScaledVector(dir, 1.3);
                if(playerPos.distanceTo(hookTarget.position) < 2) hookActive = false;
            }

            playerPos.addScaledVector(velocity, 0.16);
            if(playerPos.y < 2) {
                playerPos.y = 2;
                velocity.y = 0;
                canJump = true;
            }

            camera.position.copy(playerPos);
        }

        function animate(){
            requestAnimationFrame(animate);
            updatePlayer();
            renderer.render(scene, camera);
        }

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

Game Source: 幽灵行者 - 完美可玩版

Creator: AtomicShark71

Libraries: three

Complexity: complex (211 lines, 7.9 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: game-atomicshark71" to link back to the original. Then publish at arcadelab.ai/publish.