在碰撞时启动画布动画并使其停止

时间:2016-09-19 06:38:11

标签: javascript animation html5-canvas collision-detection

当我遇到一些问题时,我正在通过画布学习动画,在一个小jQuery的帮助下,我在画布上创建了两个矩形。一个起源于画布的中心,一个由我的鼠标位置控制。我喜欢做的是当鼠标矩形击中画布上的第二个矩形时。我希望第二个矩形朝着被击中的方向移动。

我遇到的问题是,我希望动画能够流畅而轻松地停下来。下面的代码Iv' e得到了第二个方块自动执行方式id就像动画一样,当它被击中时,但当我把它放入一个if colliding is true语句时,第二个方块只移动5px。现在这对我来说是有道理的,因为只有当盒子触摸时才会被告知移动5个像素,我甚至尝试在if语句中运行for循环运行一定次数并慢慢减小速度,因为它到达我希望它移动的长度。然而,这只会导致第二个框跳到最后并且不显示动画。很抱歉这个长篇大论的问题,但是如果有人能指出我正确的方向或者告诉我如何重构代码来完成这项工作我很欣赏它。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 1000;
canvas.height = 400;
var width = canvas.width;
var height = canvas.height;
var particles = [];

var mouseSize = 50;
var isColliding = false;
var mouseX;
var mouseY;

function particle() {
  var particle = {
    originX: width / 2,
    originY: height / 2,
    x: width / 2,
    y: height / 2,
    movement: 60, //overall movement wanted when collides
    velocity: 5,
    size: 30,
    draw: function() {
      ctx.fillStyle = "white";
      ctx.fillRect(this.x, this.y, this.size, this.size)


      this.x += this.velocity;
      this.velocity *= .98;

    }
  }
  return particle;
}

function createParticles() {

  particles.push(particle())
}

createParticles();

function draw() {
  ctx.clearRect(0, 0, width, height);

  //console.log(event.pageX+','+event.pageY)
  //mouse rect created here. did not create directly in mouseMove event because I could not
  //properly clear the canvas between each frame and keep all objects on screen.
  ctx.fillStyle = 'white';
  ctx.fillRect(mouseX, mouseY, mouseSize, mouseSize);

  particles[0].draw();

  requestAnimationFrame(draw);
}

$("#canvas").mousemove(function(event) {
  console.log(isColliding);
  mouseX = event.pageX;
  mouseY = event.pageY;

  //if objects are colliding set iscolliding to true, otherwise set it to false;
  if(event.pageX < particles[0].x + particles[0].size &&
    event.pageX + mouseSize > particles[0].x &&
    event.pageY < particles[0].y + particles[0].size &&
    mouseSize + event.pageY > particles[0].y) {
    isColliding = true;
    // console.log("collison detected");    
  } 
  else{isColliding=false;}

})

window.onload = draw();

2 个答案:

答案 0 :(得分:0)

这样的事情怎么样?我添加了一些变量(oldMouseX,oldMouseY)以跟踪鼠标的先前坐标,并且可以计算其方向。然后你的粒子也需要X和Y的速度,以允许它在这两个轴上移动......假设你想要那个。如果你不想改变Y,那就别管了。但看起来你顺利的停止动作对我有用。我不确定我是否理解你想要的东西。我还添加了一个重置​​变量,这样它就不会继续实现共谋,直到再次将这些框分开。

<html>
<body style="background-color:black">
<canvas id="canvas"></canvas>
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 1000;
canvas.height = 400;
var width = canvas.width;
var height = canvas.height;
var particles = [];

var mouseSize = 50;
var isColliding = false;
var mouseX;
var mouseY;
var oldMouseX;
var oldMouseY;
var reset = true;

function particle() {
    var particle = {
    originX: width / 2,
    originY: height / 2,
    x: width / 2,
    y: height / 2,
    movement: 60,
    velocityX: 0,
    velocityY: 0,
    size: 30,
    draw: function() {
      ctx.fillStyle = "white";
      ctx.fillRect(this.x, this.y, this.size, this.size)


      this.x += this.velocityX;
      this.y += this.velocityY;
      this.velocityX *= .98;
      this.velocityY *= .98;

    }
  }
  return particle;
}

function createParticles() {

  particles.push(particle())
}

createParticles();

function draw() {
  ctx.clearRect(0, 0, width, height);

  //console.log(event.pageX+','+event.pageY)
  //mouse rect created here. did not create directly in mouseMove event because i could not
  //properly clear the canvas between each frame and keep all objects on screen.
  ctx.fillStyle = 'white';
  ctx.fillRect(mouseX, mouseY, mouseSize, mouseSize);

  particles[0].draw();

  requestAnimationFrame(draw);
}

