-1
\$\begingroup\$

I've been looking through a lot of answers here:

I couldn't find exactly the same thing I was looking for, which is proper top-down character sliding of circle to circle movement.

I tried implementing things from other answers here but whenever the character moves around circle, for some reason it just goes inside it (I was able to get it to spin around edges at one point but was unpredictable)

var aD = []
var r

function start() {
  r = new CanvasRenderer(can)
  my = new scene();
  eventHandler();

  my.add(new mesh({
    position: {
      x: 700,
      y: 200
    },
    radius: 70,
    color: "yellow",
    primitive: "circle",
    collision: "circle"
  }))

  my.add(new mesh({
    primitive: "circle",

    collision: "circle",
    color: "blue",
    name: "player",
    radius: 40,
    position: {
      x: 311,
      y: 75
    },
    origin: {
      x: 0.5,
      y: 0.5
    },
    onupdate(me) {


      aD.push(() => {
        r.ctx.beginPath()
        r.ctx.fillStyle = "red"
        r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI * 2);
        r.ctx.fill()
        r.ctx.closePath();
      })

      if (!window.pl) pl = me
      var upKey = keys[38],
        downKey = keys[40],
        rightKey = keys[39],
        leftKey = keys[37],
        drx = 0,
        dx = 0,
        speed = 5,
        turningSpeed = 3
      //console.log(upKey)
      drx = leftKey ? -1 : rightKey ? 1 : 0
      forward = upKey ? 1 : downKey ? -1 : 0

      me.rotation.x += (
        (drx * Math.PI / 180 * turningSpeed)
      )
      me.rotation.y = 1;

      var xDir = Math.cos(me.rotation.x)
      var yDir = Math.sin(me.rotation.x)

      var xvl = xDir * forward * speed
      var yvl = yDir * forward * speed
      me.position.y //=col.y
      me.position.x // = col.x
      me.xvl = xvl
      me.yvl = yvl
      var next = {
        x: me.position.x + xvl,
        y: me.position.y + yvl
      }
      me.next = next
      var isColliding = []
      var coll;
      var curColls = []
      me.cols = []

      me.speed = speed
      me.yDir = yDir
      for (var i = 0; i < my.objects.length; i++) {
        let cur = my.objects[i];
        if (cur.name == me.name || cur == me) {
          break;
        }
        curColls = checkCollision(
          next.x,
          next.y,
          me.radius || me.scale.x / 2,
          cur
        )


        curColls.forEach(coll => {
          if (!coll) return;
          if (coll == me) {
            //  console.log("WH")
            return
          }


          cur.isCol = true
          isColliding.push({
            coll,
            object: cur
          })
          //    console.log(2222,coll.line)

        })
      }

      Array.from(me.cols)
        .forEach(q => {
          var w = isColliding.indexOf(q)
          if (w == -1 || q.object == me) {
            me.cols.splice(me.cols.indexOf(q), 1)
          }


        })
      var isme = me.cols.findIndex(q => q.object == me)
      if (isme > -1) {
        me.cols.splice(isme, 1)
        var ok = isColliding.findIndex(q => q.object == me)
        if (ok > -1) {
          isColliding.splice(ok, 1)
        }
      }
      if (!me.obs)
        me.obs = []
      var noad = false
      if (isColliding.length > 0) {

        isColliding.forEach(q => {
          var w = me.cols.indexOf(q)
          if (w == -1) {
            me.cols.push(q)
          }
        })





        var col = me.cols[0].coll
        var ob = me.cols[0].object
        if (!me.obs)
          me.obs = []

        if (col.type == "circle") {
          let vecBetweenX = next.x - ob.position.x;
          let vecBetweenY = next.y - ob.position.y;

          let heading = Math.atan2(vecBetweenY, vecBetweenX);

          var dst = Math.sqrt(
            vecBetweenX * vecBetweenX +
            vecBetweenY * vecBetweenY
          )
          var dirX = vecBetweenX / dst
          var dirY = vecBetweenY / dst

          var rt = heading

          var ris = ob.radius + me.radius

          var ax = Math.cos(rt) * ris
          var ay = Math.sin(rt) * ris

          /*
          var nx=ob.position.x+ax
          var ny=ob.position.y+ay
          */

          var nx = ob.position.x + dirX * ris
          var ny = ob.position.y + dirY + ris

          me.position.x = nx
          me.position.y = ny
          return


        }
      }

      me.position.x += xvl

      me.position.y += yvl
    }

  }));
  let i = setInterval(() => render(r, my), 16);
  r.on("resize", () => render(r, my));

}


