Gunship-3D-FPS / index.html
gini1
Update index.html
5ccac0e verified
raw
history blame
13.2 kB
<!DOCTYPE html>
<html>
<head>
<title>3D Survival Game</title>
<meta charset="utf-8">
<style>
body {
margin: 0;
overflow: hidden;
}
#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;
user-select: none;
}
#timer {
position: absolute;
top: 10px;
right: 10px;
color: white;
background: rgba(0,0,0,0.7);
padding: 10px;
font-family: Arial;
font-size: 14px;
z-index: 100;
border-radius: 5px;
}
#targetAlert {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: yellow;
background: rgba(0,0,0,0.8);
padding: 20px;
font-family: Arial;
font-size: 24px;
z-index: 100;
border-radius: 10px;
display: none;
}
</style>
</head>
<body>
<div id="info">
Click to start<br>
WASD - Move<br>
Mouse - Look around<br>
Find and touch your clone to win!<br>
Avoid the enemies!
</div>
<div id="timer">Safe Time: 10s</div>
<div id="targetAlert">Find and touch your clone to win!</div>
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.157.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.157.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
// ๊ธฐ๋ณธ ๋ณ€์ˆ˜
let scene, camera, renderer, controls;
let enemies = [], target;
let isGameOver = false;
let isSafePeriod = true;
let startTime = 0;
const SAFE_TIME = 10; // 10์ดˆ ์•ˆ์ „์‹œ๊ฐ„
// Scene ์ดˆ๊ธฐํ™”
function initScene() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
scene.fog = new THREE.Fog(0x87ceeb, 0, 300);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 0);
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 ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(50, 50, 0);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
dirLight.shadow.camera.far = 300;
scene.add(dirLight);
// ๋ฐ˜์‚ฌ๊ด‘ ์ถ”๊ฐ€
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6);
scene.add(hemiLight);
}
// ์ง€ํ˜• ์ƒ์„ฑ
function createTerrain() {
const geometry = new THREE.PlaneGeometry(300, 300, 50, 50);
const material = new THREE.MeshStandardMaterial({
color: 0x3a8c3a,
roughness: 0.6,
metalness: 0.1
});
const vertices = geometry.attributes.position.array;
for (let i = 0; i < vertices.length; i += 3) {
vertices[i + 2] = Math.random() * 3;
}
geometry.attributes.position.needsUpdate = true;
geometry.computeVertexNormals();
const terrain = new THREE.Mesh(geometry, material);
terrain.rotation.x = -Math.PI / 2;
terrain.receiveShadow = true;
scene.add(terrain);
}
// ๋‚˜๋ฌด ์ƒ์„ฑ
function createTrees() {
const treeGeo = new THREE.CylinderGeometry(0, 4, 15, 8);
const treeMat = new THREE.MeshStandardMaterial({
color: 0x0d5c0d,
roughness: 0.8,
metalness: 0.2
});
for (let i = 0; i < 100; i++) {
const tree = new THREE.Mesh(treeGeo, treeMat);
tree.position.set(
(Math.random() - 0.5) * 250,
7.5,
(Math.random() - 0.5) * 250
);
tree.castShadow = true;
tree.receiveShadow = true;
scene.add(tree);
}
}
// ๋ชฉํ‘œ๋ฌผ(Clone) ์ƒ์„ฑ
function spawnTarget() {
const loader = new GLTFLoader();
loader.load('me.glb', (gltf) => {
target = gltf.scene;
target.scale.set(0.5, 0.5, 0.5);
target.position.set(
(Math.random() - 0.5) * 200,
0,
(Math.random() - 0.5) * 200
);
target.traverse((node) => {
if (node.isMesh) {
node.material.metalness = 0.3;
node.material.roughness = 0.5;
node.castShadow = true;
node.receiveShadow = true;
}
});
scene.add(target);
document.getElementById('targetAlert').style.display = 'block';
setTimeout(() => {
document.getElementById('targetAlert').style.display = 'none';
}, 3000);
});
}
// ์  ๋กœ๋“œ
function loadEnemies() {
const loader = new GLTFLoader();
loader.load('enemy.glb', (gltf) => {
const enemyModel = gltf.scene;
for (let i = 0; i < 5; i++) {
const enemy = enemyModel.clone();
enemy.scale.set(0.5, 0.5, 0.5);
const angle = (i / 5) * Math.PI * 2;
const radius = 100;
enemy.position.set(
Math.cos(angle) * radius,
0,
Math.sin(angle) * radius
);
enemy.traverse((node) => {
if (node.isMesh) {
node.material.metalness = 0.3;
node.material.roughness = 0.5;
node.castShadow = true;
node.receiveShadow = true;
}
});
scene.add(enemy);
enemies.push({
model: enemy,
speed: 0.2,
velocity: new THREE.Vector3(),
rotation: new THREE.Euler()
});
}
console.log('Enemies loaded');
});
}
// ์ปจํŠธ๋กค ์ดˆ๊ธฐํ™”
function initControls() {
controls = new PointerLockControls(camera, document.body);
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;
}
});
document.addEventListener('click', () => {
if (!startTime) {
startTime = Date.now();
setTimeout(spawnTarget, Math.random() * 5000 + 5000);
}
controls.lock();
});
controls.addEventListener('lock', () => {
document.getElementById('info').style.display = 'none';
});
controls.addEventListener('unlock', () => {
document.getElementById('info').style.display = 'block';
});
return moveState;
}
// ์  ์—…๋ฐ์ดํŠธ
function updateEnemies() {
if (!isSafePeriod) {
enemies.forEach(enemy => {
const direction = new THREE.Vector3();
direction.subVectors(camera.position, enemy.model.position);
direction.y = 0;
direction.normalize();
// ๋ถ€๋“œ๋Ÿฌ์šด ์†๋„ ๋ณ€ํ™”
enemy.velocity.lerp(direction.multiplyScalar(enemy.speed), 0.02);
enemy.model.position.add(enemy.velocity);
// ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „
const targetRotation = Math.atan2(direction.x, direction.z);
enemy.model.rotation.y = THREE.MathUtils.lerp(
enemy.model.rotation.y,
targetRotation,
0.05
);
// ์ถฉ๋Œ ์ฒดํฌ
if (enemy.model.position.distanceTo(camera.position) < 3) {
gameOver(false);
}
});
}
}
// ๊ฒŒ์ž„ ์˜ค๋ฒ„ ์ฒ˜๋ฆฌ
function gameOver(won) {
if (!isGameOver) {
isGameOver = true;
controls.unlock();
setTimeout(() => {
alert(won ? 'You found your clone and won!' : 'Game Over! You were caught!');
location.reload();
}, 100);
}
}
// ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™”
function init() {
initScene();
createTerrain();
createTrees();
loadEnemies();
const moveState = initControls();
// ๊ฒŒ์ž„ ๋ฃจํ”„
function animate() {
requestAnimationFrame(animate);
if (controls.isLocked && !isGameOver) {
// ์•ˆ์ „ ์‹œ๊ฐ„ ์ฒดํฌ
if (startTime) {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
const remaining = SAFE_TIME - elapsed;
if (remaining > 0) {
document.getElementById('timer').textContent = `Safe Time: ${remaining}s`;
} else {
document.getElementById('timer').textContent = '';
isSafePeriod = false;
}
}
// ์ด๋™ ์ฒ˜๋ฆฌ
const speed = 0.3;
if (moveState.forward) controls.moveForward(speed);
if (moveState.backward) controls.moveForward(-speed);
if (moveState.left) controls.moveRight(-speed);
if (moveState.right) controls.moveRight(speed);
// ์  ์—…๋ฐ์ดํŠธ
updateEnemies();
// ๋ชฉํ‘œ๋ฌผ ์ฒดํฌ
if (target) {
target.rotation.y += 0.01;
if (camera.position.distanceTo(target.position) < 3) {
gameOver(true);
}
}
}
renderer.render(scene, camera);
}
animate();
}
// ํ™”๋ฉด ํฌ๊ธฐ ์กฐ์ ˆ ์ฒ˜๋ฆฌ
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// ๊ฒŒ์ž„ ์‹œ์ž‘
init();
</script>
</body>
</html>