canvas.addEventListener('mousemove', function(event) {
  console.log(isColliding);
  oldMouseX = mouseX;
  oldMouseY = mouseY;
  mouseX = event.pageX;
  mouseY = event.pageY;

  //if objects are colliding set iscolliding to true, otherwise set it to false;
  if(event.pageX < particles[0].x + particles[0].size &&
    event.pageX + mouseSize > particles[0].x &&
    event.pageY < particles[0].y + particles[0].size &&
    mouseSize + event.pageY > particles[0].y) {
    isColliding = true;
    // console.log("collison detected");
  } 
  else{isColliding=false; reset=true;}

  if (isColliding && reset) {
    particles[0].velocityX = mouseX - oldMouseX;
    particles[0].velocityY = mouseY - oldMouseY;
    reset = false;
    }


})

window.onload = draw();
</script>
</body>
</html>

答案 1 :(得分:0)

轻松功能

您可以使用缓动功能,其中有许多功能。

缓动函数取0到1之间的值,并从0-1返回修改后的值。输入值和返回值之间的差异代表了易用性。

下图显示了一个稍微复杂的缓动函数的示例,该函数采用第二个值p(或幂)来修改函数创建的曲线。它显示p的3个值,值1产生线性响应。 Pow可以是0 <0的任何值。 p&lt;无穷大x是输入,y是输出。​​

enter image description here

抱歉没有图像编辑器,所以功能不是示例中使用的

基本多项式简易函数

最基本的缓和功能(任何用途)都是多项式(简单地表示具有某种功能的功能)。输入值被钳位,输出值是p的幂。

function easeInOut(v,p){  // p < 1 is rush out ease in , 
                          //  p > 1 is ease out rush in
                          // p = 1 and the function is linear
    v = v < 0 ? 0 : v > 1 ? 1 : v;  // clamp input also clamps output
    return Math.pow(v,p); // return value
}

使用简易功能

要使用缓动功能,您需要两个位置来缓和

之间的对象位置
var fromX = 10;
var fromY = 10;
var toX = 100;
var toY = 100;

然后得到它们之间的区别

var dx = toX - fromX;
var dy = toY - fromY;

然后使用缓动功能获得修改的单位距离

var uDist = 0.5; // half way along
var uDistE = easeInOut(moveD,0.2);

// the position eased
var x = fromX + dx * uDistE;
var y = fromY + dy * uDistE;

随着时间的推移轻松

随着时间的推移,值uDist从开头的0变为结尾的1。如果您希望这是一个固定的时间,那么您记录开始时间和移动时间并将其标准化。

var startTime = performance.now(); // do this when you start movement


const timeToMove = 2000; // time to move in ms (2000 = 2 seconds)

var uDist = (performance.now()-startTime) / timeToMove; // ease function clamps 
                                                        // values so no need
                                                        // to worry about being 
                                                       // out of range

使用缓动功能进行演示

该示例显示了使用ease函数移动粒子。在演示中,有4种类型的机芯,每种机芯都具有不同的功能,便于使用。较小的圆圈已经缓和,并且大颗粒可以缓和进入。

一半代码是样板文件,与答案相关的内容位于顶部。

最佳浏览完整页面。

/** SimpleFullCanvasMouse.js begin **/
// Note set window.onResize (note capital R) to get the debounced resize call
// ie onResize = function(){ // blah blah };
// Note. mouse, canvas, ctx are set for you. Mouse is a simple mouse with
// x,y,w (wheel) alt,shift,ctrl, and buttonRaw as a bit field bits 0,1,2 for buttons left, mid, right
// ie if(mouse.buttonRaw & 1) { //is left button down (othe buttons may be down also
// ie if(mouse.buttonRaw === 1) { //is only left button down
// set debounced resize event function to put new particles on canvas
onResize = function(){ particles.length = 0; };
const pSize = 5; // particle size (radius)
var particles = []; // array of things
const mouseSize = 30; // Size of mouse (radius)
const mouseCol = "red"; // guess what this holds???
const runDist = 300; // How far a particl will move when touched
const moveSpeed = 0.01; // speed to move (will move 1 unit dist so 1 / MoveSpeed == number frames to move)
const edgeDist = 10; // distance particles will stay away from the canvas edge.
const MAX_PARTICLES = 200; // the max number oof you know whats

