From ace77964caf5b6afe2461221bdee94591e4abe46 Mon Sep 17 00:00:00 2001 From: Ryan Bell Date: Tue, 20 May 2025 13:55:31 -0400 Subject: [PATCH] Add p5.js reimplementation --- docs/Human.js | 40 +++++++++++++++++++++ docs/Vehicle.js | 83 ++++++++++++++++++++++++++++++++++++++++++++ docs/Zombie.js | 13 +++++++ docs/data/Human.png | 0 docs/data/Tree.png | 0 docs/data/Zombie.png | 0 docs/index.html | 19 ++++++++++ docs/sketch.js | 69 ++++++++++++++++++++++++++++++++++++ 8 files changed, 224 insertions(+) create mode 100644 docs/Human.js create mode 100644 docs/Vehicle.js create mode 100644 docs/Zombie.js create mode 100644 docs/data/Human.png create mode 100644 docs/data/Tree.png create mode 100644 docs/data/Zombie.png create mode 100644 docs/index.html create mode 100644 docs/sketch.js diff --git a/docs/Human.js b/docs/Human.js new file mode 100644 index 0000000..e2148ae --- /dev/null +++ b/docs/Human.js @@ -0,0 +1,40 @@ +class Human extends Vehicle { + constructor(x, y, r, ms, mf, obstacle, type) { + super(x, y, r, ms, mf, type); + this.wanderRadius = 25; + this.wanderDistance = 80; + this.wanderDelta = 0.2; + this.rotation = PI; + this.range = 180; + this.isObstacle = obstacle; + } + + getSick() { + zombies.push(new Zombie(this.position.x, this.position.y, 6, 3, 0.1, 1)); + humans.splice(humans.indexOf(this), 1); + } + + calcSteeringForces() { + this.findClosest(zombies); + if (this.minDist < this.radius) { + this.getSick(); + return; + } + if (this.minDist < this.range) { + this.target = p5.Vector.add(this.position, p5.Vector.sub(this.position, this.target)); + } else { + this.target = this.wander(); + } + this.steeringForce.mult(0); + this.steeringForce.add(this.seek(this.target)).add(this.correctiveForce.mult(5)).limit(this.maxForce); + this.applyForce(this.steeringForce); + } + + wander() { + this.rotation += random(-this.wanderDelta, this.wanderDelta); + return this.position.copy() + .add(this.velocity.copy().normalize().mult(this.wanderDistance)) + .add(this.wanderRadius * cos(this.rotation + this.velocity.heading()), + this.wanderRadius * sin(this.rotation + this.velocity.heading())); + } +} diff --git a/docs/Vehicle.js b/docs/Vehicle.js new file mode 100644 index 0000000..c10791a --- /dev/null +++ b/docs/Vehicle.js @@ -0,0 +1,83 @@ +class Vehicle { + constructor(x, y, r, ms, mf, type) { + this.position = createVector(x, y); + this.velocity = createVector(); + this.acceleration = createVector(); + this.forward = createVector(); + this.right = createVector(); + this.desired = this.position.copy(); + this.steeringForce = createVector(); + this.correctiveForce = createVector(); + this.target = this.position.copy(); + this.radius = r; + this.maxSpeed = ms; + this.maxForce = mf; + this.type = type; + this.mass = 1; + } + + display() { + push(); + translate(this.position.x, this.position.y); + rotate(this.velocity.heading()); + if (this.type === 0) image(imgTree, -this.radius/2, -this.radius/2, this.radius, this.radius); + else if (this.type === 1) image(imgZombie, -this.radius/2, -this.radius/2, this.radius, this.radius); + else image(imgHuman, -this.radius/2, -this.radius/2, this.radius, this.radius); + pop(); + return this; + } + + update() { + this.forward = this.velocity.copy().normalize(); + this.right.x = -this.forward.y; + this.right.y = this.forward.x; + this.correctiveForce.mult(0); + if (this.position.x < buffer) this.correctiveForce.x = 1; + if (this.position.x > width - buffer) this.correctiveForce.x = -1; + if (this.position.y < buffer) this.correctiveForce.y = 1; + if (this.position.y > height - buffer) this.correctiveForce.y = -1; + this.avoidObject(); + this.calcSteeringForces(); + this.velocity.add(this.acceleration).limit(this.maxSpeed); + this.position.add(this.velocity); + this.acceleration.mult(0); + return this; + } + + applyForce(f) { + this.acceleration.add(p5.Vector.div(f, this.mass)); + return this; + } + + seek(target) { + this.desired = p5.Vector.sub(target, this.position).normalize().mult(this.maxSpeed); + return p5.Vector.sub(this.desired, this.velocity); + } + + findClosest(subjects) { + let tempDist; + this.minDist = width; + for (let v of subjects) { + if ((tempDist = dist(v.position.x, v.position.y, this.position.x, this.position.y)) < this.minDist) { + this.target = v.position.copy().add(p5.Vector.mult(v.velocity, 7)); + if (showDebug) ellipse(this.target.x, this.target.y, 5, 5); + this.minDist = tempDist; + } + } + } + + avoidObject() { + let tempDist, safeDistance = 30; + for (let v of objects) { + let vecToCenter = p5.Vector.sub(v.position, this.position); + if (vecToCenter.mag() > safeDistance) continue; + if (vecToCenter.dot(this.forward) < 0) continue; + if (Math.abs(tempDist = vecToCenter.dot(this.right)) > 45) continue; + if (tempDist < 0) this.applyForce(this.right.copy().mult(this.maxSpeed * 0.5)); + else this.applyForce(this.right.copy().mult(-this.maxSpeed * 0.5)); + } + } + + // abstract + calcSteeringForces() {} +} diff --git a/docs/Zombie.js b/docs/Zombie.js new file mode 100644 index 0000000..8687177 --- /dev/null +++ b/docs/Zombie.js @@ -0,0 +1,13 @@ +class Zombie extends Vehicle { + constructor(x, y, r, ms, mf, type) { + super(x, y, r, ms, mf, type); + this.range = 120; + } + + calcSteeringForces() { + this.findClosest(humans); + this.steeringForce.mult(0); + this.steeringForce.add(this.seek(this.target)).add(this.correctiveForce.mult(5)).limit(this.maxForce); + this.applyForce(this.steeringForce); + } +} diff --git a/docs/data/Human.png b/docs/data/Human.png new file mode 100644 index 0000000..e69de29 diff --git a/docs/data/Tree.png b/docs/data/Tree.png new file mode 100644 index 0000000..e69de29 diff --git a/docs/data/Zombie.png b/docs/data/Zombie.png new file mode 100644 index 0000000..e69de29 diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..823e333 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,19 @@ + + + + + + Humans vs Zombies - p5.js + + + + + + + + + + diff --git a/docs/sketch.js b/docs/sketch.js new file mode 100644 index 0000000..69f80ac --- /dev/null +++ b/docs/sketch.js @@ -0,0 +1,69 @@ +let rightForce, upForce; +let buffer; +let humans = []; +let zombies = []; +let objects = []; +let imgZombie, imgHuman, imgTree; +let showDebug = true; + +function preload() { + imgZombie = loadImage('data/Zombie.png'); + imgHuman = loadImage('data/Human.png'); + imgTree = loadImage('data/Tree.png'); +} + +function setup() { + createCanvas(1000, 700); + rectMode(CENTER); + buffer = 90; + rightForce = createVector(1, 0); + upForce = createVector(0, -1); + for (let i = 0; i < 10; i++) { + spawnHuman(); + spawnObject(); + } + let z = new Zombie(width / 2, height / 2, 2, 3, 0.1, 1); + zombies.push(z); +} + +function draw() { + background(255); + for (let z of zombies) { + debug(z.update().display()); + } + for (let h of humans) { + debug(h.update().display()); + } + for (let o of objects) { + o.display(); + } +} + +function spawnHuman() { + humans.push(new Human(random(0, width), random(0, height), 20, 4, 0.2, true, 2)); +} + +function spawnObject() { + objects.push(new Human(random(0, width), random(0, height), 30, 0, 0, false, 0)); +} + +function debug(subject) { + if (showDebug) { + push(); + translate(subject.position.x, subject.position.y); + stroke(255, 0, 0); + line(0, 0, subject.right.x * 40, subject.right.y * 40); + stroke(0, 255, 0); + line(0, 0, subject.steeringForce.x * 200, subject.steeringForce.y * 200); + pop(); + stroke(0); + } +} + +function mousePressed() { + showDebug = !showDebug; +} + +function keyPressed() { + spawnHuman(); +}