Reputation:
I looked at other posts on this topic and I couldnt find any that actually worked for my scenario
Basically I am making a 2d sword game that where players fight each other. The "player" is just a circle and the sword is just attached on the circle and points where the mouse is pointing...
Basically I have to detect when the sword is touching another player.
Here is my code for rendering the Player on canvas...
function drawImageLookat(img, sword, x, y, lookx, looky, mouseisdown){
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.rotate(Math.atan2(looky - y, lookx - x));
ctx.drawImage(img,-img.width / 2, -img.height / 2);
ctx.save();
// draw second image
ctx.transform(1, 0, 0, 1, img.width / 2, img.height / 2);
if(mouseisdown) {
ctx.rotate(30 * Math.PI / 180);
ctx.drawImage(sword, -sword.width / 2+30, -sword.height / 2-20);
} else {
ctx.drawImage(sword, -sword.width / 2+30, -sword.height / 2-20);
}
ctx.restore(); // get the parent transform
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
imgready = false
const smiley = new Image()
const cupcake = new Image()
const sword1 = new Image()
smiley.src = "./assets/images/smiley.png"
cupcake.src = "./assets/images/cupcake.png"
sword1.src = "./assets/images/sword1.png"
smiley.onload = () => {
imgready = true;
}
const drawPlayer = (player) => {
if(imgready) {
if(player.show) {
if(player.msg) {
ctx.font = "20px Arial";
ctx.fillText(player.msgcontent, player.x, player.y-50);
}
drawImageLookat(eval(player.image), eval(player.sword), player.x, player.y, player.lookx, player.looky, player.mousedown)
}
}
};
socket.on('state', (gameState) => {
ctx.clearRect(0,0, 1200, 999999999999999999)
for (let player in gameState.players) {
drawPlayer(gameState.players[player])
}
})
Every 1000/60 milliseconds, 'state' is emitted by the server and the client draws all of the Players that gameState has. Now basically I want if mouse is down, check if sword is touching other player. And it can be a boolean result. How to check if sword is touching?
Any help appreciated thanks! Also my full code is here: https://repl.it/@CoderGautamYT/FunChat-1
Upvotes: 0
Views: 196
Reputation: 54026
I assume its the sword (as a line segment) that you want to check if contacting the character (circle)
The following function will return true
if a line segment contacts a circle
// x1, y1, x2, y2 line segement
// cx, cy center of circle
// r radius of circle
function isLineTouchingCircle(x1, y1, x2, y2, cx, cy, r){
const self = isLineTouchingCircle; // expecting that you will want the closest point
const v1x = x2 - x1;
const v1y = y2 - y1;
var u = ((cx - x1) * v1x + (cy - y1) * v1y) / (v1y * v1y + v1x * v1x);
u = u < 0 ? 0 : u > 1 ? 1 : u;
const x = (self.x = x1 + v1x * u) - cx;
const y = (self.y = y1 + v1y * u) - cy;
return (x * x + y * y) - r * r < 0;
}
To convert the transformed sword sprite to a line segment
const angle = Math.atan2(looky - y, lookx - x);
const angle1 = 30 * Math.PI / 180; // You should use radians
const swordLength = sword.height
// base of sword relative to player
var x1 = img.width / 2;
var y1 = img.height / 2;
const ax = Math.cos(angle);
const ay = Math.sin(angle);
// find base of sword rotated to player
var x2 = x1 * ax - y1 * ay + x;
var y2 = x1 * ay + y1 * ax + y;
// find tip of sword (I dont know which way the sword points
// so rotated -90 deg "the (- Math.PI / 2) part" )
var x3 = Math.cos(angle + angle1 - Math.PI / 2) * swordLength + x2;
var y3 = Math.sin(angle + angle1 - Math.PI / 2) * swordLength + y2;
// check contact
if (isLineTouchingCircle(x2, y2, x3, y3, player2.x, player2.y, player2.r)) {
}
Please note I am a little unsure as to how the sword image is oriented.
To make sure it works
const player = (x, y, r, dir, col="green") => ({x,y,r, dir, col, dx: 0, dy: 0, strikeCount: 0, lefty: false, sDir: 0, hit: false, home: false});
const SIZE = 400;
const PLY1 = player(100,SIZE/2, 30, 0);
const PLY2 = player(SIZE - 100,SIZE/2, 30, 0);
PLY2.lefty = true;
const ctx = canvas.getContext("2d");
canvas.height = canvas.width = SIZE;
const blood = document.createElement("canvas");
blood.height = blood.width = SIZE;
const ctxB = blood.getContext("2d");
const playerImg = {
w: 60,
h: 60,
path: ((p=new Path2D())=> (p.addPath(new Path2D("M16 3a23 23 0 0112-2h5a28 28 0 0118 9h1a28 28 0 017 20l-1 7a21 21 0 01-4 13h-1a26 26 0 01-9 7v1a23 23 0 01-12 1h-5a28 28 0 01-18-9 28 28 0 01-8-20l1-7a21 21 0 015-13 26 26 0 019-7zm-2 9a7 7 0 00-5 2 7 7 0 00-2 5v4a7 7 0 002 5 7 7 0 005 2h4a7 7 0 005-2 7 7 0 002-5v-4a7 7 0 00-2-5 7 7 0 00-5-2zm28 0a7 7 0 00-5 2 7 7 0 00-2 5v4a7 7 0 002 5 7 7 0 005 2h4a7 7 0 005-2 7 7 0 002-5v-4a7 7 0 00-2-5 7 7 0 00-5-2zm0 6a3 3 0 014-1 2 2 0 011 2v2a3 3 0 01-4 3 2 2 0 01-2-4zm-29 1a3 3 0 014-1 2 2 0 011 2l-1 2a2 2 0 01-3 1 2 2 0 01-1-3zm1 22a1 1 0 00-2 2 7 7 0 004 4l4 2a20 20 0 0012 2h1a19 19 0 009-4l2-3a2 2 0 001-2 2 2 0 00-3 0l-2 1a14 14 0 01-7 4h-1a15 15 0 01-10-1z"), new DOMMatrix([1,0,0,1,-30, -30])), p))(),
}
const swordImg = {
w: 29,
h: 126,
path: ((p=new Path2D())=> (p.addPath(new Path2D("M14 0l4 7a30 30 0 014 15v74h6v8h-8l-1 22-9-1v-21l-9-1v-7h7V22a37 37 0 013-15zm0 8l-2 88h6zm-1 95l-1 20 5 1 1-20z"),new DOMMatrix([1,0,0,1,-14.5, -120])), p))(),
}
function isLineTouchingCircle(x1, y1, x2, y2, cx, cy, r){
const self = isLineTouchingCircle;
const v1x = x2 - x1;
const v1y = y2 - y1;
var u = ((cx - x1) * v1x + (cy - y1) * v1y) / (v1y * v1y + v1x * v1x);
u = u < 0 ? 0 : u > 1 ? 1 : u;
const x = (self.x = x1 + v1x * u) - cx;
const y = (self.y = y1 + v1y * u) - cy;
return (x * x + y * y) - r * r < 0;
}
function updatePlayer(p, lookTo) {
p.dir = Math.atan2(p.y - lookTo.y, p.x - lookTo.x);
const dist = Math.hypot(p.y - lookTo.y, p.x - lookTo.x);
if (Math.random() < (p.hit ? 0.1 : 0.01)) {
const move = p.dir + Math.random() ** 2 * Math.sign(Math.random() - 0.5) + (p.hit ? Math.PI : 0);
const hs = p.home ? 0.5 : 1;
p.dx = Math.cos(move) * (p.hit ? 1 + Math.random() : hs);
p.dy = Math.sin(move) * (p.hit ? 1 + Math.random() : hs);
}
if (p.strikeCount < 0 && dist < 120 && Math.random() < 0.05) {
p.strike = true;
p.strikeCount = 30;
} else {
if (p.strikeCount < 20) { p.strike = false }
p.strikeCount --;
}
p.x += p.dx;
p.y += p.dy;
p.x = (p.x % SIZE + SIZE) % SIZE;
p.y = (p.y % SIZE + SIZE) % SIZE;
}
function drawPlayer(p) {
const ax = Math.cos(p.dir);
const ay = Math.sin(p.dir);
ctx.setTransform(ax, ay, -ay, ax, p.x, p.y);
if (p.hit) {
ctx.strokeStyle = "#800";
ctx.lineWidth = 8;
ctx.stroke(playerImg.path);
}
ctx.fillStyle = p.col;
ctx.fill(playerImg.path, "evenodd");
if(p.lefty) {
ctx.transform(1, 0, 0, 1, playerImg.w / 2, playerImg.h / 2);
if (p.strike) {
ctx.rotate(Math.PI * 0.4);
p.sDir = Math.PI * 0.4;
} else { p.sDir = 0 }
} else {
ctx.transform(1, 0, 0, 1, -playerImg.w / 2, playerImg.h / 2);
if (p.strike) {
ctx.rotate(-Math.PI * 0.4);
p.sDir = -Math.PI * 0.4;
} else { p.sDir = 0 }
}
ctx.fillStyle = p.home ? "#F00" : "#000";
ctx.fill(swordImg.path, "evenodd");
p.home = p.hit = false;
}
function testHit(p, p1) {
const left = p.lefty ? 1 : -1;
const x1 = playerImg.w / 2 * left;
const y1 = playerImg.h / 2;
const ax = Math.cos(p.dir)
const ay = Math.sin(p.dir)
const x2 = x1 * ax - y1 * ay + p.x;
const y2 = x1 * ay + y1 * ax + p.y;
const x3 = Math.cos(p.dir + p.sDir - Math.PI / 2) * swordImg.h + x2;
const y3 = Math.sin(p.dir + p.sDir - Math.PI / 2) * swordImg.h + y2;
if (isLineTouchingCircle(x2, y2, x3, y3, p1.x, p1.y, p1.r)) {
p.home = p1.hit = true;
drawBlood(isLineTouchingCircle.x, isLineTouchingCircle.y, Math.random() * 4 + 1);
}
}
const bCol = ["#F00","#E02","#D04"];
function drawBlood(x, y, count) {
const d = Math.random() * Math.PI * 2;
const s = Math.random() ** 3 * 40;
const r = Math.random() ** 4 * 6 + 1
ctxB.setTransform(1,0,0,1,Math.cos(d) * s + x, Math.sin(d) * s + y);
ctxB.beginPath();
ctxB.fillStyle = bCol[Math.random() * 3 | 0];
ctxB.arc(0,0, r, 0, Math.PI * 2);
ctxB.fill();
count > 0 && drawBlood(x, y, count - 1);
}
requestAnimationFrame(mainLoop);
var frame = 0;
function mainLoop() {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,SIZE,SIZE);
ctx.drawImage(blood, 0, 0);
if (frame++ % 8 === 0) {
ctxB.setTransform(1,0,0,1,0,0);
ctxB.fillStyle = "white";
ctxB.globalAlpha = 0.1;
ctxB.fillRect(0,0,SIZE,SIZE);
ctxB.globalAlpha = 1;
}
updatePlayer(PLY1, PLY2);
updatePlayer(PLY2, PLY1);
drawPlayer(PLY1);
drawPlayer(PLY2);
PLY1.strike && testHit(PLY1, PLY2);
PLY2.strike && testHit(PLY2, PLY1);
requestAnimationFrame(mainLoop);
}
div {
border: 2px solid black;
overflow: hidden;
width: 150px;
height: 150px;
}
canvas {
width: 170px;
height: 170px;
transform: matrix(1, 0, 0, 1, -10, -10);
}
<div><canvas id="canvas"></canvas></div>
Upvotes: 3