KineticJS - Swapping shape positions upon contact/mousemove trigger - javascript

I'm using KineticJS and trying to get something done that seems simple enough: trying to get a shape to change position with the shape that's currently being dragged.
The idea is that:
• You pick up a shape on the canvas. This triggers a mousedown event listener, which saves the current position of the shape you've picked up.
• While holding the shape, if you trigger the mouseover on another shape, that shape's event gets triggered and swaps its position, based on the current shape's saved position.
Here is the relevant code I've written to try to get that working:
Board setup:
Simply just setting up the board and calling the needed functions here. I'm not doing anything with the stage, gameBoard, or ctx yet (part of it was for when I tried to get drawImage to work on multiple canvases, but have abandoned that for now).
class BoardView {
constructor(stage, gameBoard, ctx) {
this.stage = stage;
this.gameBoard = gameBoard;
this.ctx = ctx;
this.orbs = [[], [], [], [], []];
this.setupBoard();
}
Board setup functions:
This is where I go about setting up the board and giving each Kinetic Circle the attributes it needs to render on the Layer. Maybe I'm missing something obvious here?
setupBoard () {
for (let colIdx = 0; colIdx < 5; colIdx++) {
this.addRow(colIdx);
}
this.renderBoard();
}
addRow (colIdx) {
for (let rowIdx = 0; rowIdx < 6; rowIdx++) {
let orbType = Math.round(Math.random() * 5);
let orbColor;
if (orbType === 0) {
orbColor = "#990000";
} else if (orbType === 1) {
orbColor = "#112288";
} else if (orbType === 2) {
orbColor = "#005544";
} else if (orbType === 3) {
orbColor = "#776611";
} else if (orbType === 4) {
orbColor = "#772299";
} else {
orbColor = "#dd2277";
}
let orbject = new Kinetic.Circle({
x: (rowIdx + 0.5) * 100,
y: (colIdx + 0.5) * 100,
width: 100,
height: 100,
fill: orbColor,
draggable: true
});
this.orbs[colIdx].push(orbject);
}
}
Board render function:
This is where I add all the Kinetic Circle objects into new layers, and give those layers all its own attributes to work with when I call the event handlers. I also set up the event handlers here after adding the layers to the stage. Am I perhaps messing this up by adding too many layers?
renderBoard () {
for (let row = 0; row < this.orbs.length; row++) {
for (let orb = 0; orb < this.orbs[row].length; orb++) {
let layer = new Kinetic.Layer();
layer.add(this.orbs[row][orb]);
layer.moving = false;
layer.orbId = `orb${row}${orb}`;
layer.pos = [this.orbs[row][orb].attrs.x, this.orbs[row][orb].attrs.y];
this.stage.add(layer);
layer.on("mousedown", this.handleMouseDown);
layer.on("mouseup", this.handleMouseUp);
layer.on("mouseout", this.handleMouseOut);
layer.on("mousemove", this.handleMouseMove);
}
}
}
Mouse event handlers:
This is where I think I'm having my main problem. How I handle the event of moving the mouse to change orbs, perhaps I'm doing something terribly wrong?
handleMouseDown (e) {
window.currentOrb = this;
console.log(window.currentOrb.orbId);
this.moving = true;
}
//handleMouseUp (e) {
// window.currentOrb = undefined;
// this.moving = false;
//}
//handleMouseOut (e) {
//}
handleMouseMove (e) {
if (window.currentOrb !== undefined && window.currentOrb.orbId != this.orbId) {
this.children[0].attrs.x = window.currentOrb.pos[0];
this.children[0].attrs.y = window.currentOrb.pos[1];
this.children[0].draw();
} else {
}
}
}
module.exports = BoardView;
I've tried looking at the KineticJS docs and many StackOverflow answers as I could in hopes of finding a solution that would work for me, but nothing I've seen and tried so far (including the suggestions that came up as I wrote this question) seemed to be of help, and I'm aware the way I've gone about this so far is probably far from the best way to accomplish what I want, so I'm open to any suggestions, pointers, answered questions, or whatever can point me in the right direction to what I'm missing in order to get this to work.
In case this is helpful, here is also a visualization of how things look when the board is rendered.
The circles are all Kinetic Circles (orbs for the purpose of what I'm going for), and clicking and dragging one to another, the one that isn't being dragged but hovered over should move to the original position of the dragged circles.
Thanks!
EDIT:
I made a few changes to the code since then. First off, I changed adding many layers to the stage, to just one:
renderBoard () {
let layer = new Kinetic.Layer();
for (let row = 0; row < this.orbs.length; row++) {
for (let orb = 0; orb < this.orbs[row].length; orb++) {
layer.add(this.orbs[row][orb]);
this.orbCanvases.push(orbCanvas.id);
}
}
this.stage.add(layer);
}
I instead added listeners to the orb objects instead:
addRow (colIdx) {
for (let rowIdx = 0; rowIdx < 6; rowIdx++) {
//same code as before
let orbject = new Kinetic.Circle({
x: (rowIdx + 0.5) * 100, y: (colIdx + 0.5) * 100,
width: 100, height: 100,
fill: orbColor, draggable: true, pos: [rowIdx, colIdx]
});
orbject.on("mousedown", this.handleMouseDown);
orbject.on("mouseup", this.handleMouseUp);
orbject.on("mouseout", this.handleMouseOut);
orbject.on("mousemove", this.handleMouseMove);
this.orbs[colIdx].push(orbject);
}
}
This has had the benefit of making drag and drop much much faster where before, it was going very slow, but I still can't get my objects to swap position.
To be clear, my main issue is knowing which x, y values I should be changing. At the moment in handleMouseMove, I've been trying to change:
e.target.attrs.x = newX;
e.target.attrs.y = newY;
// newX and newY can be any number
However, no matter what I change it to, it has no effect. So it would help me to know if I'm changing the wrong thing/place, for example, maybe I'm supposed to be changing the Kinetic Circle from the array I've stored? Thanks again.
EDIT 2:
I think I got it! However, I had to take this.orbs and set it in the window at window.orbs, and to test it I did:
window.orbs[0][0].x(450);
window.orbs[0][0].draw();
And this caused the x position to change. But putting it in a window does not seem like good practice?
EDIT 3:
I got the orbs to now swap continuously, except for when it's the same orb being swapped again while mouseover is continuing to fire. At mouseup however, it can be swapped again. I also had to set up multiple layers again to get the mouseover events to work while holding another orb, but the performance seems to have improved a little.
I'm going to try and figure out how to get them to be able to swap continuously on the same mouse hold, but in the meantime, here is the code I wrote to achieve this:
addRow (colIdx) {
for (let rowIdx = 0; rowIdx < 6; rowIdx++) {
// same code as before, changed attr given to Kinetic.Circle
let orbject = new Kinetic.Circle({
x: (rowIdx + 0.5) * 100, y: (colIdx + 0.5) * 100,
width: 100, height: 100,
fill: orbColor, draggable: true, orbId: `orb${colIdx}${rowIdx}`
});
}
}
handleMouseDown (e) {
window.currentOrb = window.orbs[e.target.attrs.orbId];
window.newX = e.target.attrs.x;
window.newY = e.target.attrs.y;
}
Mouse down saving currentOrb by ID and its X and Y
handleMouseUp (e) {
window.currentOrb.x(window.newX);
window.currentOrb.y(window.newY);
window.currentOrb.parent.clear();
window.currentOrb.parent.draw();
window.currentOrb.draw();
window.currentOrb = undefined;
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 6; j++) {
window.orbs[`orb${i}${j}`].draw();
}
}
}
When mouse is released, currently, all orbs are redrawn so they can all be used. I plan to refactor this so only orbs hovered over have this change.
handleMouseMove (e) {
if (window.currentOrb !== undefined && (window.currentOrb.attrs.orbId !== e.target.attrs.orbId)) {
window.orbMove.pause();
window.currentTime = 0;
window.orbMove.play();
let targOrbX = e.target.attrs.x;
let targOrbY = e.target.attrs.y;
// This is the target orb that's being changed's value
// We're storing this in targOrb
e.target.x(window.newX);
e.target.y(window.newY);
e.target.parent.clear();
e.target.parent.draw();
e.target.draw();
// Here we have the previously set current orb's position becoming
// the target orb's position
window.newX = targOrbX;
window.newY = targOrbY;
// Now that the move is made, we can set the newX and Y to be the
// target orb's position once mouseup
}
}
Orb swapping logic which works for passing over orbs once, but not if they are passed over again in the same turn.

