JS Canvas Collision-Detection using getImageData - javascript

As a very inexperienced programmer, I'm trying to code a game that detects when the player collides with certain colors on the canvas. I have a black square with coordinates "player.x" and "player.y" and dimensions 50x50 that moves around when you press the arrow keys. I also have a stationary red (255,0,0) square elsewhere on the canvas.
The function below is supposed to grab a slightly larger square around the "player" square and find out if there's any red in it. If there is, it will send up an alert. The problem is, this doesn't seem to be working.
function collideTest(){
var canvas = document.getElementById("canvas");
var c = canvas.getContext("2d");
var whatColor = c.getImageData(player.x - 5, player.y - 5,60,60);
for (var i = 0; i < 3600; i++) {
if (whatColor.data[i] == 255) {
alert("red");
}
}
}
I'm semi-aware that this is not the most efficient way to detect red pixels, but I wanted to simplify the code before posting it here. Is there something obviously wrong with the function?
The problem could lie in the way the function is called. It gets called at the end of another function that detects user-input and changes the coordinates of the "player" square. THAT function gets called right before everything is drawn on the canvas.
Thanks in advance for any help!

var whatColor = c.getImageData(player.x - 5, player.y - 5,60,60);
player.x and player.y must not be decimal, make sure they are rounded or getImageData will be angry and not play nice.

