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 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 {
noCursor() { }
draw() { }
setup() {
shot = loadSound('data/shot.mp3');
empty = loadSound('data/empty.mp3');
this.totshots = totShots;
this.remainingShots = totShots;
this.bullets = []; // array of Bullet objects
}
// mousePressed() {
// if () {
// shot.play();
// } else { // no bullets left
// empty.play();
// }
// }
setup() {
this.cursor = loadImage(cursor);
this.shotSound = loadSound(shot);
this.emptySound = loadSound(empty);
}
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
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 { Gun } from './Gun.js';
import { Target } from './Target';
import { BULLET_SIZE, FONT_SIZE} from './Constants.js';
import bullet from '../data/bullet.png';
class ScoreDisplay {
constructor(initialBullets) {
this.bulletImg = null;
this.img = null;
this.shotLeft = initialBullets;
this.score = 0;
loadImage(bullet, (img) => {
this.bulletImg = img;
loadImage(bullet, (PImage) => {
this.img = PImage;
});
}
draw() {
// Draw score on top-left
// score
textFont('Arial');
textSize(25);
textSize(FONT_SIZE);
fill(255, 0, 0);
textAlign(LEFT, TOP);
text(`Score: ${this.score}`, 10, 10);
if (!this.bulletImg) return;
// Draw remaining bullets on top-right
// bullets
if (!this.img) return;
const bulletSpacing = 20;
const marginRight = 10;
const bulletsWidth = this.shotLeft * bulletSpacing;
@ -34,10 +30,32 @@ class ScoreDisplay {
let y = 10;
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 };

View File

@ -1,5 +1,26 @@
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 };

View File

@ -1,21 +1,135 @@
import { MAX_TARGETS, TARGET_WIDTH } from './Constants';
import { Subject } from './Subject';
import { Gun } from './Gun';
import teddy from '../data/teddy.png';
import duck from '../data/duck.png';
import squirrel from '../data/squirrel.png';
class Target {
// TO DO
class Target extends Subject {
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 ...
// class DuckTarget ...
// class SquirrelTarget ...
class TeddyTarget extends Target {
constructor(x, y) {
super(x, y, TARGET_WIDTH);
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 {
// 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);
// 1. Init gun and score display
gun = new Gun(TOT_SHOTS);
gun.setup();
score = new ScoreDisplay(TOT_SHOTS);
// 2. Init the targets
initTargets();
// 3. Subscribe gun
gun.subscribe(score);
// Subscribe each target to gun
for (let target of targets) {
gun.subscribe(target);
target.subscribe(score);
}
}
function draw() {
background('#eeeeee');
for (let target of targets) {
target.draw();
}
gun.draw();
score.draw();
// draw targets, gun, bullets, score
}
// Shoot
function mousePressed() {
gun.shoot();
// shoot
}
@ -36,13 +51,27 @@ function mousePressed() {
function keyPressed() {
if (key === ' ') {
// reset score and targets
gun.remainingShots = TOT_SHOTS;
score.shotLeft = TOT_SHOTS;
score.score = 0;
initTargets();
}
}
// init the targets
function initTargets() {
// Create new targets from the factory
const factory = TargetFactory.getInstance();
// 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