Spaces:
Running
Running
import * as THREE from "three"; | |
import { Projectile } from "../../Projectile.js"; | |
import { findNearestWithinRange } from "../common/targeting.js"; | |
const VISUAL_TOP_INCREMENT = 0.08; | |
const VISUAL_TOP_CAP = 0.4; | |
export default { | |
key: "basic", | |
buildHead(tower) { | |
const headGeo = new THREE.BoxGeometry(0.8, 0.4, 0.8); | |
const headMat = new THREE.MeshStandardMaterial({ | |
color: 0x90caf9, | |
metalness: 0.18, | |
roughness: 0.45, | |
emissive: 0x000000, | |
emissiveIntensity: 0.6, | |
side: THREE.DoubleSide, | |
}); | |
const head = new THREE.Mesh(headGeo, headMat); | |
head.castShadow = true; | |
head.position.set(0, 0.8, 0); | |
tower.baseMesh.add(head); | |
tower.headMesh = head; | |
tower.head = head; | |
tower.headTopY = tower.mesh.position.y + head.position.y + 0.4; | |
}, | |
tryFire(tower, dt, enemies, projectiles, projectileSpeed) { | |
tower.fireCooldown -= dt; | |
if (tower.fireCooldown > 0) return; | |
const target = findNearestWithinRange(tower, enemies); | |
if (!target) return; | |
const dir = new THREE.Vector3().subVectors( | |
target.mesh.position, | |
tower.position | |
); | |
const yaw = Math.atan2(dir.x, dir.z); | |
tower.mesh.rotation.y = yaw; | |
tower.fireCooldown = 1 / tower.rate; | |
const spawnY = | |
typeof tower.headTopY === "number" ? tower.headTopY - 0.1 : 0.9; | |
const proj = new Projectile( | |
tower.position.clone().add(new THREE.Vector3(0, spawnY, 0)), | |
target, | |
projectileSpeed, | |
tower.scene, | |
tower.projectileEffect || null | |
); | |
proj.damage = tower.damage; | |
projectiles.push(proj); | |
tower.playShootSound(); | |
}, | |
applyVisualLevel(tower) { | |
const lvl = tower.level; | |
const head = tower.headMesh; | |
if (!head) return; | |
const baseMat = tower.baseMesh?.material; | |
const headMat = head.material; | |
if (tower.levelRing) { | |
tower.scene.remove(tower.levelRing); | |
tower.levelRing.geometry.dispose(); | |
if (tower.levelRing.material?.dispose) tower.levelRing.material.dispose(); | |
tower.levelRing = null; | |
} | |
// No height increment on level gain — only rings should reflect level | |
if (lvl <= 1) { | |
if (baseMat) { | |
baseMat.color?.set?.(0x5c6bc0); | |
baseMat.emissive?.set?.(0x000000); | |
baseMat.emissiveIntensity = 0.0; | |
} | |
// Keep original head height/position; recompute top using current head position | |
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.4; | |
headMat.color?.set?.(0x90caf9); | |
headMat.emissive?.set?.(0x4a0a2a); | |
headMat.emissiveIntensity = 0.2; | |
} else { | |
if (baseMat) { | |
baseMat.color?.set?.(0x6f7bd6); | |
baseMat.emissive?.set?.(0x2a0a1a); | |
baseMat.emissiveIntensity = 0.08; | |
} | |
// Keep original head height/position; recompute top using current head position | |
tower.headTopY = (tower.mesh?.position.y ?? 0.25) + head.position.y + 0.4; | |
headMat.color?.set?.(0xa5d6ff); | |
headMat.emissive?.set?.(0x9a135a); | |
headMat.emissiveIntensity = 0.35; | |
const ringGeom = new THREE.TorusGeometry(0.45, 0.035, 8, 24); | |
const ringMat = new THREE.MeshStandardMaterial({ | |
color: 0x3aa6ff, | |
emissive: 0xe01a6b, | |
emissiveIntensity: 0.55, | |
metalness: 0.3, | |
roughness: 0.45, | |
}); | |
const ring = new THREE.Mesh(ringGeom, ringMat); | |
ring.castShadow = false; | |
ring.receiveShadow = false; | |
const topY = tower.headTopY ?? head.position.y + 0.8; | |
ring.position.set( | |
tower.mesh.position.x, | |
topY + 0.02, | |
tower.mesh.position.z | |
); | |
ring.rotation.x = Math.PI / 2; | |
ring.name = "tower_level_ring"; | |
tower.levelRing = ring; | |
tower.scene.add(ring); | |
} | |
}, | |
}; | |