When does the "hover" officially take place?
When the mouse event's position enters the second orb? If yes, hit test the mouse vs every non-dragging orb:
// pseudo-code -- make this test for every non-dragging orb
var dx=mouseX-orb[n].x;
var dy=mouseY-orb[n].y;
if(dx*dx+dy*dy<orb[n].radius){
// change orb[n]'s x,y to the dragging orb's x,y (and optionally re-render)
}
When the dragging orb intersects the second orb? If yes, collision test the dragging orb vs every non-dragging orb:
// pseudo-code -- make this test for every non-dragging orb
var dx=orb[draggingIndex].x-orb[n].x;
var dy=orb[draggingIndex].y-orb[n].y;
var rSum=orb[draggingIndex].radius+orb[n].radius;
if(dx*dx+dy*dy<=rSum*rSum){
// change orb[n]'s x,y to the dragging orb's x,y (and optionally re-render)
}
BTW, if you drag an orb over all other orbs, the other orbs will all stack on the dragging orbs original position -- is that what you want?

Related

Why is my maze generator not detecting if a cell has been visited in p5.js?

I am trying to make a maze generator, and almost everything is working so far. I have been able to set my position to a random pos, and then I repeat the standard() function. In the function, I add pos to posList, and then I choose a random direction. Next, I check if the cell has been visited by running through all of the posList vectors in reverse. I haven't executed the code that backtracks yet. If visited = false then I move to the square and execute the yet-to-be-made path() function. However, for some reason, the mover just doesn't detect if a cell has been visited or not. I am using p5.js. What am I doing wrong?
var posList = [];
var pos;
var tempo;
var boole = false;
var direc;
var mka = 0;
function setup() {
createCanvas(400, 400);
//Set up position
pos = createVector(floor(random(3)), floor(random(3)));
frameRate(1)
}
//Choose a direction
function direct(dire) {
if(dire === 0) {
return(createVector(0, -1));
} else if(dire === 1) {
return(createVector(1, 0));
} else if(dire === 2) {
return(createVector(0, 1));
} else {
return(createVector(-1, 0));
}
}
/foLo stands fo forLoop
function foLo() {
//If we have checked less than three directions and know there is a possibility for moving
if(mka < 4) {
//tempoRARY, this is what we use to see if the cell has been visited
tempo = createVector(pos.x + direct(direc).x, pos.y + direct(direc).y);
//Go through posList backwards
for(var i = posList.length - 1; i >= 0; i --) {
//If the cell has been visited or the cell is off of the screen
if(tempo === posList[i]) {
//Change the direction
direc ++;
//Roll over direction value
if(direc === 4) {
direc = 0;
}
//Re-execute on next frame
foLo();
//The cell has been visited
boole = false;
//Debugging
console.log(direc)
mka++;
} else if(tempo.x < 0 || tempo.x > 2 || tempo.y < 0 || tempo.y > 2) {
direc ++;
if(direc === 4) {
direc = 0;
}
foLo();
boole = false;
console.log(direc)
mka++;
}
}
//If it wasn't visited (Happens every time for some reason)
if(boole === true) {
//position is now the temporary value
pos = tempo;
console.log("works")
mka = 0;
}
}
}
function standard() {
//Add pos to posList
posList.push(pos);
//Random direction
direc = floor(random(4));
//Convert to vector
direct(direc);
foLo();
//Tracks pos
fill(255, 255, 0);
rect(pos.x*100+50, pos.y*100+50, 50, 50)
}
function draw() {
background(255);
fill(0);
noStroke();
//draw grid
for(var i = 0; i < 4; i ++) {
rect(i*100,0,50,350);
rect(0, i*100, 350, 50);
}
standard();
boole = true;
console.log(pos)
console.log(posList);
}
Your issue is on the line where you compare two vectors if(tempo === posList[i]) {: This will never be true.
You can verify that with the following code (in setup() for example):
const v1 = new p5.Vector(1, 0);
const v2 = new p5.Vector(1, 0);
const v3 = new p5.Vector(1, 1);
console.log(v1 === v2) // false
console.log(v1 === v3) // false
This is because despite having the same value v1 and v2 are referencing two different objects.
What you could do is using the p5.Vector.equals function. The doc has the following example:
let v1 = createVector(10.0, 20.0, 30.0);
let v2 = createVector(10.0, 20.0, 30.0);
let v3 = createVector(0.0, 0.0, 0.0);
print(v1.equals(v2)); // true
print(v1.equals(v3)); // false
This might not give you a working algorithm because I suspect you have other logical errors (but I could be wrong or you will debug them later on) but at least this part of the code will do what you expect.
Another solution is to use a Set instead of your list of positions. The cons of this solution is that you will have to adapt your code to handle the "out of grid" position situation. However when you want to keep track of visited items a Set is usually a great solution because the access time is constant. That this means is that to define is a position has already been visited it will always take the same time (you'll do something like visitedSet.has(positionToCheck), whereas with your solution where you iterate through a list the more cells you have visited to longer it will take to check if the cell is in the list.
The Set solution will require that you transform your vectors before adding them to the set though sine, has I explained before you cannot simply compare vectors. So you could check for their string representation with something like this:
const visitedCells = new Set();
const vectorToString = (v) => `${v.x},{$v.y}` // function to get the vector representation
// ...
visitedCells.add(vectorToString(oneCell)); // Mark the cell as visited
visited = visitedCells.has(vectorToString(anotherCell))
Also has a general advice you should pay attention to your variables and functions name. For example
// foLo stands fo forLoop
function foLo() {
is a big smell: Your function name should be descriptive, when you see your function call foLo(); having to find the comment next to the function declaration makes the code less readable. You could call it generateMaze() and this way you'll know what it's doing without having to look at the function code.
Same for
//tempoRARY, this is what we use to see if the cell has been visited
tempo = createVector(pos.x + direct(direc).x, pos.y + direct(direc).y);
You could simply rename tempo to cellToVisit for example.
Or boole: naming a boolean boole doesn't convey a lot of information.
That could look like some minor details when you just wrote the code but when your code will be several hundred lines or when you read it again after taking several days of break, you'll thank past you for taking care of that.

JavaScript game starts fast and slows down over time

My simple JavaScript game is a Space Invaders clone.
I am using the p5.js client-side library.
I have tried many things to attempt at speeding up the game.
It start off fast, and then over time it get slower, and slower, it isn't as enjoyable.
I do not mean to show every bit of code I have. I am not showing every class, but I will show the main class where everything is happening.
Could someone eyeball this and tell me if you see anything major?
I am new to JS and new to making games, I know there is something called update()
that people use in scripting but I am not familiar with it.
Thank you.
var ship;
var flowers = []; // flowers === aliens
var drops = [];
var drops2 = [];
function setup() {
createCanvas(600, 600);
ship = new Ship();
for (var i = 0; i < 6; i ++) {
flowers[i] = new Flower(i * 80 + 80, 60);
}
flower = new Flower();
}
function draw() {
background(51);
ship.show();
ship.move();
shipDrops();
alienDrops();
dropsAndAliens();
dropDelete();
drop2Delete();
}
// if 0 drops, show and move none, if 5, etc..
function shipDrops() {
for (var i = 0; i < drops.length; i ++) {
drops[i].show();
drops[i].move();
for (var j = 0; j < flowers.length; j++) {
if(drops[i].hits(flowers[j]) ) {
flowers[j].shrink();
if (flowers[j].r === 0) {
flowers[j].destroy();
}
// get rid of drops after it encounters ship
drops[i].evaporate();
}
if(flowers[j].toDelete) {
// if this drop remove, use splice function to splice out of array
flowers.splice(j, 1); // splice out i, at 1
}
}
}
}
function alienDrops() {
// below is for alien/flower fire drops 2
for (var i = 0; i < drops2.length; i ++) {
drops2[i].show();
drops2[i].move();
if(drops2[i].hits(ship) ) {
ship.shrink();
drops2[i].evaporate(); // must evap after shrink
ship.destroy();
if (ship.toDelete) {
delete ship.x;
delete ship.y;
} // above is in progress, deletes after ten hits?
}
}
}
function dropsAndAliens() {
var randomNumber; // for aliens to shoot
var edge = false;
// loop to show multiple flowers
for (var i = 0; i < flowers.length; i ++) {
flowers[i].show();
flowers[i].move();
// ******************************************
randomNumber = Math.floor(Math.random() * (100) );
if(randomNumber === 5) {
var drop2 = new Drop2(flowers[i].x, flowers[i].y, flowers[i].r);
drops2.push(drop2);
}
//**************** above aliens shooting
// below could be method, this will ensure the flowers dont
//go offscreen and they move
//makes whtever flower hits this space become the farther most
//right flower,
if (flowers[i].x > width || flowers[i]. x < 0 ) {
edge = true;
}
}
// so if right is true, loop thru them all again and reset x
if (edge) {
for (var i = 0; i < flowers.length; i ++) {
// if any flower hits edge, all will shift down
// and start moving to the left
flowers[i].shiftDown();
}
}
}
function dropDelete() {
for (var i = drops.length - 1; i >= 0; i--) {
if(drops[i].toDelete) {
// if this drop remove, use splice function to splice out of array
drops.splice(i, 1); // splice out i, at 1
}
}
}
function drop2Delete() {
for (var i = drops2.length - 1; i >= 0; i--) {
if(drops2[i].toDelete) {
// if this drop remove, use splice function to splice out of array
drops2.splice(i, 1); // splice out i, at 1
}
}
}
function keyReleased() {
if (key != ' ') {
ship.setDir(0); // when i lift the key, stop moving
}
}
function keyPressed() {
// event triggered when user presses key, check keycode
if(key === ' ') {
var drop = new Drop(ship.x, height); // start ship x and bottom of screen
drops.push(drop); // when user hits space, add this event to array
}
if (keyCode === RIGHT_ARROW) {
// +1 move right
ship.setDir(1);
} else if (keyCode === LEFT_ARROW) {
// -1 move left
ship.setDir(-1);
} // setir only when pressing key, want continuous movement
}
Please post a MCVE instead of a disconnected snippet that we can't run. Note that this should not be your entire project. It should be a small example sketch that just shows the problem without any extra code.
But to figure out what's going on, you need to debug your program. You need to find out stuff like this:
What is the length of every array? Are they continuously growing over time?
What is the actual framerate? Is the framerate dropping, or does it just appear to be slower?
At what point does it become slower? Try hard-coding different values to see what's going on.
Please note that I'm not asking you to tell me the answers to these questions. These are the questions you need to be asking yourself. (In fact, you should have all of these answers before you post a question on Stack Overflow!)
If you still can't figure it out, then please post a MCVE in a new question post and we'll go from there. Good luck.

p5.js code doesn't throw errors, but won't load on mouse click

I'm doing an analysis of the presidential candidates speeches. I have a data file with the following variables:
> names(cl.context)
[1] "id" "category" "statement" "nchar" "polarity"
The statement variable is populated by sentences that each belong to one category from three. The polarity ranges from -1 to 1, reflecting whether the sentence has a positive bias, neutral, or negative bias.
What I'm trying to do in p5 is to have statements displayed by category, with random x,y placement, when the user clicks the mouse INSIDE the canvas. The statements themselves are colored according to their polarity.
I finally got to the point where the developer console doesn't throw any errors and it draws the canvas. But when I click within the canvas, nothing happens. No statements appear.
I'm VERY new to JavaScript, and since it's not throwing an error message, I can't resolve where the issue lies. Hoping for some advice here.
My p5 code:
var clContext;
var x;
var y;
const STATEMENTS = 118, CATEGORY = 3, QTY = STATEMENTS/CATEGORY | 0,
POLARITY = 3,
statements = Array(STATEMENTS), inds = Array(CATEGORY), polarity = Array(POLARITY);
//load the table of Clinton's words and frequencies
function preload() {
clContext = loadTable("cl_context.csv", "header");
}
function setup() {
createCanvas(647, 400);
background(51);
// Calling noStroke once here to avoid unecessary repeated function calls
noStroke();
// iterate over the table rows
for(var i=0; i<clContext.getRowCount(); i++){
//- Get data out of the relevant columns for each row -//
var inds = clContext.get(i, "category");
var statements = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity")
}
for (let i = 0; i < statements; randomCategoryStates(i++));
// create your Statement object and add to
// the statements array for use later
inds[i] = new Statement();
console.info(inds);
}
function draw() {
if(mouseClicked == true){
for(var i=0; i<inds.length; i++) {
inds[i].display();
}
}
}
function mouseClicked() {
if((mouseX < width) && (mouseY < height)) {
randomCategoryStates(~~random(CATEGORY));
redraw();
return false;
}
}
// Function to display statements by a random category with each mouse click
function randomCategoryStates(group) {
let idx = inds[group], rnd;
while ((rnd = ~~random(QTY)) == idx);
inds[group] = rnd;
}
// Function to align statements, categories, and polarity
function Statement() {
this.x = x;
this.y = y;
this.xmax = 10;
this.ymax = 4;
this.cat = inds;
this.statement = statements;
this.polarity = polarity;
// set a random x,y position for each statement
this.dx = (Math.random()*this.xmax) * (Math.random() < .5 ? -1 : 1);
this.dy = (Math.random()*this.ymax) * (Math.random() < .5 ? -1 : 1);
}
// Attach pseudo-class methods to prototype;
// Maps polarity to color and x,y to random placement on canvas
Statement.prototype.display = function() {
this.x += this.dx;
this.y += this.dy;
var cols = map(this.polarity == -1, 205, 38, 38);
var cols = map(this.polarity == 0, 148, 0, 211);
var cols = map(this.polarity == 1, 0, 145, 205);
fill(cols);
textSize(14);
text(this.statement, this.x, this.y);
};
EDIT: One thing that confused me is that the help I got with this code on the Processing forum didn't include a call for the mouseClicked() function in the draw function, so I added that. Not entirely sure that I did it correctly or if it was even necessary.
Your code has a lot going on. I'm going to try to go through everything, in no particular order:
Why do you need these variables?
var x;
var y;
I know that you think you're using them to pass in a position to a Statement, but you never set these variables! Let's just get rid of them for now, since they aren't doing anything. This will cause an error in your code, but we'll fix that in a second.
Look at this for loop:
for(var i=0; i<clContext.getRowCount(); i++){
//- Get data out of the relevant columns for each row -//
var inds = clContext.get(i, "category");
var statements = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity")
}
Here you're reading in the CSV file, but then you aren't doing anything with these variables. You then follow that up with this:
for (let i = 0; i < statements; randomCategoryStates(i++));
// create your Statement object and add to
// the statements array for use later
inds[i] = new Statement();
Notice the semicolon after that for loop! That means the inds[i] = new Statement() line is outside the loop, which doesn't make any sense. I also don't really know what you're doing with the randomCategoryStates(i++) part.
You need to combine all of that into one loop:
for (var i = 0; i < clContext.getRowCount(); i++) {
var category = clContext.get(i, "category");
var statement = clContext.get(i, "statement");
var polarity = clContext.get(i, "polarity")
inds[i] = new Statement();
}
But this still doesn't make any sense, because you're never passing those variables into your Statement class. So let's take a look at that.
I'll just add some comments:
function Statement() {
this.x = x; //when do you ever set the value of x?
this.y = y; //when do you ever set the value of y?
this.cat = inds; //cat is the array that holds all statements? What??
this.statement = statements; //statement is the statements array, but nothing is ever added to that array?
this.polarity = polarity; //when do you ever set the value of polarity?
As you can see, what you're doing here doesn't make a lot of sense. You need to change this constructor so that it takes arguments, and then you need to pass those arguments in. Something like this:
function Statement(category, polarity, statement) {
this.category = category;
this.statement = statement;
this.polarity = polarity;
}
Now that we have that, we can change the line in our for loop to this:
inds[i] = new Statement(category, statement, polarity);
But that still doesn't make sense. Why do you have separate arrays for statements, categories, and polarities? Don't you just want one array that holds them all, using instances of the Statement class? So let's get rid of the inds and polarity variables, since they aren't used for anything.
We then change that line to this:
statements[i] = new Statement(category, polarity, statement);
We also have to change everywhere else that's still using the inds variable, but we have other problems while we're at it.
Let's just start with your draw() function:
function draw() {
if (mouseClicked == true) {
for (var i = 0; i < statements.length; i++) {
statements[i].display();
}
}
}
So I guess you only want anything to display while the mouse is pressed down, and nothing to display when the mouse is not pressed down? I'm not sure that makes sense, but okay. Even so, this code doesn't make sense because mouseClicked is a function, not a variable. To determine whether the mouse is pressed, you need to use the mouseIsPressed variable, and you don't need the == true part.
if (mouseIsPressed) {
I have no idea what these two functions are supposed to do:
function mouseClicked() {
if ((mouseX < width) && (mouseY < height)) {
randomCategoryStates(~~random(CATEGORY));
redraw();
return false;
}
}
// Function to display statements by a random category with each mouse click
function randomCategoryStates(group) {
let idx = statements[group],
rnd;
while ((rnd = ~~random(QTY)) == idx);
statements[group] = rnd;
}
There are much simpler ways to get random data. I'm just going to delete these for now, since they're more trouble than they're worth. We can go back and add the random logic later.
For now, let's look at the display() function inside your Statement class:
Statement.prototype.display = function() {
this.x += this.dx;
this.y += this.dy;
var cols = map(this.polarity == -1, 205, 38, 38);
var cols = map(this.polarity == 0, 148, 0, 211);
var cols = map(this.polarity == 1, 0, 145, 205);
fill(cols);
textSize(14);
text(this.statement, this.x, this.y);
};
We never actually declared the x, y, dx, or dy, variables, so let's add them to the constructor:
this.x = random(width);
this.y = random(height);
this.dx = random(-5, 5);
this.dy = random(-5, 5);
Back to the display() function, these lines don't make any sense:
var cols = map(this.polarity == -1, 205, 38, 38);
var cols = map(this.polarity == 0, 148, 0, 211);
var cols = map(this.polarity == 1, 0, 145, 205);
Why are you declaring the same variable 3 times? Why are you trying to map a boolean value to a number value? This doesn't make any sense. For now, let's just get rid of these lines and simplify your logic:
if(this.polarity == -1){
fill(255, 0, 0);
}
else if(this.polarity == 1){
fill(0, 255, 0);
}
else{
fill(0, 0, 255);
}
This will make negative polarity red, positive polarity green, and neutral polarity blue.
Now that we have this, we can actually run the code. When you hold the mouse down, you'll see your statements display and move around randomly. However, they'll quickly fill up your screen because you aren't ever clearing out old frames. You need to call the background() function whenever you want to clear out old frames. We might do that at the beggining of the draw() function, or right inside the if(mouseIsPressed) statement, before the for loop.
if (mouseIsPressed) {
background(51);
for (var i = 0; i < statements.length; i++) {
If you make all those changes, you will have a working program. I'd be willing to bet that it still doesn't do exactly what you want. You're going to have to start much simpler. Your code is a bit of a mess, and that's a result of trying to write the whole program all at once instead of testing small pieces one at a time. This is why we ask for an MCVE, because debugging the whole thing like this is very painful. You need to start narrowing your goals down into smaller pieces.
For example, if you now want to make it so only one statement appears at a time, start over with a simpler example sketch that only shows one hardcoded statement. Get that working perfectly before you try to integrate it into your main program. If you want the statements to be ordered by category, then start over with a simpler example sketch that only shows statements based on category, without any of the extra logic. That way if you have a question about something specific, you can post that small code and it will be much easier to help you.
Good luck.

JavaScript canvas: "Random" errors with collision detection

Working on a simple canvas application where the user can shoot bullets with a gun. (click = new bullet, arrow key = new direction)
Works almost perfectly except there are seemingly "random" occurrences of where if the direction is changed, collision will fail.
Here's my jsFiddle. Relevant snippets:
Bullet.prototype - animate():
animate: function () {
if (gun.velocity.direction === keys.left) {
this.coordinates.x -= this.velocity.speed.x;
} else if (gun.velocity.direction === keys.up) {
this.coordinates.y += this.velocity.speed.y;
} else if (gun.velocity.direction === keys.right) {
this.coordinates.x += this.velocity.speed.x;
} else if (gun.velocity.direction === keys.down) {
this.coordinates.y -= this.velocity.speed.y;
}
return this;
},
Bullet.prototype - collision():
collision: function (str) {
if (str === 'boundary') {
if (this.coordinates.x + this.velocity.speed.x > canvas.width || this.coordinates.x + this.velocity.speed.x < 0) {
this.velocity.speed.x = -this.velocity.speed.x;
} else if (this.coordinates.y + this.velocity.speed.y > canvas.height || this.coordinates.y + this.velocity.speed.y < 0) {
this.velocity.speed.y = -this.velocity.speed.y;
}
}
}
Key handling:
document.onkeydown = function (e) {
e = e.keyCode;
if (e === keys.left) {
gun.velocity.direction = keys.left;
} else if (e === keys.up) {
gun.velocity.direction = keys.up;
} else if (e === keys.right) {
gun.velocity.direction = keys.right;
} else if (e === keys.down) {
gun.velocity.direction = keys.down;
}
};
How do I figure out why this is happening and how I can stop it?
Ok I had a look and found your bug.
You have bullets that you have given the property speed that is two values x, and y You also have a direction. When you animate the bullets you check the direction and move the bullet in the correct direction by adding only to x or y depending on direction. But then when you test if the bullets hit the wall you ignore the direction and test against the bullets speed. The whole time you have the bullets x and y speed both not equal to zero. You collision test is testing bullets moving diagonally.
If you add this bit of code
ctx.strokeStyle = this.color;
ctx.beginPath();
// draw a line in the direction you are testing the wall to be
ctx.moveTo(this.coordinates.x, this.coordinates.y);
ctx.lineTo(this.coordinates.x + this.velocity.speed.x*10, this.coordinates.y + this.velocity.speed.y*10);
ctx.stroke();
where you render the bullets, you will see that the bullets are not traveling in the direction indicated by this.velocity.speed, but you use these values to test for the wall.
There is to much to change for a simple fix.
What to do.
For each bullet
Keep speed as a single number.
Create delta.x, and delta.y as the bullets vector.
Keep direction as a single value. As you already have.
When you shoot you use the direction to set the bullet vector (delta);
up set delta {x:0,y:-1},
down set delta {x:0,y:1},
left set delta {x:-1,y:0},
right set delta {x:1,y:0},
To move the bullet just add the deltas times the speed;
bullet.pos.x += bullet.delta.x * bullet.speed;
bullet.pos.y += bullet.delta.y * bullet.speed;
Speed has nothing to do with direction. It is a positive value describing distance over time.
Delta x and y is really all you need for the bullets direction, but no harm in holding the direction as a single value as well as long as the two match.
To test for the wall
// check if the bullet is moving in the x direction
// then check if it's about to hit the wall
if(bullet.delta.x !== 0 && (bullet.pos.x + bullet.delta.x * bullet.speed > wallLoc ||
bullet.pos.x + bullet.delta.x * bullet.speed < 0)){
bullet.delta.x = -bullet.delta.x; // reverse bullet
}
do the same for the y direction.
I don't know if it is your intention to change all the bullet directions when you press a key. If it is just go through all the bullets and change there deltas when a key is pressed.
Hope this is clear. Do ask if you need more info.

how to keep a object in array between other objects in array (canvas slider)

I am making a slider with kineticjs where you can can dynamically add more handles to gain more handles. Important is that the handles may not cross over eachother. Because it is important that the order of rank is still te same.
I have made what checks if the object what is higher in order has a lower x value than the object that is one lower in order. If I have made 5 handles, the last one works perfect. If I drop it on the left from the 4th it will put nicely right from the 4th. But if I put the 4th on the left of the 3th it will just go there and the 5th will be put close right to the 3th instead that the 4th does that.
How can it happen? Is there a solution for this problem?
container.addEventListener('mouseup', function(e) {
handles = layer.getChildren();
for(var m=handles.length; m--;)
{
if(m>0)
{
if (layer.get('#myRect'+m)[0].getAbsolutePosition().x < layer.get('#myRect'+(m-1))[0].getAbsolutePosition().x)
{
handles[m].setX(layer.get('#myRect'+(m-1))[0].getAbsolutePosition().x + 15);
}
}
}
layer.draw();
Update();
});
I have discovered that the layer.getChildren is quite messy. Each time he gives the objects an other rankorder. The layer.get('#myRect'+(m-1))[0] however is more reliable. I have kept the layer.getChildren for the counting.
EDIT: I came to discover that when you push the layer.getChildren in a selfmade array, it is more reliable while you have still the array options (layer.getChildren is unreliable for rank order).
The problem with my design is that you can have a moment where you have true on both if. Then he does always the if and thats not what you always want. So I have made a function where it calculates if your handle goes to the right or left and then looks if its across another one. Sorry for messy explanation and code.
var latestcoordinate = 0;
var newcoordinate = 0;
container.addEventListener('mousedown', function(e) {
latestcoordinate = 0;
for (var x = 0; x < array.length; x++) {
latestcoordinate += parseInt(array[x].getAbsolutePosition().x);
}
});
container.addEventListener('mouseup', function(e) {
newcoordinate = 0;
for (var x2 = 0; x2 < array.length; x2++) {
newcoordinate += parseInt(array[x2].getAbsolutePosition().x);
}
for(var m=array.length; m--;)
{
if(m<array.length-1 && newcoordinate < latestcoordinate)
{
if (array[m].getAbsolutePosition().x < array[m+1].getAbsolutePosition().x)
{
array[m].setX(array[m+1].getAbsolutePosition().x + 15);
}
}
if(m>0 && newcoordinate > latestcoordinate)
{
if (array[m].getAbsolutePosition().x > array[m-1].getAbsolutePosition().x)
{
array[m].setX(array[m-1].getAbsolutePosition().x - 15);
}
}
}
layer.draw();
Update();
});
Not that this is the solution, but I think you want to add an escape from your for loop or make it a 'for in' loop
for(var m=handles.length; m--;)
{
if(m>0) // <--- why isn't this clause in the for loop logic?
{
if (layer.get('#myRect'+m)[0].getAbsolutePosition().x < layer.get('#myRect'+(m-1))[0].getAbsolutePosition().x)
{
handles[m].setX(layer.get('#myRect'+(m-1))[0].getAbsolutePosition().x + 15);
}
}
else
break;
}

Categories

Resources