Gunship-3D-FPS / index.html
gini1
Update index.html
26e2815 verified
raw
history blame
15.6 kB
<!DOCTYPE html>
<html>
<head>
<title>Desert Combat Game</title>
<meta charset="utf-8">
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
}
#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: 20px;
z-index: 100;
border-radius: 5px;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: rgba(255, 0, 0, 0.8);
font-size: 24px;
z-index: 100;
user-select: none;
pointer-events: none;
}
#healthBar {
position: absolute;
bottom: 20px;
left: 20px;
width: 400px;
height: 30px;
background: rgba(0,0,0,0.5);
border: 3px solid white;
z-index: 100;
border-radius: 15px;
overflow: hidden;
}
#health {
width: 100%;
height: 100%;
background: linear-gradient(90deg, #ff3333, #ff0000);
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: 20px;
z-index: 100;
border-radius: 5px;
}
#stage {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
background: rgba(0,0,0,0.8);
padding: 20px;
font-family: Arial;
font-size: 32px;
z-index: 100;
border-radius: 10px;
display: none;
}
#gameTimer {
position: absolute;
top: 60px;
right: 10px;
color: white;
background: rgba(0,0,0,0.7);
padding: 10px;
font-family: Arial;
font-size: 20px;
z-index: 100;
border-radius: 5px;
}
</style>
</head>
<body>
<div id="info">
Click to start<br>
WASD - Move Helicopter<br>
Mouse - Aim<br>
Left Click - Shoot<br>
R - Reload
</div>
<div id="timer">Safe Time: 10s</div>
<div id="gameTimer">Time: 3:00</div>
<div id="crosshair">+</div>
<div id="healthBar"><div id="health"></div></div>
<div id="ammo">Ammo: 30/30</div>
<div id="stage">Stage 1</div>
<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';
// 게임 상수
const GAME_DURATION = 180;
const MAP_SIZE = 2000;
const HELICOPTER_HEIGHT = 50;
const ENEMY_SCALE = 3;
const MAX_HEALTH = 1000;
const ENEMY_MODELS = ['1.GLB', '2.GLB', '3.GLB', '4.GLB'];
// 게임 변수
let scene, camera, renderer, controls;
let enemies = [];
let bullets = [];
let enemyBullets = [];
let playerHealth = MAX_HEALTH;
let ammo = 30;
let currentStage = 1;
let isGameOver = false;
// 사운드
const sounds = {
bgm: new Audio('Music.wav'),
gunshot: new Audio('gun.wav')
};
sounds.bgm.loop = true;
function init() {
// Scene 생성
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb); // 하늘색
scene.fog = new THREE.Fog(0x87ceeb, 0, 1500);
// Camera 설정
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 3000);
camera.position.set(0, HELICOPTER_HEIGHT, 0);
// Renderer 설정
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// 조명 설정
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
dirLight.position.set(100, 100, 50);
dirLight.castShadow = true;
scene.add(dirLight);
// 지형 생성
createTerrain();
// Controls 설정
controls = new PointerLockControls(camera, document.body);
// 이벤트 리스너
document.addEventListener('click', onClick);
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
// 적 로드
loadEnemies();
// 애니메이션 시작
animate();
}
function createTerrain() {
// 지형 생성
const geometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 200, 200);
const material = new THREE.MeshStandardMaterial({
color: 0xD2B48C, // 사막색
roughness: 0.8,
metalness: 0.2
});
// 지형 높낮이 설정
const vertices = geometry.attributes.position.array;
for (let i = 0; i < vertices.length; i += 3) {
vertices[i + 2] = Math.sin(vertices[i] * 0.01) * Math.cos(vertices[i + 1] * 0.01) * 20;
}
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);
// 장애물 추가
addObstacles();
}
function addObstacles() {
// 바위 생성
const rockGeometry = new THREE.DodecahedronGeometry(10);
const rockMaterial = new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: 0.9
});
for (let i = 0; i < 100; i++) {
const rock = new THREE.Mesh(rockGeometry, rockMaterial);
rock.position.set(
(Math.random() - 0.5) * MAP_SIZE * 0.9,
Math.random() * 10,
(Math.random() - 0.5) * MAP_SIZE * 0.9
);
rock.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
Math.random() * Math.PI
);
rock.castShadow = true;
rock.receiveShadow = true;
scene.add(rock);
}
}
function loadEnemies() {
const loader = new GLTFLoader();
const enemyCount = 3 + currentStage;
for (let i = 0; i < enemyCount; i++) {
const modelPath = ENEMY_MODELS[i % ENEMY_MODELS.length];
loader.load(modelPath, (gltf) => {
const enemy = gltf.scene;
enemy.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
// 랜덤 위치에 배치
const angle = (i / enemyCount) * Math.PI * 2;
const radius = 200;
enemy.position.set(
Math.cos(angle) * radius,
10,
Math.sin(angle) * radius
);
enemy.traverse((node) => {
if (node.isMesh) {
node.castShadow = true;
node.receiveShadow = true;
node.material.metalness = 0.2;
node.material.roughness = 0.8;
}
});
scene.add(enemy);
enemies.push({
model: enemy,
health: 100,
speed: 0.3 + (currentStage * 0.1)
});
});
}
}
function onClick() {
if (!controls.isLocked) {
controls.lock();
sounds.bgm.play();
} else if (ammo > 0) {
shoot();
}
}
function onKeyDown(event) {
switch (event.code) {
case 'KeyR':
reload();
break;
}
}
function onKeyUp(event) {
// 키 해제 처리
}
function shoot() {
if (ammo <= 0) return;
ammo--;
updateAmmoDisplay();
const bullet = createBullet();
bullets.push(bullet);
sounds.gunshot.currentTime = 0;
sounds.gunshot.play();
}
function createBullet() {
const bulletGeometry = new THREE.SphereGeometry(0.5);
const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
bullet.position.copy(camera.position);
const direction = new THREE.Vector3();
camera.getWorldDirection(direction);
bullet.velocity = direction.multiplyScalar(3);
scene.add(bullet);
return bullet;
}
function reload() {
ammo = 30;
updateAmmoDisplay();
}
function updateAmmoDisplay() {
document.getElementById('ammo').textContent = `Ammo: ${ammo}/30`;
}
function animate() {
requestAnimationFrame(animate);
if (controls.isLocked && !isGameOver) {
updateBullets();
updateEnemies();
checkGameStatus();
}
renderer.render(scene, camera);
}
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) < 5) {
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);
}
}
});
// 범위 벗어난 총알 제거
if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 1000) {
scene.remove(bullets[i]);
bullets.splice(i, 1);
}
}
}
function updateEnemies() {
enemies.forEach(enemy => {
const direction = new THREE.Vector3();
direction.subVectors(camera.position, enemy.model.position);
direction.normalize();
enemy.model.position.add(direction.multiplyScalar(enemy.speed));
enemy.model.lookAt(camera.position);
// 플레이어와의 충돌 체크
if (enemy.model.position.distanceTo(camera.position) < 10) {
gameOver(false);
}
});
}
function checkGameStatus() {
if (enemies.length === 0 && currentStage < 5) {
currentStage++;
loadEnemies();
}
}
function gameOver(won) {
isGameOver = true;
controls.unlock();
sounds.bgm.pause();
alert(won ? 'Mission Complete!' : 'Game Over!');
location.reload();
}
// 화면 크기 조절 처리
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 게임 시작
init();
// 마우스 이동 속도 제어
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 updateMovement() {
if (controls.isLocked) {
const speed = 2.0;
if (moveState.forward) controls.moveForward(speed);
if (moveState.backward) controls.moveForward(-speed);
if (moveState.left) controls.moveRight(-speed);
if (moveState.right) controls.moveRight(speed);
}
}
// 메인 게임 루프에 이동 업데이트 추가
let lastTime = performance.now();
function gameLoop() {
const time = performance.now();
const delta = (time - lastTime) / 1000;
lastTime = time;
if (controls.isLocked && !isGameOver) {
updateMovement();
updateBullets();
updateEnemies();
checkGameStatus();
}
renderer.render(scene, camera);
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
</body>
</html>