幽灵行者 网页复刻Demo
by BoldStar37818 lines26.8 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>幽灵行者 网页复刻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.