Musical Critter
Exploration of creating a lively, animated character with sound! (Including breathing noises; hopefully those aren't too disconcerting.)
Use the arrow keys to move around. No mobile support.
function setup() {
createCanvas(400, 400);
characterPanner = new p5.Panner();
breatheNoise = new p5.Noise('pink')
breatheNoise.amp(0, 0)
walkNoise = new p5.Noise('white')
walkNoise.amp(0, 0)
jumpNoise = new p5.Noise('brown')
jumpNoise.amp(0, 0)
breatheNoise.disconnect()
breatheNoise.connect(characterPanner)
walkNoise.disconnect()
walkNoise.connect(characterPanner)
jumpNoise.disconnect()
jumpNoise.connect(characterPanner)
breatheNoise.start()
walkNoise.start()
jumpNoise.start()
platformSound = new p5.SinOsc()
platformSound.start()
platformSound.amp(0)
platformEnv = new p5.Envelope()
platformSound.disconnect()
platformSound.connect(platformEnv)
// smooth(1)
strokeWeight(1.5)
backgroundGradient = createImage(1, 2)
backgroundGradient.set(0, 0, [200, 230, 240, 200])
backgroundGradient.set(0, 1, [240, 250, 255, 200])
backgroundGradient.updatePixels()
}
let characterPanner
let backgroundGradient
let jumpNoise
let walkNoise
let breatheNoise
let platformSound
let platformEnv
let x = 50 // x, y, vx, vy of critter
let y = 300
let vx = 0
let vy = 0
let jumps = 0 // Remaining jumps
let jumping = false // Is jump currently held
let pr = 0 // Platform rotation
let sx = 1 // Walk direction, snapped to -1 or +1
let cx = 0 // Walk cycle, snapped to (2n + 1)* PI
let my = 0 // "Magical" on Y, used for double jump sound
let iy = -1.6 // "Inertia" on Y
let br = 1 // Breathing cycle - position
let vbr = 0 // Breathing cycle - velocity
let act = 0 // Smoothed activity rate, breathing frequency
let act2 = 0 // Smoothed activity rate, breathing amplitude
let volume = 0.3
function draw() {
image(backgroundGradient, 0, 0, width, height, 0, 0.5, 1, 1)
// Handle movement, exposing acceleration on x and y
let ax = 1.4 * (keyIsDown(RIGHT_ARROW) - keyIsDown(LEFT_ARROW))
vx += ax
vx *= 0.7
x += vx
let ay = 1.6
let ground = 0
if (y + vy >= 300 && vy >= 0) {
ground = 1
jumps = 2
ay = -vy * 0.75
y = 300
} else {
if (vy < 0 && keyIsDown(UP_ARROW)) {
ay -= 0.6
}
}
if (keyIsDown(UP_ARROW)) {
if (!jumping && jumps > 0) {
jumping = true
jumps --
if (!ground) {
my -= vy + ay + 18
}
ay -= vy + ay + 18
br -= 0.5
}
} else {
jumping = false
}
vy += ay
y += vy
// Handle platform animation
pr *= -0.5
pr -= ground * (ay / 3 - 30) * (x/400) * 0.001
let prc = pr * 1000 // Platform specular color
fill(250 + prc, 210 + prc, 60 + prc)
translate(10, 325)
shearX(0.4)
rotate(pr)
rect(-25, -25, 390, 50)
resetMatrix()
let force = -(ay) + ground * abs(ax) * 0.1
// Platform sounds
if (ground && force > 0.01) {
platformSound.freq(min(220 * 400 / abs(400 - x), 6000))
let am = map(abs(400 - x), 0, 400, 0.0, 0.2)
platformSound.amp((force ** 0.5) * am * volume)
platformEnv.play()
}
// Handle breathing
act += abs(ax) + max(-ay, 0)
act *= 0.999
act2 += abs(ax) + max(-ay, 0)
act2 *= 0.99
let bramp = 0.3 + act2 / 100 * 0.3 // breathing amplitude
let bramps = 0.09 + act2 / 100 * 0.08 // breathing amplitude - sound
let kvbr = 0.002 + act / 1000 * 0.005 // breathing "spring" stiffness
vbr -= kvbr * br
br += vbr
{
// Restore normal breathing cycle
// The points (br, vbr * svbr) form an ellipse
// So we slowly bring them out back to radius ~1
// (sine waves are so much nicer...)
let svbr = kvbr ** -0.5
let dbr = mag(br, vbr * svbr)
let factor = 1 + (1 / dbr - 1) * 0.01
br *= factor
vbr *= factor
}
breatheNoise.amp(max((1 - br ** 2) * bramps * volume, 0))
// Handle player's other variables
let t2 = millis() / 540 // Player head bob
my *= 0.8 // Smooth "magic" value for sound
cx += sin(cx + PI) * 0.2 // Snap walk cycle
cx += vx * 0.1 // Advance walk cycle
sx += vx * 0.3 // Advance direction
sx += (1 - abs(sx)) * Math.sign(sx) * 0.4 // Snap direction
sx += (1 - abs(sx)) * Math.sign(sx) * 0.4
iy = lerp(iy, ay - 1.6, 0.08) // Smoothened acceleration
let sy = (3 + iy / 1.6) ** 0.5 * 0.7 + my * 0.01 // Scaling factor dependent on Y, for offsetting triangles' coordinates
let cy = 1 + (abs(vy) ** 0.5) + my * 0.01 // Scaling factor dependent on VY, for modifying walk cycle scaling
translate(x, y)
// Body
fill(20, 140, 100)
triangle(
-18 * sy - br * bramp * 1 + sin(cx - 0.8) * 2,
25 + cos(cx - 0.8) ** 2 * 2 * cy,
0,
-15 + br * bramp * 1,
18 * sy + br * bramp * 1 + sin(cx + 0.8) * 2,
25 + cos(cx + 0.8) ** 2 * 2 * cy,
)
// Hoodie
fill(200, 50, 50)
triangle(
-20 * sy - br * bramp * 2 + sin(cx - 0.4) * 2,
15 + cos(cx - 0.4) ** 2 * 1 * cy,
0,
-25 + br * bramp * 1,
20 * sy + br * bramp * 2 + sin(cx + 0.4) * 2,
15 + cos(cx + 0.4) ** 2 * 1 * cy,
)
// Face
fill(0)
circle(sx * sy * 3 + br * bramp * 1 + sin(t2) * 1, -3 - br * bramp * 0.4, 12)
fill(255)
circle(sx * sy * 4 + br * bramp * 1 + sin(t2) * 1, -3 - br * bramp * 0.4, 9)
// Play sounds
walkNoise.amp(ground * (1 - cos(cx * 2)) ** 2 * 0.05 * volume, deltaTime/1000)
jumpNoise.amp(max((constrain(-1.6 - iy, 0, 1) * 0.8 - my * 0.03) * volume, 0), 0)
characterPanner.pan(map(x, 0, 400, -1, 1))
}
function mousePressed() {
userStartAudio();
}(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/3caSN606M)
Browse more articles?
← Copper melange Experiments tagged p5 (70/85) Ellipse-tracing system →
← Butterfly/bat Experiments tagged critter (5/6) Space critter →
← De Rahm curves Experiments tagged interactive (22/26) Reflection fractal →
← Copper melange Experiments on this site (70/85) Ellipse-tracing system →