What Is SWGhost?
SWGhost is a self-contained composite class that draws a complete, animated
ghost character using the p5.js canvas library. It bundles a rounded
head disk, a rectangular body, configurable toe-bump disks along the bottom edge, and
two optional SWEyeball instances — all positioned relative to a
single anchor point so the whole figure moves as one.
Rather than managing a dozen separate shape variables in your sketch (six disks, one
rectangle, two point anchors…), you create one SWGhost, set a few
properties, and call three methods per frame. The class grew over six motivated
stages recorded in the SWGhost Saga.
How the Layers Fit Together
Inside every SWGhost instance, _build() assembles these parts from one anchor point:
Quick-Start: A Ghost in a Sketch
Minimum code to get a draggable, eye-equipped ghost on screen:
The _build() Pattern
SWGhost uses a compose-on-move approach: all child parts
(SWDisk, SWRectangle, SWEyeball) are created
(or recreated) by the private _build() method. This fires in the
constructor and again every time the ghost changes position or a structural property
changes. This keeps coordinate math in one single, testable place.
SWRectangle pre-computes its four vertex coordinates at construction time,
so moving one means rebuilding it from scratch. Rather than special-casing each child
type, SWGhost rebuilds all of them uniformly on every position change —
simple, predictable, and correct.
Dependency Chain
Constructor Signature
setup() — after
calling colorMode() and initializeSWColors(). The eye
geometry uses p5.js random() which is only available once p5 is running.
Omitting eyeColors or pupilColors (or passing
null) renders the ghost without eyes — useful for early stages.
Public Properties You Can Read and Write
| Property | Type | Description |
|---|---|---|
radius | number | Head-cap radius in user-units. Also controls body width (radius × 2) and eye sizing. |
bodyHeight | number | Current height of the body rectangle. Modified each frame by stretching(). |
numToes | number | Number of toe-bump disks (0–8). Change via setNumToes() to trigger a rebuild. |
color | SWColor | Fill color applied to all ghost parts. Change via setGhostColor() to trigger a rebuild. |
strokeColor | SWColor | Border color. null / undefined = no stroke. |
shouldShowStroke | boolean | Master stroke toggle. false suppresses borders on all parts even if strokeColor is set. Default: true. |
isDraggable | boolean | Whether the ghost responds to mouse drag. Default: true. |
eyeColors | SWColor[2] | [scleraFill, scleraBorder]. null = no eyes. |
pupilColors | SWColor[2] | [pupilFill, irisStroke]. null = no eyes. |
Eye Geometry (Randomized at Construction)
Eye sizing is computed once at construction from two random factors and then locked for the lifetime of the instance. This gives each ghost slightly unique eyes.
Stretch Rest Height
| Property | Type | Description |
|---|---|---|
_baseBodyHeight | number private | Rest body height captured at construction. stretching() oscillates bodyHeight around this value. Read via ghost._baseBodyHeight if needed. |
Private / Internal References
Set by _build(); available for reading but should not be overwritten directly:
| Property | Type | Description |
|---|---|---|
_headCenter | SWPoint | The single anchor point. All other positions derive from it. |
_ghostHead | SWDisk | The semicircular head cap disk. Re-uses the _headCenter reference directly. |
_ghostBody | SWRectangle | The rectangular torso. Fresh instance each _build(). |
_ghostToes | SWDisk[] | Array of toe-bump disks. Empty when numToes === 0. |
_leftEye | SWEyeball | Left eye instance. null when eyes are disabled. |
_rightEye | SWEyeball | Right eye instance. null when eyes are disabled. |
_irisColor | SWColor | Persisted iris color so setIrisColor() survives rebuilds. Initially null. |
_irisThickness | number | Persisted iris ring weight. Default: 6 px. |
_isDragging | boolean | True while a ghost-body drag is in progress. |
Drawing
drawOnGrid(grid) — Call every frame from draw().
Renders all ghost parts in the correct z-order (toes → body → head → eyes).
Applies the current shouldShowStroke toggle before drawing so you can
hide borders at any time without a rebuild.
Position
setPosition(x, y)
Moves the head anchor to (x, y) in grid user-units and
calls _build() to reposition every child part. This is the correct way
to move a ghost programmatically — for example during sinusoid-driven hover
animation.
headCenter → SWPoint (read-only getter)
Returns the internal head-anchor SWPoint. Use headCenter.x and headCenter.y to read the current position.
Animation
stretching(sinusoid, t)
Drives bodyHeight with an SWSinusoid each frame, making the
ghost grow and shrink vertically. The head stays fixed; the body rectangle and toe
bumps shift downward to accommodate the changing height. Analogous to
SWRectangle.breathe().
Call each frame from draw(), before drawOnGrid().
Can run simultaneously with hover (setPosition()) — hover fires
first to set the correct position, then stretching() modifies the height
at that new position.
Hover, stretch, and Perlin-noise eye wander can all run simultaneously because they modify different aspects of the ghost. The draw order that keeps them correct:
ghost.setPosition(x, y)— moves anchor (hover)ghost.stretching(sSin, t)— adjusts body height (stretch)- Eye look/wander calls — update pupil offsets (eye)
ghost.drawOnGrid(grid)— renders everything
Live Appearance Changes
setGhostColor(color)
Changes the fill color of the entire ghost (head, body, toes) and triggers an immediate _build() so the new color takes effect this frame.
setNumToes(n)
Sets the number of toe-bump disks (0–8, clamped). Triggers an immediate _build().
setIrisColor(color)
Updates the iris ring color on both eyes and stores it internally so the
color persists through all subsequent _build() calls. Without this
persistence, the color would reset whenever the ghost moved (because setPosition
triggers a rebuild that creates brand-new SWEyeball instances).
setIrisThickness(val)
Sets the iris ring stroke weight in screen pixels. Persisted exactly like setIrisColor().
Mouse Interaction
handleMousePressed(mx, my, grid, tolerance=0) → boolean
Call from p5.js mousePressed(). Eye-pupil clicks are checked first and
take priority over ghost-body drags. Returns true if this ghost
consumed the event so other handlers can be skipped.
handleMouseDragged(mx, my, grid) → boolean
Call from p5.js mouseDragged(). If an eye pupil is being dragged, the
eyes handle the event and the ghost body stays still. Otherwise, the entire ghost
moves, preserving the original click offset so the ghost doesn't jump.
handleMouseReleased()
Clears the drag state on both the ghost body and both eyes.
Getters (Read-Only Properties)
isDragging → boolean
True while the user is actively dragging the ghost body. Does not reflect eye-pupil drags.
Accessing the Eyes Directly
The left and right SWEyeball instances are exposed as _leftEye
and _rightEye. You can call any SWEyeball method on them
directly — useful for eye animation such as lookingAround(),
lookAt(), and lookAtAngle().
Utility
toString() → string
The Ghost Saga — Six Stages of Development
The SWGhost Saga documents how the SWGhost class was
built from scratch through six progressive stages, adding one major capability per
stage. Each stage has a live interactive demo you can open directly.
Stage 1 A Framework
The ghost is assembled by hand from primitive shapes directly in the sketch: a head disk, a body rectangle, and toe disks. No class yet — just proof that the geometry works. Establishes the anchor-point system and draw order (toes → body → head) that all later stages inherit.
Open Ghost 1 →Stage 2 SWGhost Class
All Stage 1 geometry is wrapped inside the new SWGhost class.
The constructor, _build(), drawOnGrid(),
setPosition(), and full mouse-drag interaction are established.
The sketch reduces to a handful of lines.
Stage 3 The Eyes Have It
Two SWEyeball instances are added to SWGhost, wired
through the optional eyeColors / pupilColors constructor
parameters. Eye geometry is randomized once at construction, giving each ghost
slightly unique eyes. Pupil drag and mouse-follow are both supported.
Stage 4 Haunted Hover
Sinusoid-driven hover animation is added. Two independent SWSinusoid
objects (hSin for horizontal drift, vSin for vertical
bob) drive setPosition() every frame. A Control Panel exposes
amplitude and period sliders. SPACE bar toggles the animation.
Stage 5 Living Eyes
Perlin-noise eye animation is added via SWEyeball.lookingAround().
When idle the pupils wander organically; Follow mode tracks the cursor; Track
mode locks both eyes to a shared gaze angle. Perlin noise state is preserved
through every _build() rebuild so the wander continues seamlessly
during hover. A 13-parameter Control Panel covers Eye, Hover, and Ghost &
Canvas settings.
Stage 6 Stretching
The new SWGhost.stretching(sinusoid, t) method drives
bodyHeight with a third SWSinusoid each frame,
analogous to SWRectangle.breathe(). The head stays fixed while
the body and toes shift downward, creating a rhythmic grow-and-shrink effect.
All three animations (hover H, hover V, stretch S) run simultaneously via
sinusoids with incommensurable periods, producing non-repeating composite motion.
SWGhost is composed entirely of smaller, reusable SketchWave classes.
Each one has a single, clear responsibility.
SWDisk — A Styled Circle
SWDisk draws a filled, stroked circle at an SWPoint
center. It is the primitive used for SWGhost's head cap and all
toe bumps, and internally by SWEyeball for each of its four layers.
Key features:
new SWDisk(center, radius, thickness, fillColor, strokeColor)drawOnGrid(grid)— renders the disk; call every frame.shouldShowCenter— show the center dot (debugging). SetfalseinsideSWGhost.setFillAlpha(a),setStrokeAlpha(a)— change transparency after construction.breathe(sinusoid, t)— pulse the radius with a sinusoid wave.
SWRectangle — A Styled Rectangle
SWRectangle draws a filled, stroked rectangle centered on an
SWPoint. It pre-computes its four vertex coordinates at construction,
which is why SWGhost must call _build() (not just
move an x/y value) whenever the ghost changes position.
Key features:
new SWRectangle(center, width, height, color, options)drawOnGrid(grid)— renders the rectangle each frame.breathe(sinusoid, t)— modulate height with a sinusoid (the inspiration forSWGhost.stretching()).- Options include
strokeColor,strokeWeight,showVertices,showCenter.
SWRectangle stores vertex positions computed at construction. Moving
the body requires making a new SWRectangle — there is no
setPosition() on it. SWGhost._build() exists precisely
to re-run this construction each time the anchor changes.
SWEyeball — An Animated Eyeball
SWEyeball encapsulates four disks (sclera, pupil, two glints) and
all the geometry to move, constrain, and animate the pupil. Two instances live
inside every SWGhost when eyes are enabled.
Key features:
drawOnGrid(grid),handleMousePressed/Dragged/Released()lookAt(userX, userY)— point pupil toward a world position.lookAtAngle(angleDeg)— point pupil toward a shared pre-computed angle.lookingAround(strength)— Perlin-noise idle wander.setIrisColor(color)— live iris color change (no rebuild needed).noiseSpeed— how fast the Perlin noise clock advances per frame.isDragging— true while pupil is being dragged.
SWGhost._build()
saves and restores each eye's _noiseT, _noiseOffsetX,
and _noiseOffsetY when creating new SWEyeball instances.
Without this, the eyes would snap to a random position every frame during
sinusoid-driven animation (because every frame triggers a rebuild).
SWSinusoid — A Sinusoid Wave
SWSinusoid models one full sinusoidal wave: y = A ⋅ sin(B ⋅ (x − D)) + C.
Call getValue(t) each frame with elapsed time in seconds to get the
current wave output. Used by SWGhost for hover (two sinusoids) and
stretching (one sinusoid).
Key features:
new SWSinusoid(A, B, C, D)— amplitude, frequency, DC offset, phase shift.setPeriod(seconds)— convenience method; derives B from period.getValue(t)— returns the wave output at timetseconds.- A = 0 effectively disables the sinusoid (output = C).
If the three periods (5s, 3s, 2.5s) share no simple integer ratio, the composite motion never exactly repeats. The ghost always looks slightly different — a subtle but effective way to make animation feel alive rather than mechanical.
SWPoint — A Position in Space
SWPoint stores an (x, y) coordinate in grid user-units together with
optional styling (stroke weight, color, label). It is the fundamental
anchor for every shape in the SketchWave system.
SWGhost's single _headCenter SWPoint is the source of
truth from which all other positions derive.
setPosition(x, y)— update coordinates.containsPoint(mx, my, grid, tolerance)— hit test in screen pixels.SWPoint.copy(other)— deep copy (the copy moves independently).
SWColor — A Color in HSB Space
SWColor stores a color as H (0–360), S (0–100), B (0–100), A (0–100)
compatible with p5.js colorMode(HSB, 360, 100, 100, 100).
SWColor.fromHex(hexStr, name)— convert a CSS hex string (e.g. from an HTML color picker) to HSB.withAlpha(a)— return a semi-transparent copy without modifying the original.darken(f),brighten(f),saturate(f),desaturate(f)— return adjusted copies.initializeSWColors()— call once insetup()to create all global presets:swRed,swBlue,swCyan,swGhost, and 30+ more.
Files are loaded from the project's shapeClasses/ folder.
Use the Copy button to copy any file's full text to the clipboard.
This page must be served over HTTP (not opened as a file:// URL)
for the source fetch to work.
Loading swGhost.js…
Loading swEyeball.js…
Loading swDisk.js…
Loading swRectangle.js…
Loading swSinusoid.js…
Loading swPoint.js…