function render(r, s) {

  s.update();
  r.render(s)
  aD.forEach(x => x());
  aD = []
}

onload = start;


function checkCollision(x1, y1, rad, ob) {
  var colls = [];
  if (ob.collision == "circle") {
    /*
        Ball 1:  center: p1=(x1,y1)  radius: r1
        Ball 2:  center: p2=(x2,y2)  radius: r2

        collision distance: R= r1 + r2
        actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

        collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
        actual distance:  r12= sqrt( dx*dx + dy*dy )
    */

    var r1 = rad;
    var r2 = ob.radius;

    var p1 = [x1, y1]
    var p2 = [ob.position.x, ob.position.y]

    var R = r1 + r2;
    var R2 = R * R;

    var dx = p2[0] - p1[0]
    var dy = p2[1] - p1[1]

    var r12 = (
      dx * dx + dy * dy
    )

    var r12s = Math.sqrt(r12)

    var isCollision = r12s < R

    var colPoint = {
      x: (x1 * r2 + p2[0] * r1) /
        (r1 + r2),
      y: (y1 * r2 + p2[1] * r1) /
        (r1 + r2),
      type: "circle"
    };

    aD.push(() => {
      r.ctx.beginPath()
      r.ctx.fillStyle = "salmon"
      r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI * 2);
      r.ctx.fill()
      r.ctx.closePath();
    })

    if (isCollision) {
      colls.push(colPoint)
    }
  }

  return colls;


}


function dot(x1, y1, x2, y2) {
  return (
    x1 * x2 + y1 * y2
  )
}


function eventHandler() {
  window.keys = {};
  addEventListener("keyup", e => {
    keys[e.keyCode] = false;

  });

  addEventListener("keydown", e => {
    keys[e.keyCode] = true;
  });
}

function CanvasRenderer(dom) {
  if (!dom) dom = document.createElement("canvas");

  var events = {},
    self = this;

  function rsz() {
    dom.width = dom.clientWidth;
    dom.height = dom.clientHeight;
    self.dispatchEvent("resize");
  }

  window.addEventListener("resize", rsz);

  let ctx = dom.getContext("2d");

  function render(scene) {
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    for (let i = 0; i < scene.objects.length; i++) {
      let o = scene.objects[i],
        verts = o.realVerts;


      if (o.primitive == "circle") {
        var fnc = o.drawPrimitive[o.primitive]
        if (typeof(fnc) == "function")
          fnc(ctx)
      }
      ctx.fillStyle = o.color || "blue";
      ctx.lineWidth = 1;
      ctx.fill()
      ctx.stroke();
    }
  }

  Object.defineProperties(this, {
    domElement: {
      get: () => dom
    },
    ctx: {
      get: () => ctx
    },
    render: {
      get: () => render
    },
    on: {
      get: () => (nm, cb) => {
        if (!events[nm]) {
          events[nm] = [];
        }
        events[nm].push(data => {
          if (typeof cb == "function") {
            cb(data);
          }
        });
      }
    },
    dispatchEvent: {
      get: () => (name, data) => {
        if (events[name]) {
          events[name].forEach(x => {
            x(data);
          });
        }
      }
    }
  });

  rsz();

}

function scene() {
  let objects = [];
  Object.defineProperties(this, {
    add: {
      get: () => obj => {
        objects.push(obj);
      }
    },
    objects: {
      get: () => objects
    },
    update: {
      get: () => () => {
        objects.forEach(x => {
          if (typeof x.update == "function") {
            x.update();
          }
        });

      }
    }
  });
}

