Spaces:
Runtime error
Runtime error
# app.py | |
import gradio as gr | |
import json | |
import os | |
from pathlib import Path | |
import shutil | |
def create_game_html(model_path): | |
html_template = f""" | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Advanced 3D Open World Game</title> | |
<style> | |
body {{ margin: 0; }} | |
canvas {{ display: block; }} | |
#info {{ | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
background: rgba(0,0,0,0.7); | |
color: white; | |
padding: 10px; | |
border-radius: 5px; | |
font-family: Arial, sans-serif; | |
}} | |
#weather-controls {{ | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
background: rgba(0,0,0,0.7); | |
color: white; | |
padding: 10px; | |
border-radius: 5px; | |
}} | |
</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> | |
<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 scene = new THREE.Scene(); | |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
const 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'); | |
// ํฌ์ธํฐ ๋ฝ ์ปจํธ๋กค | |
const 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); | |
scene.fog.near = 1; | |
scene.fog.far = 1000; | |
// ๋ ์จ ๋ณ๊ฒฝ ํจ์ | |
window.changeWeather = function(weather) {{ | |
switch(weather) {{ | |
case 'clear': | |
rain.visible = false; | |
scene.fog.near = 1; | |
scene.fog.far = 1000; | |
break; | |
case 'rain': | |
rain.visible = true; | |
scene.fog.near = 1; | |
scene.fog.far = 100; | |
break; | |
case 'fog': | |
rain.visible = false; | |
scene.fog.near = 1; | |
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); | |
// ๋ฐฐ๊ฒฝ ํ๋ ์ค์ | |
const skyGeometry = new THREE.SphereGeometry(500, 32, 32); | |
const skyMaterial = new THREE.MeshBasicMaterial({{ | |
color: 0x87ceeb, | |
side: THREE.BackSide | |
}}); | |
const sky = new THREE.Mesh(skyGeometry, skyMaterial); | |
scene.add(sky); | |
// ๋๋ฌด์ ๋ฐ์ ์ถ๊ฐ | |
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); | |
// ์บ๋ฆญํฐ ๋ชจ๋ธ ๋ก๋ | |
let character; | |
const loader = new GLTFLoader(); | |
loader.load('{model_path}', (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); | |
}} | |
animate(); | |
// ์๋์ฐ ๋ฆฌ์ฌ์ด์ฆ ์ฒ๋ฆฌ | |
window.addEventListener('resize', () => {{ | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
}}); | |
</script> | |
</body> | |
</html> | |
""" | |
return html_template | |
def process_upload(glb_file): | |
if glb_file is None: | |
return None | |
# ์ ๋ก๋๋ ํ์ผ ์ฒ๋ฆฌ | |
upload_dir = "uploads" | |
os.makedirs(upload_dir, exist_ok=True) | |
file_path = Path(glb_file) | |
dest_path = Path(upload_dir) / file_path.name | |
shutil.copy(file_path, dest_path) | |
# ๊ฒ์ HTML ์์ฑ | |
game_html = create_game_html(str(dest_path)) | |
# HTML ํ์ผ ์ ์ฅ | |
game_path = Path("game.html") | |
with open(game_path, "w", encoding="utf-8") as f: | |
f.write(game_html) | |
return str(game_path) | |
# Gradio ์ธํฐํ์ด์ค ์ค์ | |
iface = gr.Interface( | |
fn=process_upload, | |
inputs=[ | |
gr.File( | |
label="Upload Character Model (GLB)", | |
type="filepath", | |
file_types=[".glb"] | |
) | |
], | |
outputs=gr.HTML(label="3D Open World Game"), | |
title="Advanced 3D Open World Game Generator", | |
description="Upload your character model (GLB) to create a 3D open world game with advanced features!", | |
cache_examples=False | |
) | |
if __name__ == "__main__": | |
iface.launch() |