幽灵行者 - 完美可玩版
by AtomicShark71211 lines7.9 KB🛠️ Three.js (3D graphics)
<!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.