function mesh(data = {}) {
  let self = this,
    holder = {
      position: {},
      scale: {

      },
      rotation: {},
      origin: {}
    },
    position = {},
    scale = {},
    rotation = {},
    collision = "mesh",
    shape = null,
    origin = {},
    color,
    radius = 0,
    name,
    primitive,

    eventNames = "update",
    events = {},
    drawPrimitive = {
      circle(ctx) {
        ctx.beginPath();
        ctx.arc(
          self.position.x,
          self.position.y,
          radius,
          0,
          360 * Math.PI / 180
        );
        ctx.closePath();
      }
    },
    width = 1,
    height = 1;

  Object.defineProperties(position, {
    x: {
      get: () => holder.position.x || 0,
      set: v => holder.position.x = v
    },
    y: {
      get: () => holder.position.y || 0,
      set: v => holder.position.y = v
    },
    z: {
      get: () => holder.position.z || 0,
      set: v => holder.position.z = v
    }
  });

  Object.defineProperties(scale, {
    x: {
      get: () => holder.scale.x || 1,
      set: v => holder.scale.x = v
    },
    y: {
      get: () => holder.scale.y || 1,
      set: v => holder.scale.y = v
    },
    z: {
      get: () => holder.scale.z || 1,
      set: v => holder.scale.z = v
    }
  });

  Object.defineProperties(rotation, {
    x: {
      get: () => holder.rotation.x || 0,
      set: v => holder.rotation.x = v
    },
    y: {
      get: () => holder.rotation.y || 0,
      set: v => holder.rotation.y = v
    },
    z: {
      get: () => holder.rotation.z || 0,
      set: v => holder.rotation.z = v
    }
  });


  Object.defineProperties(this, {

    name: {
      get: () => name,
      set(v) {
        name = v
      }
    },
    primitive: {
      get: () => primitive,
      set(v) {
        primitive = v;

      }
    },

    drawPrimitive: {
      get: () => drawPrimitive
    },
    width: {
      get: () => width,
      set(v) {
        width = v
      }
    },
    height: {
      get: () => height,
      set(v) {
        height = v
      }
    },
    position: {
      get: () => position,
      set: v => {
        position.x = v.x || 0;
        position.y = v.y || 0;
        position.z = v.z || 0;
      }
    },

    radius: {
      get: () => radius || (width * scale.x) / 2,
      set: v => {
        radius = v;
        this.width = radius / 2
        this.height = radius / 2
      }
    },
    scale: {
      get: () => scale,
      set: v => {
        scale.x = v.x || v.x === 0 ? v.x : 1;
        scale.y = v.y || v.y === 0 ? v.y : 1;
        scale.z = v.z || v.z === 0 ? v.z : 1;


      }
    },
    rotation: {
      get: () => rotation,
      set: v => {
        rotation.x = v.x || 0;
        rotation.y = v.y || 0;
        rotation.z = v.z || 0;
      }
    },
    color: {
      get: () => color,
      set: v => {
        color = v;
      }
    },
    update: {
      get: () => () => {
        if (events["update"]) {
          events.update.forEach(x => {

            x(this);
          });
        }
      }
    },
    on: {
      get: () => (nm, fnc) => {
        if (!events[nm]) events[nm] = [];
        events[nm].push(stuff => {
          if (typeof fnc == "function") {
            fnc(stuff);
          }
        });
      }
    }
  });



  eventNames.split(" ").forEach(x => {
    var name = "on" + x;
    if (!this.hasOwnProperty(name)) {
      Object.defineProperty(this, name, {
        get: () => events[name],
        set(v) {
          events[x] = [
            data => {
              typeof v == "function" && v(data)
            }
          ];
        }
      });
    }
  });

  for (let k in data) {
    this[k] = data[k]
  }


}
canvas {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0px
}

.wow {
  float: right;
  z-index: 1298737198
}
<br> game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can></canvas>

Just arrow keys to move

Relevant part for circle sliding (contains code from different examples that was used at different times but nothing works):

if(col.type=="circle"){
    let vecBetweenX = next.x - ob.position.x;
    let vecBetweenY = next.y - ob.position.y;

    let heading = Math.atan2(vecBetweenY, vecBetweenX);

    var dst = Math.sqrt(
        vecBetweenX*vecBetweenX
        +
        vecBetweenY*vecBetweenY
    )
    var dirX=vecBetweenX/dst
    var dirY=vecBetweenY/dst

    var rt= heading
    
    var ris = ob.radius + me.radius

    var ax = Math.cos(rt)*ris
    var ay = Math.sin(rt)*ris

    /*
    var nx=ob.position.x+ax
    var ny=ob.position.y+ay
    */
    
    var nx=ob.position.x+dirX*ris
    var ny=ob.position.y+dirY+ris
    
    me.position.x = nx
    me.position.y = ny
    return
}

