草原AI逃生
by RocketTiger53674 lines26.0 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, maximum-scale=1.0, user-scalable=no">
<title>草原AI逃生</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #72a8e8;
touch-action: none;
}
canvas { display: block; }
#joystick {
position: fixed;
bottom: 30px;
left: 30px;
width: 120px;
height: 120px;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
border: 2px solid rgba(255,255,255,0.3);
z-index: 10;
}
#fireBtn {
position: fixed;
bottom: 30px;
right: 30px;
width: 100px;
height: 100px;
background: rgba(255, 80, 80, 0.35);
border-radius: 50%;
color: #fff;
font-size: 22px;
border: 2px solid rgba(255,255,255,0.4);
z-index: 10;
}
#pickBtn {
position: fixed;
bottom: 140px;
right: 30px;
width: 80px;
height: 80px;
background: rgba(255, 220, 0, 0.45);
border-radius: 50%;
color: #000;
font-weight: bold;
font-size: 18px;
border: 3px solid #ffdd00;
z-index: 10;
}
#nightVisionBtn {
position: fixed;
bottom: 30px;
right: 150px;
width: 60px;
height: 60px;
background: rgba(80, 255, 80, 0.35);
border-radius: 50%;
color: #fff;
font-size: 16px;
border: 2px solid rgba(80, 255, 80, 0.6);
z-index: 10;
}
#hud {
position: fixed;
top: 20px;
left: 20px;
color: #fff;
font-size: 18px;
text-shadow: 0 0 4px #000;
z-index: 10;
}
#taskBar {
position: fixed;
top: 60px;
left: 20px;
color: #ffe644;
font-size: 19px;
font-weight: bold;
text-shadow: 0 0 5px #000;
z-index: 10;
}
#crosshair {
position: fixed;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
background: #ffffff;
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 5;
box-shadow: 0 0 5px #ff3333;
}
#nightVisionOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 6;
background: rgba(0, 255, 0, 0.18);
opacity: 0;
transition: opacity 0.15s ease;
}
#nightVisionOverlay.active { opacity: 1; }
#nvGoggleFrame {
position: fixed;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: radial-gradient(circle, transparent 25%, rgba(0, 0, 0, 0.65) 48%, rgba(0, 0, 0.88) 100%);
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 7;
opacity: 0;
transition: all 0.18s ease;
}
#nvGoggleFrame.active {
width: 280px;
height: 280px;
opacity: 1;
}
.scanLine {
position: absolute;
left: 0;
width: 100%;
height: 2px;
background: rgba(255, 255, 255, 0.85);
opacity: 0;
z-index: 8;
pointer-events: none;
}
</style>
</head>
<body>
<div id="hud">血量:100 | 弹药:30 | AI血量:1000</div>
<div id="taskBar">任务:收集电池 0/10 | 集齐后爬绳索通关</div>
<div id="crosshair"></div>
<div id="joystick"></div>
<button id="fireBtn">开火</button>
<button id="pickBtn">拾取</button>
<button id="nightVisionBtn">夜视仪</button>
<div id="nightVisionOverlay"></div>
<div id="nvGoggleFrame"></div>
<script>
let scene, camera, renderer, raycaster;
let gun, nightVisionGoggle, rightHand;
let aiList = [];
let rope = null;
let health = 100;
let ammo = 30;
let singleAiHP = 1000;
let batteryCount = 0;
const targetBatteryTotal = 10;
let batteryList = [];
let almondWaterList = [];
const hud = document.getElementById('hud');
const taskBar = document.getElementById('taskBar');
let moveForward = false;
const moveSpeed = 0.08;
let touchStartX = 0;
let touchStartY = 0;
let yaw = 0;
let pitch = 0.12;
const maxPitch = 0.6;
const minPitch = -0.6;
let nightVisionEnabled = false;
let isGoggleRaising = false;
let aiSpawned = false;
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x72a8e8);
scene.fog = new THREE.Fog(0x72a8e8, 40, 180);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1.6, 0);
camera.rotation.order = "YXZ";
camera.rotation.y = yaw;
camera.rotation.x = pitch;
renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = false;
renderer.toneMapping = THREE.NoToneMapping;
document.body.appendChild(renderer.domElement);
raycaster = new THREE.Raycaster();
raycaster.far = 100;
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xfff4d6, 0.8);
sunLight.position.set(25, 35, 15);
scene.add(sunLight);
createLightGrassScene();
spawnAllBatteries();
spawnAlmondWater();
createGunSimple();
createHandWithGoggle();
delayedSpawnAI();
initTouchControls();
initNightScanLines();
window.addEventListener('resize', onWindowResize);
animateLoop();
}
function createLightGrassScene() {
const grassMat = new THREE.MeshLambertMaterial({ color: 0x8fa860 });
const roadMat = new THREE.MeshLambertMaterial({ color: 0x5a6058 });
const roadLineMat = new THREE.MeshLambertMaterial({ color: 0xd8d8c8 });
const houseMat = new THREE.MeshLambertMaterial({ color: 0xd9c9a3 });
const roofMat = new THREE.MeshLambertMaterial({ color: 0x8b6914 });
const towerMat = new THREE.MeshLambertMaterial({ color: 0x919191 });
const ground = new THREE.Mesh(new THREE.PlaneGeometry(200, 200), grassMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = 0;
scene.add(ground);
const road = new THREE.Mesh(new THREE.PlaneGeometry(6, 120), roadMat);
road.rotation.x = -Math.PI / 2;
road.position.set(-50, 0.01, 0);
scene.add(road);
for(let i = -50; i < 50; i += 8) {
const line = new THREE.Mesh(new THREE.PlaneGeometry(0.4, 3), roadLineMat);
line.rotation.x = -Math.PI / 2;
line.position.set(-50, 0.02, i);
scene.add(line);
}
const house1 = new THREE.Group();
const h1Body = new THREE.Mesh(new THREE.BoxGeometry(4, 2.5, 5), houseMat);
h1Body.position.y = 1.25;
house1.add(h1Body);
const h1Roof = new THREE.Mesh(new THREE.ConeGeometry(3.2, 1.5, 4), roofMat);
h1Roof.position.y = 3.2;
h1Roof.rotation.y = Math.PI / 4;
house1.add(h1Roof);
house1.position.set(-75, 0, -40);
scene.add(house1);
const house2 = house1.clone();
house2.position.set(-10, 0, -70);
scene.add(house2);
const house3 = house1.clone();
house3.position.set(60, 0, -85);
scene.add(house3);
const tower = new THREE.Group();
const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.25, 6, 6), towerMat);
pole.position.y = 3;
tower.add(pole);
const tank = new THREE.Mesh(new THREE.CylinderGeometry(1.2, 1.2, 2.5, 6), towerMat);
tank.position.y = 7.5;
tower.add(tank);
tower.position.set(30, 0, -90);
scene.add(tower);
}
function spawnRope() {
const ropeMat = new THREE.MeshStandardMaterial({color:0x554433,roughness:0.8});
rope = new THREE.Mesh(new THREE.CylinderGeometry(0.15,0.15,12,6), ropeMat);
rope.position.set(0,6,-95);
scene.add(rope);
}
function spawnAllBatteries() {
const batteryMaterial = new THREE.MeshStandardMaterial({
color: 0xffdd00,
emissive: 0xffdd00,
emissiveIntensity: 0.5
});
for(let i = 0; i < targetBatteryTotal; i++){
const batteryMesh = new THREE.Mesh(
new THREE.BoxGeometry(0.22, 0.42, 0.16),
batteryMaterial
);
batteryMesh.userData.type = "battery";
batteryMesh.position.set(
(Math.random() - 0.5) * 160,
0.3,
(Math.random() - 0.5) * 160
);
scene.add(batteryMesh);
batteryList.push(batteryMesh);
}
}
function spawnAlmondWater() {
const waterMat = new THREE.MeshStandardMaterial({
color:0x88ddff,
emissive:0x66bbff,
emissiveIntensity:0.4
});
for(let i=0;i<12;i++){
const bottle = new THREE.Mesh(new THREE.CylinderGeometry(0.12,0.12,0.35,6), waterMat);
bottle.userData.type = "almondWater";
bottle.position.set((Math.random()-0.5)*160,0.25,(Math.random()-0.5)*160);
scene.add(bottle);
almondWaterList.push(bottle);
}
}
function delayedSpawnAI() {
const randomDelay = 3000 + Math.random() * 8000;
setTimeout(()=>{
createSingleAI(18,0,-12);
createSingleAI(-22,0,-35);
aiSpawned = true;
refreshHud();
}, randomDelay);
}
function createSingleAI(posX, posY, posZ) {
const aiEnemy = new THREE.Group();
aiEnemy.userData.hp = singleAiHP;
const bodyMat = new THREE.MeshLambertMaterial({color:0x111111});
const metalMat = new THREE.MeshStandardMaterial({color:0x333333,metalness:0.5,roughness:0.4});
const torso = new THREE.Mesh(new THREE.BoxGeometry(0.9,1.1,0.5), bodyMat);
torso.position.y = 1.1;
aiEnemy.add(torso);
const head = new THREE.Mesh(new THREE.SphereGeometry(0.3,8,6), new THREE.MeshLambertMaterial({color:0x1a0000}));
head.position.y = 2.2;
aiEnemy.add(head);
const eyeLight = new THREE.PointLight(0xff0000, 2, 8);
eyeLight.position.set(0,0.1,0.3);
head.add(eyeLight);
const armL = new THREE.Mesh(new THREE.BoxGeometry(0.25,0.9,0.25), metalMat);
armL.position.set(-0.7,1.1,0);
aiEnemy.add(armL);
const armR = armL.clone();
armR.position.set(0.7,1.1,0);
aiEnemy.add(armR);
const legL = new THREE.Mesh(new THREE.BoxGeometry(0.25,1.1,0.25), metalMat);
legL.position.set(-0.35,0.45,0);
aiEnemy.add(legL);
const legR = legL.clone();
legR.position.set(0.35,0.45,0);
aiEnemy.add(legR);
aiEnemy.position.set(posX, posY, posZ);
scene.add(aiEnemy);
aiList.push(aiEnemy);
}
function createGunSimple() {
gun = new THREE.Group();
const metalMat = new THREE.MeshStandardMaterial({color:0x222222,metalness:0.7,roughness:0.4});
const gripMat = new THREE.MeshStandardMaterial({color:0x111111,metalness:0.1,roughness:0.9});
const barrel = new THREE.Mesh(new THREE.CylinderGeometry(0.03,0.03,0.3,8), metalMat);
barrel.rotation.z = Math.PI/2;
barrel.position.set(0.3,-0.18,-0.6);
gun.add(barrel);
const slide = new THREE.Mesh(new THREE.BoxGeometry(0.16,0.07,0.35), metalMat);
slide.position.set(0.3,-0.18,-0.4);
gun.add(slide);
const grip = new THREE.Mesh(new THREE.BoxGeometry(0.05,0.16,0.06), gripMat);
grip.position.set(0.35,-0.28,-0.18);
grip.rotation.x = -0.3;
gun.add(grip);
camera.add(gun);
}
function createHandWithGoggle() {
rightHand = new THREE.Group();
const handMat = new THREE.MeshStandardMaterial({color:0xd8b98a,roughness:0.7});
const forearm = new THREE.Mesh(new THREE.BoxGeometry(0.12,0.35,0.08), handMat);
forearm.position.set(0.55,-0.25,-0.45);
rightHand.add(forearm);
const hand = new THREE.Mesh(new THREE.BoxGeometry(0.15,0.18,0.1), handMat);
hand.position.set(0.55,-0.12,-0.55);
rightHand.add(hand);
nightVisionGoggle = new THREE.Group();
const goggleBody = new THREE.Mesh(new THREE.BoxGeometry(0.22,0.12,0.08), new THREE.MeshStandardMaterial({color:0x1a1a1a,metalness:0.9,roughness:0.2}));
nightVisionGoggle.add(goggleBody);
const lens = new THREE.Mesh(new THREE.CylinderGeometry(0.04,0.04,0.02,8), new THREE.MeshStandardMaterial({color:0x00ff00,emissive:0x00ff00,emissiveIntensity:0.5,transparent:true,opacity:0.35}));
lens.rotation.z = Math.PI/2;
lens.position.set(-0.08,0,0.05);
nightVisionGoggle.add(lens);
const lens2 = lens.clone();
lens2.position.set(0.08,0,0.05);
nightVisionGoggle.add(lens2);
nightVisionGoggle.position.set(0.65,-0.15,-0.65);
nightVisionGoggle.rotation.x = -0.25;
rightHand.add(nightVisionGoggle);
camera.add(rightHand);
}
function initTouchControls() {
const joy = document.getElementById('joystick');
const fireBtn = document.getElementById('fireBtn');
const pickBtn = document.getElementById('pickBtn');
const nvBtn = document.getElementById('nightVisionBtn');
fireBtn.addEventListener('touchstart', e=>{
e.preventDefault();
if(ammo<=0) return;
ammo--;
shootRay();
recoilAnim();
refreshHud();
})
pickBtn.addEventListener('touchstart', e=>{
e.preventDefault();
tryPickItem();
})
joy.addEventListener('touchstart', ()=>moveForward=true);
joy.addEventListener('touchend', ()=>moveForward=false);
nvBtn.addEventListener('touchstart', e=>{
e.preventDefault();
switchNightVision();
})
document.addEventListener('touchstart', e=>{
const x = e.touches[0].clientX;
const y = e.touches[0].clientY;
const jRect = joy.getBoundingClientRect();
const fRect = fireBtn.getBoundingClientRect();
const pRect = pickBtn.getBoundingClientRect();
const nRect = nvBtn.getBoundingClientRect();
const inJoy = (x>=jRect.left&&x<=jRect.right&&y>=jRect.top&&y<=jRect.bottom);
const inFire = (x>=fRect.left&&x<=fRect.right&&y>=fRect.top&&y<=fRect.bottom);
const inPick = (x>=pRect.left&&x<=pRect.right&&y>=pRect.top&&y<=pRect.bottom);
const inNV = (x>=nRect.left&&x<=nRect.right&&y>=nRect.top&&y<=nRect.bottom);
if(!inJoy&&!inFire&&!inPick&&!inNV){
touchStartX = x;
touchStartY = y;
}
})
document.addEventListener('touchmove', e=>{
const x = e.touches[0].clientX;
const y = e.touches[0].clientY;
const jRect = joy.getBoundingClientRect();
const fRect = fireBtn.getBoundingClientRect();
const pRect = pickBtn.getBoundingClientRect();
const nRect = nvBtn.getBoundingClientRect();
const inJoy = (x>=jRect.left&&x<=jRect.right&&y>=jRect.top&&y<=jRect.bottom);
const inFire = (x>=fRect.left&&x<=fRect.right&&y>=fRect.top&&y<=fRect.bottom);
const inPick = (x>=pRect.left&&x<=pRect.right&&y>=pRect.top&&y<=pRect.bottom);
const inNV = (x>=nRect.left&&x<=nRect.right&&y>=nRect.top&&y<=nRect.bottom);
if(!inJoy&&!inFire&&!inPick&&!inNV) {
const dx = x - touchStartX;
const dy = y - touchStartY;
yaw += dx * 0.005;
pitch += dy * 0.005;
pitch = Math.max(minPitch, Math.min(maxPitch, pitch));
touchStartX = x;
touchStartY = y;
}
})
}
function switchNightVision() {
if(isGoggleRaising) return;
nightVisionEnabled = !nightVisionEnabled;
isGoggleRaising = true;
const overlay = document.getElementById('nightVisionOverlay');
const frame = document.getElementById('nvGoggleFrame');
if(nightVisionEnabled) {
raiseGoggle(()=>{
overlay.classList.add('active');
frame.classList.add('active');
aiList.forEach(ai=>ai.traverse(c=>{if(c.material.emissive) c.material.emissiveIntensity=1.2;}))
isGoggleRaising = false;
})
} else {
overlay.classList.remove('active');
frame.classList.remove('active');
aiList.forEach(ai=>ai.traverse(c=>{if(c.material.emissive) c.material.emissiveIntensity=0.6;}))
lowerGoggle(()=>isGoggleRaising=false);
}
}
function raiseGoggle(cb) {
let t = 0;
const start = {x:0.65,y:-0.15,z:-0.65,rx:-0.25};
const end = {x:0.35,y:0.15,z:-0.9,rx:0.1};
const anim = ()=>{
t += 0.08;
if(t>=1) {
nightVisionGoggle.position.set(end.x,end.y,end.z);
nightVisionGoggle.rotation.x = end.rx;
cb();
return;
}
nightVisionGoggle.position.x = start.x + (end.x-start.x)*t;
nightVisionGoggle.position.y = start.y + (end.y-start.y)*t;
nightVisionGoggle.position.z = start.z + (end.z-start.z)*t;
nightVisionGoggle.rotation.x = start.rx + (end.rx-start.rx)*t;
requestAnimationFrame(anim);
}
anim();
}
function lowerGoggle(cb) {
let t = 0;
const start = {x:0.35,y:0.15,z:-0.9,rx:0.1};
const end = {x:0.65,y:-0.15,z:-0.65,rx:-0.25};
const anim = ()=>{
t += 0.08;
if(t>=1) {
nightVisionGoggle.position.set(end.x,end.y,end.z);
nightVisionGoggle.rotation.x = end.rx;
cb();
return;
}
nightVisionGoggle.position.x = start.x + (end.x-start.x)*t;
nightVisionGoggle.position.y = start.y + (end.y-start.y)*t;
nightVisionGoggle.position.z = start.z + (end.z-start.z)*t;
nightVisionGoggle.rotation.x = start.rx + (end.rx-start.rx)*t;
requestAnimationFrame(anim);
}
anim();
}
function initNightScanLines() {
setInterval(()=>{
if(!nightVisionEnabled) return;
const overlay = document.getElementById('nightVisionOverlay');
const line = document.createElement('div');
line.className = 'scanLine';
line.style.top = Math.random()*100 + '%';
overlay.appendChild(line);
setTimeout(()=>{
line.style.opacity = '0';
setTimeout(()=>line.remove(),300);
},150);
},1500)
}
function shootRay() {
raycaster.setFromCamera(new THREE.Vector2(0,0), camera);
const hits = raycaster.intersectObjects(scene.children, true);
if(hits.length>0) {
const hitObj = hits[0].object;
for(let ai of aiList){
if(hitObj.parent === ai || hitObj.parent?.parent === ai){
hitAI(ai);
break;
}
}
}
createBulletVfx();
}
function createBulletVfx() {
const bullet = new THREE.Mesh(new THREE.SphereGeometry(0.06,4,4), new THREE.MeshBasicMaterial({color:0xffdd00}));
bullet.position.copy(camera.position);
bullet.position.add(camera.getWorldDirection().clone().multiplyScalar(0.8));
scene.add(bullet);
const dir = camera.getWorldDirection().clone().multiplyScalar(0.3);
const timer = setInterval(()=>bullet.position.add(dir),30);
setTimeout(()=>{
clearInterval(timer);
if(scene.children.includes(bullet)) scene.remove(bullet);
},700);
}
function hitAI(targetAi) {
targetAi.userData.hp -= 20;
refreshHud();
if(targetAi.userData.hp <= 0) killAI(targetAi);
}
function killAI(targetAi) {
targetAi.rotation.z = Math.PI/2;
targetAi.position.y = 0.5;
setTimeout(()=>{
scene.remove(targetAi);
const idx = aiList.indexOf(targetAi);
if(idx>-1) aiList.splice(idx,1);
refreshHud();
},800);
}
function recoilAnim() {
gun.position.z += 0.12;
gun.rotation.x -= 0.06;
setTimeout(()=>{
gun.position.z = -0.5;
gun.rotation.x = 0;
},100);
}
function refreshHud() {
if(aiList.length === 0){
hud.innerText = `血量:${Math.floor(health)} | 弹药:${ammo} | 全部AI已清除`;
}else{
hud.innerText = `血量:${Math.floor(health)} | 弹药:${ammo} | 剩余AI血量:${aiList[0].userData.hp}`;
}
}
function refreshTaskUI() {
if(batteryCount >= targetBatteryTotal){
taskBar.innerText = "任务完成!对准绳索按拾取通关!";
if(!rope) spawnRope();
}else{
taskBar.innerText = `任务:收集电池 ${batteryCount}/${targetBatteryTotal} | 集齐后爬绳索通关`;
}
}
function tryPickItem() {
raycaster.setFromCamera(new THREE.Vector2(0,0), camera);
const hits = raycaster.intersectObjects(scene.children, true);
if(hits.length === 0) return;
const hitObj = hits[0].object;
const hitParent = hitObj.parent;
if(batteryList.includes(hitParent)){
scene.remove(hitParent);
const idx = batteryList.indexOf(hitParent);
batteryList.splice(idx,1);
batteryCount++;
refreshTaskUI();
return;
}
if(almondWaterList.includes(hitParent)){
scene.remove(hitParent);
const idx = almondWaterList.indexOf(hitParent);
almondWaterList.splice(idx,1);
health = Math.min(100, health + 30);
refreshHud();
return;
}
if(rope && hitParent === rope){
taskBar.innerText = "恭喜成功逃离!游戏通关!";
}
}
function aiTick() {
const pPos = camera.position;
for(let ai of aiList){
if(!ai) continue;
const aPos = ai.position;
ai.lookAt(pPos.x, aPos.y, pPos.z);
const dir = new THREE.Vector3().subVectors(pPos, aPos).normalize();
ai.position.add(dir.multiplyScalar(0.018));
ai.position.y = 0;
const dist = aPos.distanceTo(pPos);
if(dist < 12) {
health -= 0.04;
refreshHud();
if(health <= 0) hud.innerText = "你被AI击杀!游戏结束";
}
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animateLoop() {
requestAnimationFrame(animateLoop);
camera.position.y = 1.6;
camera.rotation.y = yaw;
camera.rotation.x = pitch;
if(moveForward) camera.translateZ(-moveSpeed);
aiTick();
renderer.render(scene, camera);
}
// 平台自动注入Three,无需外部script引入
window.addEventListener('load', ()=>{
window.THREE = window.three;
init();
})
</script>
</body>
</html>
Game Source: 草原AI逃生
Creator: RocketTiger53
Libraries: three
Complexity: complex (674 lines, 26.0 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: ai-rockettiger53" to link back to the original. Then publish at arcadelab.ai/publish.