I have a player of 50 width and 30 height. My problem is calculating the collision correctly. I figured how to do this by checking the middle of the player (you can see it below), but I have problem with accounting for his width and height as well - half of him will go through the wall before he stops.
The map is divided into many squares (for example 100x100 pixels, the 100 is called tileSize) and between the squares there either is a wall or isn't (each square is an object which can have top or/and left true, when true, you cannot pass that side of the square - there is a wall).
the collision detection (item == player):
//inner walls - map collisions
var tileX = Math.floor(item.pos.x / this.tileSize);
var tileY = Math.floor(item.pos.y / this.tileSize);
var tileX = Math.floor(item.pos.x / this.tileSize);
var tileY = Math.floor(item.pos.y / this.tileSize);
if(tileX == item.last_X && tileY == item.last_Y){return;} //if its the same tile..
var colidedX = false;
var colidedY = false;
/* Check X */
if (item.last_X > tileX) {
if (this.map[item.last_Y][item.last_X].left) {
item.pos.x = item.last_X * this.tileSize;
colidedX = true;
}
}
if (item.last_X < tileX) {
if (this.map[item.last_Y][item.last_X+1].left) {
item.pos.x = (item.last_X+1) * this.tileSize;
colidedX = true;
}
}
/* Check Y */
if (item.last_Y > tileY) {
if (this.map[item.last_Y][item.last_X].top) {
item.pos.y = item.last_Y * this.tileSize;
colidedY = true;
}
}
if (item.last_Y < tileY) {
if (this.map[item.last_Y+1][item.last_X].top) {
item.pos.y = (item.last_Y+1) * this.tileSize;
colidedY = true;
}
}
if(colidedX == false){
item.last_X = tileX;
}
if(colidedY == false){
item.last_Y = tileY;
}
I have an idea how I could do it, but it would be very complicated and long, so I thought there must be a simpler way to do this.
Related
I have some P5js code that I made.
It basically moves the circle around the screen to predetermined targets stored in an array. I will be using it within my game that uses A* pathfinding and stores the path points in an array. I then am looking at moving the player to each of those points smoothly with the code below.
What I am asking
Most importantly, can this code be optimized/cleaner if so, how?
How can I allow for a larger speed value. Currently if I change the speed to 3, it doesnt trigger the if statement (I would need to cicle to be at the target within a 1 px varience regardless of spped).
Other notes
I tried using the P5 Lerp function however it did not allow for a constant movement speed.
Thank you :)
var targets
var target_index = 0
var position
var x_complete = true
var y_complete = true
var speed = 1
function setup() {
createCanvas(400, 400);
targets = [createVector(0, 0), createVector(50, 50), createVector(0, 0), createVector(0, 50), createVector(200, 200)]
position = targets[0]
}
var count = 0;
function draw() {
background(220);
move_c()
draw_c()
}
function draw_c() {
strokeWeight(10);
point(position.x, position.y);
}
function move_c() {
var x_needs_to_goes_up
var x_is_currently_smaller
var y_needs_to_goes_up
var y_is_currently_smaller
x_complete = false
y_complete = false
if (targets.length - 1 > target_index) {
x_needs_to_goes_up = !number_is_larger(targets[target_index].x, targets[target_index + 1].x) //X goes up
x_is_currently_smaller = number_is_smaller(targets[target_index].x, targets[target_index + 1].x)
y_needs_to_goes_up = !number_is_larger(targets[target_index].y, targets[target_index + 1].y) //X goes up
y_is_currently_smaller = number_is_smaller(targets[target_index].y, targets[target_index + 1].y)
if (x_needs_to_goes_up && x_is_currently_smaller) { //If x is currently smaller then target
move_x(true)
} else if (x_needs_to_goes_up && !x_is_currently_smaller) {
x_complete = true
}
if (!x_needs_to_goes_up && !x_is_currently_smaller) { //If x is currently smaller then target
move_x(false)
} else if (!x_needs_to_goes_up && x_is_currently_smaller) {
x_complete = true
}
if (y_needs_to_goes_up && y_is_currently_smaller) { //If x is currently smaller then target
move_y(true)
} else if (y_needs_to_goes_up && !y_is_currently_smaller) {
y_complete = true
}
if (!y_needs_to_goes_up && !y_is_currently_smaller) { //If x is currently smaller then target
move_y(false)
} else if (!y_needs_to_goes_up && y_is_currently_smaller) {
y_complete = true
}
if (y_complete && x_complete) {
target_index++
}
}
}
function move_y(up) {
if (up) {
position = createVector(targets[target_index].x, targets[target_index].y += speed)
} else {
position = createVector(targets[target_index].x, targets[target_index].y -= speed)
}
}
function move_x(up) {
if (up) {
position = createVector(targets[target_index].x += speed, targets[target_index].y)
} else {
position = createVector(targets[target_index].x -= speed, targets[target_index].y)
}
}
function number_is_larger(num1, num2) {
return num1 > num2 ? true : false
}
function number_is_smaller(num1, num2) {
return num1 < num2 - 1 ? true : false
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
The main problem in your code is that when you increase the speed you shoot over the target as you may not hit the target accurately.
First of all the initial position vector should be a copy of the first target:
position = targets[0].copy()
Calculate the vector from the position to the next target and the Euclidean lenght of the vector (this is the distance of the position to the next target:
let direction = p5.Vector.sub(targets[target_index], position)
let distance = direction.mag()
Distinguish whether the distance to the next target is greater or less than the speed:
if (distance <= speed) {
// less
// [...]
} else {
// greater
// [...]
}
If it is less, the next position is the next target and the target index need to be incremented:
position = targets[target_index].copy()
target_index ++;
If it is greater, make on step forwards. Calculate the normalized direction vector. A normalized vector is a Unit vector and has a length of 1. Multiply the vector with the speed. Now the vector is length the speed. Add the vector to the position:
position.add(direction.normalize().mult(speed))
var targets
var target_index = 0
var position
var speed = 3
function setup() {
createCanvas(400, 400);
targets = [createVector(0, 0), createVector(50, 50), createVector(0, 0), createVector(0, 50), createVector(200, 200)]
position = targets[0].copy()
}
function draw() {
background(220);
move_c()
draw_c()
}
function draw_c() {
strokeWeight(10);
point(position.x, position.y);
}
function move_c() {
if (targets.length - 1 > target_index) {
let direction = p5.Vector.sub(targets[target_index], position)
let distance = direction.mag()
if (distance <= speed) {
position = targets[target_index].copy()
target_index ++;
} else {
position.add(direction.normalize().mult(speed))
}
} else {
target_index = 0
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
So, I am currently reinventing the wheel (and learning a lot) by trying my hand at making a simple physics engine for my game engine. I have been searching the internet, trying (and failing) to fix my current problem. There are a lot of resources out there on the subject, but none of those that I have found seem to apply to my case.
THE PROBLEM IN SHORT: The collision resolution does not work as intended on some of the corners when two rectangles are colliding. How it fails varies based on the dimensions of the rectangles. What I am looking for is a "shortest overlap" kind of resolution for the collision or another fairly simple solution (I am open for suggestions!). (Scroll down for a better explaination and illustrations).
WARNING: The following code is probably not very efficient...
First of all, here is my physics loop. It simply loops through all of the game entities and checks if they collide with any other game entities. It is not efficient (n^2 and all of that), but it works for now.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target);
break;
}
}
}
}
}
}
}
},
Here is the code for the actual collision detection. This also seems to work alright.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() || entity.getRight() < target.getLeft() || entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is where the problems start to pop up. I want the entity to simply be "pushed" out of the target entity and have the velocity set to 0. This works fine as long as both the entity and the target are perfect squares. If let's say the entity (the player figure in the gif) is a rectangle, then the collision will "slipp" when colliding the longest sides (the X axis) with the target (the square). If I swap the player dimensions so that it is short and wide, then the same problem appears for the Y axis instead.
resolveTestTest: function(entity, target) {
var normalizedX = (target.getMidX() - entity.getMidX());
var normalizedY = (target.getMidY() - entity.getMidY());
var absoluteNormalizedX = Math.abs(normalizedX);
var absoluteNormalizedY = Math.abs(normalizedY);
console.log(absoluteNormalizedX, absoluteNormalizedY);
// The collision is comming from the left or right
if (absoluteNormalizedX > absoluteNormalizedY) {
if (normalizedX < 0) {
entity.pos.x = target.getRight();
} else {
entity.pos.x = target.getLeft() - entity.dim.width;
}
// Set velocity to 0
entity.vel.x = 0;
// The collision is comming from the top or bottom
} else {
if (normalizedY < 0) {
entity.pos.y = target.getBottom();
} else {
entity.pos.y = target.getTop() - entity.dim.height;
}
// Set velocity to 0
entity.vel.y = 0;
}
},
Collision on the Y axis works with these shapes
Collision on the X axis slips with these shapes
What can I do to fix this slipping problem? I have been bashing my head against this for the last 5 days, so I would be immensely grateful if some one could help push me in the right direction!
Thank you :)
-- EDIT: --
The slipping also happens if only moving in one direction along the left or right side.
-- EDIT 2 WORKING CODE: --
See my answer below for an example of the working code!
The important logical error you have made is this line:
if (absoluteNormalizedX > absoluteNormalizedY) {
This only works if both entities are square.
Consider a near-extremal case for your X-slipping example: if they almost touch at the corner:
Although the diagram is a little exaggerated, you can see that absoluteNormalizedX < absoluteNormalizedY in this case - your implementation would move on to resolve a vertical collision instead of the expected horizontal one.
Another error is that you always set the corresponding velocity component to zero regardless of which side the collision is on: you must only zero the component if is it in the opposite direction to the collision normal, or you won't be able to move away from the surface.
A good way to overcome this is to also record the collided face(s) when you do collision detection:
collideEntity: function(entity, target) {
// adjust this parameter to your liking
var eps = 1e-3;
// no collision
var coll_X = entity.getRight() > target.getLeft() && entity.getLeft() < target.getRight();
var coll_Y = entity.getBottom() > target.getTop() && entity.getTop() < target.getBottom();
if (!(coll_X && coll_Y)) return 0;
// calculate bias flag in each direction
var bias_X = entity.targetX() < target.getMidX();
var bias_Y = entity.targetY() < target.getMidY();
// calculate penetration depths in each direction
var pen_X = bias_X ? (entity.getRight() - target.getLeft())
: (target.getRight() - entity.getLeft());
var pen_Y = bias_Y ? (entity.getBottom() - target.getUp())
: (target.getBottom() - entity.getUp());
var diff = pen_X - pen_Y;
// X penetration greater
if (diff > eps)
return (1 << (bias_Y ? 0 : 1));
// Y pentration greater
else if (diff < -eps)
return (1 << (bias_X ? 2 : 3));
// both penetrations are approximately equal -> treat as corner collision
else
return (1 << (bias_Y ? 0 : 1)) | (1 << (bias_X ? 2 : 3));
},
updatePhysics: function(step) {
// ...
// pass collision flag to resolver function
var result = Vroom.collideEntity(entity, target);
if (result > 0) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target, result);
break;
}
}
// ...
}
Using a bit flag instead of a boolean array for efficiency. The resolver function can then be re-written as:
resolveTestTest: function(entity, target, flags) {
if (!!(flags & (1 << 0))) { // collision with upper surface
entity.pos.y = target.getTop() - entity.dim.height;
if (entity.vel.y > 0) // travelling downwards
entity.vel.y = 0;
}
else
if (!!(flags & (1 << 1))) { // collision with lower surface
entity.pos.y = target.getBottom();
if (entity.vel.y < 0) // travelling upwards
entity.vel.y = 0;
}
if (!!(flags & (1 << 2))) { // collision with left surface
entity.pos.x = target.getLeft() - entity.dim.width;
if (entity.vel.x > 0) // travelling rightwards
entity.vel.x = 0;
}
else
if (!!(flags & (1 << 3))) { // collision with right surface
entity.pos.x = target.getRight();
if (entity.vel.x < 0) // travelling leftwards
entity.vel.x = 0;
}
},
Note that unlike your original code, the above also allows corners to collide - i.e. for velocities and positions to be resolved along both axes.
MY WORKING CODE
So with some help and guidance from the amazing #meowgoesthedog I finally got on the right track and found what I was looking for. The problem (as #meowgoesthedog pointed out) was that my code was really only going to work with squares. The solution was to check the intersection of the colliding bodies and solve based on the shortest intersection. Note: this will probably not be a suitable solution if you need accurate physics with small and fast moving objects. The code for finding the intersection depth is based on this: https://github.com/kg/PlatformerStarterKit/blob/0e2fafb8dbc845279fe4116c37b6f2cdd3e636d6/RectangleExtensions.cs which is related to this project: https://msdn.microsoft.com/en-us/library/dd254916(v=xnagamestudio.31).aspx.
Here is my working code:
My physics loop has not been changed much, except for some better names for some functions.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveDisplace(entity, target);
break;
}
}
}
}
}
}
}
},
The collision detection remains the same as well.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() || entity.getRight() < target.getLeft() || entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is the code that basically fixes the problem. The comments in the code should explain what it does pretty well.
getIntersectionDepth: function(entity, target) {
// Calculate current and minimum-non-intersecting distances between centers.
var distanceX = entity.getMidX() - target.getMidX();
var distanceY = entity.getMidY() - target.getMidY();
var minDistanceX = entity.halfDim.width + target.halfDim.width;
var minDistanceY = entity.halfDim.height + target.halfDim.height;
// If we are not intersecting at all, return 0.
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY) {
return {
x: 0,
y: 0,
};
}
// Calculate and return intersection depths.
var depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
var depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return {
x: depthX,
y: depthY,
};
},
Here is the updated resolving function. It now takes intersection depth in to account when determining axis of collision and then uses the sign of the intersection depth for the colliding axis when determining the direction to resolve.
resolveDisplace: function(entity, target) {
var intersection = Vroom.getIntersectionDepth(entity, target);
if (intersection.x !== 0 && intersection.y !== 0) {
if (Math.abs(intersection.x) < Math.abs(intersection.y)) {
// Collision on the X axis
if (Math.sign(intersection.x) < 0) {
// Collision on entity right
entity.pos.x = target.getLeft() - entity.dim.width;
} else {
// Collision on entity left
entity.pos.x = target.getRight();
}
entity.vel.x = 0;
} else if (Math.abs(intersection.x) > Math.abs(intersection.y)) {
// Collision on the Y axis
if (Math.sign(intersection.y) < 0) {
// Collision on entity bottom
entity.pos.y = target.getTop() - entity.dim.height;
} else {
// Collision on entity top
entity.pos.y = target.getBottom();
}
entity.vel.y = 0;
}
}
},
Thank you all for your help!
The problem may be that you're correcting both X and Y collision based on the same position:
Player is at a certain position. Let's check collision.
Player's bottom right corner overlaps top left corner of object.
X position is corrected: Player is moved to the left.
Player's bottom right corner overlaps top left corner of object.
Y position is corrected: Player is moved up.
End result: The player is moved up and to the left.
You probably need to "get" the player's position again, between checks.
Context :
I'm making a version of the popular game, Snake. And I encounter some issues when I try to avoid the appearance of the food at the same place of the parts of my snake.
So I produced this code with the framework Phaser :
generateFood: function() {
var randomX, randomY;
var rightLocation = false;
var foodOnSnake = false;
while(rightLocation === false) {
randomX = Math.floor(Math.random() * (this.game.width / squareSize)) * squareSize;
randomY = Math.floor(Math.random() * (this.game.height / squareSize)) * squareSize;
foodOnSnake = false;
for (var i = 0; i < snake.length; i++) {
if (snake[i].x === food.x && snake[i].y === food.y) {
foodOnSnake = true;
break;
}
}
if(foodOnSnake === false) {
rightLocation = true;
}
}
food = this.game.add.sprite(randomX, randomY, 'snake', 15);
}
The aim, is to create some random coordinates on the game. And, while the food is generate on a part of the snake (the for loop), I will generate other coordinates. But for unknown reason, after my snake is eaten the first food, the game crashed and the tab of Google Chrome does not responding.
I think there is a mistake with a loop but I can't find it.
You compare the coordinates of the snake's segments with food.x and food.y, which you never update inside the loop:
if (snake[i].x === food.x && snake[i].y === food.y) {
I believe you want to compare it to randomX and randomY instead:
if (snake[i].x === randomX && snake[i].y === randomY) {
Depending on the value of food's coordinates, your function likely results in an infinite loop.
I am trying to write a script to place 100 circles of varying sizes onto a stage. I've outlined the concise requirements below.
Given the following:
var stage; // contains a "width" and "height" property.
var circle; // the circle class. contains x, y, radius & a unique id property.
var circleArray; // contains 100 circle instances
requirements:
write a function to place 100 circles of varying radius onto the stage.
placements must be random but evenly distributed (no clumping).
placement must be performant - this will be executing on a mobile web browser.
circles must not intersect/overlap other circles.
circle.x >= 0 must be true.
circle.y >= 0 && circle.y <= stage.height must be true.
circles may have any of the following radius sizes (assigned at creation):
150
120
90
80
65
My current attempt is a brute-force method, which does not operate efficiently. If I attempt to insert any more than ~10 circles, the browser hangs. Below is my current implementation, which I am completely OK with throwing away in favor of a more performant / better one.
Here is a live demo (NOTE: there is no actual drawing code, just the logic, but it will still lock up the browser so be warned!!) http://jsbin.com/muhiziduxu/2/edit?js,console
function adjustForOverlap (circleArray) {
// a reference to the circle that is invoking this function.
var _this = this;
// remove this circle from the array we are iterating over.
var arr = circleArray.filter(function (circle){
return circle.id !== _this.id;
});
// while repeat == true, the circle may be overlapping something.
var repeat = true;
while(repeat) {
var hasOverlap = false;
for (var i=0; i<arr.length; i++) {
var other = arr[i];
var dx = _self.x - other.x;
var dy = _self.y - other.y;
var rr = _self.radius + other.radius;
if (dx * dx + dy * dy < rr * rr) {
// if here, then an overlap was detected.
hit = true;
break;
}
}
// if hit is false, the circle didn't overlap anything, so break.
if (hit === false) {
repeat = false;
break;
} else {
// an overlap was detected, so randomize position.
_self.x = Math.random() * (stage.width*2);
_self.y = Math.random() * stage.height;
}
}
}
There are lots of efficient collision detection algorithms. Many of them work by dividing up the space into cells and maintaining a separate data structure with efficient lookup of other objects in the cell. The basic steps are:
Identify a random spot for your new circle
Determine which cells it's in
Look in each of those cells for a collision
If there's a collision, goto 1.
Else, add the new circle to each of the cells it overlaps.
You can use a simple square grid (i.e. a 2-d array) for the cell data structure, or something else like a quadtree. You can also in some cases get a bit of extra speed by trying a cheap-but-coarse collision check first (do the bounding boxes overlap), and if that returns true try the slightly more expensive and exact check.
Update
For quadtrees, check out d3-quadtree, which ought to give you a pretty good implementation, with examples.
For a (very quick, untested) 2-d array implementation:
function Grid(radius, width, height) {
// I'm not sure offhand how to find the optimum grid size.
// Let's use a radius as a starting point
this.gridX = Math.ceil(width / radius);
this.gridY = Math.ceil(height / radius);
// Determine cell size
this.cellWidth = width / this.gridX;
this.cellHeight = height / this.gridY;
// Create the grid structure
this.grid = [];
for (var i = 0; i < gridY; i++) {
// grid row
this.grid[i] = [];
for (var j = 0; j < gridX; j++) {
// Grid cell, holds refs to all circles
this.grid[i][j] = [];
}
}
}
Grid.prototype = {
// Return all cells the circle intersects. Each cell is an array
getCells: function(circle) {
var cells = [];
var grid = this.grid;
// For simplicity, just intersect the bounding boxes
var gridX1Index = Math.floor(
(circle.x - circle.radius) / this.cellWidth
);
var gridX2Index = Math.ceil(
(circle.x + circle.radius) / this.cellWidth
);
var gridY1Index = Math.floor(
(circle.y - circle.radius) / this.cellHeight
);
var gridY2Index = Math.ceil(
(circle.y + circle.radius) / this.cellHeight
);
for (var i = gridY1Index; i < gridY2Index; i++) {
for (var j = gridX1Index; j < gridX2Index; j++) {
// Add cell to list
cells.push(grid[i][j]);
}
}
return cells;
},
add: function(circle) {
this.getCells(circle).forEach(function(cell) {
cell.push(circle);
});
},
hasCollisions: function(circle) {
return this.getCells(circle).some(function(cell) {
return cell.some(function(other) {
return this.collides(circle, other);
}, this);
}, this);
},
collides: function (circle, other) {
if (circle === other) {
return false;
}
var dx = circle.x - other.x;
var dy = circle.y - other.y;
var rr = circle.radius + other.radius;
return (dx * dx + dy * dy < rr * rr);
}
};
var g = new Grid(150, 1000, 800);
g.add({x: 100, y: 100, radius: 50});
g.hasCollisions({x: 100, y:80, radius: 100});
Here's a fully-functional example: http://jsbin.com/cojoxoxufu/1/edit?js,output
Note that this only shows 30 circles. It looks like the problem is often unsolvable with your current radii, width, and height. This is set up to look for up to 500 locations for each circle before giving up and accepting a collision.
Into this simple code I use an eventListener which doesn't look to work at all. The canvas display an image and the given hitpaint() function is supposed determines whether a click occurs. I cant understand why the eventListener behaves like that. Any insight would be helpful.
mycanv.addEventListener("click", function(e) {
var output = document.getElementByID("output");
ctx.fillStyle = 'blue';
//ctx.clearRect(0,0,100,20);
if (hitpaint) {
//ctx.fillText("hit",100,20);
output.innerHTML = "hit";
} else {
//ctx.fillText("miss",100,20);
output.innerHTML = "miss";
}
}, false);
The hitpaint() function is defined as:
function hitpaint(mouse_event) {
var bounding_box = mycanv.getBoundingClientRect();
var mousex = (mouse_event.clientX - bounding_box.left) *
(mycanv.width / bounding_box.width);
var mousey = (mouse_event.clientY - bounding_box.top) *
(mycanv.height / bounding_box.height);
var pixels = ctx.getImageData(mousex, mousey, 1, 1);
for (var i = 3; i < pixels.data.length; i += 4) {
// If we find a non-zero alpha we can just stop and return
// "true" - the click was on a part of the canvas that's
// got colour on it.
if (pixels.data[i] !== 0) return true;
}
// The function will only get here if none of the pixels matched in
return false;
}
Finally, the main loop which display the picture in random location into the canvas:
function start() {
// main game function, called on page load
setInterval(function() {
ctx.clearRect(cat_x, cat_y, 100, 100);
cat_x = Math.random() * mycanv.width - 20;
cat_y = Math.random() * mycanv.height - 20;
draw_katy(cat_x, cat_y);
}, 1000);
}
There are a some issues here:
As Grundy points out in the comment, the hitpaint is never called; right now it checks for it's existence and will always return true
The mouse coordinates risk ending up as fractional values which is no-go with getImageData
Scaling the mouse coordinates is usually not necessary. Canvas should preferably have a fixed size without an additional CSS size
Add boundary check for x/y to make sure they are inside canvas bitmap
I would suggest this rewrite:
mycanv.addEventListener("click", function(e) {
var output = document.getElementByID("output");
ctx.fillStyle = 'blue';
//ctx.clearRect(0,0,100,20);
if (hitpaint(e)) { // here, call hitpaint()
//ctx.fillText("hit",100,20);
output.innerHTML = "hit";
} else {
//ctx.fillText("miss",100,20);
output.innerHTML = "miss";
}
}, false);
Then in hitpaint:
function hitpaint(mouse_event) {
var bounding_box = mycanv.getBoundingClientRect();
var x = ((mouse_event.clientX - bounding_box.left) *
(mycanv.width / bounding_box.width))|0; // |0 cuts off any fraction
var y = ((mouse_event.clientY - bounding_box.top) *
(mycanv.height / bounding_box.height))|0;
if (x >= 0 && x < mycanv.width && y >= 0 && y < mycanv.height) {
// as we only have one pixel, we can address alpha channel directly
return ctx.getImageData(x, y, 1, 1).data[3] !== 0;
}
else return false; // x/y out of range
}