Openworld3D / app.py
aiqtech's picture
Update app.py
644fbbd verified
raw
history blame
16 kB
# 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()