Spaces:
Running
Running
<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> |