feat: App-Gerüst mit Spieler auf Testfläche

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-12 13:28:09 +02:00
1 parent a523dce676
commit b27b9b7a61
10 files changed
+3630

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
import { ACESFilmicToneMapping, AmbientLight, DirectionalLight, Mesh, MeshStandardMaterial,
PerspectiveCamera, PlaneGeometry, Scene, Timer, Vector3, WebGLRenderer } from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { playerController } from "three-player-controller";
const container = document.querySelector("#container");
const scene = new Scene();
const renderer = new WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.toneMapping = ACESFilmicToneMapping;
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
const camera = new PerspectiveCamera(55, container.clientWidth / container.clientHeight, 0.1, 2500);
scene.add(new AmbientLight(0xffffff, 1.5));
const sun = new DirectionalLight(0xffffff, 2);
sun.position.set(60, 120, 40);
sun.castShadow = true;
scene.add(sun);
const ground = new Mesh(new PlaneGeometry(400, 400), new MeshStandardMaterial({ color: 0x9aa88a }));
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
const gltf = await new GLTFLoader().loadAsync("./models/ual.glb");
const player = new playerController();
await player.init({
scene, camera,
colliders: [{ motion: "static", shape: { kind: "mesh", source: ground } }],
initPos: new Vector3(0, 2, 0),
playerModelConfig: {
model: gltf.scene, animations: gltf.animations, scale: 0.005,
idleAnim: "Idle_Loop", walkAnim: "Walk_Loop", runAnim: "Sprint_Loop",
jumpAnim: ["Jump_Start", "Jump_Loop", "Jump_Land"],
headBoneName: "Head", rotateY: -Math.PI / 2, speed: 100, runSpeed: 600,
},
});
const timer = new Timer();
timer.connect(document);
renderer.setAnimationLoop((t) => {
timer.update(t);
player.update(Math.min(timer.getDelta(), 1 / 40));
renderer.render(scene, camera);
});
addEventListener("resize", () => {
renderer.setSize(container.clientWidth, container.clientHeight);
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
});
+27
View File
@@ -0,0 +1,27 @@
export const TILE_PX = 256;
export const WORLD_HALF = 16384; // gtadb-Weltgrenze in Metern
export const CHUNK_SIZE = 250;
export const SLICE = { x0: -1000, x1: 2000, y0: -500, y1: 2500 };
export const CHUNKS_PER_SIDE = (SLICE.x1 - SLICE.x0) / CHUNK_SIZE; // 12
export const worldToThree = (X, Y) => ({ x: X, z: -Y });
export const threeToWorld = (x, z) => ({ X: x, Y: -z });
export function tileForWorld(X, Y) {
return {
row: Math.floor((WORLD_HALF - Y) / TILE_PX),
col: Math.floor((X + WORLD_HALF) / TILE_PX),
};
}
export function chunkForWorld(X, Y) {
return {
i: Math.floor((X - SLICE.x0) / CHUNK_SIZE),
j: Math.floor((SLICE.y1 - Y) / CHUNK_SIZE),
};
}
/** Nordwest-Ecke eines Chunks in Weltkoordinaten. */
export function chunkOrigin(i, j) {
return { X: SLICE.x0 + i * CHUNK_SIZE, Y: SLICE.y1 - j * CHUNK_SIZE };
}