Как привязать камеру к игроку в алгаритме А* на чистом 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>


Ответы (0 шт):