Add skill system, level-ups, and new enemy types
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import * as THREE from 'three';
|
||||
import type { Game } from './Game';
|
||||
import { Enemy } from './Enemy';
|
||||
import { playSound } from './SoundManager';
|
||||
|
||||
export class Slime extends Enemy {
|
||||
private model: THREE.Group;
|
||||
private bodyMesh: THREE.Mesh;
|
||||
private time = Math.random() * Math.PI * 2;
|
||||
private size: number;
|
||||
|
||||
constructor(size = 0) {
|
||||
super();
|
||||
this.size = size;
|
||||
|
||||
this.model = new THREE.Group();
|
||||
this.fadeOutModel = this.model;
|
||||
this.body.add(this.model);
|
||||
|
||||
const color = size === 0 ? 0x33cc88 : 0x66eebb;
|
||||
const mat = new THREE.MeshToonMaterial({ color });
|
||||
this.bodyMesh = new THREE.Mesh(new THREE.SphereGeometry(0.6, 20, 16), mat);
|
||||
this.bodyMesh.position.y = 0.55;
|
||||
this.bodyMesh.castShadow = true;
|
||||
this.registerFadeMesh(this.bodyMesh);
|
||||
this.model.add(this.bodyMesh);
|
||||
|
||||
// Cute eyes (face toward the player: -Z)
|
||||
const eyeMat = new THREE.MeshToonMaterial({ color: 0x111122 });
|
||||
for (const side of [-1, 1]) {
|
||||
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.09, 10, 8), eyeMat);
|
||||
eye.position.set(side * 0.18, 0.72, -0.42);
|
||||
this.registerFadeMesh(eye);
|
||||
this.model.add(eye);
|
||||
}
|
||||
|
||||
if (size === 0) {
|
||||
this.health = 40;
|
||||
this.speed = 3.2;
|
||||
this.xp = 12;
|
||||
this.contactDps = 25;
|
||||
} else {
|
||||
this.health = 10;
|
||||
this.speed = 4;
|
||||
this.xp = 3;
|
||||
this.contactDps = 12;
|
||||
this.contactRadius = 1.5;
|
||||
this.model.scale.setScalar(0.55);
|
||||
this.grace = 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
playAnim() {
|
||||
// Procedural visuals only
|
||||
}
|
||||
|
||||
updateAnimation(dt: number) {
|
||||
if (this.grace > 0) this.grace -= dt;
|
||||
this.updateFadeDeath(dt, { duration: 0.7, shrink: 0.9 });
|
||||
}
|
||||
|
||||
update(dt: number, game: Game) {
|
||||
this.time += dt;
|
||||
|
||||
// Squishy pulse
|
||||
const pulse = 1 + Math.sin(this.time * 5) * 0.08;
|
||||
this.model.scale.y = (this.size === 0 ? 1 : 0.55) * pulse;
|
||||
const squash = 1 + (1 - pulse) * 0.5;
|
||||
this.model.scale.x = (this.size === 0 ? 1 : 0.55) * squash;
|
||||
this.model.scale.z = this.model.scale.x;
|
||||
|
||||
// Hop-wiggle toward the player
|
||||
const toPlayer = new THREE.Vector3()
|
||||
.subVectors(game.player.body.position, this.body.position);
|
||||
toPlayer.y = 0;
|
||||
const dist = toPlayer.length();
|
||||
if (dist > 0.1) {
|
||||
const dir = toPlayer.normalize();
|
||||
this.body.lookAt(
|
||||
this.body.position.x + dir.x,
|
||||
this.body.position.y,
|
||||
this.body.position.z + dir.z
|
||||
);
|
||||
let moveDir = dir.multiplyScalar(this.speed);
|
||||
if (this.knockback > 0) {
|
||||
moveDir = dir.multiplyScalar(-this.speed);
|
||||
this.knockback -= dt;
|
||||
}
|
||||
this.body.position.x += moveDir.x * dt;
|
||||
this.body.position.z += moveDir.z * dt;
|
||||
game.clampToArena(this.body.position);
|
||||
}
|
||||
}
|
||||
|
||||
protected onDeath(game: Game) {
|
||||
this.beginFadeDeath();
|
||||
if (this.size === 0) {
|
||||
playSound('spawn', 0.5);
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const child = new Slime(1);
|
||||
game.spawnEnemyInstance(
|
||||
child,
|
||||
this.body.position.x + (Math.random() - 0.5) * 1.2,
|
||||
this.body.position.z + (Math.random() - 0.5) * 1.2,
|
||||
0.1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user