Emitter follows Particle in canvas - javascript

in this HTML5 code, I try to fire particles from the Emitter in objects[0].
This can be run with objects[0].play in the console.
However, the Emitter follows the Particle fired.
My question is, why?
HTML Code
<html>
<head>
<style>
#canv{
border: 1px solid black;
}
</style>
<script src = "setup.js"></script>
<script src = "main.js"></script>
</head>
<body onLoad = "start()">
<canvas width = "500" height = "500" id = "canv"></canvas>
</body>
</html>
setup.js
var objects = [];
var sprites = [];
var c;
var ctx;
var time;
var timeInterval;
function newSprite(url){
var a = sprites.length;
var b = new Image();
b.src = url;
b.onload = function(){
sprites[a] = b;
};
}
function randomN(min, max){
return Math.floor((Math.random()*max) - min);
}
function Vector2(x,y){
this.x = x;
this.y = y;
}
function Particle(pos, life, vel, accel){
this.pos = pos;
this.life = life || Infinity;
this.vel = vel || new Vector2(0,0);
this.accel = accel || new Vector2(0,0);
this.update = function(){
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
this.vel.x += this.accel.x;
this.vel.y += this.accel.y;
this.life--;
if(this.life <= 0){
return false;
}else{
return true;
}
};
}
function Emitter(pos){
this.pos = pos;
this.actualPos = this.pos;
this.particles = [];
this.forces = [];
this.playing = false;
this.newParticle = function(life, vel, accel){
var a = this.particles.length;
this.particles[a] = new Particle(this.pos, life, vel, accel);
};
this.update = function(){
console.log("(" + this.actualPos.x + ", " + this.actualPos.y + ") " + "(" + this.pos.x + ", " + this.pos.y + ")");
for(var a = 0; a < this.particles.length; a++){
for(var b = 0; b < this.forces.length; b++){
this.forces[b](this.particles[a]);
}
var that = this.particles[a];
var particleAlive = that.update();
if(particleAlive == false){
this.particles.splice(a, 1);
a--;
}
}
};
this.play = function(){
this.playing = true;
};
}
function timeStep(){
for(var a = 0; a < objects.length; a++){
if(objects[a].__proto__.constructor.name == "Emitter"){
if(objects[a].playing == true){
objects[a].update();
}
}
}
objects[1].newParticle(1000, new Vector2(1, 0), new Vector2(0.1, 0));
time++;
}
function gravity(p){
//p.vel.y += 0.1;
}
main.js
function start(){
c = document.getElementById("canv");
ctx = c.getContext('2d');
newSprite("spark.png");
objects[0] = new Emitter(new Vector2(50, 60));
objects[0].forces.push(gravity);
objects[0].newParticle(1000, new Vector2(1, 0), new Vector2(0.1, 0));
objects[1] = new Emitter(new Vector2(100, 100));
objects[1].forces.push(gravity);
time = 0;
timeInterval = window.setInterval(timeStep, 10);
reDraw();
}
function reDraw(){
ctx.clearRect(0, 0, c.width, c.height);
for(var a = 0; a < objects.length; a++){
if(objects[a].__proto__.constructor.name == "Emitter"){
ctx.beginPath();
ctx.arc(objects[a].pos.x, objects[a].pos.y, 5, 0, 2*Math.PI);
ctx.fillStyle = "black";
ctx.fill();
ctx.closePath();
if(objects[a].playing == true){
for(var b = 0; b < objects[a].particles.length; b++){
ctx.beginPath();
ctx.drawImage(sprites[0], objects[a].particles[b].pos.x, objects[a].particles[b].pos.y, 8,8);
//ctx.arc(objects[a].particles[b].pos.x, objects[a].particles[b].pos.y, 5, 0, 2*Math.PI);
ctx.fillStyle = "black";
ctx.fill();
ctx.closePath();
}
}
}
}
requestAnimationFrame(reDraw);
}

