Galactic Explorer
Every journey starts with a single step.
Source Code for this Sketch
class Timer{
constructor(delay) {
this.count = 0
this.delta = 0
this.delay = delay
}
update() {
this.delta += deltaTime
if(this.delta > this.delay) {
this.count++
this.delta %= this.delay
}
}
}
class StepToMouse{
// x and y are measured from the canvas origin (top left)
x = 0
y = 0
// cx and cy are centered coordinates; i.e. 0,0 is
// at the midpoint in the canvas
cx = 0
cy = 0
static def(input, defaultValue) {
if (typeof input === "number") {
return input
}
return defaultValue
}
constructor(step, minX, minY, maxX, maxY) {
// step determines how quickly we orient towards
// the mouse position. Each frame, we move at
// most this many units in any direction.
this.step = StepToMouse.def(step, 3)
// Set the bounds for valid input values
this.minX = StepToMouse.def(minX, 0)
this.minY = StepToMouse.def(minY, 0)
this.maxX = StepToMouse.def(maxX, width)
this.maxY = StepToMouse.def(maxY, height)
}
update() {
let deltaX = mouseX-this.x
let deltaY = mouseY-this.y
// Calculate the hypotenuse of our delta triangle
// so we can limit angular movement by this.step
let c = sqrt(pow(deltaX, 2)+pow(deltaY, 2))
// Multiply our x & y movement by a ratio of their
// length to the hypotenuse. For lateral or
// horizonal movement we'll have 1:0 or 0:1.
let ratioX = abs(deltaX) / c || 0
let ratioY = abs(deltaY) / c || 0
this.x += max(min(deltaX, this.step), -this.step)*ratioX
this.y += max(min(deltaY, this.step), -this.step)*ratioY
// Constrain x and y to valid area
this.x = min(this.x, this.maxX)
this.x = max(this.x, this.minX)
this.y = min(this.y, this.maxY)
this.y = max(this.y, this.minY)
this.cx = this.x - width/2
this.cy = this.y - height/2
}
}
class Star {
static minSize = 3
static maxSize = 6
// speedFactor controls how quickly stars approach
static speedFactor = 1
constructor() {
this.reset()
// More evenly distribute stars in their initial
// starting positions
this.r = pow(random(0, sqrt(this.limit)), 1.8)
}
reset() {
this.r = 0
this.x = 0
this.y = 0
this.speed = sqrt(random(.1, 8)) * Star.speedFactor
this.size = random(Star.minSize, Star.maxSize)
this.deg = random(0, 360)
this.hue = random(0, 360)
this.limit = max(width, height)
}
// multiplier returns a ratio of this.r to this.limit
// with the given minimum value. min should be between
// 0 and 1
multiplier(min) {
if (typeof min !== 'number') {
min = 0
}
return min + (this.r / this.limit * (1 - min))
}
draw() {
// Position from center
this.r += this.speed * this.multiplier(.2)
fill(this.hue, 70, 90)
this.x = cos(this.deg - 90) * this.r * width / this.limit
this.y = sin(this.deg - 90) * this.r * height / this.limit
circle(this.x, this.y, this.size * this.multiplier())
// detect when stars are outside the visible area and
// reset. note: assumes 0,0 coordinates are centered
if (this.x < -(width + this.size) ||
this.x > (width + this.size)) {
this.reset()
}
if (this.y < -(height + this.size) *.75 ||
this.y > (height + this.size) *.75) {
this.reset()
}
}
}
class Alien{
constructor() {
this.lights = new Timer(250)
}
radX(deg, r) {
return cos(deg-90)*r
}
radY(deg, r) {
return sin(deg-90)*r
}
eye(x, y) {
let deg = round(abs(atan2(mouseX-width/2, mouseY-height/2+30)-180))
// Stalks
stroke(80, 100, 50)
noFill()
bezier(x, 0, x+this.radX(deg, 5), this.radY(deg, 10), x-this.radX(deg, 15), y-this.radY(deg, 20), x, y)
// Eyeballs
noStroke()
fill(80, 100, 50)
circle(x+this.radX(deg, 2), y+this.radY(deg, 2), 10)
fill( 100)
circle(x+this.radX(deg, 3), y+this.radY(deg, 3), 6)
fill( 0)
circle(x+this.radX(deg, 4), y+this.radY(deg, 4), 3)
}
draw(input) {
this.lights.update()
push()
rectMode(CENTER)
// Position the ship
translate(width/2+input.cx/2, height/2+input.cy/2+30)
// Tilt for movement
let deltaX = min(max(mouseX, width*.1), width*.9) - width/2
rotate((deltaX-input.cx)/10)
// Cockpit
strokeWeight(1)
stroke(200, 100, 50, .7)
fill(200, 100, 50, .3)
arc(0, -10, 100, 100, 170, 10)
// Pilot
let alienElevation = 16
noStroke()
fill(80, 100, 50)
arc(0, -alienElevation, 40, 20, 180, 0)
rect(0, -alienElevation, 40, 5, 0, 0, 5, 5)
this.eye(-10, -45)
this.eye(10, -45)
// Hull
strokeWeight(1)
stroke(20)
fill(40)
for(let i = 0; i < 6; i++) {
ellipse(0, 0, 120-i*20, 30)
}
rect(0, 0, 120, 10, 5, 5, 5, 5)
// Hull lights
for(let i = 0; i < 11; i++) {
if(i%4 === this.lights.count) {
fill(65, 100, 50)
} else if ((i+1)%4 === this.lights.count) {
fill(310, 100, 50)
} else {
fill(200, 100, 50)
}
circle(-50+i*10, 0, 5)
if(this.lights.count >=4) {
this.lights.count = 0
}
}
pop()
}
}
let inputStarfield
let inputShip
let stars = []
let alien
function setup() {
createCanvas(800, 400, P2D, canvas)
colorMode(HSL)
angleMode(DEGREES)
ellipseMode(CENTER)
noStroke()
inputStarfield = new StepToMouse(.5, width*.4, height*.4, width*.6, height*.6)
// Initialize at center instead of 0,0
inputStarfield.x = width / 2
inputStarfield.y = height / 2
inputShip = new StepToMouse(5, width*.2, height*.2, width*.8, height*.8)
// Initialize at center instead of 0,0
inputShip.x = width / 2
inputShip.y = height / 2
for (const x of Array(400).keys()) {
stars.push(new Star())
}
alien = new Alien()
}
function draw() {
inputStarfield.update()
inputShip.update()
background(0)
push()
translate(width/2+inputStarfield.cx*4, inputStarfield.y)
// Flight sim controls, maybe
shearY(inputStarfield.cx/16)
shearX(inputStarfield.cy/16)
stars.map((star) => { star.draw() })
pop()
alien.draw(inputShip)
}
You may use all or part of the p5 code seen on this page for your own creations, without restriction. Attribution is appreciated but not required.
This sketch also includes the following sources: p5common.js . Sources are available under their respective licenses.