Spaces:
Running
Running
<html> | |
<head> | |
<title>3D Survival Game</title> | |
<style> | |
body { margin: 0; } | |
#info { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
color: white; | |
background: rgba(0,0,0,0.7); | |
padding: 10px; | |
font-family: Arial; | |
font-size: 14px; | |
z-index: 100; | |
border-radius: 5px; | |
} | |
#weaponAlert { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
color: white; | |
background: rgba(255,0,0,0.7); | |
padding: 20px; | |
font-family: Arial; | |
font-size: 24px; | |
display: none; | |
z-index: 100; | |
border-radius: 10px; | |
} | |
#safeTimer { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
color: white; | |
background: rgba(0,128,0,0.7); | |
padding: 10px; | |
font-family: Arial; | |
font-size: 18px; | |
z-index: 100; | |
border-radius: 5px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="info"> | |
Click to start<br> | |
WASD - Move<br> | |
Mouse - Look around<br> | |
Find the weapon to win!<br> | |
Avoid the enemies! | |
</div> | |
<div id="weaponAlert">Weapon has appeared!</div> | |
<div id="safeTimer"></div> | |
<script type="module"> | |
import * as THREE from 'https://unpkg.com/three@0.157.0/build/three.module.js'; | |
import { GLTFLoader } from 'https://unpkg.com/three@0.157.0/examples/jsm/loaders/GLTFLoader.js'; | |
import { PointerLockControls } from 'https://unpkg.com/three@0.157.0/examples/jsm/controls/PointerLockControls.js'; | |
// ๊ธฐ๋ณธ ์ค์ | |
const scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0x87ceeb); | |
scene.fog = new THREE.Fog(0x87ceeb, 0, 500); | |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.shadowMap.enabled = true; | |
renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
document.body.appendChild(renderer.domElement); | |
// ๊ฒ์ ๋ณ์ | |
const SAFE_TIME = 15; // 15์ด ์์ ์๊ฐ | |
let gameStartTime = 0; | |
let isSafePeriod = true; | |
let enemies = []; | |
let weapon = null; | |
let playerModel = null; | |
let isGameOver = false; | |
// ํฌ์ธํฐ ๋ฝ ์ปจํธ๋กค | |
const controls = new PointerLockControls(camera, document.body); | |
controls.addEventListener('lock', () => { | |
if (!gameStartTime) { | |
gameStartTime = Date.now(); | |
setTimeout(spawnWeapon, Math.random() * 10000 + 5000); // 5-15์ด ์ฌ์ด์ ๋ฌด๊ธฐ ์คํฐ | |
} | |
}); | |
// ์งํ ์์ฑ | |
const terrainGeometry = new THREE.PlaneGeometry(500, 500, 100, 100); | |
const terrainMaterial = new THREE.MeshStandardMaterial({ | |
color: 0x3a8c3a, | |
roughness: 0.8, | |
metalness: 0.2 | |
}); | |
// ์งํ ๋๋ฎ์ด ์ค์ | |
const vertices = terrainGeometry.attributes.position.array; | |
for (let i = 0; i < vertices.length; i += 3) { | |
vertices[i + 2] = Math.sin(vertices[i] * 0.05) * Math.cos(vertices[i + 1] * 0.05) * 5; | |
} | |
terrainGeometry.attributes.position.needsUpdate = true; | |
terrainGeometry.computeVertexNormals(); | |
const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial); | |
terrain.rotation.x = -Math.PI / 2; | |
terrain.receiveShadow = true; | |
scene.add(terrain); | |
// ์กฐ๋ช ์ค์ | |
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
directionalLight.position.set(100, 100, 50); | |
directionalLight.castShadow = true; | |
directionalLight.shadow.mapSize.width = 2048; | |
directionalLight.shadow.mapSize.height = 2048; | |
directionalLight.shadow.camera.near = 0.5; | |
directionalLight.shadow.camera.far = 500; | |
directionalLight.shadow.camera.left = -100; | |
directionalLight.shadow.camera.right = 100; | |
directionalLight.shadow.camera.top = 100; | |
directionalLight.shadow.camera.bottom = -100; | |
scene.add(directionalLight); | |
// ๋๋ฌด ์์ฑ | |
function createTrees() { | |
const treeGeometry = new THREE.CylinderGeometry(0, 4, 20, 8); | |
const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x0d5c0d }); | |
for (let i = 0; i < 200; i++) { | |
const tree = new THREE.Mesh(treeGeometry, treeMaterial); | |
tree.position.set( | |
(Math.random() - 0.5) * 400, | |
10, | |
(Math.random() - 0.5) * 400 | |
); | |
tree.castShadow = true; | |
tree.receiveShadow = true; | |
scene.add(tree); | |
} | |
} | |
// ํ๋ ์ด์ด ๋ชจ๋ธ ๋ก๋ | |
const loader = new GLTFLoader(); | |
loader.load('me.glb', (gltf) => { | |
playerModel = gltf.scene; | |
playerModel.scale.set(0.5, 0.5, 0.5); | |
playerModel.traverse((node) => { | |
if (node.isMesh) { | |
node.castShadow = true; | |
node.receiveShadow = true; | |
} | |
}); | |
scene.add(playerModel); | |
}); | |
// ์ ์์ฑ | |
function createEnemies() { | |
loader.load('enemy.glb', (gltf) => { | |
const enemyModel = gltf.scene; | |
for (let i = 0; i < 8; i++) { | |
const enemy = enemyModel.clone(); | |
enemy.scale.set(0.5, 0.5, 0.5); | |
// ํ๋ ์ด์ด๋ก๋ถํฐ ๋ฉ๋ฆฌ ์คํฐ | |
const angle = (i / 8) * Math.PI * 2; | |
const radius = 100; | |
enemy.position.set( | |
Math.cos(angle) * radius, | |
5, | |
Math.sin(angle) * radius | |
); | |
enemy.traverse((node) => { | |
if (node.isMesh) { | |
node.castShadow = true; | |
node.receiveShadow = true; | |
} | |
}); | |
scene.add(enemy); | |
enemies.push({ | |
model: enemy, | |
velocity: new THREE.Vector3(), | |
speed: 0.2 + Math.random() * 0.2 | |
}); | |
} | |
}); | |
} | |
// ๋ฌด๊ธฐ ์คํฐ | |
function spawnWeapon() { | |
const weaponGeometry = new THREE.BoxGeometry(1, 1, 3); | |
const weaponMaterial = new THREE.MeshStandardMaterial({ | |
color: 0xffd700, | |
metalness: 0.7, | |
roughness: 0.3, | |
emissive: 0xffd700, | |
emissiveIntensity: 0.5 | |
}); | |
weapon = new THREE.Mesh(weaponGeometry, weaponMaterial); | |
weapon.position.set( | |
(Math.random() - 0.5) * 200, | |
2, | |
(Math.random() - 0.5) * 200 | |
); | |
weapon.castShadow = true; | |
scene.add(weapon); | |
// ๋ฌด๊ธฐ ์ถํ ์๋ฆผ | |
const weaponAlert = document.getElementById('weaponAlert'); | |
weaponAlert.style.display = 'block'; | |
setTimeout(() => { | |
weaponAlert.style.display = 'none'; | |
}, 3000); | |
} | |
// ์ด๋ ์ํ | |
const moveState = { | |
forward: false, | |
backward: false, | |
left: false, | |
right: false | |
}; | |
// ํค๋ณด๋ ์ด๋ฒคํธ | |
document.addEventListener('keydown', (event) => { | |
switch (event.code) { | |
case 'KeyW': moveState.forward = true; break; | |
case 'KeyS': moveState.backward = true; break; | |
case 'KeyA': moveState.left = true; break; | |
case 'KeyD': moveState.right = true; break; | |
} | |
}); | |
document.addEventListener('keyup', (event) => { | |
switch (event.code) { | |
case 'KeyW': moveState.forward = false; break; | |
case 'KeyS': moveState.backward = false; break; | |
case 'KeyA': moveState.left = false; break; | |
case 'KeyD': moveState.right = false; break; | |
} | |
}); | |
// ๊ฒ์ ์ค๋ฒ | |
function gameOver(won = false) { | |
if (!isGameOver) { | |
isGameOver = true; | |
controls.unlock(); | |
alert(won ? 'You found the weapon and won!' : 'Game Over! You were caught!'); | |
location.reload(); | |
} | |
} | |
// ์ด๊ธฐ ์ค์ | |
camera.position.set(0, 5, 0); | |
createTrees(); | |
createEnemies(); | |
// ๊ฒ์ ๋ฃจํ | |
function animate() { | |
requestAnimationFrame(animate); | |
if (controls.isLocked && !isGameOver) { | |
// ์์ ์๊ฐ ์ฒดํฌ | |
const elapsedTime = Math.floor((Date.now() - gameStartTime) / 1000); | |
const safeTimer = document.getElementById('safeTimer'); | |
if (elapsedTime < SAFE_TIME) { | |
safeTimer.textContent = `Safe Time: ${SAFE_TIME - elapsedTime}s`; | |
isSafePeriod = true; | |
} else { | |
safeTimer.textContent = ''; | |
isSafePeriod = false; | |
} | |
// ์ด๋ ์ฒ๋ฆฌ | |
const speed = 0.5; | |
if (moveState.forward) controls.moveForward(speed); | |
if (moveState.backward) controls.moveForward(-speed); | |
if (moveState.left) controls.moveRight(-speed); | |
if (moveState.right) controls.moveRight(speed); | |
// ํ๋ ์ด์ด ๋ชจ๋ธ ์ ๋ฐ์ดํธ | |
if (playerModel) { | |
playerModel.position.copy(camera.position); | |
playerModel.position.y -= 2; | |
// ์ด๋ ๋ฐฉํฅ์ ๋ฐ๋ฅธ ํ์ | |
if (moveState.forward || moveState.backward || moveState.left || moveState.right) { | |
const direction = new THREE.Vector3(); | |
camera.getWorldDirection(direction); | |
playerModel.rotation.y = Math.atan2(direction.x, direction.z); | |
} | |
} | |
// ์ ์ ๋ฐ์ดํธ | |
if (!isSafePeriod) { | |
enemies.forEach(enemy => { | |
const direction = new THREE.Vector3(); | |
direction.subVectors(camera.position, enemy.model.position); | |
direction.y = 0; | |
direction.normalize(); | |
enemy.velocity.add(direction.multiplyScalar(enemy.speed)); | |
enemy.velocity.multiplyScalar(0.98); | |
enemy.model.position.add(enemy.velocity); | |
// ์ ํ์ | |
enemy.model.lookAt(camera.position); | |
// ์ถฉ๋ ์ฒดํฌ | |
if (enemy.model.position.distanceTo(camera.position) < 3) { | |
gameOver(false); | |
} | |
}); | |
} | |
// ๋ฌด๊ธฐ ์ฒดํฌ | |
if (weapon) { | |
weapon.rotation.y += 0.02; | |
if (camera.position.distanceTo(weapon.position) < 3) { | |
gameOver(true); | |
} | |
} | |
} | |
renderer.render(scene, camera); | |
} | |
// ํ๋ฉด ํฌ๊ธฐ ์กฐ์ | |
window.addEventListener('resize', () => { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
}); | |
// ์์ | |
document.addEventListener('click', () => { | |
controls.lock(); | |
}); | |
animate(); | |
</script> | |
</body> | |
</html> |