Your mistake is a quite classical error of using an object reference rather than using the object's values : when you create a new particle, you set its position to be the very position of the emitter.
So any latter change to the particle's position changes the emitter's position : again, the position (pos) Object is the very same object.
What you want is each particle to have its own position, that has the initial value of the emitter's position.
Done, for instance, with :
function Particle(pos, life, vel, accel){
this.pos = new Vector2(pos.x, pos.y);
//...
Notice that velocity and acceleration are also references, which here is not an issue since you call the function calling the constructor with a new Vector2.
However you might want to protect your code against future possible errors by performing a copy also for those two properties.

I havne't followed the full logic so sorry if this isn't the cause, but there's something odd about this :
function newSprite(url){
var a = sprites.length;
var b = new Image();
b.src = url;
b.onload = function(){
sprites[a] = b;
};
}
At the time when onload runs, a and b are lno longer in scope (I believe): The onload event would be fired as a new function call outside of newSprite.
Wouldn't you need to do something like this ...
b.onload = function(){
sprites[sprites.length] = this;
};
so that the function runs correctly when it's called ?

Related

How do I track and store the position of a current closest class instance relative to another class instance (again...)

In my UFO shooter game I would like the player to be able to spike the closest UFO, to insure chaos and awesomeness. Lol. Anyways, I can't seem to figure out how to store the closest current UFO (or drone, as I call them in the code)...
Here are all of the functions and classes for the game. (kinda messy, I know, but I'll probably revise it later)
var score = 0;
var bullets = [];
var drones = [];
var keys = [];
keyPressed = function () {
keys[keyCode]=true;
};
keyReleased = function () {
keys[keyCode]=false;
};
var player = function (x,y) {
this.x = 25;
this.y = 440;
this.w = 15;
this.h = 20;
this.xvel = 0;
this.yvel = 0;
this.accel = 0.2;
this.frict = 0.08;
this.isShooting = false;
this.update = function () {
this.leftGun = this.x-11-this.xvel;
this.rightGun = this.x+20-this.xvel;
if (this.isShooting) {
this.gunsY = random(this.y,this.y+7);
} else {
this.gunsY = this.y+3;
}
if (keys[LEFT]) {
this.xvel -= this.accel;
}
if (keys[RIGHT]) {
this.xvel += this.accel;
}
if (keys[UP]) {
this.yvel -= this.accel;
}
if (keys[DOWN]) {
this.yvel += this.accel;
}
if (this.xvel > 0 || this.xvel < 0) {
this.xvel -= this.xvel * this.frict;
}
if (this.yvel > 0 || this.yvel < 0) {
this.yvel -= this.yvel * this.frict;
}
fill(255, 255, 255);
//rect(this.x-this.w/2,this.y + 5,this.w,this.h);
triangle(this.x,this.y+this.h-this.xvel+this.yvel,this.x+this.xvel+7,this.y,this.x+15,this.y+this.h+this.xvel+this.yvel);
fill(0,0,255);
//ellipse(this.x+7,this.y+14,7,7);
rect(this.leftGun,this.gunsY,4,15);
rect(this.rightGun,this.gunsY,4,15);
this.x += this.xvel;
this.y += this.yvel;
};
};
var p1 = new player();
// verify status
var active = function(obj){
return obj.x>0 && obj.y<height+100;
};
var bulletActive = function(blt){
return blt.x>0 && blt.x < width && blt.y>0 && blt.y<height+20;
};
var droneArt = function(){
var zz = this.z/2;
var dam = map(this.h, 0, 100, 0, this.z);
if (this.h<0){
this.x = -100;
score+=(80-this.z);
return;
}
fill(255, 0, 0);
rect(this.x-zz, this.y-zz-10, this.z, 4);
fill(0, 255, 0);
rect(this.x-zz, this.y-zz-10, dam, 4);
pushMatrix();
translate(this.x,this.y);
//rotate(r);
fill(255, 255, 255);
ellipse(0,0,this.z,this.z);
fill(0, 0, 0);
ellipse(0,0,this.z-this.z/1.8,this.z-this.z/1.8);
for(var i = 0; i < 360; i += 45){
rotate(i);
fill(0, 0, 0);
noStroke();
ellipse(this.z-this.z/1.6,0,this.z-this.z/1.15,this.z-this.z/1.15);
}
//r++;
popMatrix();
this.y += 0.4;
/*
for(var i = 0; i < 360; i += 45){
rotate(i);
fill(255,0,0);
ellipse(this.z-this.z/1.6,0,this.z-this.z/2,5);
}
*/
};
// drone object
var drone = function(x, y){
return {
x:x,
y:y,
z:20 + floor(random(2)) * 20,
h:100,
draw:droneArt };
};
// bullet object
var bullet = function(x, y, s, id){
return {
x:x, y:y, s:s, z:3,
draw: function(){
if (id==="canon"){
this.y -= 7;
fill(255, 97, 97);
ellipse(this.x+2,this.y+9,this.z*3,this.z*4);
fill(255, 0, 0);
ellipse(this.x,this.y+9,this.z*3.2,this.z*4.2);
} else {
this.y -= 14;
fill(102, 102, 255);
rect(this.x-this.z/2, this.y, this.z, this.z+10);
}
}
};
};
// collision test
var kill = function(obj){
var test = function(blt){
if (dist(obj.x, obj.y, blt.x, blt.y)<(obj.z+blt.z)/2){
obj.h -= blt.s;
blt.x = -100;
}
};
return test;
};
var r = 0;
var drawDrone = function(drn){
//r += 0.2;
pushMatrix();
translate(drone.x,0);
rotate(r);
drn.draw();
popMatrix();
bullets.forEach(kill(drn));
};
var drawBullet = function(blt){
blt.draw();
};
var ufo;
var distance;
var minUFO = 10000;
var drones = [drone(100, 100), drone(200, -100), drone(300, 0)];
/*minUFO = minUFO === null || t > minUFO ? t : minUFO;*/
var minUFO = 9999999999;
var findNearestDrone = function () {
for(var i=0; i < drones.length; i++) {
var ufo = drones[i];
var po = dist(ufo.x,ufo.y,p1.x,p1.y);
if (po < minUFO) {
minUFO = po;
}
fill(255, 0, 0);
stroke(255,0,0);
strokeWeight(2);
line(ufo.x,ufo.y,p1.x+8,p1.y);
noStroke();
//println(ufo + " drone " + i);
text(minUFO,30,100);
}
};
It turns out that, unlike what I expected; but logically, the minUFO only ever gets smaller. Instead of storing the position of the closest current UFO, it seems to store the smallest distance between the UFO and the player ever recorded in the session. Which, turns out, isn't what I want either. Anybody know how to store the current closest drone's position? Not just the distance like I have it right now, but its actual position. Thank you, and I'm sincerely sorry for my incredible ability to take a millennia to get to the point.
You should put the var minUFO = 9999999999; inside the findNearestDrone function. That way the variable is reset every time you call the function and is no longer influenced by the previous smallest distance.
Next, similarly to the way you store the minUFO, you can use a variable that keeps track of either the ufo itself or the index that is associated with the nearest ufo (so far).
I've modified your code with both options:
//initiate vars outside function
var nearestUFO;
var nearestIndex;
var findNearestDrone = function () {
//initiate var that tracks min distance inside function
var minUFO = 9999999999;
for(var i=0; i < drones.length; i++) {
var ufo = drones[i];
var po = dist(ufo.x,ufo.y,p1.x,p1.y);
if (po < minUFO) {
minUFO = po;
//store the current nearest ufo / index
nearestUFO= ufo;
nearestIndex= i;
}
fill(255, 0, 0);
stroke(255,0,0);
strokeWeight(2);
line(ufo.x,ufo.y,p1.x+8,p1.y);
noStroke();
//println(ufo + " drone " + i);
text(minUFO,30,100);
}
};

Code not outputting to the screen

I'm practicing with JavaScript and p5, but when I tried to run this example on my browser locally, all I get is a black screen and the GUI. What I'm supposed to see is text being displayed via lines and some particle motion both on the screen and when I click my mouse. I double checked to make sure that the correct libraries have been included, and I re-downloaded the code from the tutorial multiple times. Right now, the only thing showing up on my screen is the GUI (and it also seems that the "message" control in the GUI doesn't work either). And just a fyi, I'm using Chrome.
HTML CODE:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style> body {padding: 0; margin: 0;} </style>
<script src="./libraries/p5.js"></script>
<script src="./libraries/dat.gui.min.js"></script>
<script src="./libraries/rune.js"></script>
<script src="./libraries/rune.font.js"></script>
<script src="./libraries/rune.font.js"></script>
<script src="sketch.js"></script>
</head>
<body>
</body>
</html>
JAVASCRIPT CODE:
// update particles position on/off
var f;
var path;
var polys;
var drawing = false;
var gui, params;
var anchorX, anchorY;
var particles = [];
// this function loads a font, and create an array of polygons
// a polygon being itself an array of vectors with x/y coordinates
// in this example we want to create a new 'tentacle' on each point on the outline
// of the font path
function getPoints(){
drawing = false;
// create new font : we use rune
console.log(params.font);
f = new Rune.Font(params.font)
particles = [];
// load the font
f.load(function(err){
path = f.toPath(params.message, 0, 0, params.size)
polys = path.toPolygons({ spacing:params.spacing })
for (var j=0; j < polys.length ; j++){ // get each polygon
var poly = polys[j];
for(var k = 0; k < poly.state.vectors.length; k++) { // get each point of each polygon
var vec = poly.state.vectors[k];
particles.push(new Particle(random(0,windowWidth), random(0,windowHeight), vec.x,vec.y));
}
}
drawing= true;
});
}
function setup(){
createCanvas(windowWidth,windowHeight)
background(0)
// init all parameters
params = new Parameters();
// create dat.gui drawer
gui = new dat.GUI();
// gui setup
var f2 = gui.addFolder('configuration / path generation');
var f1 = gui.addFolder('real-time parameters');
// Configuration parameters
// font selector
f2.add(params, 'font', {Avenir : "../fonts/AvenirNextLTW01-Medium.woff", BlackOpsOne : "../fonts/Black_Ops_One/BlackOpsOne-Regular.ttf",
Comfortaa : "../fonts/Comfortaa/Comfortaa-Bold.ttf",
NovaMono : "../fonts/Nova_Mono/NovaMono.ttf", ShadowsIntoLight : "../fonts/Shadows_Into_Light/ShadowsIntoLight.ttf",
Sniglet: "../fonts/Sniglet/Sniglet-ExtraBold.ttf",Tangerine : "../fonts/Tangerine/Tangerine_Bold.ttf",
UnicaOne : "../fonts/Unica_One/UnicaOne-Regular.ttf"});
f2.add(params, 'message').listen();
f2.add(params, 'spacing', 1, 40).listen();
f2.add(params, 'size', 100, 1000).listen();
f2.add(params, 'regenerate');
f1.addColor(params, 'background').listen();
f1.addColor(params, 'color').listen();
f1.add(params, 'strokeWeight',0.1,2).listen();
f1.add(params, 'threshold',10,150 ).listen();
f1.add(params, 'show_particles').listen();
f1.add(params, 'xoffset',0,windowWidth-300).listen();
f1.add(params, 'yoffset',0,windowHeight).listen();
gui.add(params, 'preset0')
gui.add(params, 'preset1')
gui.add(params, 'preset2')
gui.add(params, 'preset3')
gui.add(params, 'save')
getPoints();
}
function draw(){
noStroke();
fill(params.background)
rect(0,0,windowWidth,windowHeight)
if (drawing){
for (var i = 0 ; i < particles.length ; i++){
particles[i].update();
particles[i].check_bounds();
if (params.show_particles)particles[i].draw();
}
push()
translate(params.xoffset, params.yoffset)
strokeWeight(params.strokeWeight )
stroke(params.color);
for (var j=0; j < polys.length ; j++){ // get each polygon
var poly = polys[j];
for(var k = 0; k < poly.state.vectors.length; k++) { // get each point of each polygon
var vec = poly.state.vectors[k];
for (var i = 0 ; i < particles.length ; i++){
if (dist(particles[i].x-params.xoffset, particles[i].y-params.yoffset, vec.x, vec.y) < params.threshold){
line(particles[i].x-params.xoffset, particles[i].y-params.yoffset, vec.x, vec.y)
}
}
}
}
pop()
}
if(mouseIsPressed && mouseX < windowWidth-300) particles.push(new Particle(mouseX,mouseY,mouseX,mouseY));
}
function Particle(x,y,tx,ty){
this.x =x ;
this.y =y;
this.size = 5;
var r = random(1)
var signe = 1
if(r < 0.5){
signe = -1
}
this.xspeed = signe * random(0.15,1.5)
r = random(1)
signe = 1
if(r < 0.5){
signe = -1
}
this.yspeed = signe * random(0.15,1.5)
//this.xspeed = 0;
//this.yspeed = 0;
this.xacc= 0;
this.yacc =0;
this.targetX = tx;
this.targetY = ty;
this.draw = function(){
fill(params.color);
noStroke();
ellipse(this.x, this.y, this.size, this.size);
}
this.update = function(){
this.xspeed += this.xacc
this.yspeed += this.yacc
this.x = this.x + this.xspeed
this.y = this.y + this.yspeed
this.xacc = 0;
this.yacc = 0;
}
this.check_bounds = function(){
if (this.x < 0 || this.x> windowWidth){
this.xspeed = - this.xspeed;
}
else if (this.y<0 || this.y>windowHeight){
this.yspeed = - this.yspeed;
}
}
}
var Parameters = function(){
this.font = "../fonts/AvenirNextLTW01-Medium.woff"
this.message = 'p5*js';
this.spacing = 20;
this.size = 600;
this.background = [0,0,0,150];
this.color = [237,34,93];
this.strokeWeight = 0.51;
this.threshold = 50;
this.xoffset = windowWidth/3 - this.size
this.yoffset = windowHeight*2/3
this.show_particles = true;
this.regenerate = function(){
background(0);
getPoints();
}
this.save = function(){
saveCanvas()
}
this.clear = function(){
background(0);
}
this.preset0 = function(){
this.spacing = 20;
this.size = 600;
this.background = [0,0,0,150];
this.color = [237,34,93];
this.strokeWeight = 0.51;
this.threshold = 50;
this.show_particles =true;
getPoints();
}
this.preset1 = function(){
this.spacing = 40;
this.size = 600;
this.background = [0,0,0,5];
this.color = [237,34,93];
this.strokeWeight = 0.25;
this.threshold = 75;
this.show_particles =false;
getPoints();
}
this.preset2 = function(){
this.spacing = 8;
this.size = 600;
this.background = [0,0,0];
this.color = [237,34,93];
this.strokeWeight = 0.41;
this.threshold = 85;
this.show_particles =false;
getPoints();
}
this.preset3 = function(){
this.spacing = 4;
this.size = 600;
this.background = [0,0,0,50];
this.color = [237,34,93];
this.strokeWeight = 0.41;
this.threshold = 25;
this.show_particles = false;
getPoints();
}
}

Prototypal Inheritance in Javascript Working with Basic but not Complex Program

What is confusing is how this simple script works fine:
function A() {
this.value = 0;
}
A.prototype.foo = function() {
console.log(this.value);
};
function B() {
this.value = 1;
this.foo();
}
B.prototype = Object.create(A.prototype);
B.prototype.bar = function() {
console.log(this instanceof A);
}
new B().bar();
// outputs 1, true
However, this larger script gives an error this.loadDimensions is not a function:
Basically, there is a Player class, which inherits from a MovingComponent class, which inherits from a VisibleComponent class. They all have methods attached to them.
const PX_SZ = 4, MAX_HEIGHT = 100, MIN_HEIGHT = 300;
var resources = {};
resources.sprites = {};
resources.sprites.player = new Image();
resources.sprites.player.src = "resources/sprites/player.png";
resources.sprites['default'] = new Image();
resources.sprites['default'].src = "resources/sprites/default.png";
resources.sprites.items = {};
resources.sprites.backgroundEntities = {};
var itemsTemp = ['default', 'coin0'];
for (var i=0; i<itemsTemp.length; i++) {
var item = itemsTemp[i];
resources.sprites.items[item] = new Image();
resources.sprites.items[item].src = "resources/sprites/items/" + item + ".png";
}
var backgroundEntitiesTemp = ['tree0'];
for (var i=0; i<backgroundEntitiesTemp.length; i++) {
var ent = backgroundEntitiesTemp[i];
resources.sprites.backgroundEntities[ent] = new Image();
resources.sprites.backgroundEntities[ent].src = "resources/sprites/background-entities/" + ent + ".png";
}
var canvas, ctx;
var player = new Player();
var keys = {};
var game = new Game(Math.floor(Math.random()*1000000));
var world = new World();
/** #class */
function Game(seed) {
this.seed = seed;
}
/** #class */
function World() {
this.gravity = 0.4;
this.chances = {
items: {
coin0: 0.005
},
backgroundEntities: {
tree0: 0.05
}
};
this.itemsFloating = [];
this.backgroundEntities = [];
// for spawning
this.exploredRightBound = 0;
this.exploredLeftBound = 0;
}
World.prototype.generate = function(left, right) {
if (left >= right) throw "left >= right in World#generate(left,right)";
for (x = left; x < right; x += PX_SZ) {
// world generation code here
// coin0
var level = getGroundHeightAt(x)
if (Math.random() <= this.chances.items.coin0) {
var item = new ItemFloating("coin0", x, level-20);
this.itemsFloating.push(item);
}
if (Math.random() <= this.chances.backgroundEntities.tree0) {
var ent = new BackgroundEntity("tree0", x, level-resources.sprites.backgroundEntities.tree0.height);
this.backgroundEntities.push(ent);
}
}
};
/**
* #class
* anything that has a sprite attached to it
*/
function VisibleComponent() {
this.sprite = resources.sprites['default'];
}
VisibleComponent.prototype.loadDimensions = function() {
console.log('load');
};
VisibleComponent.prototype.draw = function() {
ctx.drawImage(this.sprite, this.x, this.y, this.width, this.height);
};
/** #class */
function Item(name="default") {
VisibleComponent.call(this);
this.name = name || "default";
this.sprite = resources.sprites.items[name];
this.loadDimensions();
}
Item.prototype = Object.create(VisibleComponent.prototype);
/** #class */
function ItemFloating(name, x, y) {
Item.call(this, name);
this.name = name;
this.x = x;
this.y = y;
this.loadDimensions(); // (when ready of now)
}
ItemFloating.prototype = Object.create(Item.prototype);
/** #class */
function BackgroundEntity(name="default", x=0, y=0) {
VisibleComponent.call(this);
this.name = name;
this.x = x;
this.y = y;
this.width = 1;
this.height = 1;
this.sprite = resources.sprites.backgroundEntities[this.name];
this.loadDimensions();
}
BackgroundEntity.prototype = Object.create(VisibleComponent.prototype);
/** #class */
function MovingEntity(x=0, y=0) {
VisibleComponent.call(this);
this.x = x;
this.y = y;
this.width = 1;
this.height = 1;
}
MovingEntity.prototype = Object.create(VisibleComponent.prototype);
MovingEntity.prototype.collisionWith = function(ent) {
return ((this.x>=ent.x&&this.x<=ent.x+ent.width) || (ent.x>=this.x&&ent.x<=this.x+this.width))
&& ((this.y>=ent.y&&this.y<=ent.y+ent.height) || (ent.y>=this.y&&ent.y<=this.y+this.height));
};
/** #class */
function Player() {
MovingEntity.call(this);
this.inventory = {};
console.log(this instanceof VisibleComponent);
this.speed = 4;
this.jumpSpeed = 8;
this.vspeed = 0;
this.sprite = resources.sprites.player;
this.loadDimensions();
this.direction = "right";
}
Player.prototype = Object.create(MovingEntity.prototype);
Player.prototype.draw = function() {
ctx.save();
ctx.translate(this.x, this.y);
if (this.direction == "left") ctx.scale(-1, 1); // flip over y-axis
ctx.translate(-this.sprite.width, 0);
ctx.drawImage(this.sprite, 0, 0, this.width, this.height);
ctx.restore();
}
Player.prototype.move = function() {
if (keys['ArrowLeft']) {
this.x -= this.speed;
this.direction = "left";
var leftEdge = this.x-canvas.width/2-this.width/2;
if (leftEdge < world.exploredLeftBound) {
world.generate(leftEdge, world.exploredLeftBound);
world.exploredLeftBound = leftEdge;
}
}
if (keys['ArrowRight']) {
this.x += this.speed;
this.direction = "right";
var rightEdge = this.x+canvas.width/2+this.width/2;
if (rightEdge > world.exploredRightBound) {
world.generate(world.exploredRightBound, rightEdge);
world.exploredRightBound = rightEdge;
}
}
var level = getGroundHeightAt(this.x+this.width/2);
if (this.y + this.height < level) {
this.vspeed -= world.gravity;
} else if (this.y + this.height > level) {
this.y = level - this.height;
this.vspeed = 0;
}
if (keys[' '] && this.y+this.height == getGroundHeightAt(this.x+this.width/2)) this.vspeed += this.jumpSpeed;
this.y -= this.vspeed;
for (var i=0; i<world.itemsFloating.length; i++) {
var item = world.itemsFloating[i];
if (this.collisionWith(item)) {
if (this.inventory.hasOwnProperty(item.name)) this.inventory[item.name]++;
else this.inventory[item.name] = 1;
world.itemsFloating.splice(i, 1);
}
}
};
I'm fairly new to javascript inheritance, so I don't understand what I'm doing wrong. Also, since the first script worked, I figured there's something I'm just overlooking in my second script. Any help would be appreciated.
EDIT
In the beginning of the file, I declare player as a new Player(). resources contains Image instances that point to various image files. ctx and canvas are pretty self-explanatory globals.
Also, player isn't recognized as an instance of MovingEntity or VisibleComponent, even though Player's prototype is set to Object.create(MovingEntity.prototype), which has its prototype set to Object.create(VisibleComponent.prototype).
One other thing to mention is that in the definition of loadDimensions() in VisibleComponent, either the onload property of this.sprite is set to a function, or addEventListener() is called for 'load', depending on whether this.sprite has loaded (width != 0) or not.
In the beginning of the file, I declare player as a new Player().
That's the problem, you need to call the constructor after having set up your class. It currently doesn't throw an error about Player not being a function because the declaration is hoisted, but the prototype is not yet initialised with the value you expect so it indeed does not have a .loadDimensions() method yet.

jQuery Canvas Pause and Start listener based on HasClass

I'm having problems setting up a listener that will pause and start a canvas animation based on HasClass. Basically I want the animation to pause when a <a href="#"> has a certain class. Using jQuery I know enough how to add and remove but not tie it all together so they work together nicely. This is a little out of my comfort zone and appreciate any help.
Below is my simple HTML code:
<a id="eY" href="#" class="paused">
<span class="pause">Pause</span>
<span class="resume">Resume</span>
</a>
<canvas id="world"> </canvas>
Below is my JavaScript:
jQuery(function($) {
!+-+-+!+-+-+!+-+-+!+-+-+!+-+-+!+-+-+!+-+-+!+-+-+!
function(d, w){
var FPS = 30;
var F = 300;
var N = 3;
var VERTEX_MAX = 10;
var TRAIL_QUALITY = 4000;
var mu = 0.5;
var bmRandom = function(mu, sigma){
var x, y, r, tmp=null, tmp2;
return function(){
if(tmp !== null){
tmp2 = tmp;
tmp = null;
return y*tmp2+mu;
}
do{
x = Math.random()*2-1;
y = Math.random()*2-1;
r = x*x+y*y;
}while(r>=1);
tmp = sigma*Math.sqrt(-2*Math.log(r)/r);
return x*tmp+mu;
};
};
pointCopy = function(src, dst){
dst.x = src.x;
dst.y = src.y;
dst.z = src.z;
return dst;
};
Trail = function(pos, t, color_f){
this.pos={x:0,y:0,z:0};
this.start={x:0,y:0,z:0};
this.goal={x:0,y:0,z:0};
this.anchor_1={x:0,y:0,z:0};
this.anchor_2={x:0,y:0,z:0};
this.start_time = 0;
this.take_time = 1;
this.vertexes = [];
this.anchors_1 = [];
this.anchors_2 = [];
this.color_f = color_f;
pointCopy(pos, this.pos);
pointCopy(pos, this.start);
pointCopy(pos, this.goal);
this.setNextGoal(t);
};
Trail.prototype.setNextGoal = function(t, target){
pointCopy(this.goal, this.start);
this.anchor_1.x = this.start.x+(this.start.x-this.anchor_2.x)*mu;
this.anchor_1.y = this.start.y+(this.start.y-this.anchor_2.y)*mu;
this.anchor_1.z = this.start.z+(this.start.z-this.anchor_2.z)*mu;
if(target){
this.anchor_2.x = (this.anchor_1.x+target.x)/2+myrand();
this.anchor_2.y = (this.anchor_1.y+target.y)/2+myrand();
this.anchor_2.z = (this.anchor_1.z+target.z)/2+myrand();
this.goal.x = target.x;
this.goal.y = target.y;
this.goal.z = target.z;
}else{
this.anchor_2.x = this.anchor_1.x+myrand();
this.anchor_2.y = this.anchor_1.y+myrand();
this.anchor_2.z = this.anchor_1.z+myrand();
this.goal.x = this.anchor_2.x+myrand();
this.goal.y = this.anchor_2.y+myrand();
this.goal.z = this.anchor_2.z+myrand();
}
this.start_time = t;
this.take_time = 200+Math.random()*200;
this.vertexes.push(pointCopy(this.start, {x:0,y:0,z:0}));
this.anchors_1.push(pointCopy(this.anchor_1, {x:0,y:0,z:0}));
this.anchors_2.push(pointCopy(this.anchor_2, {x:0,y:0,z:0}));
if(this.vertexes.length > VERTEX_MAX){
this.vertexes.splice(0,this.vertexes.length-VERTEX_MAX);
this.anchors_1.splice(0,this.anchors_1.length-VERTEX_MAX);
this.anchors_2.splice(0,this.anchors_2.length-VERTEX_MAX);
}
};
Trail.prototype.update = function(t, target){
bezier3(
t-this.start_time,
this.start,
this.anchor_1,
this.anchor_2,
this.goal,
this.take_time,
this.pos
);
if(t-this.start_time > this.take_time){
this.setNextGoal(this.start_time+this.take_time, target);
this.update(t, target);
}
};
Trail.prototype.draw = function(ctx, camera, t){
var i, dz, dt, ddt, rt, a, v={x:0, y:0, z:0};
var ps = {x:0, y:0};
ctx.beginPath();
if(perspective(this.vertexes[0], camera, ps)){
ctx.moveTo(ps.x, ps.y);
}
var x0 = ps.x;
rt = (t-this.start_time)/this.take_time;
for(i=1; i<this.vertexes.length; i++){
ddt = 0.01;
for(dt=0; dt<1; dt+=ddt){
bezier3(dt,
this.vertexes[i-1],
this.anchors_1[i-1],
this.anchors_2[i-1],
this.vertexes[i],
1,
v);
if(perspective(v, camera, ps)){
dz = v.z-camera.z;
a = 1-(this.vertexes.length-i+1-dt+rt)/VERTEX_MAX;
this.color_f(ctx, a, dz);
ctx.lineTo(ps.x, ps.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(ps.x, ps.y);
ddt = dz/TRAIL_QUALITY+0.01;
}
}
}
ddt = 0.01;
for(dt=0; dt<rt; dt+=ddt){
bezier3(dt,
this.start,
this.anchor_1,
this.anchor_2,
this.goal,
1,
v);
if(perspective(v, camera, ps)){
dz = v.z-camera.z;
a = 1-(1-dt+rt)/VERTEX_MAX;
this.color_f(ctx, a, dz);
ctx.lineTo(ps.x, ps.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(ps.x, ps.y);
ddt = dz/TRAIL_QUALITY+0.01;
}
}
if(perspective(this.pos, camera, ps)){
dz = this.pos.z-camera.z;
a = 1-1/VERTEX_MAX;
this.color_f(ctx, a, dz);
ctx.lineTo(ps.x, ps.y);
ctx.stroke();
}
};
bezier3 = function(t, a, b, c, d, e, dst){
t /= e;
dst.x =
a.x*(1-t)*(1-t)*(1-t)+
b.x*3*t*(1-t)*(1-t)+
c.x*3*t*t*(1-t)+
d.x*t*t*t;
dst.y =
a.y*(1-t)*(1-t)*(1-t)+
b.y*3*t*(1-t)*(1-t)+
c.y*3*t*t*(1-t)+
d.y*t*t*t;
dst.z =
a.z*(1-t)*(1-t)*(1-t)+
b.z*3*t*(1-t)*(1-t)+
c.z*3*t*t*(1-t)+
d.z*t*t*t;
};
perspective = function(point, camera, dst){
var dx = point.x-camera.x;
var dy = point.y-camera.y;
var dz = point.z-camera.z;
if(dz > 0){
dst.x = F*dx/dz;
dst.y = F*dy/dz;
return true;
}
return false;
};
updateScene = function(ctx){
var i, goal;
time_now = new Date().getTime();
var time_d = time_now-time_pre;
trails[0].update(time_now);
for(i=1; i<trails.length; i++){
trails[i].update(time_now, trails[i-1].pos);
}
camera.x += (trails[0].pos.x-camera.x)*0.0005*time_d;
camera.y += (trails[0].pos.y-camera.y)*0.0005*time_d;
camera.z += (trails[0].pos.z-camera.z-100)*0.0005*time_d;
time_pre = time_now;
};
drawScene = function(ctx){
var i;
ctx.clearRect(-canvas.width/2, -canvas.height/2, canvas.width, canvas.height);
for(i=0; i<trails.length; i++){
trails[i].draw(ctx, camera, time_now);
}
};
var myrand = bmRandom(0,20);
var canvas = d.getElementById("world");
var ctx = canvas.getContext("2d");
var trails = [];
var i;
var time_now = new Date().getTime();
var time_pre = time_now;
var camera = {x:0, y:0, z:-200};
for(i=0; i<N; i++){
trails.push(new Trail({x:myrand(), y:myrand(), z:myrand()},
time_now,
function(a,z){return "#FFFFFF";}));
}
for(i=0; i<N; i++){
switch(i%3){
case 0:
trails[i].color_f=function(ctx, a, dz){
var b = dz<10?0:a*F/dz;
b = (b>1?1:b)*(dz<30?(dz-10)/20:1);
ctx.strokeStyle = "rgba(230,230,230,"+b+")";
ctx.lineWidth = F/dz;
ctx.lineCap = b>0.8?"round":"butt";
};
break;
case 1:
trails[i].color_f=function(ctx, a, dz){
var b = dz<10?0:a*F/dz;
b = (b>1?1:b)*(dz<30?(dz-10)/20:1);
ctx.strokeStyle = "rgba(230, 230,230,"+b+")";
ctx.lineWidth = F/dz;
ctx.lineCap = b>0.8?"round":"butt";
};
break;
default:
trails[i].color_f=function(ctx, a, dz){
var b = dz<10?0:a*F/dz;
b = (b>1?1:b)*(dz<30?(dz-10)/20:1);
ctx.strokeStyle = "rgba(132,232,251,"+b+")";
ctx.lineWidth = F/dz;
ctx.lineCap = b>0.8?"round":"butt";
};
break;
}
}
var loop = function(){
canvas.width = w.innerWidth;
canvas.height = w.innerHeight;
ctx.translate(canvas.width/2, canvas.height/2);
updateScene();
drawScene(ctx);
w.requestAnimationFrame(loop);
}
loop();
}(document, window);
$("#eY").click(function(){
$("#eY").toggleClass("start");
});
});
And here you can see my JsFiddle to make things easier. The other solution I've thought about is using CSS to hide the canvas but am I right in saying using display:none; on a canvas doesn't stop the calculations which effectively will put load on the browser?
Hiding the canvas will only hide the element itself, but it won't stop the loop from running. You need something to tell the animation loop to stop from inside it. You can for example use a boolean flag.
Example:
var isRunning = true; // this will keep animation running or stop it
var loop = function(){
canvas.width = w.innerWidth;
canvas.height = w.innerHeight;
ctx.translate(canvas.width/2, canvas.height/2);
updateScene();
drawScene(ctx);
if (isRunning) w.requestAnimationFrame(loop); // check if we're running...
}
loop();
// helper function to restart the loop when toggled from not running, to running
function toggle() {
isRunning = !isRunning;
if (isRunning) loop(); // if new status is running, start loop
};
// move this inside the animation scope so we have access to the loop etc.
$("#eY").click(function(){
$("#eY").toggleClass("start");
toggle(); // also call this
});
Updated fiddle
There may be other issues, I only addressed this one. Just open new question if there should be.
Off-topic: cool animation, reminds me of TrapCode's Strokes in AE/Combustion.

Simple memory game doesn't update its state. Bad game loop?

I'm trying to make a simple (or so I thought) memory game. Unfortunately it does not update state of cards when user clicks on them. I'm running out of ideas, probably because it's my first javascript game. I suppose there is a problem with game loop. Could anyone at least point me in the right direction and help me understand what needs to be changed/rewritten?
//HTML5 Memory Game implementation
//main variables
var cards = [1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8];
var exposed = [makeArray("false",16)];
var first_card = 0;
var second_card = 0;
var moves = 0;
var WIDTH = 800;
var HEIGHT = 100;
var state = 0;
var mouseX = 0;
var mouseY = 0;
//creating canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = WIDTH;
canvas.height = HEIGHT;
document.getElementById("game").appendChild(canvas);
//filling empty array with number,character,object
function makeArray(value, length) {
var newArray = [];
var i = 0;
while (i<length) {
newArray[i] = value;
i++;
}
return newArray;
}
//shuffling algorithm
function shuffle(array) {
var copy = [];
var n = array.length;
var i;
while (n) {
i = Math.floor(Math.random() * n--);
copy.push(array.splice(i, 1)[0]);
}
return copy;
}
//where user clicks
function getClickPosition(event) {
var X = event.pageX - canvas.offsetLeft;
var Y = event.pageY - canvas.offsetTop;
return mouse = [X, Y];
}
//read click position
function readPos(event) {
mousePos = getClickPosition(event);
mouseX = mousePos[0];
mouseY = mousePos[1];
}
//initializing
function init() {
state = 0;
moves = 0;
exposed = [makeArray("false",16)];
cards = shuffle(cards);
}
//drawing cards
function draw() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (var i in cards) {
if (exposed[i] === true) {
ctx.fillStyle = "rgb(250, 250, 250)";
ctx.font = "50px Courier New";
ctx.fillText(cards[i], (i*50+12), 65);
} else {
ctx.strokeStyle = "rgb(250, 0, 0)";
ctx.fillStyle = "rgb(0, 0, 250)";
ctx.fillRect(i*50, 0, 50, 100);
ctx.strokeRect(i*50, 0, 50, 100);
}
}
};
//update cards
function update() {
if (exposed[parseInt(mouseX / 50)] === false) {
if (state == 0) {
state = 1;
first_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
} else if (state == 1) {
state = 2;
second_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
} else {
if (cards[first_card] != cards[second_card]) {
exposed[first_card] = false;
exposed[second_card] = false;
}
state = 1;
first_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
}
}
}
addEventListener('click', readPos, false);
setInterval(function() {
update();
draw();
}, 16);
I would check your addEventListener method: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener
I also recommend you look into using jQuery.
After copy and pasting your code I found a couple of things:
You didn't add an event listener to anything, you should add it to something so I added it to document.
You initialize the exposed array with values "false" and later check if they are false. These are not the same, the string "false" isn't the Boolean false.
You initializes the exposed array as a multi dimensional array [[false,false,false ...]] this should be a single dimension array because later you check exposed[1] (1 depending on the mouse x position.
No need to call draw and update every 16 milliseconds, you can call it after someone clicked.
Wrapped the whole thing up in a function so there are no global variables created.
Here is the code after changing these obvious errors. There might be room for optimization but for now I've gotten the problems out.
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<div id="game"></div>
<script type="text/javascript">
(function(){
//HTML5 Memory Game implementation
//main variables
var cards = [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8];
var exposed = makeArray(false, 16);
var first_card = 0;
var second_card = 0;
var moves = 0;
var WIDTH = 800;
var HEIGHT = 100;
var state = 0;
var mouseX = 0;
var mouseY = 0;
//creating canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = WIDTH;
canvas.height = HEIGHT;
document.getElementById("game").appendChild(canvas);
//filling empty array with number,character,object
function makeArray(value, length) {
var newArray = [];
var i = 0;
while (i < length) {
newArray.push(value);
i++;
}
return newArray;
}
//shuffling algorithm
function shuffle(array) {
var copy = [];
var n = array.length;
var i;
while (n) {
i = Math.floor(Math.random() * n--);
copy.push(array.splice(i, 1)[0]);
}
return copy;
}
//where user clicks
function getClickPosition(event) {
var X = event.pageX - canvas.offsetLeft;
var Y = event.pageY - canvas.offsetTop;
return mouse = [X, Y];
}
//read click position
function readPos(event) {
mousePos = getClickPosition(event);
mouseX = mousePos[0];
mouseY = mousePos[1];
update();
draw();
}
//initializing
function init() {
state = 0;
moves = 0;
exposed = makeArray(false, 16);
cards = shuffle(cards);
}
//drawing cards
function draw() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (var i in cards) {
if (exposed[i] === true) {
ctx.fillStyle = "rgb(150, 150, 150)";
ctx.font = "50px Courier New";
ctx.fillText(cards[i], (i * 50 + 12), 65);
} else {
ctx.strokeStyle = "rgb(250, 0, 0)";
ctx.fillStyle = "rgb(0, 0, 250)";
ctx.fillRect(i * 50, 0, 50, 100);
ctx.strokeRect(i * 50, 0, 50, 100);
}
}
};
//update cards
function update() {
if (exposed[parseInt(mouseX / 50)] === false) {
if (state == 0) {
state = 1;
first_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
} else if (state == 1) {
state = 2;
second_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
} else {
if (cards[first_card] != cards[second_card]) {
exposed[first_card] = false;
exposed[second_card] = false;
}
state = 1;
first_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
}
}
}
document.body.addEventListener('click', readPos, false);
init();
draw();
})();
</script>
</body>
</html>
Your overall logic was good.
The point that was 'bad' was the way you handle the event :
the event handler should store some valuable information that
the update will later process and clear.
Here you mix your update with event handling, which cannot work
especially since the event will not fire on every update.
So i did a little fiddle to show you, the main change is
the click event handler, which update the var last_clicked_card :
http://jsfiddle.net/wpymH/
//read click position
function readPos(event) {
last_clicked_card = -1;
mousePos = getClickPosition(event);
mouseX = mousePos[0];
mouseY = mousePos[1];
// on canvas ?
if ((mouseY>100)||(mouseX<0)||(mouseX>WIDTH)) return;
// now yes : which card clicked ?
last_clicked_card = Math.floor(mouseX/50);
}
and then update is the processing of this information :
//update cards
function update() {
// return if no new card clicked
if (last_clicked_card == -1) return;
// read and clear last card clicked
var newCard = last_clicked_card;
last_clicked_card=-1;
// flip, store it as first card and return
// if there was no card flipped
if (state==0) { exposed[newCard] = true;
first_card = newCard;
state = 1 ;
return; }
// just unflip card if card was flipped
if ((state = 1) && exposed[newCard]) {
exposed[newCard]=false ;
state=0;
return;
}
// we have a second card now
second_card = newCard;
exposed[second_card] = true;
draw();
// ... i don't know what you want to do ...
if (cards[first_card] == cards[second_card]) {
alert('win'); }
else {
alert('loose'); }
exposed[first_card]=false;
exposed[second_card]=false;
state=0;
}

Categories