Загрузка данных
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Apartment V5</title>
<style>
body{margin:0;overflow:hidden;background:black;font-family:monospace;}
#menu{
position:absolute;width:100%;height:100%;
display:flex;flex-direction:column;
justify-content:center;align-items:center;
background:black;color:white;z-index:10;
}
button{padding:12px 18px;font-weight:bold;cursor:pointer;}
#hud{position:absolute;top:10px;left:10px;color:white;z-index:5;}
#inv{position:absolute;bottom:10px;left:10px;color:white;z-index:5;}
#vhs{
position:absolute;width:100%;height:100%;
pointer-events:none;
background:repeating-linear-gradient(0deg,rgba(255,255,255,0.02),rgba(0,0,0,0.02)2px);
animation:noise .1s infinite;
mix-blend-mode:overlay;
}
@keyframes noise{
0%{transform:translate(0,0)}
50%{transform:translate(1px,-1px)}
100%{transform:translate(0,0)}
}
</style>
</head>
<body>
<div id="menu">
<h1>THE APARTMENT SIGNAL</h1>
<p>Оно слушает тебя</p>
<button onclick="start()">START</button>
</div>
<div id="hud">
<button onclick="toggleFlashlight()">Flashlight (F)</button>
</div>
<div id="inv">Inventory: []</div>
<div id="vhs"></div>
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
<script>
//////////////////////////////////////////////////////
// CORE
//////////////////////////////////////////////////////
let scene,camera,renderer;
let flashlight;
let keys={};
let inventory=[];
let objects=[];
let creature;
let clonePlayer;
let flashlightOn=true;
let audioCtx;
let heartbeatOsc;
let ambientGain;
//////////////////////////////////////////////////////
// AUDIO (страшный музон)
//////////////////////////////////////////////////////
function initAudio(){
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// AMBIENT DRONE
let osc = audioCtx.createOscillator();
osc.type="sine";
osc.frequency.value=40;
ambientGain = audioCtx.createGain();
ambientGain.gain.value=0.05;
osc.connect(ambientGain);
ambientGain.connect(audioCtx.destination);
osc.start();
// HEART BEAT
heartbeatOsc = audioCtx.createOscillator();
heartbeatOsc.type="square";
heartbeatOsc.frequency.value=1;
let beatGain = audioCtx.createGain();
beatGain.gain.value=0;
heartbeatOsc.connect(beatGain);
beatGain.connect(audioCtx.destination);
heartbeatOsc.start();
setInterval(()=>{
beatGain.gain.value = 0.2;
setTimeout(()=>beatGain.gain.value=0,100);
}, 2000);
}
//////////////////////////////////////////////////////
// START
//////////////////////////////////////////////////////
function start(){
document.getElementById("menu").style.display="none";
init();
initAudio();
animate();
setInterval(horrorEvents, 5000);
setInterval(ai, 50);
}
//////////////////////////////////////////////////////
// WORLD
//////////////////////////////////////////////////////
function init(){
scene=new THREE.Scene();
scene.fog=new THREE.Fog(0x000000,1,30);
camera=new THREE.PerspectiveCamera(75,innerWidth/innerHeight,0.1,1000);
camera.position.set(0,1.6,5);
renderer=new THREE.WebGLRenderer();
renderer.setSize(innerWidth,innerHeight);
document.body.appendChild(renderer.domElement);
// LIGHT
let light=new THREE.PointLight(0xffffff,1,20);
light.position.set(0,3,0);
scene.add(light);
// FLASHLIGHT
flashlight=new THREE.SpotLight(0xffffff,2,30,0.6);
flashlight.position.copy(camera.position);
scene.add(flashlight);
// FLOOR
scene.add(new THREE.Mesh(
new THREE.BoxGeometry(40,0.1,40),
new THREE.MeshBasicMaterial({color:0x111111})
));
// WALLS
wall(0,-20); wall(0,20);
wall(-20,0,Math.PI/2);
wall(20,0,Math.PI/2);
// OBJECTS
spawnRoomObjects();
spawnCreature();
spawnClonePlayer();
spawnDoor();
spawnKey();
// CONTROLS
document.addEventListener("keydown",e=>{
keys[e.key.toLowerCase()]=true;
if(e.key.toLowerCase()==="f") toggleFlashlight();
});
document.addEventListener("keyup",e=>{
keys[e.key.toLowerCase()]=false;
});
document.addEventListener("mousemove",(e)=>{
if(document.pointerLockElement){
camera.rotation.y -= e.movementX*0.002;
camera.rotation.x -= e.movementY*0.002;
}
});
document.body.addEventListener("click",()=>document.body.requestPointerLock());
}
//////////////////////////////////////////////////////
// ROOM
//////////////////////////////////////////////////////
function wall(x,z,rot){
let w=new THREE.Mesh(
new THREE.BoxGeometry(40,4,0.2),
new THREE.MeshBasicMaterial({color:0x222222})
);
w.position.set(x,2,z);
if(rot) w.rotation.y=rot;
scene.add(w);
}
function spawnRoomObjects(){
for(let i=0;i<40;i++){
let o=new THREE.Mesh(
new THREE.BoxGeometry(0.3,0.3,0.3),
new THREE.MeshBasicMaterial({color:0x444444})
);
o.position.set((Math.random()-0.5)*20,0.3,(Math.random()-0.5)*20);
scene.add(o);
}
}
//////////////////////////////////////////////////////
// ITEMS
//////////////////////////////////////////////////////
function spawnKey(){
let k=new THREE.Mesh(
new THREE.BoxGeometry(0.2,0.2,0.2),
new THREE.MeshBasicMaterial({color:0xffff00})
);
k.position.set(3,1,-3);
k.name="key";
scene.add(k);
objects.push(k);
}
function spawnDoor(){
let d=new THREE.Mesh(
new THREE.BoxGeometry(1,2,0.2),
new THREE.MeshBasicMaterial({color:0x552200})
);
d.position.set(0,1,-19);
d.name="door";
scene.add(d);
objects.push(d);
}
//////////////////////////////////////////////////////
// CREATURE AI (умнее)
//////////////////////////////////////////////////////
function spawnCreature(){
creature=new THREE.Mesh(
new THREE.BoxGeometry(0.6,2,0.6),
new THREE.MeshBasicMaterial({color:0xff0000})
);
creature.position.set(10,1,10);
scene.add(creature);
}
function ai(){
let dir=new THREE.Vector3().subVectors(camera.position,creature.position);
dir.y=0;
let dist=dir.length();
dir.normalize();
// если фонарь включен — он боится
let speed = flashlightOn ? 0.01 : 0.05;
// если близко — резко ускоряется
if(dist<6) speed=0.08;
creature.position.addScaledVector(dir,speed);
// если очень близко — атака
if(dist<1.2){
ending("death");
}
}
//////////////////////////////////////////////////////
// CLONE PLAYER (скример)
//////////////////////////////////////////////////////
function spawnClonePlayer(){
clonePlayer=new THREE.Mesh(
new THREE.BoxGeometry(0.6,1.8,0.6),
new THREE.MeshBasicMaterial({color:0x8800ff})
);
clonePlayer.position.set(-8,1,-8);
scene.add(clonePlayer);
}
function horrorEvents(){
let r=Math.random();
// clone appears behind you
if(r<0.2){
clonePlayer.position.set(
camera.position.x - Math.random()*2,
1,
camera.position.z - Math.random()*2
);
}
// doors “breathing”
if(r>0.7){
flash();
}
}
//////////////////////////////////////////////////////
// FLASH
//////////////////////////////////////////////////////
function flash(){
let d=document.createElement("div");
d.style="position:absolute;top:0;left:0;width:100%;height:100%;background:white;z-index:999";
document.body.appendChild(d);
setTimeout(()=>d.remove(),80);
}
//////////////////////////////////////////////////////
// FLASHLIGHT
//////////////////////////////////////////////////////
function toggleFlashlight(){
flashlightOn=!flashlightOn;
}
//////////////////////////////////////////////////////
// ENDINGS
//////////////////////////////////////////////////////
function ending(type){
if(type==="death"){
alert("ENDING: YOU ARE PART OF THE SIGNAL");
location.reload();
}
if(type==="escape"){
alert("ENDING: YOU LEFT... BUT IT DIDN'T");
location.reload();
}
}
//////////////////////////////////////////////////////
// LOOP
//////////////////////////////////////////////////////
function animate(){
requestAnimationFrame(animate);
// movement
if(keys["w"]) camera.position.z -= 0.12;
if(keys["s"]) camera.position.z += 0.12;
if(keys["a"]) camera.position.x -= 0.12;
if(keys["d"]) camera.position.x += 0.12;
// flashlight follows
flashlight.position.copy(camera.position);
renderer.render(scene,camera);
}
</script>
</body>
</html>