I tried using method suggested in answer, but player is still getting stuck at circle, while the ideal intended behavior is for the player to slide along the edges

\$\endgroup\$
4
  • 2
    \$\begingroup\$ You might want to reduce your example to an MCVE to allow more people to help you. The full example you provide lets us move a square, and not a circle, and the "relevant" part you supply is hard to place in its own context. \$\endgroup\$
    – Vaillancourt
    Jul 5, 2022 at 13:46
  • \$\begingroup\$ @Vaillancourt thanks 4 the tip, made a new version with only circle related things \$\endgroup\$ Jul 5, 2022 at 14:11
  • \$\begingroup\$ Sorry, but I don't understand this question. I have no idea what you mean with "character sliding of circle to circle movement". I also don't understand what the example is supposed to illustrate. As far as I can tell when the blue circle overlaps the yellow circle, then it gets warped to the other side. I assume that's not what you want? What behavior would you like to see instead? \$\endgroup\$
    – Philipp
    Jul 7, 2022 at 16:11
  • \$\begingroup\$ @Philipp correct it's not what I want. Imagine any rpg, where the character moves and slides off of objects it collides with. That's it \$\endgroup\$ Jul 7, 2022 at 18:21

1 Answer 1

1
+50
\$\begingroup\$

Your collision resolution is using player movement speed and angle, which are irrelevant if you're just trying to find a point to place them that's outside of the collision. You want to push the player in the direction of the angle from the collider to the player, so use heading for rt, and instead of multiplying the sine and cosine by speed*forward, multiply it by the sum of the circles' radii, because that's the distance you want between their centers. Then just add that to ob.position to get the final point. Remove the rr bit.

So, something like this:

let vecBetweenX = me.position.x - ob.position.x;
let vecBetweenY = me.position.y - ob.position.y;

let heading = Math.atan2(vecBetweenY, vecBetweenX);

var rt = heading

var radiiSum = ob.radius + me.radius

var ax = Math.cos(rt) * radiiSum
var ay = Math.sin(rt) * radiiSum

var nx = ob.position.x + ax
var ny = ob.position.y + ay

me.position.x = nx
me.position.y = ny
return

A simpler way of doing the same thing is just subtracting the collider's position from the player's position to get a direction vector, normalize it and multiply it by the sum of the radii, and then as before add it to the collider's position.

let vecBetweenX = me.position.x - ob.position.x;
let vecBetweenY = me.position.y - ob.position.y;
let dist = Math.sqrt( vecBetweenX*vecBetweenX + vecBetweenY*vecBetweenY )

// normalize
let dirX = vecBetweenX / dist
let dirY = vecBetweenY / dist

var radiiSum = ob.radius + me.radius

var nx = ob.position.x + dirX * radiiSum
var ny = ob.position.y + dirY * radiiSum

me.position.x = nx
me.position.y = ny
return
\$\endgroup\$
6
  • \$\begingroup\$ Hi thanks for the answer. I tried both ways, and it almost works as expected, except [if u plug in the code from above] the player simply stops when it collides with the circle, but the expected behaviour Im trying to get is like in MMORPGs where the character slides off of the walls / edges of things it comes in contact to, so in this case ideally not only would the player stop, but slide around the edges, if that makes sense \$\endgroup\$ Jul 10, 2022 at 7:31
  • 1
    \$\begingroup\$ Ah, I didn't realize you were testing collision with a different vector (next) than the player's actual position. I think it will work if you use me.next instead of me.position to calculate vecBetween, because then it will respond to the intended movement. \$\endgroup\$ Jul 10, 2022 at 7:35
  • \$\begingroup\$ Hi thanks again for the suggestion, I tried it but for some reason it still appears to have the same result [and changed the sketch in the edit] \$\endgroup\$ Jul 10, 2022 at 7:40
  • \$\begingroup\$ Actually I realized in the last edit I had misspelled "dist" and "dst" causing it to resolve the position as NaN which made it stop, I fixed that now, but the behavior is still unexpected as it jitters different directions for some reason, although if it's hit from the bottom it appears to slide for a brief amount of time, not sure why \$\endgroup\$ Jul 10, 2022 at 7:48
  • 1
    \$\begingroup\$ ya youre right, it works now! its a miracle \$\endgroup\$ Jul 10, 2022 at 21:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .