Compare commits

...

10 Commits

5 changed files with 280 additions and 43 deletions

View File

@ -7,33 +7,88 @@ import bulletHole from '../data/bulletHole.png';
import shot from '../data/shot.mp3'; import shot from '../data/shot.mp3';
import empty from '../data/empty.mp3'; import empty from '../data/empty.mp3';
/*
The Gun is the mouse. To draw it, hide the cursor with the noCursor function and then draw instead the image cursor.png at the location of the mouse. To load and play sounds with p5.js take a look at this example. The sound files are shot.mp3 and empty.mp3, depending on whether there are still shots available. Finally, when you shoot with the gun, add a random CURSOR_SIZE noise (or a similarly small amount) to alter the bullet's final hitting location.
*/
class Gun extends Subject {
constructor(totShots) {
super();
this.cursor = null;
this.shotSound = null;
this.emptySound = null;
class Gun { this.totshots = totShots;
noCursor() { } this.remainingShots = totShots;
draw() { } this.bullets = []; // array of Bullet objects
setup() {
shot = loadSound('data/shot.mp3');
empty = loadSound('data/empty.mp3');
} }
// mousePressed() { setup() {
// if () { this.cursor = loadImage(cursor);
// shot.play(); this.shotSound = loadSound(shot);
// } else { // no bullets left this.emptySound = loadSound(empty);
// empty.play(); }
// } draw() {
// } noCursor();
if (this.cursor) {
image(this.cursor, mouseX - CURSOR_SIZE / 2, mouseY - CURSOR_SIZE / 2, CURSOR_SIZE, CURSOR_SIZE);
} // custom cursor
for (let bullet of this.bullets) {
bullet.draw();
} // draw bullet holes
this.bullets = this.bullets.filter(bullet => bullet.visible); // clean up bullet holes
}
shoot() {
if (this.remainingShots > 0) {
this.remainingShots--;
if (this.shotSound) this.shotSound.play();
const x = mouseX;
const y = mouseY;
const bullet = new Bullet(x, y);
this.bullets.push(bullet);
this.notifySubscribers('gun', x, y, this.remainingShots);
} else {
if (this.emptySound) this.emptySound.play();
}
}
reload() {
this.remainingShots = this.totShots;
}
getRemainingShots() {
return this.remainingShots;
}
} }
// Bullet // Bullet
class Bullet { class Bullet {
constructor(x, y) {
this.x = x;
this.y = y;
this.visible = true;
// TO DO this.img = null;
loadImage(bulletHole, (img) => {
this.img = img;
});
setTimeout(() => {
this.visible = false;
}, BULLET_DURATION);
}
draw() {
if (this.visible && this.img) {
image(this.img, this.x - BULLET_HOLE_SIZE / 2, this.y - BULLET_HOLE_SIZE / 2, BULLET_HOLE_SIZE, BULLET_HOLE_SIZE);
}
}
} }

View File

@ -1,32 +1,28 @@
import { BULLET_SIZE, FONT_SIZE, TOT_SHOTS } from './Constants.js'; import { BULLET_SIZE, FONT_SIZE} from './Constants.js';
import { Gun } from './Gun.js';
import { Target } from './Target';
import bullet from '../data/bullet.png'; import bullet from '../data/bullet.png';
class ScoreDisplay { class ScoreDisplay {
constructor(initialBullets) { constructor(initialBullets) {
this.bulletImg = null; this.img = null;
this.shotLeft = initialBullets; this.shotLeft = initialBullets;
this.score = 0; this.score = 0;
loadImage(bullet, (img) => { loadImage(bullet, (PImage) => {
this.bulletImg = img; this.img = PImage;
}); });
} }
draw() { draw() {
// Draw score on top-left // score
textFont('Arial'); textFont('Arial');
textSize(25); textSize(FONT_SIZE);
fill(255, 0, 0); fill(255, 0, 0);
textAlign(LEFT, TOP); textAlign(LEFT, TOP);
text(`Score: ${this.score}`, 10, 10); text(`Score: ${this.score}`, 10, 10);
if (!this.bulletImg) return; // bullets
if (!this.img) return;
// Draw remaining bullets on top-right
const bulletSpacing = 20; const bulletSpacing = 20;
const marginRight = 10; const marginRight = 10;
const bulletsWidth = this.shotLeft * bulletSpacing; const bulletsWidth = this.shotLeft * bulletSpacing;
@ -34,10 +30,32 @@ class ScoreDisplay {
let y = 10; let y = 10;
for (let i = 0; i < this.shotLeft; i++) { for (let i = 0; i < this.shotLeft; i++) {
image(this.bulletImg, startX + i * bulletSpacing, y, BULLET_SIZE, BULLET_SIZE); image(this.img, startX + i * bulletSpacing, y, BULLET_SIZE, BULLET_SIZE);
} }
} }
resetScore() {
this.score = 0;
}
addScore(scoreToAdd) {
this.score += scoreToAdd;
}
setBullets(numBullets) {
this.shotLeft = numBullets;
}
update(source, ...others) {
if (source === 'gun') {
const [, , remainingShots] = others;
this.setBullets(remainingShots);
} else if (source === 'target-hit') {
const [points] = others;
this.addScore(points);
}
}
} }
export { ScoreDisplay }; export { ScoreDisplay };

View File

@ -1,5 +1,26 @@
class Subject { class Subject {
// TO DO constructor() {
this.observers = [];
}
subscribe(observer) {
if (!observer || typeof observer.update !== 'function') return;
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(o => o !== observer);
}
unsubscribeAll() {
this.observers = [];
}
notifySubscribers(source, ...others) {
for (let observer of this.observers) {
observer.update(source, ...others);
}
}
} }
export { Subject }; export { Subject };

View File

@ -1,21 +1,135 @@
import { MAX_TARGETS, TARGET_WIDTH } from './Constants';
import { Subject } from './Subject'; import { Subject } from './Subject';
import { Gun } from './Gun';
import teddy from '../data/teddy.png'; import teddy from '../data/teddy.png';
import duck from '../data/duck.png'; import duck from '../data/duck.png';
import squirrel from '../data/squirrel.png'; import squirrel from '../data/squirrel.png';
class Target { class Target extends Subject {
// TO DO constructor(x, y, width) {
super();
this.x = x;
this.y = y;
this.width = width;
this.height = width;
this.visible = true;
this.img = null;
}
draw() {
if (this.visible && this.img) {
image(this.img, this.x, this.y, this.width, this.height);
}
}
getPoints() {
return 0; // to be overridden
}
isHit(x, y) {
return (
this.visible &&
x >= this.x &&
x <= this.x + this.width &&
y >= this.y &&
y <= this.y + this.height
);
}
shoot(x, y) {
if (this.isHit(x, y)) {
this.visible = false;
this.notifySubscribers('target-hit', this.getPoints());
}
}
update(source, ...others) {
if (source === 'gun') {
const [x, y] = others;
this.shoot(x, y);
}
}
} }
// TO DO class TeddyTarget extends Target {
// class TeddyTarget ... constructor(x, y) {
// class DuckTarget ... super(x, y, TARGET_WIDTH);
// class SquirrelTarget ... this.img = loadImage(teddy);
}
getPoints() {
return 1;
}
}
class DuckTarget extends Target {
constructor(x, y) {
super(x, y, TARGET_WIDTH);
this.img = loadImage(duck);
}
getPoints() {
return 3;
}
}
class SquirrelTarget extends Target {
constructor(x, y) {
super(x, y, TARGET_WIDTH);
this.img = loadImage(squirrel);
}
getPoints() {
return 5;
}
}
class TargetFactory { class TargetFactory {
// TO DO static instance;
static getInstance() {
if (!TargetFactory.instance) {
TargetFactory.instance = new TargetFactory();
}
return TargetFactory.instance;
}
getTargetsByName(targetNames, targetWidth, y) {
const targets = [];
for (let i = 0; i < targetNames.length && i < MAX_TARGETS; i += 1) {
const name = targetNames[i];
const x = 100 + i * targetWidth;
switch (name) {
case 'teddy':
targets.push(new TeddyTarget(x, y));
break;
case 'duck':
targets.push(new DuckTarget(x, y));
break;
case 'squirrel':
targets.push(new SquirrelTarget(x, y));
break;
}
}
return targets;
}
getRandomTargets(numTargets, targetWidth, y) {
const names = ['teddy', 'duck', 'squirrel'];
const targetNames = [];
for (let i = 0; i < numTargets; i++) {
targetNames.push(random(names));
}
return this.getTargetsByName(targetNames, targetWidth, y);
} // create random targets
} }
export { Target, TargetFactory }; export {
Target, TeddyTarget,
DuckTarget,
SquirrelTarget, TargetFactory
};

View File

@ -14,21 +14,36 @@ function setup() {
createCanvas(800, 600); createCanvas(800, 600);
// 1. Init gun and score display // 1. Init gun and score display
gun = new Gun(TOT_SHOTS);
gun.setup();
score = new ScoreDisplay(TOT_SHOTS); score = new ScoreDisplay(TOT_SHOTS);
// 2. Init the targets // 2. Init the targets
initTargets();
// 3. Subscribe gun // 3. Subscribe gun
gun.subscribe(score);
// Subscribe each target to gun
for (let target of targets) {
gun.subscribe(target);
target.subscribe(score);
}
} }
function draw() { function draw() {
background('#eeeeee'); background('#eeeeee');
for (let target of targets) {
target.draw();
}
gun.draw();
score.draw(); score.draw();
// draw targets, gun, bullets, score // draw targets, gun, bullets, score
} }
// Shoot // Shoot
function mousePressed() { function mousePressed() {
gun.shoot();
// shoot // shoot
} }
@ -36,13 +51,27 @@ function mousePressed() {
function keyPressed() { function keyPressed() {
if (key === ' ') { if (key === ' ') {
// reset score and targets // reset score and targets
gun.remainingShots = TOT_SHOTS;
score.shotLeft = TOT_SHOTS;
score.score = 0;
initTargets();
} }
} }
// init the targets // init the targets
function initTargets() { function initTargets() {
// Create new targets from the factory // Create new targets from the factory
const factory = TargetFactory.getInstance();
// Remember to unsubscribe the previous targets and to subscribe the new ones // Remember to unsubscribe the previous targets and to subscribe the new ones
gun.unsubscribeAll();
targets = factory.getRandomTargets(MAX_TARGETS, TARGET_WIDTH, 200);
gun.subscribe(score);
for (let target of targets) {
gun.subscribe(target);
target.subscribe(score);
}
} }
// Do not touch these // Do not touch these