← Public packages
@kentcdodds/space-shooter
Vertical scrolling spaceship shooter built with Three.js, playable on phone or desktop
src/starfield.ts
75 lines · 2.4 KB · TypeScriptimport * as THREE from 'three'
type StarfieldOptions = {
count: number
halfWidth: number
halfHeight: number
}
const NEAR_DEPTH = -2
const FAR_DEPTH = -40
const palette = [0xffffff, 0xbae6fd, 0xf5d0fe, 0xfde68a].map(
(hex) => new THREE.Color(hex),
)
export class Starfield {
readonly points: THREE.Points
private readonly positions: Float32Array
private readonly speeds: Float32Array
private readonly halfWidth: number
private readonly minY: number
private readonly maxY: number
constructor({ count, halfWidth, halfHeight }: StarfieldOptions) {
// Deeper stars are visible across a much wider area than the play field.
this.halfWidth = halfWidth * 3
this.minY = -halfHeight * 2.2
this.maxY = halfHeight * 2.6
this.positions = new Float32Array(count * 3)
this.speeds = new Float32Array(count)
const colors = new Float32Array(count * 3)
for (let i = 0; i < count; i++) {
const depth = THREE.MathUtils.randFloat(FAR_DEPTH, NEAR_DEPTH)
const nearness = (depth - FAR_DEPTH) / (NEAR_DEPTH - FAR_DEPTH)
this.positions[i * 3] = THREE.MathUtils.randFloatSpread(this.halfWidth * 2)
this.positions[i * 3 + 1] = THREE.MathUtils.randFloat(this.minY, this.maxY)
this.positions[i * 3 + 2] = depth
this.speeds[i] = 2 + nearness * 14
const color = palette[Math.floor(Math.random() * palette.length)]!
const brightness = 0.4 + nearness * 0.6
colors[i * 3] = color.r * brightness
colors[i * 3 + 1] = color.g * brightness
colors[i * 3 + 2] = color.b * brightness
}
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.BufferAttribute(this.positions, 3))
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
this.points = new THREE.Points(
geometry,
new THREE.PointsMaterial({
size: 0.24,
vertexColors: true,
transparent: true,
depthWrite: false,
}),
)
}
update(dt: number) {
const range = this.maxY - this.minY
for (let i = 0; i < this.speeds.length; i++) {
const yIndex = i * 3 + 1
this.positions[yIndex]! -= this.speeds[i]! * dt
if (this.positions[yIndex]! < this.minY) {
this.positions[yIndex]! += range
this.positions[i * 3] = THREE.MathUtils.randFloatSpread(this.halfWidth * 2)
}
}
this.points.geometry.attributes.position!.needsUpdate = true
}
}