Add game source, converter pipeline, and web port

This commit is contained in:
2026-08-12 18:59:06 +02:00
parent 3e6635eac6
commit bd13ad67cc
97 changed files with 8781 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
import * as THREE from 'three';
import { Weapon } from './Weapon';
import { Player } from './Player';
import { loadGLB } from './MeshLoader';
import { playSound } from './SoundManager';
export class Sword extends Weapon {
private mixer: THREE.AnimationMixer | null = null;
private clips: Map<string, THREE.AnimationAction> = new Map();
constructor() {
super();
this.COOLDOWN1_TIME = 0.5;
this.mesh.position.set(-0.35, 0.3, 0.35);
this.mesh.scale.set(0.2, 0.2, 0.2);
this.mesh.rotation.set(0.3, 0, 0.3);
// Fallback geometry
const bladeGeom = new THREE.BoxGeometry(0.08, 1.0, 0.15);
const handleGeom = new THREE.CylinderGeometry(0.05, 0.05, 0.2, 8);
const guardGeom = new THREE.BoxGeometry(0.25, 0.06, 0.06);
const metalMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, metalness: 0.9, roughness: 0.3 });
const darkMat = new THREE.MeshStandardMaterial({ color: 0x444444, roughness: 0.6 });
const blade = new THREE.Mesh(bladeGeom, metalMat);
blade.position.y = 0.4;
blade.castShadow = true;
const guard = new THREE.Mesh(guardGeom, metalMat);
guard.position.y = -0.1;
const handle = new THREE.Mesh(handleGeom, darkMat);
handle.position.y = -0.25;
this.mesh.add(blade);
this.mesh.add(guard);
this.mesh.add(handle);
this._fallback = true;
}
async init() {
try {
const gltf = await loadGLB('sword');
const model = gltf.scene;
model.scale.set(1, 1, 1);
const swordTexture = new THREE.TextureLoader().load('./textures/sword.png');
swordTexture.flipY = false;
swordTexture.colorSpace = THREE.SRGBColorSpace;
model.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.castShadow = true;
child.material = new THREE.MeshToonMaterial({ map: swordTexture });
}
});
// Remove fallback geometry
while (this.mesh.children.length > 0) {
this.mesh.remove(this.mesh.children[0]);
}
this._fallback = false;
this.mesh.add(model);
if (gltf.animations.length > 0) {
this.mixer = new THREE.AnimationMixer(model);
for (const clip of gltf.animations) {
const action = this.mixer.clipAction(clip);
this.clips.set(clip.name, action);
}
const normal = this.clips.get('normal');
if (normal) {
normal.play();
}
}
} catch (e) {
console.warn('Failed to load sword model', e);
}
}
private _fallback = false;
updateAnimation(dt: number) {
if (this.mixer) this.mixer.update(dt);
}
skill1(_groundPoint: THREE.Vector3, player: Player): void {
if (this.cooldown1 > 0) return;
this.cooldown1 = this.COOLDOWN1_TIME;
playSound('sword_hit1', 0.5);
// Play skill1 animation
const action = this.clips.get('skill1');
if (action) {
action.reset();
action.setLoop(THREE.LoopOnce, 1);
action.setEffectiveTimeScale(2);
action.play();
}
const { game } = player;
for (const enemy of game.enemies) {
if (enemy.dead) continue;
const dist = enemy.body.position.distanceTo(player.body.position);
if (dist < 3) {
const toEnemy = new THREE.Vector3()
.subVectors(enemy.body.position, player.body.position).normalize();
const playerFwd = new THREE.Vector3(0, 0, 1);
playerFwd.applyQuaternion(player.body.quaternion);
const dot = playerFwd.dot(toEnemy);
if (dot > 0.3) {
enemy.takeDamage(50, game);
}
}
}
}
skill2(): void {
// Not implemented
}
}