// ease function return unit value clamped from 0-1 based on the input value clamped 0-1
// most functions take a second argument that is a curve modifier. Usualy the power
const EASE_FUNC = {
    linear : function (val) {
        val = val < 0 ? 0 : val > 1 ? 1 : val;
        return val;
    },
    easePow : function (val, pow) { // for pow > 1 this is ease out rush in
        // for pow > 0 & pow < 1 this is rush out ease in
        val = val < 0 ? 0 : val > 1 ? 1 : val;
        return Math.pow(val, pow);
    },
}
const particleMoveFunction = EASE_FUNC.easePow; // ease function to use


// creates a particle at x,y
function createParticle(x, y) {
    var typesC = "#F80,#8F0,#0D0,#00F".split(",");
    var curves = [0.2,0.5,1.2,2];
    var sizes = [1,1.2,1.6,2.2];
    var type = Math.floor(4-Math.sqrt(Math.random() * 16)); // more small fast
    
    return {
        x : x,
        y : y,
        lx : x,
        ly : y,
        dest : {
            x : x,
            y : y,
        },
        from : {
            x : x,
            y : y,
        },
        size : pSize * sizes[type],
        col : typesC[type] ,
        moveTime : 0,
        moveSpeed : moveSpeed,
        moveCurve : curves[type],
    }
}

// Move particles.
// checks for mouse interaction
// checks for edge distance
// moves particles is particle moveTime > 0
function updateAllParticles() {
    var i;
    for (i = 0; i < particles.length; i++) {
        var p = particles[i];
        p.lx = p.x;
        p.ly = p.y;
        if (p.moveTime > 0) {
            p.moveTime -= p.moveSpeed;
            var pos = particleMoveFunction(1 - p.moveTime, p.moveCurve);
            p.x = (p.dest.x - p.from.x) * pos + p.from.x;
            p.y = (p.dest.y - p.from.y) * pos + p.from.y;
        }
        p.x = p.x < p.size + edgeDist ? p.size + edgeDist : (p.x >= canvas.width - p.size - edgeDist ? canvas.width - p.size - edgeDist : p.x);
        p.y = p.y < p.size + edgeDist ? p.size + edgeDist : (p.y >= canvas.height - p.size - edgeDist ? canvas.height - p.size - edgeDist : p.y);

        var dist = Math.sqrt(Math.pow(mouse.x - p.x, 2) + Math.pow(mouse.y - p.y, 2));
        if (dist < mouseSize + p.size) {
            var dx = (p.x - mouse.x) / dist;
            var dy = (p.y - mouse.y) / dist;
            p.x = mouse.x + dx * (mouseSize + p.size); // move to edge of mouse
            p.y = mouse.y + dy * (mouseSize + p.size);
            // random is to stop them grouping to much
            var rd = runDist / 2 + Math.random() * runDist / 2;
            p.dest.x = p.x + dx * rd + Math.random() * 5 - 2.5;
            p.dest.y = p.y + dy * rd + Math.random() * 5 - 2.5;
            p.from.x = p.x;
            p.from.y = p.y;
            p.moveTime = 1;
        }
        p.distMoved = Math.sqrt(Math.pow(p.lx - p.x, 2) + Math.pow(p.ly - p.y, 2));
    }
}

// draw particles
function drawAllParticles() {
    var i;
    ctx.lineCap = "round";
    for (i = 0; i < particles.length; i++) {
        var p = particles[i];
        ctx.strokeStyle = p.col;
        ctx.lineWidth = p.size * 2;
        ctx.beginPath();
        ctx.moveTo(p.lx, p.ly);
        ctx.lineTo(p.x, p.y);
        ctx.stroke();
    }
}

// draws the mouse object
function drawMouse() {
    ctx.fillStyle = mouseCol;
    ctx.beginPath();
    ctx.moveTo(mouse.x + mouseSize, mouse.y);
    ctx.arc(mouse.x, mouse.y, mouseSize, 0, Math.PI * 2);
    ctx.fill();
}

// Main display function
function display() {
    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    ctx.globalAlpha = 0.33; // fade background
    ctx.fillStyle = "white";
    ctx.fillRect(0, 0, w, h);


    ctx.globalAlpha = 1; // reset alpha
    if (particles.length < MAX_PARTICLES) {
        particles.push(createParticle(
                Math.random() * (canvas.width - pSize * 2) + pSize,
                Math.random() * (canvas.height - pSize * 2) + pSize));
    }
    updateAllParticles();
    drawAllParticles();
    drawMouse();
}