For each single pixel on the canvas, the whatColor.data array holds 4 sequential pieces of color information: red,green,blue,alpha(opacity). So the whatColor.data looks like this for each pixel:
whatColor.data[i] is the red component of the color.
whatColor.data[i+1] is the green component of the color.
whatColor.data[i+2] is the blue component of the color.
whatColor.data[i+3] is the alpha(opacity) component of the color.
So your iteration would look like this (4 indexes per pixel):
for(var i = 0, n = whatColor.data.length; i < n; i += 4) {
var red = whatColor.data[i];
var green = whatColor.data[i + 1];
var blue = whatColor.data[i + 2];
var alpha = whatColor.data[i + 3];
if(red==255){ ... it's a hit, do your thing! ... }
}
See here for a mini-tutorial on the imageData.data array: http://www.html5canvastutorials.com/advanced/html5-canvas-get-image-data-tutorial/
By the way, you might look at one of the canvas libraries that simplify game making with canvas. Here are just a few: easelJs, KineticJs, FabricJs, and more!

Related

Animate a flying bird in p5.js

I want to create an animation of a bird with p5 js. I have 6 pictures of the bird - when the wings are up, in the middle, and so on... When I press 'space bar' on the keyboard, the bird should fly - so all the pics should be shown as an animation (as if the bird is really flying). I want to build this code snippet without spritemap.
This is my code, but somehow it doesn't work..
let time = 0;
let frame = 0;
let img = [];
function preload() {
img.push(loadImage("assets/Bird/bird-1.png"));
img.push(loadImage("assets/Bird/bird-2.png"));
img.push(loadImage("assets/Bird/bird-3.png"));
img.push(loadImage("assets/Bird/bird-4.png"));
img.push(loadImage("assets/Bird/bird-5.png"));
}
function draw() {
function keyPressed() {
if (key == ' ') {
const speed = 1;
const numImage = img.length;
let current = frame % numImage;
let display = img[current];
image(display, width / 2, height / 2, display.width, display.length);
time += speed;
if (time > 5) {
time = 0;
frame++;
}
}
}
}
Looking forward to reading some ideas! Thank you in advance.
First things first you should not need to handle frames and such things. It is better to use keyPressed function outside of scope draw since it is a special event function and automatically called when a key is pressed.
It is better to use setup functionality instead of preload since preload is a little bit more early function then we needed. Setup is more relevant in such things like loading an array and so on.
I see that you forgot to create a canvas to draw an image on it. I added that on setup and also set the framerate of canvas regarding the img array's size.
function setup() {
createCanvas(400, 400);
background(51);
img.push(loadImage("1.png"));
img.push(loadImage("2.png"));
img.push(loadImage("3.png"));
img.push(loadImage("4.png"));
frameRate(img.length * 2); // double speed on animate sprites
}
From this point it is only a matter of checking the keyCode and looping through array.
function draw() {
if (keyIsDown(32)) {
background(51);
const numImage = img.length;
let current = frameCount % numImage;
let display = img[current];
image(display, width / 2 - display.width , height / 2 - display.height , display.width, display.length);
}
}
Here in the keyIsDown(32) check, 32 represents spacebar. You can check others from here easily : http://keycode.info/
You want to re-set the background of canvas on each sprite display. If not, they will still be showing on each render.
You can see the working version of your code in here :
https://editor.p5js.org/darcane/sketches/rVl22hkv7

Drag canvas images/pieces on a chess board

I am trying to build a chess game using Javascript(ES 6) and Canvas.
I have built a basic chessboard and also rendered pawns on top of it.
Now I intend to either click on the pawn or drag it to make a move.
document.addEventListener('click',board_click)
or
canvas.addEventListener('click', board_click, false);
is the way I manage to listen to these events.
Now how would I know where I have clicked? I figured I can try to get the current position of the click, but how will I know what item is there at the present location.
Any libraries or already implemented logics will also be helpful
I have rendered my pawns like this
const drawPawns = (white, black, ctx, boardDimension, allPieces) => {
const rows = [0, 1, 2, 3, 4, 5, 6, 7];
const cols = [1, 6];
let pawn;
let side;
// boardDimension = 90;
cols.forEach((col) => {
rows.forEach((row) => {
// pawn = Pawn.call(this, 'black');
side = col === 1 ? 'black' : 'white';
// That little tinkering is to center align the Pawn in the tile
pawn = new Pawn(side, row * boardDimension + 30, col * boardDimension + 5, true);
spriteImage.draw(ctx, pawn.canvasPosition, pawn.x, pawn.y);
allPieces.push(pawn);
});
});
};
The pawn.canvasPostion is to draw the image(using Sprite), whereas x and y are the places where its coordinates are. Now how would I get this particular Coor ordinates?
The listener callback (your board_click function) gets the browser event as an argument. This event object contains a bunch of data, including the clicked element and the coordinates of the click.
Since you are using canvas, you should attach the click listener on the canvas element. Canvas does not have a state graph, which means it has no concept of objects in the canvas - it is just a bunch of pixels. This means it is your responsibility to keep track of which piece is where.
Since you are already placing the pieces on the board, you know exactly where they are!
The normal way to do this is to get the click position, map that to a grid cell on the board, and then check whether anything was on that cell. How exactly this will happen depends on the data structure that describes your game.
For illustrative purposes let's assume that the chess pieces reside in an array, and each piece knows its cell coordinates (for chess it probably makes sense to do the opposite - each cell would know whether it has a piece on it, but bear with me).
var cellSize = 16; // let's assume a cell on your board is 16x16 pixels
var pieces = []; // this keeps all chess pieces
function board_click(evt) {
// get cell coordinates
var cellX = Math.floor(evt.clientX/cellSize);
var cellY = Math.floor(evt.clientY/cellSize);
var piece = pieces.find((p) => {
return p.x == cellX && p.y == cellY;
});
if (piece) {
// a piece was clicked
} else {
// an empty cell was clicked
}
}
I hope this is enough to get you started.
Edit: had forgotten to divide the mouse coordinates by the cell size...

Tooltips for data in javascript using p5.js

I am trying to make tooltips for a data visualization I made using p5.js but I am completely lost. Nothing I tried works. This is my code as is.
var table;
var i;
var j;
var cellValue;
var label;
var test;
function preload() {
matrix = loadTable("dataLayer2matrix.csv","csv")
labels = loadTable("dataLayer2labels.csv","csv")
test = matrix
}
function setup() {
createCanvas(1500,1500)
noStroke()
fill(0,0,255,10)
angleMode(DEGREES)
background(255,255,255)
matrixStartX = 200
matrixStartY = 250
var matrixRows = matrix.getRows()
var matrixSize = matrixRows.length
// Experiment with grid
fill(75, 75, 75, 50)
for (r = 0; r <= matrixSize; r++) {
rect(matrixStartX , matrixStartY + r * 20 - 1 , 20 * matrixSize, 1)
rect(matrixStartX + r * 20 - 1 , matrixStartY, 1, 20 * matrixSize)
}
// Draw matrix
for (var mr = 0; mr < matrixSize; mr++) {
for (var mc = 0; mc < matrixSize; mc++) {
cellValue = matrixRows[mr].getNum(mc)
fill(49,130,189,cellValue*10)
rect(mc * 20 + matrixStartX, mr * 20 + matrixStartY, 19 ,19)
}
}
// Labels - horizontal
fill(75, 75, 75, 255)
labelsRow = labels.getRows()
for (mc = 0; mc < matrixSize; mc++) {
label = labelsRow[0].getString(mc)
text(label, 10, mc*20+matrixStartY + 15)
}
// Labels - vertical
push()
translate(matrixStartX + 15, matrixStartY - 15)
rotate(-90)
for (mc = 0; mc < matrixSize; mc++) {
label = labelsRow[0].getString(mc)
text(label, 0, mc*20)
}
pop()
//Tooltip when clicked
}
/* if(mouseIsPressed){
fill(50);
text(cellValue, 10,10,70,80);
}*/
}
}
It makes this image:
I want it so that when I go over a square I get the data in it. I really can't seem to do it. Thanks.
I think the advice telling you to use bootstrap is missing the fact that you're using p5.js. Bootstrap is more for dealing with html components, not internal Processing sketches.
Instead, you probably want to do this with p5.js code. The best thing you can do is break your problem down into smaller steps:
Step 1: Can you draw a single rectangle?
Instead of trying to add this new functionality to your existing sketch, it might be easier if you start with a simpler example sketch with just a single rectangle.
Step 2: Can you detect when the mouse is inside that rectangle?
If you know where you're drawing the rectangle, you know its coordinates. You also know the coordinates of the mouse from the mouseX and mouseY variables. So to detect whether the mouse is inside the rectangle, you simply have to use if statements that compare the coordinates of the mouse to the coordinates of the rectangle. There are a ton of resources on google for this, and it might help if you draw some examples out on a piece of paper.
Also, don't worry about the tooltip just yet. Just do something simple like change the color of the rectangle when the mouse is inside it.
Step 3: Can you display the information box?
Again, do this in its own sketch first. Maybe create a function that takes a position and the information you want to display as parameters and displays it in a rectangle. Don't worry about making it a tooltip yet. Just get it displaying. Use hard-coded values for the information.
Step 4: Can you combine your small example sketches?
You have code that triggers when the mouse is inside a rectangle. You have code that draws the tooltip. Can you make it so the tooltip is drawn when the mouse is inside the rectangle?
Step 5: Only when all of the above works, then you should start thinking about adding it to your full sketch.
Instead of using an example rectangle, you'll have to use the rectangles you're drawing on the screen. Instead of calling the tooltip function with hard-coded values, use the values you get from the squares.
Take on those pieces one at a time, and make small steps toward your goal. Then if you get stuck, you can post an MCVE of the specific step you're on. Good luck!

