Как привязать камеру к игроку в алгаритме А* на чистом JS?
Есть большая карта с непроходимыми клетками, алгоритм просчитывает путь и корабль плывет по нему, как привязаться к кораблю, чтобы камера двигалась за ним? Отрисовка карты начинается от верхнего левого угла, подскажите хотя бы? как изменить отрисовку с другого места, дальше я уже привяжу корабль, если будет понятно, как менять в алгоритме место отрисовки карты. Ссылка на работающий код
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 1024;
canvas.height = 640;
let tileSize = 32;
let imageNumTiles = 2
// const Viewport = function(x, y, w, h) {
// this.x = x; this.y = y; this.w = w; this.h = h;
// };
// Viewport.prototype = {
// scrollTo:function(x, y) {
// this.x += (x - this.x - this.w * 0.5) * 0.05;
// this.y += (y - this.y - this.h * 0.5) * 0.05;
// }
// };
// let viewport = new Viewport(0, 0, 1024, 640);
const objectShip = new Image();
const backgraund = new Image();
objectShip.src = './IMG/objectShip.png';
backgraund.src = './IMG/backgraund.png';
// инициализация массива с непроходимыми клетками =============================================
const points = {
0: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,43,56,75,88],
1: [24,43,56,75,88], 2: [11,24,43,56,75,88],
3: [11,24,43,56,75,88], 4: [11,24,43,56,75,88], 5: [11,24,43,56,75,88],
6: [11,24,43,56,75,88], 7: [11,24,43,56,75,88], 8: [11,24,43,56,75,88],
9: [11,24,43,56,75,88], 10: [11,24,43,56,75,88], 11: [11,24,43,56,75,88],
12: [11,24,43,56,75,88], 13: [11,24,43,56,75,88], 14: [11,24,43,56,75,88],
15: [11,24,43,56,75,88], 16: [11,24,43,56,75,88], 17: [11,24,43,56,75,88],
18: [11,24,43,56,75,88], 19: [11,24,43,56,75,88], 20: [11,24,43,56,75,88],
21: [11,24,43,56,75,88], 22: [11,24,43,56,75,88], 23: [11,24,43,56,75,88],
24: [11,24,43,56,75,88], 25: [11,24,43,56,75,88], 26: [11,24,43,56,75,88],
27: [11,24,43,56,75,88], 28: [11,24,43,56,75,88], 29: [11,24,43,56,75,88],
30: [11,24,43,56,75,88], 31: [11,24,43,56,75,88], 32: [11,24,43,56,75,88],
33: [11,24,43,56,75,88], 34: [11,24,43,56,75,88], 36: [11,24,43,56,75,88],
37: [11,24,43,56,75,88], 38: [11,24,43,56,75,88], 39: [11,24,43,56,75,88],
40: [11,24,43,56,75,88], 42: [11,24,43,56,75,88], 43: [11,24,43,56,75,88],
44: [11,24,43,56,75,88], 45: [11,24,43,56,75,88], 46: [11,24,43,56,75,88],
47: [11,24,43,56,75,88], 48: [11,24,43,56,75,88], 49: [11,24,43,56,75,88],
50: [11,24,43,56,75,88], 51: [11,24,43,56,75,88], 52: [11,24,43,56,75,88],
53: [11,24,43,56,75,88], 54: [11,24,43,56,75,88], 55: [11,24,43,56,75,88],
56: [11,24,43,56,75,88], 57: [11,24,43,56,75,88], 58: [11,24,43,56,75,88],
59: [11,24,43,56,75,88]
};
const a = Array(60)
.fill()
.map((_, y) => {
const row = Array(96).fill(0);
if (points[y] !== undefined) {
points[y].forEach(p => row[p] = 1);
}
return row;
});
// стартовое и финишное положение игрока
let sx=16,sy=10,ex=16,ey=10;
// =============================================================================================
let way = [];
let wx = 0,wy = 0;
let oex = -1,oey = -1,clicknum = 1;
let delWay = [];
let launchDelWay;
// клетки на карте с шагом в 32 пикселя
const step = {
grid(n) {
return n / 32
}
};
// проверка на старт с непроходимым обьектом под кораблем ========================
function BadStartEnd()
{
if (a[sy][sx]==1) return 1;
if (a[ey][ex]==1) return 1;
return 0;
}
// вычисления дистанции пути с учетом диагонали или горизонталь/вертикаль ==============================
function Dist(x1,x2,y1,y2)
{
let dx = Math.abs(x1-x2);
let dy = Math.abs(y1-y2);
if (dx>dy)
{
return dy*14+(dx-dy)*10;
}
else
{
return dx*14+(dy-dx)*10;
}
}
function Calc() {
if (BadStartEnd()==1) {
return;
}
for (let i=0;i<60;i++)
{
for (let j=0;j<96;j++)
{
if (a[i][j]>1) {a[i][j]=0;}
}
}
a[sy][sx] = Dist(sx,ex,sy,ey)*100+10;
var b = true,i = 0;
while ((b)&&(i<300))
{
b = AlgStep();
}
RecalcA();
b = true,i = 0;
while ((b)&&(i<300))
{
b = AlgStepWay();
}
if (clicknum == 1)
{
if ((oex==ex)&&(oey==ey))
clicknum = 2;
}
if (clicknum === 2)
{
sx = ex;
sy = ey;
}
oex = ex;
oey = ey;
}
function AlgStep()
{
minv=3072, xminv=-1, yminv = -1;
for (let i=0;i<60;i++)
{
for (let j=0;j<96;j++)
{
if ( (a[i][j] % 10 == 0) && (Math.floor(a[i][j]/10) % 10 == 1) )
{//поиск минимального значения среди открытых вершин не препятствий
let temp = Math.floor(a[i][j]/100);
if (temp<minv)
{
yminv = i;
xminv = j;
minv = temp;
}
}
}
}
if (minv === 3072)
{
return false;
}
let af0=Math.floor(a[yminv][xminv]/100);
let ah0 = Dist(xminv,ex,yminv,ey);
let ag0 = af0 - ah0;
//2. open/close для этой точки ищем соседей не препятствие у которых пересчитаваем массив
for (let i=-1;i<=1;i++)
{
for (let j=-1;j<=1;j++)
{
if ((xminv+j>=0)&&(xminv+j<96)&&(yminv+i>=0)&&(yminv+i<60)&&(i*i+j*j>0))
{
let temp = a[yminv+i][xminv+j];
if ( (Math.floor(temp/10) % 10 < 2) && (temp % 10 == 0) )
{
let incr = 10;
if (i*i+j*j==2)
{
incr = 14;
}
let agtmp = ag0 + incr;
let aftmp = agtmp + Dist(xminv+j,ex,yminv+i,ey);
if ( (temp==0)||(temp>aftmp*100) )
{
a[yminv+i][xminv+j] = aftmp*100+10+0;
}
}
}
}
}
a[yminv][xminv]+=10;
if ((yminv==ey)&&(xminv==ex))
{
return false;
}
return true;
}
function RecalcA()
{
for (let i=0;i<60;i++)
{
for (let j=0;j<96;j++)
{
if (a[i][j]==1)
{
a[i][j]=1;
}
else if (Math.floor(a[i][j]/10)%10<2)
{
a[i][j]=0;
}
else
{
a[i][j]=a[i][j]-100*Dist(ex,j,ey,i)+10000;
}
}
}
//Путешествуем с финиша, находим клетку где минимальное значение но с учетом равенства по соседям
way = [];
let tmp = {};
wx=ex;
wy=ey;
tmp.x = wx;
tmp.y = wy;
way.push(tmp);
}
function AlgStepWay()
{
let d = 0,itmp = 0, jtmp = 0, dtmp = 0;
for (let i=-1;i<=1;i++) for (let j=-1;j<=1;j++) if (i*i+j*j>0)
{
if (i*i+j*j==1) d=10;
if (i*i+j*j==2) d=14;
if ((a[wy+i][wx+j]>100)&&(a[wy+i][wx+j]+d*100==a[wy][wx])&&(dtmp<d))
{
itmp = i;
jtmp = j;
dtmp = d;
}
}
wx+=jtmp;
wy+=itmp;
let tmp = {};
tmp.x = wx;
tmp.y = wy;
way.push(tmp);
if ((wx==sx)&&(wy==sy))
return false;
else
return true;
}
canvas.onmousedown = function(e) {
let rect = this.getBoundingClientRect();
x = e.clientX - rect.left ;
y = e.clientY - rect.top ;
if (clicknum===1)
{
ex = Math.floor(step.grid(x));
ey = Math.floor(step.grid(y));
Calc();
}
// проверка запуска замедленного удаления пути и что удаление еще не началось, не допускает баг при повторном клике по карте в процессе перемещения
if(clicknum === 2 && delWay === false) {
launchDelWay();
}
};
// отрисовка объектов на канвасе ======================================================================================
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "rgb(255, 255, 0)";
// let x_min = Math.floor(viewport.x / 32);
// let y_min = Math.floor(viewport.y / 32);
// let x_max = Math.ceil((viewport.x + viewport.w) / 32);
// let y_max = Math.ceil((viewport.y + viewport.h) / 32);
// for (let i = y_min; i < y_max; i++) {
// for (let j = x_min; j < x_max; j++) {
// let tile = a[i][j]%10;
// let tileRow = (tile / imageNumTiles) | 0; // Операция "побитовое ИЛИ"
// let tileCol = (tile % imageNumTiles) | 0;
// let tileX = Math.floor(j * tileSize - viewport.x + 1024 * 0.5 - viewport.w * 0.5);
// let tileY = Math.floor(i * tileSize - viewport.y + 640 * 0.5 - viewport.h * 0.5);
// ctx.drawImage(backgraund, (tileCol * tileSize), (tileRow * tileSize), tileSize, tileSize, tileX, tileY, tileSize, tileSize);
// }
// }
for (let i = 0; i < 60; i++) {
for (let j = 0; j < 96; j++) {
let tile = a[i][j]%10;
let tileRow = (tile / imageNumTiles) | 0; // Операция "побитовое ИЛИ"
let tileCol = (tile % imageNumTiles) | 0;
ctx.drawImage(backgraund, (tileCol * tileSize), (tileRow * tileSize), tileSize, tileSize, j * tileSize, i * tileSize, tileSize, tileSize);
}
}
// viewport.scrollTo(sx*32, sy*32);
// если путь просчитан, то обрисовать желтым===================================================================
if ((way.length>0)&&(ex===oex)&&(ey===oey))
{
for (let i=0;i<way.length;i++) {
ctx.beginPath();
ctx.arc(way[i].x*32+16,way[i].y*32+16,7,0,Math.PI*2)
ctx.stroke();
ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
ctx.arc(way[i].x*32+16,way[i].y*32+16,5,0,Math.PI*2)
ctx.fill();
}
}
if (clicknum === 1) {ctx.drawImage(objectShip, sx*32, sy*32, 32, 32);}
else {ctx.drawImage(objectShip, delWay.x*32, delWay.y*32, 32, 32);}
};
// обновление объектов на канвасе ======================================================================================
const update = function() {
if (way.length == 0) {
clicknum = 1;
delWay = false;
}
};
// замедленное удаление массива с путем для корабля=====================================================================
const sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time))
}
launchDelWay = async () => {
// если в цикле вместо 10000 поставить way.length то проходит половину цикла хз почему, пришлось хитрить
for (let i = 0; i < 10000; i++) {
if(way.length === 0) {
return
}
await sleep(150)
delWay = way.pop();
}
}
// функция игрового цикла, замкнутая петля в канвас ============================================================
let game = function() {
render();
update();
requestAnimationFrame(game);
};
game();
#canvas {
display: block;
margin: 0 auto;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./style.css">
<title>Document</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="./script.js/astar.js"></script>
</body>
</html>