admin管理员组文章数量:1356045
Note: *A plete JSFiddle can be found at the bottom of my post*.
Problem: I am trying to destroy all enemies that touch the blue line in the center of the canvas. However, this is not the case and my implementation is only "half working". When one side works, the other doesnt. How can I fix this problem?
What I tried: Once I set up the basic drawing functions, I calculated the difference between x and y of the colliding objects. Used the pythagorean distance to calculate the distance between the two points. Finally checked if the distance is less or equal to the bined radius of the two objects. Using arctangent I calculated the rotation of the movement of the objects.
Alternative Solution I Thought Of: Using a loop to creating various invisible circles or dots along the blue line that act as a collision receptor. Problem is: it eats more resources and it wouldnt be elegant at all.
The Javascript function of most interest to you would be:
function (player, spawn) {
return (this.distance(player, spawn) <= player.radius + spawn.radius) && (this.tangent(player, spawn) <= angle - Math.PI * 1);
}
The angle is the angle of the rotating blue line (which is a semi circle with a stoke).
this.tangent(player, spawn) <= angle - Math.PI * 1)
This works only for the -- and +- sections. Changing <= to >= does the opposite as expected. I would need to find a way to loop from -1 to 1.
this.tangent(player, spawn) >= angle - Math.PI * 2 && this.tangent(player, spawn) >= angle
works for --, -+, ++ but not for +- (bottom right).
So in the end im utterly confused why my logic doesnt work but I am eager to learn how this can be achieved:
Below the JSFiddle:
/
I would be happy for a response as I love learning new things in Javascript : )
Edit (03.11.2015): If possible only purely mathematical solutions but feel free to also post other solutions. For the sake of learning new techniques, every piece of information is wele.
Note: *A plete JSFiddle can be found at the bottom of my post*.
Problem: I am trying to destroy all enemies that touch the blue line in the center of the canvas. However, this is not the case and my implementation is only "half working". When one side works, the other doesnt. How can I fix this problem?
What I tried: Once I set up the basic drawing functions, I calculated the difference between x and y of the colliding objects. Used the pythagorean distance to calculate the distance between the two points. Finally checked if the distance is less or equal to the bined radius of the two objects. Using arctangent I calculated the rotation of the movement of the objects.
Alternative Solution I Thought Of: Using a loop to creating various invisible circles or dots along the blue line that act as a collision receptor. Problem is: it eats more resources and it wouldnt be elegant at all.
The Javascript function of most interest to you would be:
function (player, spawn) {
return (this.distance(player, spawn) <= player.radius + spawn.radius) && (this.tangent(player, spawn) <= angle - Math.PI * 1);
}
The angle is the angle of the rotating blue line (which is a semi circle with a stoke).
this.tangent(player, spawn) <= angle - Math.PI * 1)
This works only for the -- and +- sections. Changing <= to >= does the opposite as expected. I would need to find a way to loop from -1 to 1.
this.tangent(player, spawn) >= angle - Math.PI * 2 && this.tangent(player, spawn) >= angle
works for --, -+, ++ but not for +- (bottom right).
So in the end im utterly confused why my logic doesnt work but I am eager to learn how this can be achieved:
Below the JSFiddle:
http://jsfiddle/mzg635p9/
I would be happy for a response as I love learning new things in Javascript : )
Edit (03.11.2015): If possible only purely mathematical solutions but feel free to also post other solutions. For the sake of learning new techniques, every piece of information is wele.
Share Improve this question edited Nov 8, 2015 at 17:31 Asperger asked Oct 31, 2015 at 18:09 AspergerAsperger 3,22211 gold badges59 silver badges106 bronze badges3 Answers
Reset to default 6 +150made something simplified on the problem of collision detection between a disk and an arc http://jsfiddle/crl/2rz296tf/31 (edit: with @markE suggestion http://jsfiddle/crl/2rz296tf/32/) (for debugging: http://jsfiddle/crl/2rz296tf/27/)
some util functions for paring angles:
function mod(x, value){ // Euclidean modulo http://jsfiddle/cLvmrs6m/4/
return x>=0 ? x%value : value+ x%value;
}
function angularize(x){
return mod(x+pi, 2*pi)-pi;
}
collision detection:
var d_enemy_player = dist(enemy.pos, player.pos)
if (d_enemy_player>player.shieldradius-enemy.radius && d_enemy_player<player.shieldradius+enemy.radius){
//only worth checking when we are approaching the shield distance
var angle_enemy = atan2(enemy.pos.y-player.pos.y, enemy.pos.x-player.pos.x)
var delta_with_leftofshield = angularize(angle_enemy-player.angle-player.shieldwidth)
var delta_with_rightofshield = angularize(angle_enemy-player.angle+player.shieldwidth)
var delta_with_shield = angularize(angle_enemy-player.angle)
if (delta_with_leftofshield<0 && delta_with_rightofshield>0){
console.log('boom')
enemy.destroyed = true;
} else if(delta_with_shield>=0 ){
// check distance with right extremety of shield's arc
console.log('right')
var d_rightofshield_enemy = dist(enemy.pos, {x:player.pos.x+player.shieldradius*cos(player.angle+player.shieldwidth), y:player.pos.y+player.shieldradius*sin(player.angle+player.shieldwidth)});
if (d_rightofshield_enemy<enemy.radius){
console.log('right boom')
enemy.destroyed = true;
}
} else {
console.log('left')
var d_leftofshield_enemy = dist(enemy.pos, {x:player.pos.x+player.shieldradius*cos(player.angle-player.shieldwidth), y:player.pos.y+player.shieldradius*sin(player.angle-player.shieldwidth)});
if (d_leftofshield_enemy<enemy.radius){
console.log('left boom')
enemy.destroyed = true;
}
}
}
Html5 canvas has a very nice hit-testing method: context.isPointInPath
.
You can use this method to test if a circle is collides with your shield. It will work at all angles of the shield.
In your case, the path would be an inner arc with radius of player.shield.radius-enemy.radius
and an outer arc with radius of player.shield.radius+enemy.radius
.
Inside mousemove
, just draw (without stroking) the 2 arcs of the shield-path and test each enemy's centerpoint using context.isPointInside( enemy.centerX, enemy.centerY )
.
For better accuracy, extend the shield-path sweep by the radius of the enemy on both ends.
Here's example code and a Demo:
function log() {
console.log.apply(console, arguments);
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
function reOffset() {
var BB = canvas.getBoundingClientRect();
offsetX = BB.left;
offsetY = BB.top;
}
var offsetX, offsetY;
reOffset();
window.onscroll = function(e) {
reOffset();
}
window.onresize = function(e) {
reOffset();
}
var isDown = false;
var startX, startY;
var cx = cw / 2;
var cy = ch / 2;
var radius = 100;
var startAngle = Math.PI/6;
var enemyRadius = 15;
var shieldStrokeWidth = 8;
var endRadians = enemyRadius / (2 * Math.PI * radius) * (Math.PI * 2);
defineShieldHitPath(cx, cy, radius, enemyRadius, startAngle);
drawShield(cx, cy, radius, startAngle, shieldStrokeWidth);
$("#canvas").mousemove(function(e) {
handleMouseMove(e);
});
function defineShieldHitPath(cx, cy, r, enemyRadius, startAngle) {
ctx.beginPath();
ctx.arc(cx, cy, r - enemyRadius - shieldStrokeWidth / 2, startAngle - endRadians, startAngle + Math.PI + endRadians);
ctx.arc(cx, cy, r + enemyRadius + shieldStrokeWidth / 2, startAngle + Math.PI + endRadians, startAngle - endRadians, true);
ctx.closePath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
// stroked just for the demo.
// you don't have to stroke() if all you're doing is 'isPointInPath'
ctx.stroke();
}
function drawShield(cx, cy, r, startAngle, strokeWidth) {
ctx.beginPath();
ctx.arc(cx, cy, r, startAngle, startAngle + Math.PI);
ctx.lineWidth = strokeWidth;
ctx.strokeStyle = 'blue';
ctx.stroke();
}
function drawEnemy(cx, cy, r, fill) {
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.fillStyle = fill;
ctx.fill();
}
function handleMouseMove(e) {
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
ctx.clearRect(0, 0, cw, ch);
drawShield(cx, cy, radius, startAngle, shieldStrokeWidth);
defineShieldHitPath(cx, cy, radius, enemyRadius, startAngle);
if (ctx.isPointInPath(mouseX, mouseY)) {
drawEnemy(mouseX, mouseY, enemyRadius, 'red');
} else {
drawEnemy(mouseX, mouseY, enemyRadius, 'green');
}
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis./ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>The shield is the blue arc.<br>The filled circle that moves with the mouse is the enemy.<br>The black stroked arc is the shield perimiter.<br>The enemy turns red when colliding with the blue shield.<br>Test by moving the mouse-enemy in / out of the shield perimiter.</h4>
<canvas id="canvas" width=400 height=400></canvas>
For optimum performance, you can alternatively do the same isPointInPath
hit test mathematically.
It's a 2 part test. Test#1: Is the enemy centerpoint is within the sweep angle of the shield (using Math.atan2). Test#2: Is the enemy centerpoint at a distance between the inner and outer radii of the shield-path arcs. If both tests are true then the enemy is colliding with the shield-path.
The problem with your code seems to be the way you pare angles. Don't forget that 2Pi is exactly the same thing as 0. Take a look at this example: You have 2 angles, a and b.
a = 0.1 * Pi
b = 1.9 * Pi
a is slightly above the x axis, while b is slightly bellow it.
It seems that a is ahead of b when looking at both, so you'd expect a > b to be true. But wait! Look at the numbers, b is way bigger than a! When you want to check if an angle is between an interval, you have to make sure that your interval is continuous, which in this case is false for angle = 0.
Here's my solution. I tested it as best as I could, but you can never know if you missed something.
// Gets the equivalent angle between 0 and MAX
var normalize_angle = function( angle )
{
var MAX = Math.PI * 2; // Value for a full rotation. Should be 360 in degrees
angle %= MAX;
return angle < 0 ? angle + MAX : angle;
};
var is_angle_between = function( alpha, min, max )
{
// Convert all the angles to be on the same rotation, between 0 and MAX
alpha = normalize_angle( alpha );
min = normalize_angle( min );
max = normalize_angle( max );
if( max > min )
{ // Check if the equal case fits your needs. It's a bit pointless for floats
return max >= alpha && min <= alpha; // Traditional method works
} else { // This happens when max goes beyond MAX, so it starts from 0 again
return max >= alpha || min <= alpha; // alpha has to be between max and 0 or
// between min and MAX
}
};
To use it, change your shield function to:
shield:
function (player, spawn) {
return (this.distance(player, spawn) <= player.radius + spawn.radius) &&
is_angle_between(this.tangent(player, spawn), angle , angle - Math.PI );
}
}
本文标签: Javascript Canvas Collision against enemies not entirely working when rotating playerStack Overflow
版权声明:本文标题:Javascript Canvas: Collision against enemies not entirely working when rotating player - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1743947655a2566704.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论