← Public packages
@kentcdodds/space-shooter
Vertical scrolling spaceship shooter built with Three.js, playable on phone or desktop
src/models.ts
96 lines · 2.5 KB · TypeScriptimport * as THREE from 'three'
export type EnemyKind = 'drone' | 'weaver' | 'tank'
export type EnemyStats = {
radius: number
hp: number
speed: number
score: number
color: number
}
export function getEnemyStats(kind: EnemyKind): EnemyStats {
switch (kind) {
case 'drone':
return { radius: 0.8, hp: 1, speed: 6, score: 100, color: 0xf87171 }
case 'weaver':
return { radius: 0.8, hp: 2, speed: 4.5, score: 150, color: 0xe879f9 }
case 'tank':
return { radius: 1.1, hp: 5, speed: 2.2, score: 300, color: 0xfbbf24 }
default: {
const unknownKind: never = kind
throw new Error(`Unknown enemy kind: ${String(unknownKind)}`)
}
}
}
export const ENEMY_EMISSIVE_INTENSITY = 0.35
function createGlowMaterial(color: number, emissiveIntensity: number) {
return new THREE.MeshStandardMaterial({
color,
emissive: color,
emissiveIntensity,
flatShading: true,
roughness: 0.45,
metalness: 0.2,
})
}
const enemyGeometries: Record<EnemyKind, THREE.BufferGeometry> = {
drone: new THREE.OctahedronGeometry(0.85),
weaver: new THREE.TorusGeometry(0.6, 0.24, 6, 12),
tank: new THREE.IcosahedronGeometry(1.15),
}
export function createEnemyMesh(kind: EnemyKind) {
const { color } = getEnemyStats(kind)
// Each enemy gets its own material so hit flashes don't affect the others.
return new THREE.Mesh(
enemyGeometries[kind],
createGlowMaterial(color, ENEMY_EMISSIVE_INTENSITY),
)
}
export const playerBulletGeometry = new THREE.BoxGeometry(0.16, 0.9, 0.16)
export const playerBulletMaterial = new THREE.MeshBasicMaterial({ color: 0xa5f3fc })
export type PlayerShip = {
group: THREE.Group
flame: THREE.Mesh
}
export function createPlayerShip(): PlayerShip {
const group = new THREE.Group()
const hull = new THREE.Mesh(
new THREE.ConeGeometry(0.6, 2.2, 6),
createGlowMaterial(0x7dd3fc, 0.25),
)
group.add(hull)
const wings = new THREE.Mesh(
new THREE.BoxGeometry(2.4, 0.6, 0.15),
createGlowMaterial(0x38bdf8, 0.2),
)
wings.position.y = -0.4
group.add(wings)
const cockpit = new THREE.Mesh(
new THREE.SphereGeometry(0.28, 10, 8),
createGlowMaterial(0xf0abfc, 0.6),
)
cockpit.position.set(0, 0.15, 0.35)
group.add(cockpit)
const flame = new THREE.Mesh(
new THREE.ConeGeometry(0.35, 1, 8),
new THREE.MeshBasicMaterial({ color: 0xfb923c, transparent: true, opacity: 0.85 }),
)
flame.rotation.z = Math.PI
flame.position.y = -1.6
group.add(flame)
return { group, flame }
}