//==================================================================================================
// Boilerplate code not part of answer
//
//
// The following code is support code that provides me with a standard interface to various forums.
// It provides a mouse interface, a full screen canvas, and some global often used variable
// like canvas, ctx, mouse, w, h (width and height), globalTime
// This code is not intended to be part of the answer unless specified and has been formated to reduce
// display size. It should not be used as an example of how to write a canvas interface.
// By Blindman67
if (typeof onResize === "undefined") {
    window["onResize"] = undefined; // create without the JS parser knowing it exists.
    // this allows for it to be declared in an outside
    // modal.
}
const RESIZE_DEBOUNCE_TIME = 100;
var w, h, cw, ch, canvas, ctx, mouse, createCanvas, resizeCanvas, setGlobals, globalTime = 0, resizeCount = 0;
createCanvas = function () {
    var c,
    cs;
    cs = (c = document.createElement("canvas")).style;
    cs.position = "absolute";
    cs.top = cs.left = "0px";
    cs.zIndex = 1000;
    document.body.appendChild(c);
    return c;
}
resizeCanvas = function () {
    if (canvas === undefined) {
        canvas = createCanvas();
    }
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    ctx = canvas.getContext("2d");
    if (typeof setGlobals === "function") {
        setGlobals();
    }
    if (typeof onResize === "function") {
        resizeCount += 1;
        setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
    }
}
function debounceResize() {
    resizeCount -= 1;
    if (resizeCount <= 0) {
        onResize();
    }
}
setGlobals = function () {
    cw = (w = canvas.width) / 2;
    ch = (h = canvas.height) / 2;
    mouse.updateBounds();
}
mouse = (function () {
    function preventDefault(e) {
        e.preventDefault();
    }
    var mouse = {
        x : 0,
        y : 0,
        w : 0,
        alt : false,
        shift : false,
        ctrl : false,
        buttonRaw : 0,
        over : false,
        bm : [1, 2, 4, 6, 5, 3],
        active : false,
        bounds : null,
        mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
    };
    var m = mouse;
    function mouseMove(e) {
        var t = e.type;
        m.x = e.clientX - m.bounds.left;
        m.y = e.clientY - m.bounds.top;
        m.alt = e.altKey;
        m.shift = e.shiftKey;
        m.ctrl = e.ctrlKey;
        if (t === "mousedown") {
            m.buttonRaw |= m.bm[e.which - 1];
        } else if (t === "mouseup") {
            m.buttonRaw &= m.bm[e.which + 2];
        } else if (t === "mouseout") {
            m.buttonRaw = 0;
            m.over = false;
        } else if (t === "mouseover") {
            m.over = true;
        } else if (t === "mousewheel") {
            m.w = e.wheelDelta;
        } else if (t === "DOMMouseScroll") {
            m.w = -e.detail;
        }
        if (m.callbacks) {
            m.callbacks.forEach(c => c(e));
        }
        e.preventDefault();
    }
    m.updateBounds = function () {
        if (m.active) {
            m.bounds = m.element.getBoundingClientRect();
        }
    }
    m.addCallback = function (callback) {
        if (typeof callback === "function") {
            if (m.callbacks === undefined) {
                m.callbacks = [callback];
            } else {
                m.callbacks.push(callback);
            }
        } else {
            throw new TypeError("mouse.addCallback argument must be a function");
        }
    }
    m.start = function (element, blockContextMenu) {
        if (m.element !== undefined) {
            m.removeMouse();
        }
        m.element = element === undefined ? document : element;
        m.blockContextMenu = blockContextMenu === undefined ? false : blockContextMenu;
        m.mouseEvents.forEach(n => {
            m.element.addEventListener(n, mouseMove);
        });
        if (m.blockContextMenu === true) {
            m.element.addEventListener("contextmenu", preventDefault, false);
        }
        m.active = true;
        m.updateBounds();
    }
    m.remove = function () {
        if (m.element !== undefined) {
            m.mouseEvents.forEach(n => {
                m.element.removeEventListener(n, mouseMove);
            });
            if (m.contextMenuBlocked === true) {
                m.element.removeEventListener("contextmenu", preventDefault);
            }
            m.element = m.callbacks = m.contextMenuBlocked = undefined;
            m.active = false;
        }
    }
    return mouse;
})();
resizeCanvas();
mouse.start(canvas, true);
window.addEventListener("resize", resizeCanvas);
function update(timer) { // Main update loop
    globalTime = timer;
    display(); // call demo code
    requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/