I have been learning about konva and html canvas and with the help of the community here I have a canvas with draggable shapes which stay in the bounds of the stage even when rotated.
Also, my konva code is detecting intersections in dragboundfunc and setting strokeEnabled to true if a intersection is detected. I was using my own collision detection code/function until yesterday but because I wasn't getting correct detection results when shapes had been rotated I changed my code to put the shape being dragged in a tempLayer to enable getIntersection to work during dragBoundFunc hoping that would fix it but it didn't.
My problem is I cannot get correct collision detection even with getIntersection rather than my own collision detection code and have spent since last night trying. I can get it detecting collision fine if the shape rotation is 0 using the code below which is my shapes dragBoundFunc.
I know I can't be far off but I'm also at a loss. I also know the part of my code where I set collisionz is probably the wrong way of going about this even though it works for 0 degrees rotation but I have left it in to show what I have tried and that I am trying.
Does anyone know the answer to help me on my way with Konva please?
function theDragFunc(pos) {
var thisRect;
if(parseInt(this.getClientRect().width) != parseInt(this.width())){
if(userRotation == 90 || userRotation == 270)
thisRect = {x: this.x(), y: this.y(), width: this.getClientRect().height, height: this.getClientRect().width};
else
thisRect = {x: this.x(), y: this.y(), width: this.getClientRect().width, height: this.getClientRect().height};
console.log("must have changed userRotation = "+userRotation);
}
else{
thisRect = {x: this.x(), y: this.y(), width: this.width(), height: this.height()};
console.log("must not have changed userRotation = " +userRotation);
}
isCollision = false;
// copy the boundary rect into a testRect which defines the extent of the dragbounds without
// accounting for the width and height of dragging rectangle.
// This is changed below depending on rotation.
var testRect={
left: boundary.x,
top: boundary.y,
right: boundary.x + boundary.width,
bottom: boundary.y + boundary.height
};
// the userRotation value is calculated in the rotation button onclick
// to be one of 0, 90, 180, 270
switch (userRotation){
case 0: // for 0 degrees compute as per a normal bounds rect
testRect.right = testRect.right - thisRect.width;
testRect.bottom = testRect.bottom - thisRect.height;
break;
case 90: // for 90 degs we have to modify the test boundary left and bottom
testRect.left = testRect.left + thisRect.height;
testRect.bottom = testRect.bottom - thisRect.width;
break;
case 180: // for 180 degs we have to modify the test boundary left and top
testRect.left = testRect.left + thisRect.width;
testRect.top = testRect.top + thisRect.height;
break;
case 270: // for 270 degs we have to modify the test boundary right and top
testRect.right = testRect.right - thisRect.height;
testRect.top = testRect.top + thisRect.width;
break;
}
// get new pos as: if pos inside bounday ranges then use it, otherwise user boundary
var newX = (pos.x < testRect.left ? testRect.left : pos.x);
// looking at the far x pos we need to consider width of the dragging rect...
newX = (newX > testRect.right ? testRect.right : newX);
// get new pos as: if pos inside bounday ranges then use it, otherwise user boundary
var newY = (pos.y < testRect.top ? testRect.top : pos.y);
// looking at the far y pos we need to consider height of the dragging rect...
newY = (newY > testRect.bottom ? testRect.bottom : newY);
var collisionz = stage.getIntersection({x:newX+this.getClientRect().width,y:newY}, ".rect");
if(!collisionz)collisionz = stage.getIntersection({x:newX,y:newY}, ".rect");
else if(!collisionz)collisionz = stage.getIntersection({x:newX,y:newY+this.getClientRect().height}, ".rect");
else if(!collisionz)collisionz = stage.getIntersection({x:newX,y:newY+this.getClientRect().height/2}, ".rect");
else if(!collisionz)collisionz = stage.getIntersection({x:newX+this.getClientRect().width,y:newY+this.getClientRect().height/2}, ".rect");
else if(!collisionz)collisionz = stage.getIntersection({x:newX + this.getClientRect().height/2,y:newY + this. getClientRect().height}, ".rect");
if(!collisionz)collisionz = stage.getIntersection({x:newX + this.getClientRect().width,y:newY + this.getClientRect().height}, ".rect");
var search_term = 'funcRect';
if(collisionz && collisionz != this){
console.log("INTERSECTION detected"+collisionz.getName());
isCollision = true;
}
else
isCollision = false;
if(!isCollision && this.getStrokeEnabled())
{
this.setStrokeEnabled(false);
layer.draw();
}
else if(isCollision && !this.getStrokeEnabled())
{
this.setStrokeWidth(2);
this.setStroke('black');
this.setStrokeEnabled(true);
layer.draw();
}
return {
x: newX,
y: newY
}
}
Related
This is my first post so I'm trying to make my problem as clear as possible. I'm making a game and I want to improve my collision detection. This is because I want to check what side is being hit and stop the player from moving past it without using something general like if(collision(player, enemy)) player.x = enemy.x - player.w(width) because if the player were to collide with the top it wouldn't keep the player on top.
In the code it checks if any one of the statements is true and then returns it but it doesn't tell me which statement was the one that was equal to true so I can stop the player from moving accordingly, if that makes sense. If you have a more efficient collision detection for me to use it would be greatly appreciated.
I've already tried to make a position variable to be equal to whatever side gets collided into and then stop the player from moving past it but it only works for the left side and won't let my player jump over the enemy or block.
function collision(object1, object2) {
return !(
object1.x > object2.x + object2.w ||
object1.x + object1.w < object2.x ||
object1.y > object2.y + object2.h ||
object1.y + object1.h < object2.y
)
}
//Only works for the left side
if(collision(player, enemy)) player.x = enemy.x - player.w
I expect it to be able to tell me what side is being collided into and then either stop the player from moving past/into it and for the player to be able to be on top of the block/enemy without just being pushed to the left.
You'll want to calculate the distance between the x's and y's and also use the minimum distance that they could be colliding along each axis to find the depth along both axes. Then you can pick the smaller depth and move along that one. Here's an example:
if(collision(player, enemy)){
// Most of this stuff would probably be good to keep stored inside the player
// along side their x and y position. That way it doesn't have to be recalculated
// every collision check
var playerHalfW = player.w/2
var playerHalfH = player.h/2
var enemyHalfW = enemy.w/2
var enemyHalfH = enemy.h/2
var playerCenterX = player.x + player.w/2
var playerCenterY = player.y + player.h/2
var enemyCenterX = enemy.x + enemy.w/2
var enemyCenterY = enemy.y + enemy.h/2
// Calculate the distance between centers
var diffX = playerCenterX - enemyCenterX
var diffY = playerCenterY - enemyCenterY
// Calculate the minimum distance to separate along X and Y
var minXDist = playerHalfW + enemyHalfW
var minYDist = playerHalfH + enemyHalfH
// Calculate the depth of collision for both the X and Y axis
var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX
var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY
// Now that you have the depth, you can pick the smaller depth and move
// along that axis.
if(depthX != 0 && depthY != 0){
if(Math.abs(depthX) < Math.abs(depthY)){
// Collision along the X axis. React accordingly
if(depthX > 0){
// Left side collision
}
else{
// Right side collision
}
}
else{
// Collision along the Y axis.
if(depthY > 0){
// Top side collision
}
else{
// Bottom side collision
}
}
}
}
Working example
Here's a working example that you can play around with. Use the arrow keys to move the player around.
player = {
x: 9,
y: 50,
w: 100,
h: 100
}
enemy = {
x: 100,
y: 100,
w: 100,
h: 100
}
output = document.getElementById("collisionType");
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d")
function collision(object1, object2) {
return !(
object1.x > object2.x + object2.w ||
object1.x + object1.w < object2.x ||
object1.y > object2.y + object2.h ||
object1.y + object1.h < object2.y
)
}
function draw() {
ctx.clearRect(0, 0, 400, 400)
ctx.lineWidth = "5"
ctx.beginPath();
ctx.strokeStyle = "red";
ctx.rect(player.x, player.y, player.w, player.h);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.rect(enemy.x, enemy.y, enemy.w, enemy.h);
ctx.stroke();
}
function handleCollision() {
if (collision(player, enemy)) {
var playerHalfW = player.w / 2
var playerHalfH = player.h / 2
var enemyHalfW = enemy.w / 2
var enemyHalfH = enemy.h / 2
var playerCenterX = player.x + player.w / 2
var playerCenterY = player.y + player.h / 2
var enemyCenterX = enemy.x + enemy.w / 2
var enemyCenterY = enemy.y + enemy.h / 2
// Calculate the distance between centers
var diffX = playerCenterX - enemyCenterX
var diffY = playerCenterY - enemyCenterY
// Calculate the minimum distance to separate along X and Y
var minXDist = playerHalfW + enemyHalfW
var minYDist = playerHalfH + enemyHalfH
// Calculate the depth of collision for both the X and Y axis
var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX
var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY
// Now that you have the depth, you can pick the smaller depth and move
// along that axis.
if (depthX != 0 && depthY != 0) {
if (Math.abs(depthX) < Math.abs(depthY)) {
// Collision along the X axis. React accordingly
if (depthX > 0) {
output.innerHTML = "left side collision"
} else {
output.innerHTML = "right side collision"
}
} else {
// Collision along the Y axis.
if (depthY > 0) {
output.innerHTML = "top side collision"
} else {
output.innerHTML = "bottom side collision"
}
}
}
} else {
output.innerHTML = "No collision"
}
}
keyStates = []
function handleKeys() {
if (keyStates[39]) {
player.x += 2 //Move right
} else if (keyStates[37]) {
player.x -= 2 //Move left
}
if (keyStates[38]) {
player.y -= 2 //Move up
}
if (keyStates[40]) {
player.y += 2 //Move down
}
}
function main() {
handleKeys();
draw();
handleCollision();
window.requestAnimationFrame(main);
}
window.onkeydown = function(e) {
keyStates[e.keyCode] = true
}
window.onkeyup = function(e) {
keyStates[e.keyCode] = false
}
main();
<h2 id="collisionType"></h2>
<canvas id="canvas" width='300' height='300'></canvas>
Reacting to the collision
Now that you know the side the collision happened on, it should be fairly trivial to decide how to react. It would be very similar to what you are currently doing for the left side just flip some signs around and change the axis.
Other Considerations
You may want to take into account your player's velocity (if it has one) otherwise the detection may fail.
If the player's velocity is too high, it might 'tunnel' through the enemy and no collision will be detected.
The player's movement can also look jittery if the velocity is not stopped upon collision
Can your objects rotate or have more than 4 sides? If so, you'll probably want to use another method as described below.
Here's a good answer to another post that talks in depth about collision engines
Other Methods
As for other collision detection methods, there's quite a few but one that comes to mind is Separating Axis Theorem which is a little more complex than what you have but will work with more complex convex shapes and rotation. It also tells you the direction and distance needed to move to resolve the collision. Here's a site that has interactive examples and goes in-depth on the subject. It doesn't appear to give a full implementation but those can be found other places.
It's like catching an object with a basket. (Bottom part of first object and Top part of second object). This is a sample of my code, but it is detecting all sides.
this.hitPocket = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) ||
(mytop > otherbottom) ||
(myright < otherleft) ||
(myleft > otherright)) {
crash = false;
}
return crash;
}
UPDATED:
Hi Kenneth Mitchell De Leon, i tried your solution and changed the radius into height and width and it worked. My problem now is the basket is movable or draggable to catch the ball. (Sorry that I mentioned it late) So when i drag the basket into the ball it still catches the ball even it did not pass through the top of the basket.
function hasContact(basket, ball){
const hoop = {
topLeft: {x:basket.x+10, y: basket.y},
topRight: {x: basket.x+basket.width-10,
y: basket.y}
//not necessary if your only concern is the top panel
// botLeft: {x: basket.position.x,
// y: basket.position.y+basket.height},
// botRight: {x:basket.position.x+basket.width,
// y:basket.position.y+basket.height}
}
//determine if ball is in between top left or top right of the basket in x axis
if(ball.x > hoop.topLeft.x && ball.x+ball.width < hoop.topRight.x
//determine if the ball is in contact with top panel of basket in y axis
&& ball.y-basket.y < ball.height/2 && basket.y - ball.y < ball.height/2){
return true;
}
return false;
}
//point = {x: value, y: value} - assuming this is point object
function hasContact(point1, point2, ball){
//determine if ball is in between point1 and point2 in x axis
if(ball.position.x-ball.radius > point1.x && ball.position.x+ball.radius < point2.x
//determine if the ball is in contact in y axis
&& ball.position.y-point1.y < ball.radius && point1.y - ball.position.y < ball.radius){
return true;
}
return false;
}
//position 1 is top left
//position 2 is top right
if(hasContact(position1, position2, circle)
//use same x coords in position 1 and 2
//then add y coordinates with the ball/image height-n
//position 3 is bottom left - just low enough but not more than image/ball height
//position 5 is bottom right
&& hasContact(position3, position4, circle)){
//shot is made - do something here....
}
i am assuming something like basketball...
this will return true if the ENTIRE ball is in between left and right edge of the horizontal line, you can adjust it yourself to set it to return true if any part of the ball is in contact with the line. However it will not detect if the ball has passed from one point to another ie(goung through top to bottom);
a bit of information would be more useful...
what kind of collision detectin are you aiming for?
line and box?
line and line?
line and circle? //i assumed this one,i'll edit my answer depending on more info.
I EDITED the function. Just use it twice where the gap is not more than the image/ball height.
I have a page that shows a grid of job positions and I am showing the progression from one to another by using SVG + paths to draw the connection between boxes.
My code is working just fine when I am connecting an element at the top to one at the bottom. It is finding the XY of the top box and the XY of the bottom box and connects the two.
My issue is I want to flip this code and go from the bottom up. This means I need the top XY of the bottom element and the bottom XY of the top element and draw the path.
I have been trying to flip offsets around and basically do the opposite of what is working but I think my math is wrong somewhere.
Here is what the top down approach looks like. Works just fine.
The bottom up approach however is not correct. Theres some math errors somewhere and the calculations are causing the SVG to be cut off.
I believe the answer lies within the connectElements() function as that is where the coordinates are determined.
Any thoughts on how I can get these calculations corrected?
Fiddle: http://jsfiddle.net/Ly59a2hf/2/
JS Code:
function getOffset(el) {
var rect = el.getBoundingClientRect();
return {
left: rect.left + window.pageXOffset,
top: rect.top + window.pageYOffset,
width: rect.width || el.offsetWidth,
height: rect.height || el.offsetHeight
};
}
function drawPath(svg, path, startX, startY, endX, endY) {
// get the path's stroke width (if one wanted to be really precize, one could use half the stroke size)
var style = getComputedStyle(path)
var stroke = parseFloat(style.strokeWidth);
// check if the svg is big enough to draw the path, if not, set heigh/width
if (svg.getAttribute("height") < endY) svg.setAttribute("height", endY);
if (svg.getAttribute("width") < (startX + stroke)) svg.setAttribute("width", (startX + stroke));
if (svg.getAttribute("width") < (endX + stroke * 3)) svg.setAttribute("width", (endX + stroke * 3));
var deltaX = (endX - startX) * 0.15;
var deltaY = (endY - startY) * 0.15;
// for further calculations which ever is the shortest distance
var delta = deltaY < absolute(deltaX) ? deltaY : absolute(deltaX);
// set sweep-flag (counter/clock-wise)
// if start element is closer to the left edge,
// draw the first arc counter-clockwise, and the second one clock-wise
var arc1 = 0;
var arc2 = 1;
if (startX > endX) {
arc1 = 1;
arc2 = 0;
}
// draw tha pipe-like path
// 1. move a bit down, 2. arch, 3. move a bit to the right, 4.arch, 5. move down to the end
path.setAttribute("d", "M" + startX + " " + startY +
" V" + (startY + delta) +
" A" + delta + " " + delta + " 0 0 " + arc1 + " " + (startX + delta * signum(deltaX)) + " " + (startY + 2 * delta) +
" H" + (endX - delta * signum(deltaX)) +
" A" + delta + " " + delta + " 0 0 " + arc2 + " " + endX + " " + (startY + 3 * delta) +
" V" + (endY - 30));
}
function connectElements(svg, path, startElem, endElem, type, direction) {
// Define our container
var svgContainer = document.getElementById('svgContainer'),
svgTop = getOffset(svgContainer).top,
svgLeft = getOffset(svgContainer).left,
startX,
startY,
endX,
endY,
startCoord = startElem,
endCoord = endElem;
console.log(svg, path, startElem, endElem, type, direction)
/**
* bottomUp - This means we need the top XY of the starting box and the bottom XY of the destination box
* topDown - This means we need the bottom XY of the starting box and the top XY of the destination box
*/
switch (direction) {
case 'bottomUp': // Not Working
// Calculate path's start (x,y) coords
// We want the x coordinate to visually result in the element's mid point
startX = getOffset(startCoord).left + 0.5 * getOffset(startElem).width - svgLeft; // x = left offset + 0.5*width - svg's left offset
startY = getOffset(startCoord).top + getOffset(startElem).height - svgTop; // y = top offset + height - svg's top offset
// Calculate path's end (x,y) coords
endX = endCoord.getBoundingClientRect().left + 0.5 * endElem.offsetWidth - svgLeft;
endY = endCoord.getBoundingClientRect().top - svgTop;
break;
case 'topDown': // Working
// If first element is lower than the second, swap!
if (startElem.offsetTop > endElem.offsetTop) {
var temp = startElem;
startElem = endElem;
endElem = temp;
}
// Calculate path's start (x,y) coords
// We want the x coordinate to visually result in the element's mid point
startX = getOffset(startCoord).left + 0.5 * getOffset(startElem).width - svgLeft; // x = left offset + 0.5*width - svg's left offset
startY = getOffset(startCoord).top + getOffset(startElem).height - svgTop; // y = top offset + height - svg's top offset
// Calculate path's end (x,y) coords
endX = endCoord.getBoundingClientRect().left + 0.5 * endElem.offsetWidth - svgLeft;
endY = endCoord.getBoundingClientRect().top - svgTop;
break;
}
// Call function for drawing the path
drawPath(svg, path, startX, startY, endX, endY, type);
}
function connectAll(direction) {
var svg = document.getElementById('svg1'),
path = document.getElementById('path1');
// This is just to help with example.
if (direction == 'topDown') {
var div1 = document.getElementById('box_1'),
div2 = document.getElementById('box_20');
} else {
var div1 = document.getElementById('box_20'),
div2 = document.getElementById('box_1');
}
// connect all the paths you want!
connectElements(svg, path, div1, div2, 'line', direction);
}
//connectAll('topDown'); // Works fine. Path goes from the bottom of box_1 to the top of box_20
connectAll('bottomUp'); // Doesn't work. I expect path to go from top of box_20 to the bottom of box_1
IMO, you can simplify things by making the SVG the exact right size. Ie. fit it between the two elements vertically, and have it start at the leftmost X coord.
If you do that, the path starts and ends at either:
X: 0 or svgWidth
Y: 0 or svgHeight.
Then as far as drawing the path goes, it's just a matter of using the relative directions (startX -> endX and startY -> endY) in your calculations. I've called these variables xSign and ySign. If you are consistent with those, everything works out correctly.
The last remaining complication is working out which direction the arcs for the rounded corners have to go - clockwise or anticlockwise. You just have to work out the first one, and the other one is the opposite.
function getOffset(el) {
var rect = el.getBoundingClientRect();
return {
left: rect.left + window.pageXOffset,
top: rect.top + window.pageYOffset,
width: rect.width || el.offsetWidth,
height: rect.height || el.offsetHeight
};
}
function drawPath(svg, path, start, end) {
// get the path's stroke width (if one wanted to be really precise, one could use half the stroke size)
var style = getComputedStyle(path)
var stroke = parseFloat(style.strokeWidth);
var arrowHeadLength = stroke * 3;
var deltaX = (end.x - start.x) * 0.15;
var deltaY = (end.y - start.y) * 0.15;
// for further calculations which ever is the shortest distance
var delta = Math.min(Math.abs(deltaX), Math.abs(deltaY));
var xSign = Math.sign(deltaX);
var ySign = Math.sign(deltaY);
// set sweep-flag (counter/clock-wise)
// If xSign and ySign are opposite, then the first turn is clockwise
var arc1 = (xSign !== ySign) ? 1 : 0;
var arc2 = 1 - arc1;
// draw tha pipe-like path
// 1. move a bit vertically, 2. arc, 3. move a bit to the horizontally, 4.arc, 5. move vertically to the end
path.setAttribute("d", ["M", start.x, start.y,
"V", start.y + delta * ySign,
"A", delta, delta, 0, 0, arc1, start.x + delta * xSign, start.y + 2 * delta * ySign,
"H", end.x - delta * xSign,
"A", delta, delta, 0, 0, arc2, end.x, start.y + 3 * delta * ySign,
"V", end.y - arrowHeadLength * ySign].join(" "));
}
function connectElements(svg, path, startElem, endElem, type, direction) {
// Define our container
var svgContainer = document.getElementById('svgContainer');
// Calculate SVG size and position
// SVG is sized to fit between the elements vertically, start at the left edge of the leftmost
// element and end at the right edge of the rightmost element
var startRect = getOffset(startElem),
endRect = getOffset(endElem),
pathStartX = startRect.left + startRect.width / 2,
pathEndX = endRect.left + endRect.width / 2,
startElemBottom = startRect.top + startRect.height,
svgTop = Math.min(startElemBottom, endRect.top + endRect.height),
svgBottom = Math.max(startRect.top, endRect.top),
svgLeft = Math.min(pathStartX, pathEndX),
svgHeight = svgBottom - svgTop;
// Position the SVG
svg.style.left = svgLeft + 'px';
svg.style.top = svgTop + 'px';
svg.style.width = Math.abs(pathEndX - pathStartX) + 'px';
svg.style.height = svgHeight + 'px';
// Call function for drawing the path
var pathStart = {x: pathStartX - svgLeft, y: (svgTop === startElemBottom) ? 0 : svgHeight};
var pathEnd = {x: pathEndX - svgLeft, y: (svgTop === startElemBottom) ? svgHeight : 0};
drawPath(svg, path, pathStart, pathEnd);
}
function connectAll(direction) {
var svg = document.getElementById('svg1'),
path = document.getElementById('path1');
// This is just to help with example.
if (direction == 'topDown') {
var div1 = document.getElementById('box_1'),
div2 = document.getElementById('box_20');
} else {
var div1 = document.getElementById('box_20'),
div2 = document.getElementById('box_1');
}
// connect all the paths you want!
connectElements(svg, path, div1, div2, 'line');
}
//connectAll('topDown');
connectAll('bottomUp');
http://jsfiddle.net/93Le85tk/3/
Has anyone managed to make the labels around the Chart.js Radar perimeter clickable?
There doesn't seem to be an immediately obvious solution.
I came up with a solution for this for version 2.8.0 by copying the label position calculations from the RadialLinear scale into an event handler.
document.getElementById("myChart").onclick = function (e) {
var helpers = Chart.helpers;
var scale = myRadarChart.scale;
var opts = scale.options;
var tickOpts = opts.ticks;
// Position of click relative to canvas.
var mouseX = e.offsetX;
var mouseY = e.offsetY;
var labelPadding = 5; // number pixels to expand label bounding box by
// get the label render position
// calcs taken from drawPointLabels() in scale.radialLinear.js
var tickBackdropHeight = (tickOpts.display && opts.display) ?
helpers.valueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize)
+ 5: 0;
var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
for (var i = 0; i < scale.pointLabels.length; i++) {
// Extra spacing for top value due to axis labels
var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
// get label size info.
// TODO fix width=0 calc in Brave?
// https://github.com/brave/brave-browser/issues/1738
var plSize = scale._pointLabelSizes[i];
// get label textAlign info
var angleRadians = scale.getIndexAngle(i);
var angle = helpers.toDegrees(angleRadians);
var textAlign = 'right';
if (angle == 0 || angle == 180) {
textAlign = 'center';
} else if (angle < 180) {
textAlign = 'left';
}
// get label vertical offset info
// also from drawPointLabels() calcs
var verticalTextOffset = 0;
if (angle === 90 || angle === 270) {
verticalTextOffset = plSize.h / 2;
} else if (angle > 270 || angle < 90) {
verticalTextOffset = plSize.h;
}
// Calculate bounding box based on textAlign
var labelTop = pointLabelPosition.y - verticalTextOffset - labelPadding;
var labelHeight = 2*labelPadding + plSize.h;
var labelBottom = labelTop + labelHeight;
var labelWidth = plSize.w + 2*labelPadding;
var labelLeft;
switch (textAlign) {
case 'center':
var labelLeft = pointLabelPosition.x - labelWidth/2;
break;
case 'left':
var labelLeft = pointLabelPosition.x - labelPadding;
break;
case 'right':
var labelLeft = pointLabelPosition.x - labelWidth + labelPadding;
break;
default:
console.log('ERROR: unknown textAlign '+textAlign);
}
var labelRight = labelLeft + labelWidth;
// Render a rectangle for testing purposes
ctx.save();
ctx.strokeStyle = 'red';
ctx.lineWidth = 1;
ctx.strokeRect(labelLeft, labelTop, labelWidth, labelHeight);
ctx.restore();
// compare to the current click
if (mouseX >= labelLeft && mouseX <= labelRight && mouseY <= labelBottom && mouseY >= labelTop) {
alert(scale.pointLabels[i]+' clicked');
// Break loop to prevent multiple clicks, if they overlap we take the first one.
break;
}
}
};
JSFiddle here:
https://jsfiddle.net/simoncoggins/7r08uLk9/
The downside of this approach is it that it will break if the core labelling implementation changes in the future. It would be better if the library separated the calculation of label position from its rendering and started exposing the position info via the API. Then this solution could be greatly simplified and would be more robust to library changes.
I've opened a ticket offering to make that change here:
https://github.com/chartjs/Chart.js/issues/6549
Please comment on that issue if it would be useful to you.
Thanks Pogrindis. Answer here works for Chart.js v2.1: Chart.js click on labels, using bar chart
To make that work for Chart.js v5.0+, add the following function back into the Chart.js code.
LinearRadialScale = Chart.LinearScaleBase.extend({...})
getValueCount: function() {
return this.chart.data.labels.length;
}
I'm working on a top down shooter, and basically the character starts in the middle of the screen, inside a rect (Safe Zone). The character isn't static, the scene is. He can walk around, inside the safe zone. As soon as the character walks out of this zone, the statics switch over ... the character is static, and the scene is moving around him.
The only problem with this is that I can't walk back into the safe zone, allowing my statics to switch over again.
So I'm forever stuck outside the zone. All I'm doing is checking to see whether my character position is 'within' a certain value (which is the rect), if he's out - then my KeyControls then affect the Map, not the character.
So this is my boundary (Safe Zone) checker:
//Walking Window Boundaries
var boundarySizeX = 400;
var boundarySizeY = 200;
ctxWalkBoundary.fillStyle = "grey";
ctxWalkBoundary.fillRect(gameWidth/2 - boundarySizeX/2, gameHeight/2 - boundarySizeY/2, boundarySizeX, boundarySizeY);
ctxWalkBoundary.clearRect((gameWidth/2 - boundarySizeX/2) + 2, (gameHeight/2 - boundarySizeY/2) + 2, (boundarySizeX) - 4, (boundarySizeY) -4 );
var paddingLeft = (gameWidth - boundarySizeX) / 2;
var paddingRight = gameWidth - ((gameWidth - boundarySizeX) / 2) - this.charWidth;
var paddingTop = (gameHeight - boundarySizeY) / 2;
var paddingBottom = gameHeight - ((gameHeight - boundarySizeY) / 2) - this.charHeight;
var paddingY = (gameHeight - boundarySizeY) / 2;
if(this.drawX > paddingLeft && this.drawX < paddingRight && this.drawY > paddingTop && this.drawY < paddingBottom){
inBoundary = true;
}
else{
inBoundary = false;
console.debug("Out Of Boundary!");
}
And this is my KeyChecker:
//UP
if(this.isUpKey){
//Character movement
if(inBoundary){
this.drawX = this.drawX + this.speed * Math.cos((this.characterRotation));
this.drawY = this.drawY + this.speed * Math.sin((this.characterRotation));
}
else{
mapPositionX = mapPositionX - this.speed * Math.cos((this.characterRotation));
mapPositionY = mapPositionY - this.speed * Math.sin((this.characterRotation));
}
My character always faces my mouse (rotates). So every time the user pressed W, or Up - the character will always walk towards the mouse position.
Any ideas how I can get back into the zone?
----- Update -----
I guess I need to somehow check if I'm still facing outside the safe zone - if not, then reverse he statics.
Just separate two things: map and view.
Map is your level, you keep there objects with coordinates.
View is part of map you see on screen.
View has 4 properties: x, y, widht and height, where widht and height most likely is your canvas size.
If your game start with view on map point (0,0) in the middle of screen, then your view (x,y) coordinates should be (-view.width/2, -view.height/2).
How to draw your character and objects in a view?
In first place, draw only thing that are in the view rectangle.
So loop over all objects and check if
object.x >= view.x && object.x <= view.x + view.width && object.y >= view.y && object.y <= view.y + view.height
(you probably should take into account objects boundaries too).
If object is in view area then draw it at position (object.x - view.x, object.y - view.y).
And that's all about drawing things.
Moving character and view area with him.
Now when your character collides with boundary, in example (colliding with right border)
character.x >= view.x + view.width
then move view to the right by incrementing view.x with some value (that might be character.width/2).
-- UPDATE --
I see that you are not using OOP in your game (actually you are because everything in JS is an object, but you are not using it on purpose).
OOP in JS is a lot of explaining, so I'll try to make it short.
You can make objects like your Character, Map and View using JSON format.
character = {
x: 0,
y: 0,
xspeed: 0,
yspeed: 0,
speed: 0,
radius: 20,
}
map = {
objects: [
{sprite: 'tree.jpg', x: 100, y: 50},
{sprite: 'stone.jpg', x: 20, y: 30},
],
}
view = {
width: canvas.width,
height: canvas.height,
x: -this.width/2,
y: -this.height/2,
}
These are objects that you can use like in your functions like that:
for (var i=0; i++, i<map.objects.length) {
if (map_obj.x >= view.x && map_obj.x <= view.x + view.width && map_obj.y >= view.y && map_obj.y <= view.y + view.height) {
var map_obj = map.objects[i];
draw_sprite(map_obj.sprite, map_obj.x - view.x, map_obj.y - view.y);
}
}
It's not the best way, but it's still much better than yours right now. When you understand what OOP is about you will make it better for your own.
The problem here is that you're waiting for the character to go out of bounds, then moving the map instead. But the flag has already been tripped, and now the character movement is static no matter what direction you go in, because you're already out of bounds.
You could instead detect when a character is going to cross the boundary and prevent it by moving the map instead:
//UP
if(this.isUpKey){
// save the x and y offset to prevent needless recalculation
var xOffset = this.speed * Math.cos(this.characterRotation),
yOffset = this.speed * Math.sin(this.characterRotation);
//Character movement
if( boundaryCheck(xOffset, yOffset) ){
this.drawX = this.drawX + xOffset;
this.drawY = this.drawY + yOffset;
}
else{
mapPositionX = mapPositionX - xOffset
mapPositionY = mapPositionY - yOffset;
}
then boundaryCheck takes the x and y delta's and figures out if they're still in bounds. If the character will still be in bounds, return true and the character will move, otherwise the map will move.
function boundaryCheck(xOffset, yOffset){
// variables set and other stuff done...
if(this.drawX + xOffset > paddingLeft && this.drawX + xOffset < paddingRight && this.drawY + yOffset > paddingTop && this.drawY + yOffset < paddingBottom){
return true;
}
else{
console.debug("Out Of Boundary!");
return false;
}
};
This way you don't have to figure out whether an out of bounds character is moving toward the boundary or not. Instead, you pre-determine where the character is going, and adjust accordingly, always keeping him in boundaries.
Without full code this isn't testable, of course, but I think it should work with what you've given.