JavaScript Canvas: столкновение с врагами не полностью работает при вращении игрока

Примечание: *полный JSFiddle можно найти в нижней части моего поста*.

: Я пытаюсь уничтожить всех врагов, которые касаются синей линии в центре холста. Однако это не так, и моя реализация работает только наполовину. Когда одна сторона работает, другая нет. Как я могу решить эту проблему?


что я пробовал: как только я настроил основные функции рисования, я рассчитал разница между x и y сталкивающихся объектов. Использовал расстояние Пифагора для вычисления расстояния между двумя точками. Наконец, проверено, если расстояние меньше или равно объединенному радиусу двух объектов. Используя арктангенс, я рассчитал вращение движения объектов.


Альтернативное Решение, О Котором Я Подумал: использование петли для создания различных невидимых кругов или точек вдоль синей линии, которые действуют как рецептор столкновения. Проблема в том, что он съедает больше ресурсов, и он вообще не будет элегантным.


наиболее интересной для вас функцией Javascript будет:

function (player, spawn) {
    return (this.distance(player, spawn) <= player.radius + spawn.radius) && (this.tangent(player, spawn) <= angle - Math.PI * 1);
}

угол-это угол вращающейся синей линии (которая представляет собой полукруг с косточкой).

this.tangent(player, spawn) <= angle - Math.PI * 1)

это работает только для-и +- секций. Изменение = делает противоположное, как и ожидалось. Мне нужно будет найти способ сделать цикл от -1 до 1.

this.tangent(player, spawn) >= angle - Math.PI * 2 && this.tangent(player, spawn) >= angle

работает Для--, -+,++, но не для +- (правый нижний угол.)

Так что в конце концов я совершенно запутался, почему моя логика не работает, но я хочу узнать, как это может быть достигнуто:


под JSFiddle:

http://jsfiddle.net/mzg635p9/

Я был бы рад получить ответ, поскольку я люблю изучать новые вещи в Javascript:)

изменить (03.11.2015): если это возможно только чисто математические решения, но не стесняйтесь размещать иное решение. Ради изучения новых методов приветствуется любая информация.

3 ответов


сделал что-то упрощенное по проблеме обнаружения столкновения между диском и дугой http://jsfiddle.net/crl/2rz296tf/31 (edit: с предложением @markE http://jsfiddle.net/crl/2rz296tf/32/) (для отладки:http://jsfiddle.net/crl/2rz296tf/27/)

некоторые функции util для сравнения углов:

function mod(x, value){ // Euclidean modulo http://jsfiddle.net/cLvmrs6m/4/
    return x>=0 ? x%value : value+ x%value;
}

function angularize(x){
    return mod(x+pi, 2*pi)-pi;
}

обнаружение столкновений:

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 имеет очень хороший метод тестирования хитов:context.isPointInPath.

вы можете использовать этот метод для проверки, если круг сталкивается с вашим щитом. Он будет работать под всеми углами щита.

в вашем случае путь будет внутренней дугой с радиусом player.shield.radius-enemy.radius и внешняя дуга с радиусом player.shield.radius+enemy.radius.

внутри mousemove, просто нарисуйте (без поглаживания) 2 дуги пути щита и проверьте центральную точку каждого врага, используя context.isPointInside( enemy.centerX, enemy.centerY ).

для большей точности, расширить щит-путь развертки по радиусу противника на обоих концах.

вот пример кода и демо:

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.com/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>

для обеспечения оптимальной производительности, вы также можете сделать то же самое isPointInPath нажмите Проверить математически.

это 2 часть теста. Тест№1: является ли центр противника находится в пределах угла стреловидности щита (используя математику.инструмент atan2). Тест № 2: враг центрпойнт на расстояние между внутренним и внешним радиусами дуг щита-пути. Если оба теста верны, то противник сталкивается со щитом-путем.


проблема с вашим кодом, похоже, заключается в том, как вы сравниваете углы. Не забывайте, что 2Pi-это то же самое, что и 0. Взгляните на этот пример: У вас есть 2 угла, a и b.

a = 0.1 * Pi

b = 1.9 * Pi

a немного выше оси x, в то время как b немного ниже ее.

Кажется, что a опережает b при взгляде на оба, поэтому вы ожидаете, что A > b будет истинным. Но подождите! Посмотрите на цифры, B-это путь больше! Если вы хотите проверить, находится ли угол между интервалом, вы должны убедиться, что ваш интервал непрерывен, что в данном случае ложно для angle = 0.

вот мое решение. Я проверил его как мог, но никогда не знаешь, пропустил ли ты что-нибудь.

// 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
    }
};

чтобы использовать его, измените функцию shield на:

shield: 
    function (player, spawn) {
        return (this.distance(player, spawn) <= player.radius + spawn.radius) && 
            is_angle_between(this.tangent(player, spawn), angle , angle - Math.PI );
    }
}