Gunship-3D-FPS / index.html
gini1
Update index.html
b01cf37 verified
raw
history blame
15.8 kB
<!DOCTYPE html>
<html>
<head>
<title>3D Open World Game</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
}
canvas {
display: block;
}
#info {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px;
border-radius: 5px;
}
#weather-controls {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px;
border-radius: 5px;
}
#fileUpload {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
}
#fileUpload.hidden {
display: none;
}
#startButton {
margin-top: 10px;
padding: 10px 20px;
background: #4CAF50;
border: none;
color: white;
border-radius: 5px;
cursor: pointer;
}
#startButton:disabled {
background: #666;
cursor: not-allowed;
}
</style>
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.149.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.149.0/examples/jsm/"
}
}
</script>
</head>
<body>
<div id="info">
Controls:<br>
W/S/A/D - Move<br>
SPACE - Jump<br>
Mouse - Look Around<br>
HP: <span id="hp">100</span>
</div>
<div id="weather-controls">
Weather:
<select id="weather-select" onchange="changeWeather(this.value)">
<option value="clear">Clear</option>
<option value="rain">Rain</option>
<option value="fog">Fog</option>
</select>
</div>
<div id="fileUpload">
<h2>3D Open World Game</h2>
<p>Upload your character model (GLB file)</p>
<input type="file" id="glbFile" accept=".glb" />
<br>
<button id="startButton" disabled>Start Game</button>
</div>
<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 character = null;
let uploadedModel = null;
// File upload handling
const fileInput = document.getElementById('glbFile');
const startButton = document.getElementById('startButton');
const uploadDiv = document.getElementById('fileUpload');
fileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
uploadedModel = URL.createObjectURL(file);
startButton.disabled = false;
}
});
startButton.addEventListener('click', function() {
if (uploadedModel) {
uploadDiv.classList.add('hidden');
initGame();
}
});
function initGame() {
// ๊ธฐ๋ณธ ์„ค์ •
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// ๋ฌผ๋ฆฌ ์‹œ์Šคํ…œ ๋ณ€์ˆ˜
const gravity = -0.5;
let velocity = new THREE.Vector3();
let isJumping = false;
let canJump = true;
let playerHeight = 2;
// ์บ๋ฆญํ„ฐ ์ƒํƒœ
let hp = 100;
const hpElement = document.getElementById('hp');
// ํฌ์ธํ„ฐ ๋ฝ ์ปจํŠธ๋กค
controls = new PointerLockControls(camera, document.body);
// ํ‚ค๋ณด๋“œ ์ž…๋ ฅ ์ฒ˜๋ฆฌ
const moveState = {
forward: false,
backward: false,
left: false,
right: false,
jump: 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 'Space':
if (canJump) {
moveState.jump = true;
velocity.y = 10;
isJumping = true;
canJump = false;
}
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;
case 'Space': moveState.jump = false; break;
}
});
// ํด๋ฆญ์œผ๋กœ ๊ฒŒ์ž„ ์‹œ์ž‘
document.addEventListener('click', () => {
controls.lock();
});
// ๋‚ ์”จ ์‹œ์Šคํ…œ
let raindrops = [];
const rainCount = 1000;
const rainGeometry = new THREE.BufferGeometry();
const rainMaterial = new THREE.PointsMaterial({
color: 0xaaaaaa,
size: 0.1,
transparent: true
});
function createRain() {
const positions = new Float32Array(rainCount * 3);
for (let i = 0; i < rainCount * 3; i += 3) {
positions[i] = (Math.random() - 0.5) * 1000;
positions[i + 1] = Math.random() * 500;
positions[i + 2] = (Math.random() - 0.5) * 1000;
}
rainGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const rain = new THREE.Points(rainGeometry, rainMaterial);
scene.add(rain);
return rain;
}
let rain = createRain();
rain.visible = false;
// ์•ˆ๊ฐœ ์„ค์ •
scene.fog = new THREE.Fog(0xcce0ff, 1, 1000);
// ๋‚ ์”จ ๋ณ€๊ฒฝ ํ•จ์ˆ˜
window.changeWeather = function(weather) {
switch(weather) {
case 'clear':
rain.visible = false;
scene.fog.far = 1000;
break;
case 'rain':
rain.visible = true;
scene.fog.far = 100;
break;
case 'fog':
rain.visible = false;
scene.fog.far = 50;
break;
}
};
// NPC ์‹œ์Šคํ…œ
class NPC {
constructor(position) {
const geometry = new THREE.CapsuleGeometry(1, 2, 4, 8);
const material = new THREE.MeshPhongMaterial({ color: 0xff0000 });
this.mesh = new THREE.Mesh(geometry, material);
this.mesh.position.copy(position);
this.mesh.castShadow = true;
this.velocity = new THREE.Vector3();
this.direction = new THREE.Vector3();
scene.add(this.mesh);
}
update(playerPosition) {
this.direction.subVectors(playerPosition, this.mesh.position);
this.direction.normalize();
this.velocity.add(this.direction.multiplyScalar(0.1));
this.velocity.multiplyScalar(0.95);
this.mesh.position.add(this.velocity);
const distance = this.mesh.position.distanceTo(playerPosition);
if (distance < 2) {
hp -= 1;
hpElement.textContent = hp;
if (hp <= 0) {
alert('Game Over!');
location.reload();
}
}
}
}
// NPC ์ƒ์„ฑ
const npcs = [];
for (let i = 0; i < 5; i++) {
const position = new THREE.Vector3(
(Math.random() - 0.5) * 100,
2,
(Math.random() - 0.5) * 100
);
npcs.push(new NPC(position));
}
// ์ง€ํ˜• ์ƒ์„ฑ
const terrainGeometry = new THREE.PlaneGeometry(1000, 1000, 100, 100);
const terrainMaterial = new THREE.MeshStandardMaterial({
color: 0x3a8c3a,
wireframe: false
});
const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
terrain.rotation.x = -Math.PI / 2;
terrain.receiveShadow = true;
scene.add(terrain);
// ์ง€ํ˜• ๋†’๋‚ฎ์ด ์„ค์ •
const vertices = terrainGeometry.attributes.position.array;
for (let i = 0; i < vertices.length; i += 3) {
vertices[i + 2] = Math.random() * 10;
}
terrainGeometry.attributes.position.needsUpdate = true;
terrainGeometry.computeVertexNormals();
// ์กฐ๋ช… ์„ค์ •
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(100, 100, 100);
directionalLight.castShadow = true;
scene.add(directionalLight);
// ํ™˜๊ฒฝ ์˜ค๋ธŒ์ ํŠธ ์ถ”๊ฐ€
function addEnvironmentObject(geometry, material, count, yOffset) {
for (let i = 0; i < count; i++) {
const mesh = new THREE.Mesh(geometry, material);
const x = (Math.random() - 0.5) * 900;
const z = (Math.random() - 0.5) * 900;
const y = yOffset;
mesh.position.set(x, y, z);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
}
}
// ๋‚˜๋ฌด ์ƒ์„ฑ
const treeGeometry = new THREE.ConeGeometry(2, 8, 8);
const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x0d5c0d });
addEnvironmentObject(treeGeometry, treeMaterial, 100, 4);
// ๋ฐ”์œ„ ์ƒ์„ฑ
const rockGeometry = new THREE.DodecahedronGeometry(2);
const rockMaterial = new THREE.MeshStandardMaterial({ color: 0x666666 });
addEnvironmentObject(rockGeometry, rockMaterial, 50, 1);
// ์บ๋ฆญํ„ฐ ๋ชจ๋ธ ๋กœ๋“œ
const loader = new GLTFLoader();
loader.load(uploadedModel, (gltf) => {
character = gltf.scene;
character.scale.set(0.5, 0.5, 0.5);
character.position.y = 2;
character.castShadow = true;
character.receiveShadow = true;
scene.add(character);
character.traverse((node) => {
if (node.isMesh) {
node.castShadow = true;
node.receiveShadow = true;
}
});
});
// ์นด๋ฉ”๋ผ ์ดˆ๊ธฐ ์œ„์น˜
camera.position.set(0, playerHeight, 0);
// ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฃจํ”„
function animate() {
requestAnimationFrame(animate);
if (controls.isLocked) {
const direction = new THREE.Vector3();
const rotation = camera.getWorldDirection(new THREE.Vector3());
if (moveState.forward) direction.add(rotation);
if (moveState.backward) direction.sub(rotation);
if (moveState.left) direction.cross(camera.up).negate();
if (moveState.right) direction.cross(camera.up);
direction.y = 0;
direction.normalize();
// ๋ฌผ๋ฆฌ ์‹œ์Šคํ…œ ์ ์šฉ
velocity.y += gravity;
camera.position.y += velocity.y * 0.1;
// ๋ฐ”๋‹ฅ ์ถฉ๋Œ ๊ฒ€์‚ฌ
if (camera.position.y <= playerHeight) {
camera.position.y = playerHeight;
velocity.y = 0;
isJumping = false;
canJump = true;
}
// ์ด๋™ ์†๋„ ์ ์šฉ
const moveSpeed = 0.5;
controls.moveRight(-direction.z * moveSpeed);
controls.moveForward(direction.x * moveSpeed);
// ์บ๋ฆญํ„ฐ ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ
if (character) {
character.position.copy(camera.position);
character.position.y -= playerHeight;
if (direction.length() > 0) {
const angle = Math.atan2(direction.x, direction.z);
character.rotation.y = angle;
}
}
// ๋น„ ์—…๋ฐ์ดํŠธ
if (rain.visible) {
const positions = rain.geometry.attributes.position.array;
for (let i = 1; i < positions.length; i +=3) {
positions[i] -= 2;
if (positions[i] < 0) {
positions[i] = 500;
}
}
rain.geometry.attributes.position.needsUpdate = true;
}
// NPC ์—…๋ฐ์ดํŠธ
npcs.forEach(npc => npc.update(camera.position));
}
renderer.render(scene, camera);
}
// ์œˆ๋„์šฐ ๋ฆฌ์‚ฌ์ด์ฆˆ ์ฒ˜๋ฆฌ
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ์ž‘
animate();
}
// ๋‚ ์”จ ๋ณ€๊ฒฝ ํ•จ์ˆ˜๋ฅผ ์ „์—ญ์œผ๋กœ ๋…ธ์ถœ
window.changeWeather = function(weather) {
// ์ด ํ•จ์ˆ˜๋Š” ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™” ํ›„ ์žฌ์ •์˜๋ฉ๋‹ˆ๋‹ค
console.log('Game not initialized yet');
};
</script>
</body>
</html>