Compare commits
10 Commits
1e48f4a69e
...
f673efc1dd
Author | SHA1 | Date | |
---|---|---|---|
f673efc1dd | |||
04e887f706 | |||
f688f75ce6 | |||
d27f84134e | |||
9c9865f149 | |||
79b023829f | |||
df9229cb78 | |||
71aeb0057a | |||
687009f8fc | |||
035cd541a0 |
89
src/Gun.js
89
src/Gun.js
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
134
src/Target.js
134
src/Target.js
|
@ -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
|
||||
};
|
||||
|
|
33
src/main.js
33
src/main.js
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user