Gunship-3D-FPS / game.js
gini1
Update game.js
12d84a4 verified
raw
history blame
13.4 kB
// game.js
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 = [
'./models/enemy1.glb', // μ‹€μ œ λͺ¨λΈ 파일 경둜둜 μˆ˜μ •
'./models/enemy2.glb',
'./models/enemy3.glb',
'./models/enemy4.glb'
];
const ENEMY_CONFIG = {
ATTACK_RANGE: 100,
ATTACK_INTERVAL: 2000,
BULLET_SPEED: 2
};
// κ²Œμž„ λ³€μˆ˜
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 createSoundPool = (soundUrl, poolSize = 10) => {
const sounds = [];
for (let i = 0; i < poolSize; i++) {
const sound = new Audio(soundUrl);
sounds.push(sound);
}
let currentIndex = 0;
return {
play: function() {
sounds[currentIndex].currentTime = 0;
sounds[currentIndex].play();
currentIndex = (currentIndex + 1) % poolSize;
}
};
};
// μ‚¬μš΄λ“œ μ΄ˆκΈ°ν™”
const sounds = {
bgm: new Audio('Music.wav'),
gunshot: createSoundPool('gun.wav', 10)
};
sounds.bgm.loop = true;
// 이동 μƒνƒœ
const moveState = {
forward: false,
backward: false,
left: false,
right: false
};
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);
// Controls μ„€μ •
controls = new PointerLockControls(camera, document.body);
// 이벀트 λ¦¬μŠ€λ„ˆ μ„€μ •
document.addEventListener('click', onClick);
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
window.addEventListener('resize', onWindowResize);
// μ§€ν˜• 생성
createTerrain();
// 적 λ‘œλ“œ
loadEnemies();
}
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) => {
console.log('Enemy model loaded successfully:', modelPath);
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),
lastAttackTime: 0
});
},
(xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
(error) => {
console.error('Error loading enemy model:', error);
}
);
}
}
function onClick() {
if (!controls.isLocked) {
controls.lock();
sounds.bgm.play();
} else if (ammo > 0) {
shoot();
}
}
function onKeyDown(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;
}
}
function onKeyUp(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 onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function shoot() {
if (ammo <= 0) return;
ammo--;
updateAmmoDisplay();
const bullet = createBullet();
bullets.push(bullet);
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 createEnemyBullet(enemy) {
const bulletGeometry = new THREE.SphereGeometry(0.5);
const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
bullet.position.copy(enemy.model.position);
const direction = new THREE.Vector3();
direction.subVectors(camera.position, enemy.model.position).normalize();
bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
scene.add(bullet);
return bullet;
}
function reload() {
ammo = 30;
updateAmmoDisplay();
}
function updateAmmoDisplay() {
document.getElementById('ammo').textContent = `Ammo: ${ammo}/30`;
}
function updateHealthBar() {
const healthElement = document.getElementById('health');
const healthPercentage = (playerHealth / MAX_HEALTH) * 100;
healthElement.style.width = `${healthPercentage}%`;
}
function updateHelicopterHUD() {
const altitude = Math.round(camera.position.y);
document.querySelector('#altitude-indicator span').textContent = altitude;
const speed = Math.round(
Math.sqrt(
moveState.forward * moveState.forward +
moveState.right * moveState.right
) * 100
);
document.querySelector('#speed-indicator span').textContent = speed;
const heading = Math.round(
(camera.rotation.y * (180 / Math.PI) + 360) % 360
);
document.querySelector('#compass span').textContent = heading;
updateRadar();
}
function updateRadar() {
const radarTargets = document.querySelector('.radar-targets');
radarTargets.innerHTML = '';
enemies.forEach(enemy => {
const relativePos = enemy.model.position.clone().sub(camera.position);
const distance = relativePos.length();
if (distance < 500) {
const angle = Math.atan2(relativePos.x, relativePos.z);
const normalizedDistance = distance / 500;
const dot = document.createElement('div');
dot.className = 'radar-dot';
dot.style.left = `${50 + Math.sin(angle) * normalizedDistance * 45}%`;
dot.style.top = `${50 + Math.cos(angle) * normalizedDistance * 45}%`;
radarTargets.appendChild(dot);
}
});
}
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);
}
}
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 updateEnemyBullets() {
for (let i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].position.add(enemyBullets[i].velocity);
if (enemyBullets[i].position.distanceTo(camera.position) < 3) {
playerHealth -= 10;
updateHealthBar();
scene.remove(enemyBullets[i]);
enemyBullets.splice(i, 1);
if (playerHealth <= 0) {
gameOver(false);
}
continue;
}
if (enemyBullets[i].position.distanceTo(camera.position) > 1000) {
scene.remove(enemyBullets[i]);
enemyBullets.splice(i, 1);
}
}
}
function updateEnemies() {
const currentTime = Date.now();
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);
const distanceToPlayer = enemy.model.position.distanceTo(camera.position);
if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE &&
currentTime - enemy.lastAttackTime > ENEMY_CONFIG.ATTACK_INTERVAL) {
enemyBullets.push(createEnemyBullet(enemy));
enemy.lastAttackTime = currentTime;
}
if (distanceToPlayer < 10) {
gameOver(false);
}
});
}
function checkGameStatus() {
if (enemies.length === 0 && currentStage < 5) {
currentStage++;
document.getElementById('stage').style.display = 'block';
document.getElementById('stage').textContent = `Stage ${currentStage}`;
setTimeout(() => {
document.getElementById('stage').style.display = 'none';
loadEnemies();
}, 2000);
}
}
function gameOver(won) {
isGameOver = true;
controls.unlock();
sounds.bgm.pause();
alert(won ? 'Mission Complete!' : 'Game Over!');
location.reload();
}
let lastTime = performance.now();
function gameLoop() {
const time = performance.now();
const delta = (time - lastTime) / 1000;
lastTime = time;
if (controls.isLocked && !isGameOver) {
updateMovement();
updateBullets();
updateEnemies();
updateEnemyBullets();
updateHelicopterHUD();
checkGameStatus();
}
renderer.render(scene, camera);
requestAnimationFrame(gameLoop);
}
// κ²Œμž„ μ‹œμž‘
init();
gameLoop();