HTML5 : getImageData with onmousemove make slow my application in Firefox

I create a little html5 game with canvas.
In the canvas, there are many displayed sprites and one of them move automatically from left to right. The others are statics.
When I move the mouse onto the canvas, I draw all sprites in a temporary canvas and I use getImageData to find the sprite onto which the mouse is over.
But getImageData make slow anormally the moving sprite in Firefox.
So what is the solution to avoid this deceleration ?
Here my code :
function getSelectedObject(array_objects)
{
//Clear the temporary canvas :
tmpcx.clearRect(0, 0, tmpc.width, tmpc.height);
/*Search the right sprite object :*/
for(var i = array_objects.length-1; i >= 0; i--)
{
array_objects[i].draw(tmpcx);
imageData = tmpcx.getImageData(mouse_x, mouse_y, 1, 1);
component = imageData.data;
if(component[3] > 0)
{
//Return the sprite object found :
return array_objects[i];
}
else
{
tmpcx.clearRect(0, 0, tmpc.width, tmpc.height);
}
}
return false;
}
canvas.onmousemove = function(event)
{
selectedObject = getSelectedObject(array_objects);
}
Not sure how much of a performance gain you'd get with this - no need to clear the temp canvas between sprites .... the pixel is clear until a sprite is painted on it!
I've referenced a function called checkBoundingBoxisOver - not sure if you could write this function, but I can't right now - besides, I don't even know what your array_objects are!!!
I would think it were simple, just need the x, y, width, height of a sprite to do a preliminary check if the sprite could even possibly be under the mouse before doing the expensive draw
function getSelectedObject(array_objects) {
//Clear the temporary canvas :
tmpcx.clearRect(0, 0, tmpc.width, tmpc.height);
var sprite;
/*Search the right sprite object :*/
for (var i = array_objects.length - 1; i >= 0; i--) {
sprite = array_objects[i];
if (checkBoundingBoxisOver(sprite, mouse_x, mouse_y)) {
sprite.draw(tmpcx);
imageData = tmpcx.getImageData(mouse_x, mouse_y, 1, 1);
component = imageData.data;
if (component[3] > 0) {
return sprite;
}
}
}
return false;
}
I ran into a similar issue reading pixels from a large bitmap every frame of the animation. In my case it is a black and white image showing where the world is water or land.
getImageData is extremely slow on Firefox even when reading just a single pixel.
My solution is to call getImageData only once and store the result in a imageData variable
var imageData = self.context.getImageData(0,0,image.width, image.height);
Then you can make repeated calls to the image data and pull out the part of the image you want. In my case I just need a single pixel or a single color which looks like this
var pixelRed = this.imageData.data[(y* imageWidth * 4) + (x * 4)] == 0;
x and y are self explanatory and since the pixels are 4 byte values (Red, Green, Blue, Alpha) I need to multiply my array index by 4. It proves to be very fast for me.
It be pretty easy to use this code to grab any portion out of the array directly as long as it is not too big.

