按圆度导航
Navigate according to degrees in circle
我尝试在 javascript 和 nodejs 中构建一个 2d canvas 游戏。
在游戏中,玩家是一艘可以在大海中航行的船。
船对象是 sprite sheet 有 16 个船位置(每帧 22.5 度)从 0 到 360 度开始。
为了导航我使用 NippleJS 的船,它给我 0-360 的度数(例如:328.9051138238949)。
现在,我需要算法来检查 16 个中的哪个精灵应该在每个时刻显示。
船必须在每个检查度数向左或向右移动1个精灵,并且它必须是最佳方式(最佳方式意味着如果船舶度数为 0 且 Nipplejs 的度数为 335,则船舶应向右导航而不是向左导航..)。
我开始考虑它并且我知道如果度数在 -(22.5/2) 和 (22.5/2) 之间我需要显示图像帧但是当我超过 360 度和更多东西时我遇到了很多问题..
为了相互理解,我们将船舶度数称为 ship.degree,我们将 Nipplejs 的度数称为 nipplejs.degree,船舶精灵将由 ship.sprite[0-16] 表示( 0 = 正确的方向)。
请帮忙。
要沿最小角度转动,下面的函数会将角度按每 360 度的步数 angleMoveSteps
步进。例如,90 步将以 4 度步进。
如果角度差小于此步,将停止步进
const angleSteps = 16;
const angleMoveSteps = 90;
function getNextAngle(newDirection, currentDirection) {
// normalize both directions to 360
newDirection %= 360;
currentDirection %= 360;
// if new direction is less than current move it ahead by 360
newDirection = newDirection < currentDirection ? newDirection + 360 : newDirection;
// get the difference (will always be positive)
const dif = newDirection - currentDirection;
// if the difference is greater than 180 and less than 360 - step
// turn CCW
if (dif > 360 / 2 && dif < 360 - (360 / angleMoveSteps)) {
return currentDirection - (360 / angleMoveSteps);
}
// if the difference is greater than step and less than 180
// turn CW
if (dif > (360 / angleMoveSteps) && dif < 360 / 2){
return currentDirection + (360 / angleMoveSteps);
}
// do nothing if within step angle of target
return currentDirection
}
要将 deg 转换为图像索引,可以使用以下函数。请参阅精简版演示。
function getStepAngle(dir) { // dir in degrees
// normalise the angle
dir = (dir % 360 + 360) % 360;
// scale to angleSteps 0 to 16
dir /= 360 / angleSteps;
// offset by half a step
dir += 0.5;
// floor the result to get integer
dir = Math.floor(dir);
// Get remainder to ensure value between 0 and 16 not including 16
return dir % angleSteps;
}
演示
演示显示了正在使用的两个函数。
点击转盘设置新的目标方向,当前方向会向新方向移动,移动时显示移动角度(绿色)和图像索引(蓝色)。
const angleSteps = 16;
const angleMoveSteps = 90;
var currentDir = 0;
var shipDir = 0;
var targetAngle = 0;
function getNextAngle(newDirection, currentDirection) {
const step = 360 / angleMoveSteps
newDirection %= 360;
currentDirection %= 360;
newDirection = newDirection < currentDirection ? newDirection + 360 : newDirection;
const dif = newDirection - currentDirection;
if (dif > 360 / 2 && dif < 360 - step) { return currentDirection - step }
if (dif > step && dif < 360 / 2) { return currentDirection + step }
return currentDirection
}
function getStepAngle(dir) {
return Math.floor(((dir % 360 + 360) % 360) / (360 / angleSteps) + 0.5) % angleSteps;
}
/* Demo code from here down */
Math.TAU = Math.PI * 2;
const ctx = canvas.getContext("2d")
const w = canvas.width;
const h = canvas.height;
var seeking = false;
const speed = 100; // milliseconds
update();
canvas.addEventListener("click", event => {
const bounds = canvas.getBoundingClientRect();
const x = event.pageX - bounds.left - scrollX;
const y = event.pageY - bounds.top - scrollY;
targetAngle = Math.atan2(y - w / 2, x - h / 2) * 180 / Math.PI;
if(!seeking){ render() }
});
function render() {
requestAnimationFrame(update);
var newDir = getNextAngle(targetAngle, currentDir);
if(newDir !== currentDir) {
currentDir = newDir;
seeking = true;
setTimeout(render, speed);
} else {
currentDir = targetAngle;
setTimeout(()=>requestAnimationFrame(update), speed);
seeking = false;
}
}
function update() {
shipDir = getStepAngle(currentDir);
clear();
drawCompase();
drawTargetAngle(targetAngle);
drawCurrentAngle(currentDir);
drawStepAngle(shipDir);
}
function clear() { ctx.clearRect(0,0,w,h) }
function angleText(text,x,y,angle,size = 12, col = "#000") {
const xAX = Math.cos(angle);
const xAY = Math.sin(angle);
ctx.fillStyle = col;
ctx.font = size + "px arial";
ctx.textAlign = "right";
ctx.textBaseline = "middle";
if(xAX < 0) {
ctx.setTransform(-xAX, -xAY, xAY, -xAX, x, y);
ctx.textAlign = "left";
} else {
ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
ctx.textAlign = "right";
}
ctx.fillText(text,0,0);
}
function drawCompase() {
var i;
const rad = h * 0.4, rad1 = h * 0.395, rad2 = h * 0.41;
ctx.lineWidth = 1;
ctx.strokeStyle = "#000";
ctx.beginPath();
ctx.arc(w / 2, h / 2, rad, 0, Math.TAU);
ctx.stroke();
ctx.lineWidth = 2;
ctx.beginPath();
for (i = 0; i < 1; i += 1 / angleSteps) {
const ang = i * Math.TAU;
ctx.moveTo(Math.cos(ang) * rad1 + w / 2, Math.sin(ang) * rad1 + h / 2);
ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
}
ctx.stroke();
for (i = 0; i < 1; i += 1 / angleSteps) {
const ang = i * Math.TAU;
angleText(
(ang * 180 / Math.PI).toFixed(1).replace(".0",""),
Math.cos(ang) * (rad1 - 2) + w / 2,
Math.sin(ang) * (rad1 - 2) + h / 2,
ang
);
}
ctx.setTransform(1,0,0,1,0,0);
}
function drawTargetAngle(angle) { // angle in deg
const rad = h * 0.30, rad1 = h * 0.1, rad2 = h * 0.34;
const ang = angle * Math.PI / 180;
const fromA = ang - Math.PI / (angleSteps * 4);
const toA = ang + Math.PI / (angleSteps * 4);
ctx.linewidth = 2;
ctx.strokeStyle = "#F00";
ctx.beginPath();
ctx.moveTo(Math.cos(fromA) * rad + w / 2, Math.sin(fromA) * rad + h / 2);
ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
ctx.lineTo(Math.cos(toA) * rad + w / 2, Math.sin(toA) * rad + h / 2);
ctx.stroke();
angleText(
angle.toFixed(1).replace(".0",""),
Math.cos(ang) * (rad - 4) + w / 2,
Math.sin(ang) * (rad - 4) + h / 2,
ang,
12, "#F00"
);
ctx.setTransform(1,0,0,1,0,0);
}
function drawCurrentAngle(angle) { // angle in deg
const rad = h * 0.14, rad2 = h * 0.17;
const ang = angle * Math.PI / 180;
const fromA = ang - Math.PI / (angleSteps * 2);
const toA = ang + Math.PI / (angleSteps * 2);
ctx.linewidth = 2;
ctx.strokeStyle = "#0A0";
ctx.beginPath();
ctx.moveTo(Math.cos(fromA) * rad + w / 2, Math.sin(fromA) * rad + h / 2);
ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
ctx.lineTo(Math.cos(toA) * rad + w / 2, Math.sin(toA) * rad + h / 2);
ctx.stroke();
angleText(
angle.toFixed(1).replace(".0",""),
Math.cos(ang) * (rad - 4) + w / 2,
Math.sin(ang) * (rad - 4) + h / 2,
ang,
12, "#0A0"
);
ctx.setTransform(1,0,0,1,0,0);
}
function drawStepAngle(angle) { // ang 0 to angleSteps cyclic
var ang = angle % angleSteps;
ang *= Math.PI / angleSteps*2;
const fromA = ang - Math.PI / angleSteps;
const toA = ang + Math.PI / angleSteps;
const rad = h * 0.4, rad1 = h * 0.35, rad2 = h * 0.44;
const rad3 = h * 0.34, rad4 = h * 0.45;
ctx.linewidth = 1;
ctx.strokeStyle = "#08F";
ctx.beginPath();
ctx.arc(w / 2, h / 2, rad1, fromA, toA);
ctx.moveTo(w / 2 + Math.cos(fromA) * rad2, h / 2 + Math.sin(fromA) * rad2, 0, Math.TAU);
ctx.arc(w / 2, h / 2, rad2, fromA, toA);
ctx.stroke();
ctx.linewidth = 2;
ctx.beginPath();
ctx.moveTo(Math.cos(fromA) * rad3 + w / 2, Math.sin(fromA) * rad3 + h / 2);
ctx.lineTo(Math.cos(fromA) * rad4 + w / 2, Math.sin(fromA) * rad4 + h / 2);
ctx.moveTo(Math.cos(toA) * rad3 + w / 2, Math.sin(toA) * rad3 + h / 2);
ctx.lineTo(Math.cos(toA) * rad4 + w / 2, Math.sin(toA) * rad4 + h / 2);
ctx.stroke();
angleText(
angle,
Math.cos(ang + 0.1) * (rad - 2) + w / 2,
Math.sin(ang + 0.1) * (rad - 2) + h / 2,
ang,
16, "#08F"
);
ctx.setTransform(1,0,0,1,0,0);
}
body { font-family: arial }
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<span> Click to set new target direction</span>
<canvas id="canvas" width="400" height="400"></canvas>
我尝试在 javascript 和 nodejs 中构建一个 2d canvas 游戏。 在游戏中,玩家是一艘可以在大海中航行的船。 船对象是 sprite sheet 有 16 个船位置(每帧 22.5 度)从 0 到 360 度开始。
为了导航我使用 NippleJS 的船,它给我 0-360 的度数(例如:328.9051138238949)。
现在,我需要算法来检查 16 个中的哪个精灵应该在每个时刻显示。
船必须在每个检查度数向左或向右移动1个精灵,并且它必须是最佳方式(最佳方式意味着如果船舶度数为 0 且 Nipplejs 的度数为 335,则船舶应向右导航而不是向左导航..)。
我开始考虑它并且我知道如果度数在 -(22.5/2) 和 (22.5/2) 之间我需要显示图像帧但是当我超过 360 度和更多东西时我遇到了很多问题..
为了相互理解,我们将船舶度数称为 ship.degree,我们将 Nipplejs 的度数称为 nipplejs.degree,船舶精灵将由 ship.sprite[0-16] 表示( 0 = 正确的方向)。
请帮忙。
要沿最小角度转动,下面的函数会将角度按每 360 度的步数 angleMoveSteps
步进。例如,90 步将以 4 度步进。
如果角度差小于此步,将停止步进
const angleSteps = 16;
const angleMoveSteps = 90;
function getNextAngle(newDirection, currentDirection) {
// normalize both directions to 360
newDirection %= 360;
currentDirection %= 360;
// if new direction is less than current move it ahead by 360
newDirection = newDirection < currentDirection ? newDirection + 360 : newDirection;
// get the difference (will always be positive)
const dif = newDirection - currentDirection;
// if the difference is greater than 180 and less than 360 - step
// turn CCW
if (dif > 360 / 2 && dif < 360 - (360 / angleMoveSteps)) {
return currentDirection - (360 / angleMoveSteps);
}
// if the difference is greater than step and less than 180
// turn CW
if (dif > (360 / angleMoveSteps) && dif < 360 / 2){
return currentDirection + (360 / angleMoveSteps);
}
// do nothing if within step angle of target
return currentDirection
}
要将 deg 转换为图像索引,可以使用以下函数。请参阅精简版演示。
function getStepAngle(dir) { // dir in degrees
// normalise the angle
dir = (dir % 360 + 360) % 360;
// scale to angleSteps 0 to 16
dir /= 360 / angleSteps;
// offset by half a step
dir += 0.5;
// floor the result to get integer
dir = Math.floor(dir);
// Get remainder to ensure value between 0 and 16 not including 16
return dir % angleSteps;
}
演示
演示显示了正在使用的两个函数。 点击转盘设置新的目标方向,当前方向会向新方向移动,移动时显示移动角度(绿色)和图像索引(蓝色)。
const angleSteps = 16;
const angleMoveSteps = 90;
var currentDir = 0;
var shipDir = 0;
var targetAngle = 0;
function getNextAngle(newDirection, currentDirection) {
const step = 360 / angleMoveSteps
newDirection %= 360;
currentDirection %= 360;
newDirection = newDirection < currentDirection ? newDirection + 360 : newDirection;
const dif = newDirection - currentDirection;
if (dif > 360 / 2 && dif < 360 - step) { return currentDirection - step }
if (dif > step && dif < 360 / 2) { return currentDirection + step }
return currentDirection
}
function getStepAngle(dir) {
return Math.floor(((dir % 360 + 360) % 360) / (360 / angleSteps) + 0.5) % angleSteps;
}
/* Demo code from here down */
Math.TAU = Math.PI * 2;
const ctx = canvas.getContext("2d")
const w = canvas.width;
const h = canvas.height;
var seeking = false;
const speed = 100; // milliseconds
update();
canvas.addEventListener("click", event => {
const bounds = canvas.getBoundingClientRect();
const x = event.pageX - bounds.left - scrollX;
const y = event.pageY - bounds.top - scrollY;
targetAngle = Math.atan2(y - w / 2, x - h / 2) * 180 / Math.PI;
if(!seeking){ render() }
});
function render() {
requestAnimationFrame(update);
var newDir = getNextAngle(targetAngle, currentDir);
if(newDir !== currentDir) {
currentDir = newDir;
seeking = true;
setTimeout(render, speed);
} else {
currentDir = targetAngle;
setTimeout(()=>requestAnimationFrame(update), speed);
seeking = false;
}
}
function update() {
shipDir = getStepAngle(currentDir);
clear();
drawCompase();
drawTargetAngle(targetAngle);
drawCurrentAngle(currentDir);
drawStepAngle(shipDir);
}
function clear() { ctx.clearRect(0,0,w,h) }
function angleText(text,x,y,angle,size = 12, col = "#000") {
const xAX = Math.cos(angle);
const xAY = Math.sin(angle);
ctx.fillStyle = col;
ctx.font = size + "px arial";
ctx.textAlign = "right";
ctx.textBaseline = "middle";
if(xAX < 0) {
ctx.setTransform(-xAX, -xAY, xAY, -xAX, x, y);
ctx.textAlign = "left";
} else {
ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
ctx.textAlign = "right";
}
ctx.fillText(text,0,0);
}
function drawCompase() {
var i;
const rad = h * 0.4, rad1 = h * 0.395, rad2 = h * 0.41;
ctx.lineWidth = 1;
ctx.strokeStyle = "#000";
ctx.beginPath();
ctx.arc(w / 2, h / 2, rad, 0, Math.TAU);
ctx.stroke();
ctx.lineWidth = 2;
ctx.beginPath();
for (i = 0; i < 1; i += 1 / angleSteps) {
const ang = i * Math.TAU;
ctx.moveTo(Math.cos(ang) * rad1 + w / 2, Math.sin(ang) * rad1 + h / 2);
ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
}
ctx.stroke();
for (i = 0; i < 1; i += 1 / angleSteps) {
const ang = i * Math.TAU;
angleText(
(ang * 180 / Math.PI).toFixed(1).replace(".0",""),
Math.cos(ang) * (rad1 - 2) + w / 2,
Math.sin(ang) * (rad1 - 2) + h / 2,
ang
);
}
ctx.setTransform(1,0,0,1,0,0);
}
function drawTargetAngle(angle) { // angle in deg
const rad = h * 0.30, rad1 = h * 0.1, rad2 = h * 0.34;
const ang = angle * Math.PI / 180;
const fromA = ang - Math.PI / (angleSteps * 4);
const toA = ang + Math.PI / (angleSteps * 4);
ctx.linewidth = 2;
ctx.strokeStyle = "#F00";
ctx.beginPath();
ctx.moveTo(Math.cos(fromA) * rad + w / 2, Math.sin(fromA) * rad + h / 2);
ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
ctx.lineTo(Math.cos(toA) * rad + w / 2, Math.sin(toA) * rad + h / 2);
ctx.stroke();
angleText(
angle.toFixed(1).replace(".0",""),
Math.cos(ang) * (rad - 4) + w / 2,
Math.sin(ang) * (rad - 4) + h / 2,
ang,
12, "#F00"
);
ctx.setTransform(1,0,0,1,0,0);
}
function drawCurrentAngle(angle) { // angle in deg
const rad = h * 0.14, rad2 = h * 0.17;
const ang = angle * Math.PI / 180;
const fromA = ang - Math.PI / (angleSteps * 2);
const toA = ang + Math.PI / (angleSteps * 2);
ctx.linewidth = 2;
ctx.strokeStyle = "#0A0";
ctx.beginPath();
ctx.moveTo(Math.cos(fromA) * rad + w / 2, Math.sin(fromA) * rad + h / 2);
ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
ctx.lineTo(Math.cos(toA) * rad + w / 2, Math.sin(toA) * rad + h / 2);
ctx.stroke();
angleText(
angle.toFixed(1).replace(".0",""),
Math.cos(ang) * (rad - 4) + w / 2,
Math.sin(ang) * (rad - 4) + h / 2,
ang,
12, "#0A0"
);
ctx.setTransform(1,0,0,1,0,0);
}
function drawStepAngle(angle) { // ang 0 to angleSteps cyclic
var ang = angle % angleSteps;
ang *= Math.PI / angleSteps*2;
const fromA = ang - Math.PI / angleSteps;
const toA = ang + Math.PI / angleSteps;
const rad = h * 0.4, rad1 = h * 0.35, rad2 = h * 0.44;
const rad3 = h * 0.34, rad4 = h * 0.45;
ctx.linewidth = 1;
ctx.strokeStyle = "#08F";
ctx.beginPath();
ctx.arc(w / 2, h / 2, rad1, fromA, toA);
ctx.moveTo(w / 2 + Math.cos(fromA) * rad2, h / 2 + Math.sin(fromA) * rad2, 0, Math.TAU);
ctx.arc(w / 2, h / 2, rad2, fromA, toA);
ctx.stroke();
ctx.linewidth = 2;
ctx.beginPath();
ctx.moveTo(Math.cos(fromA) * rad3 + w / 2, Math.sin(fromA) * rad3 + h / 2);
ctx.lineTo(Math.cos(fromA) * rad4 + w / 2, Math.sin(fromA) * rad4 + h / 2);
ctx.moveTo(Math.cos(toA) * rad3 + w / 2, Math.sin(toA) * rad3 + h / 2);
ctx.lineTo(Math.cos(toA) * rad4 + w / 2, Math.sin(toA) * rad4 + h / 2);
ctx.stroke();
angleText(
angle,
Math.cos(ang + 0.1) * (rad - 2) + w / 2,
Math.sin(ang + 0.1) * (rad - 2) + h / 2,
ang,
16, "#08F"
);
ctx.setTransform(1,0,0,1,0,0);
}
body { font-family: arial }
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<span> Click to set new target direction</span>
<canvas id="canvas" width="400" height="400"></canvas>