Gunship-3D-FPS / index.html
gini1
Update index.html
975cece verified
raw
history blame
16.6 kB
<!DOCTYPE html>
<html>
<head>
<title>3D Shooter 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;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: rgba(255, 255, 255, 0.8);
font-size: 24px;
z-index: 100;
user-select: none;
pointer-events: none;
}
#healthBar {
position: absolute;
bottom: 20px;
left: 20px;
width: 200px;
height: 20px;
background: rgba(0,0,0,0.5);
border: 2px solid white;
z-index: 100;
border-radius: 10px;
overflow: hidden;
}
#health {
width: 100%;
height: 100%;
background: #ff3333;
transition: width 0.3s;
}
#ammo {
position: absolute;
bottom: 20px;
right: 20px;
color: white;
background: rgba(0,0,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>
Left Click - Shoot<br>
R - Reload
</div>
<div id="timer">Safe Time: 10s</div>
<div id="crosshair">+</div>
<div id="healthBar"><div id="health"></div></div>
<div id="ammo">Ammo: 30/30</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 = [], bullets = [];
let enemyBullets = [];
let isGameOver = false;
let isSafePeriod = true;
let startTime = 0;
let playerHealth = 100;
let ammo = 30;
const maxAmmo = 30;
const SAFE_TIME = 10;
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// Scene ์ดˆ๊ธฐํ™”
function initScene() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
scene.fog = new THREE.Fog(0x87ceeb, 0, 500);
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 createBullet(isEnemy = false) {
const bulletGeometry = new THREE.SphereGeometry(0.1, 8, 8);
const bulletMaterial = new THREE.MeshBasicMaterial({
color: isEnemy ? 0xff0000 : 0xffff00
});
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
if (!isEnemy) {
bullet.position.copy(camera.position);
const direction = new THREE.Vector3();
camera.getWorldDirection(direction);
bullet.velocity = direction.multiplyScalar(2);
}
scene.add(bullet);
return bullet;
}
// ๋ฐœ์‚ฌ
function shoot() {
if (ammo <= 0) return;
ammo--;
updateAmmoDisplay();
const bullet = createBullet();
bullets.push(bullet);
// ๋ฐœ์‚ฌ ์‚ฌ์šด๋“œ๋‚˜ ์ดํŽ™ํŠธ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ
}
// ์  ๋ฐœ์‚ฌ
function enemyShoot(enemy) {
const bullet = createBullet(true);
bullet.position.copy(enemy.model.position);
const direction = new THREE.Vector3();
direction.subVectors(camera.position, enemy.model.position);
direction.normalize();
bullet.velocity = direction.multiplyScalar(1);
enemyBullets.push(bullet);
}
// ์ด์•Œ ์—…๋ฐ์ดํŠธ
function updateBullets() {
// ํ”Œ๋ ˆ์ด์–ด ์ด์•Œ
for (let i = bullets.length - 1; i >= 0; i--) {
bullets[i].position.add(bullets[i].velocity);
// ์ ๊ณผ์˜ ์ถฉ๋Œ ์ฒดํฌ
enemies.forEach(enemy => {
if (bullets[i].position.distanceTo(enemy.model.position) < 2) {
scene.remove(bullets[i]);
bullets.splice(i, 1);
enemy.health -= 25;
if (enemy.health <= 0) {
scene.remove(enemy.model);
enemies = enemies.filter(e => e !== enemy);
}
return;
}
});
// ์ˆ˜๋ช… ์ œํ•œ
if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 100) {
scene.remove(bullets[i]);
bullets.splice(i, 1);
}
}
// ์  ์ด์•Œ
for (let i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].position.add(enemyBullets[i].velocity);
// ํ”Œ๋ ˆ์ด์–ด์™€์˜ ์ถฉ๋Œ ์ฒดํฌ
if (enemyBullets[i].position.distanceTo(camera.position) < 2) {
scene.remove(enemyBullets[i]);
enemyBullets.splice(i, 1);
playerHealth -= 10;
updateHealthBar();
if (playerHealth <= 0) {
gameOver(false);
}
continue;
}
// ์ˆ˜๋ช… ์ œํ•œ
if (enemyBullets[i] && enemyBullets[i].position.distanceTo(camera.position) > 100) {
scene.remove(enemyBullets[i]);
enemyBullets.splice(i, 1);
}
}
}
function updateHealthBar() {
const healthBar = document.getElementById('health');
healthBar.style.width = `${playerHealth}%`;
}
function updateAmmoDisplay() {
document.getElementById('ammo').textContent = `Ammo: ${ammo}/${maxAmmo}`;
}
// ์žฌ์žฅ์ „
function reload() {
ammo = maxAmmo;
updateAmmoDisplay();
}
// ์ง€ํ˜• ์ƒ์„ฑ
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 loadEnemies() {
const loader = new GLTFLoader();
loader.load('enemy.glb', (gltf) => {
console.log('Enemy model loaded successfully');
const enemyModel = gltf.scene;
for (let i = 0; i < 5; i++) {
const enemy = enemyModel.clone();
enemy.scale.set(1, 1, 1); // ํฌ๊ธฐ ์ฆ๊ฐ€
const angle = (i / 5) * Math.PI * 2;
const radius = 50; // ๋” ๊ฐ€๊น๊ฒŒ ์‹œ์ž‘
enemy.position.set(
Math.cos(angle) * radius,
2, // ์ง€๋ฉด์—์„œ ์•ฝ๊ฐ„ ์œ„๋กœ
Math.sin(angle) * radius
);
enemy.traverse((node) => {
if (node.isMesh) {
node.material = node.material.clone(); // ์žฌ์งˆ ๋ณต์ œ
node.material.metalness = 0.3;
node.material.roughness = 0.5;
node.castShadow = true;
node.receiveShadow = true;
}
});
scene.add(enemy);
enemies.push({
model: enemy,
health: 100,
speed: 0.2,
velocity: new THREE.Vector3(),
lastShot: 0
});
}
},
// ๋กœ๋”ฉ ์ง„ํ–‰์ƒํ™ฉ
(xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
// ์—๋Ÿฌ ์ฒ˜๋ฆฌ
(error) => {
console.error('Error loading enemy model:', error);
});
}
// ์ปจํŠธ๋กค ์ดˆ๊ธฐํ™”
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;
case 'KeyR': reload(); 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 (controls.isLocked) {
shoot();
} else {
controls.lock();
}
});
controls.addEventListener('lock', () => {
document.getElementById('info').style.display = 'none';
if (!startTime) {
startTime = Date.now();
}
});
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);
// ํšŒ์ „
enemy.model.lookAt(camera.position);
// ๋ฐœ์‚ฌ ๋กœ์ง
const now = Date.now();
if (now - enemy.lastShot > 2000) { // 2์ดˆ๋งˆ๋‹ค ๋ฐœ์‚ฌ
enemyShoot(enemy);
enemy.lastShot = now;
}
});
}
}
// ๊ฒŒ์ž„ ์˜ค๋ฒ„ ์ฒ˜๋ฆฌ
function gameOver(won) {
if (!isGameOver) {
isGameOver = true;
controls.unlock();
setTimeout(() => {
alert(won ? 'You won!' : 'Game Over! You were eliminated!');
location.reload();
}, 100);
}
}
// ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™”
function init() {
initScene();
createTerrain();
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);
// ์ด์•Œ ์—…๋ฐ์ดํŠธ
updateBullets();
// ์  ์—…๋ฐ์ดํŠธ
updateEnemies();
// ์Šน๋ฆฌ ์กฐ๊ฑด ์ฒดํฌ
if (!isSafePeriod && enemies.length === 0) {
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>