HTML5 Canvas image rotation with dynamic values and images

I have the following code -
for(var i = 0; i < treesLength; i++){
var tmpTree = trees[i];
tmpTreeX = 1+Math.random()*($("#gameBoard").width()-95);
tmpTreeY = 1+Math.random()*($("#gameBoard").height()-90);
var imgTreeFile = new Image();
imgTreeFile.onload = function() {
context.save();
context.translate(tmpTreeX,tmpTreeY);
context.rotate(47 * Math.PI / 180);
context.translate(-tmpTreeX,-tmpTreeY);
context.drawImage(imgTreeFile, tmpTreeX, tmpTreeY);
context.restore();
};
imgTreeFile.src = 'img/tree.png';
}
What I am trying to achieve is to add 'n' amount of images to the canvas (That happen to be trees) I want to rotate the images a random amount of radians. Currently just to get it working I have set this number rather than randomly generate the radians.
If I do no try to rotate the images, I successfully get all the images in the loop randomly placed throughout the canvas. When I try to rotate I just get all of the images in the same place with no rotation.
Can anyone point me in the right direction, my searching has only left me frustrated as I "appear" to be doing it correctly (Clearly I am not!)
In a quick test here there doesn't seem to be an issue: http://jsfiddle.net/ZZ7MQ/
But I do notice two weird things about your code:
.width() and not .width seems weird, but maybe thats a jQuery thing? it should be canvas.width in any case.
You are using a loop variable inside of code called asynchronously, which is 99% likely your problem. I highly recommend that you refactor your code so that all the images are loaded before the loop ever happens. If they are all the same image, that makes this very easy:
var imgTreeFile = new Image();
imgTreeFile.onload = function() {
for(var i = 0; i < treesLength; i++){
var tmpTree = trees[i];
tmpTreeX = 1+Math.random()*($("#gameBoard").width()-95);
tmpTreeY = 1+Math.random()*($("#gameBoard").height()-90);
context.save();
context.translate(tmpTreeX,tmpTreeY);
context.rotate(47 * Math.PI / 180);
context.translate(-tmpTreeX,-tmpTreeY);
context.drawImage(imgTreeFile, tmpTreeX, tmpTreeY);
context.restore();
}
};
imgTreeFile.src = 'img/tree.png';

Categories