[JavaScript game development] Follow the character 2D dynamic map drawing, automatic path finding, small map display (character red dot display)

Series Article Directory

Chapter 1 2D two-dimensional map drawing, character movement, obstacle detection
Chapter 2 Follow the character 2D dynamic map drawing, automatic path finding, small map display (character red dot display)



foreword

Take everyone to review the content of the first chapter.

  • Use JavaScript to draw a simple two-dimensional map
    Use a two-dimensional array to store map information, use a table to draw a map, and store data in each td cell
  • Keyboard up, down, left, and right controls
    Use JavaScript keyPress keyboard events to monitor WASD keys, and characters will perform corresponding operations when the keys are triggered
  • Obstacle collision detection (using grid collision detection)
    when the character collides with a stone in the next step, it prompts that it has encountered an obstacle and terminates the movement of the character

1. Renderings of this chapter

Please add a picture description

2. Introduction

The game interface is divided into two areas, the left side has a small map, hero coordinates, automatic path finding path, and the right side is a large map area
insert image description here

2.1. Left area

  • The leftmost top area of ​​the small map
    will scale the data of the big map proportionally, and the hero grid will be replaced by a red dot;
    clicking on the small map will also trigger automatic pathfinding
  • Hero coordinates
    Display the real-time coordinate address of the hero, including
  • The coordinates of automatic pathfinding
    use ChatGpt to generate the JavaScript version aStart algorithm, which stores all the path data from the starting point coordinates to the end point coordinates, which is [[x,y],[x,y],[x,y]] such data, the coordinate format is exactly the opposite of the coordinates in the first chapter (because the y axis must be rendered first, that is, the tr data)
x x1 x2
y 0, 0 0,1 0,2
y1 1,0 1,1 1,2
y2 2,0 2,1 2,2

2.2. Right area

  • Large map
    The large area on the right, rendering map, hero information, obstacle information, hero automatic path finding path, hero moving map dynamic following drawing, etc.

3. Column plan

3.1. Goals

3.1.1. Complete the drawing of the two-dimensional dynamic map following the characters (only high dynamics are completed in this issue)

3.12. Automatic path finding

3.13. Small map display (red dot display of characters)

3.2. Steps

  • Fix the height of the canvas (only complete the dynamic height in this issue)
    Determine the content of the two-dimensional map rendering according to the position of the center point of the character and the hero
    Need to judge the top and bottom margins
  • Automatic path finding
    uses the JavaScript version aStart algorithm generated by ChatGpt to calculate all the path data from the starting point coordinates to the end point coordinates, and uses the JavaScript timer to obtain the head data of the path array every 100 milliseconds (delete upon access), and update the map information in real time (including real-time hero coordinate points, automatic path-finding data)
  • Small map display (character red dot display)
    scales the large map proportionally, heroes are replaced by red dots, and the small map can also trigger automatic pathfinding

4. Actual operation process

4.1, fixed canvas height

4.1.1. Map drawing (map data, hero initial data, item data)

         /**
         * 加载地图数据
         * 0 空地/草坪
         * 1 石头
         * 9 英雄
         * @type {number[]}
         */
        var mapData = [
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1],
            [1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1],
            [1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 0, 1, 3, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1],
            [1, 1, 8, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 7, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 4, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 8, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 1, 0, 6, 7, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 5, 0, 0, 2, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 0, 6, 0, 1, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1],
            [1, 0, 7, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1],
            [1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        ]

        var item = {
    
    };
        item.empty = 0;   //空地或草坪
        item.stone = 1;   //石头的标记是1
        item.factory = 2; //工厂
        item.girl = 3;  //女子
        item.girl_01 = 4; //女孩
        item.kt = 5; //空投大礼包
        item.lz = 6; //路障
        item.pz = 7; //喷子
        item.zz = 8; //沼泽
        item.hero = 9;   //英雄的标记是9
        item.heroHasPath = 10;   //自动寻径的英雄标记是10

        var items = [];
		var itemPrefixPath = "../img/item/";
        items[0] = "";
        items[1] = itemPrefixPath + "stone.png";
        items[2] = itemPrefixPath + "gc.png";
        items[3] = itemPrefixPath + "girl.png";
        items[4] = itemPrefixPath + "girl.bmp";
        items[5] = itemPrefixPath + "kt.png";
        items[6] = itemPrefixPath + "lz.png";
        items[7] = itemPrefixPath + "pz.png";
        items[8] = itemPrefixPath + "zz.png";
        items[9] = itemPrefixPath + "/spine/hero002.gif";
        items[10] = itemPrefixPath + "/spine/tank.gif";

        var heroPoint = [1, 4];   //初始化英雄的位置是 1,4
        
   		// 自动寻径的路径
        var path = [];

4.1.2. Set the maximum height of the map, the distance between the hero and the upper and lower borders

		 // 设定地图最大的高度
        var maxRow = 7;
      
		// 地图的行
        var row = mapData.length > maxRow ? maxRow : mapData.length;
        
  		// 英雄与上下边框的距离
        var heroMargin = Math.floor(row / 2);
        
        //地图的列
        var column = mapData[0].length;
        

4.1.3. Determine the content of the two-dimensional map rendering according to the position of the center point of the character hero

1. Judging
the y coordinate point of the hero on the top margin, if it is in the first few lines, the map will be loaded from the beginning.
If it is not in the first few lines, it needs to start loading data from [the y coordinate point of the hero - round down (fixed length/2)] 2. Judging the y coordinate point of the hero in the bottom margin, if it is in the last few lines, the map will start rendering data from [map data length - fixed distance] to the map data length. Start loading data 3. Go directly to the middle part [the y coordinate point of the hero - round down (fixed length /
2 ) ]


	function loadData() {
    
    
       // 获取地图对象
       var map = document.getElementById("map1001");
       // 获取小地图
       var smallMap = document.getElementById("smallMap1001");
       // 英雄的坐标位置
       var heroPointElement = document.getElementById("heroPoint")

       // i的初始值
       // 判断上边距
       var nowI = heroPoint[0] - heroMargin > 0 ? heroPoint[0] - heroMargin : 0;

       if (heroPoint[0] + heroMargin > mapData.length - 1) {
    
    
           // 判断下边距
           nowI = heroPoint[0] + maxRow > mapData.length ? mapData.length - maxRow : nowI;
       }

       //渲染 row 行 column 列的数据
       var mapHTML = "";
       for (var i = nowI; i < nowI + row; i++) {
    
    
           mapHTML += "<tr>";
           for (var j = 0; j < column; j++) {
    
    
               if (mapData[i][j] == item.empty) {
    
       //只有点击路,才能自动寻径
                   mapHTML += "<td οnclick='tdClick(" + i + "," + j + ")'></td>";
               } else {
    
    
                   mapHTML += '<td><img src="'+ items[mapData[i][j]] +'" style="width: 100%; height: 100%; border-radius: 0%;" ></td>';
               }
           }
           mapHTML += "</tr>";
       }
       // 渲染大地图
       map.innerHTML = mapHTML;


       //渲染小地图数据
       var smallMapHTML = "";
       for (var i = 0; i < mapData.length; i++) {
    
    
           smallMapHTML += "<tr>";
           for (var j = 0; j < column; j++) {
    
    
               if (mapData[i][j] == item.empty) {
    
     //只有点击路,才能自动寻径
                   smallMapHTML += "<td οnclick='tdClick(" + i + "," + j + ")'></td>";
               } else if (mapData[i][j] == item.stone) {
    
    
                   smallMapHTML += '<td><img src="'+ items[mapData[i][j]] +'" style="width: 100%; height: 100%; border-radius: 0%;" ></td>';
               } else if (mapData[i][j] == item.hero || mapData[i][j] == item.heroHasPath) {
    
    
                   smallMapHTML += '<td><div style="background-color: red; border-radius: 50%;height: 50%; width: 50%;"></div></td>';
               }
           }
           smallMapHTML += "</tr>";
       }
       // 渲染小地图
       smallMap.innerHTML = smallMapHTML;

       // 渲染英雄坐标信息
       heroPointElement.innerText =  heroPoint[1] + " , " + heroPoint[0]
   }
        

4.2. Automatic path finding

Use the JavaScript version aStart algorithm generated by ChatGpt to calculate all path data from the starting point coordinates to the ending point coordinates. Among them, the Manhattan distance and the cost calculation of each step from the end point to the end point are required.

  • Manhattan distance (if you are interested, you can study it: the distance between two points in the north-south direction plus the distance in the east-west direction)
    Math.abs(this.x - target.x) + Math.abs(this.y - target.y)

  • Total cost (g + h)
    g: cumulative moving cost (the actual cost from the starting point to the current node)
    h: heuristic evaluation cost (the estimated cost from the current node to the target node)
    f: total cost (g + h)
    insert image description here

4.2.1. Use the JavaScript version aStart algorithm generated by ChatGpt to calculate all the path data from the starting point coordinates to the end point coordinates


class Node {
    
    
    constructor(x, y) {
    
    
        this.x = x;
        this.y = y;
        this.g = 0; // 累计移动代价(从起点到当前节点的实际代价)
        this.h = 0; // 启发式评估代价(当前节点到目标节点的估算代价)
        this.f = 0; // 总代价(g + h)
        this.parent = null; // 用于记录当前节点的父节点,构成路径
    }

    // 计算当前节点到目标节点的曼哈顿距离(启发式评估函数)
    calculateManhattanDistance(target) {
    
    
        return Math.abs(this.x - target.x) + Math.abs(this.y - target.y);
    }
}

function isValidNode(x, y, maze) {
    
    
    const numRows = maze.length;
    const numCols = maze[0].length;
    return x >= 0 && x < numRows && y >= 0 && y < numCols && maze[x][y] === 0;
}

function findMinCostNode(openSet) {
    
    
    let minCostNode = openSet[0];
    for (const node of openSet) {
    
    
        if (node.f < minCostNode.f) {
    
    
            minCostNode = node;
        }
    }
    return minCostNode;
}

function reconstructPath(currentNode) {
    
    
    const path = [];
    while (currentNode !== null) {
    
    
        path.unshift([currentNode.x, currentNode.y]);
        currentNode = currentNode.parent;
    }
    return path;
}

function aStar(maze, start, end) {
    
    
    const numRows = maze.length;
    const numCols = maze[0].length;

    // 创建起始节点和目标节点
    const startNode = new Node(start[0], start[1]);
    const endNode = new Node(end[0], end[1]);

    const openSet = [startNode]; // 待探索节点集合
    const closedSet = []; // 已探索节点集合

    while (openSet.length > 0) {
    
    
        // 从待探索节点集合中选择F值最小的节点
        const currentNode = findMinCostNode(openSet);
        if (currentNode.x === endNode.x && currentNode.y === endNode.y) {
    
    
            return reconstructPath(currentNode);
        }

        // 将当前节点从待探索节点集合移除,并添加到已探索节点集合
        openSet.splice(openSet.indexOf(currentNode), 1);
        closedSet.push(currentNode);

        // 探索当前节点的邻居节点
        const neighbors = [
            [currentNode.x - 1, currentNode.y],
            [currentNode.x + 1, currentNode.y],
            [currentNode.x, currentNode.y - 1],
            [currentNode.x, currentNode.y + 1],
        ];

        for (const [nx, ny] of neighbors) {
    
    
            if (isValidNode(nx, ny, maze)) {
    
    
                const neighborNode = new Node(nx, ny);
                if (closedSet.some((node) => node.x === neighborNode.x && node.y === neighborNode.y)) {
    
    
                    continue;
                }

                const tentativeG = currentNode.g + 1; // 假设移动代价为1(每个格子的代价都为1)
                if (!openSet.includes(neighborNode) || tentativeG < neighborNode.g) {
    
    
                    neighborNode.g = tentativeG;
                    neighborNode.h = neighborNode.calculateManhattanDistance(endNode);
                    neighborNode.f = neighborNode.g + neighborNode.h;
                    neighborNode.parent = currentNode;

                    if (!openSet.includes(neighborNode)) {
    
    
                        openSet.push(neighborNode);
                    }
                }
            }
        }
    }

    return null; // 如果无法找到路径,则返回null
}

4.2.2. Use the JavaScript timer to obtain the header data of the path array every 100 milliseconds (delete upon access), and update the map information in real time (including real-time hero coordinate points and automatic path-finding data)

	// 定时任务,每隔100毫秒绘制地图
    var timer = setInterval(function () {
    
    
        if (path.length > 0) {
    
    
            // 如果只有一个点了,证明已经到目标点了,清除自动寻径的路径
            if (path.length == 1) {
    
    
                mapData[heroPoint[0]][heroPoint[1]] = item.hero;
                path = [];
                console.log("已抵达目的")
            } else {
    
    
                //清除当前点的英雄
                mapData[path[0][0]][path[0][1]] = item.empty;
                //把英雄放置到下一个点
                mapData[path[1][0]][path[1][1]] = item.heroHasPath;

                //队头出栈
                path.splice(0, 1);
                 //设置当前英雄坐标
                heroPoint = path[0];
                
                console.log("绘制路径");
            }
            document.getElementById("autoPath").innerText = JSON.stringify(path)
            // 重新绘制地图数据
            loadData();
        }
    }, 100);

Summarize

The above is what I want to talk about today. This article only briefly introduces the dynamic drawing of the y-axis of the map, automatic pathfinding, and small map display. There will also be blood volume, traps, gift packs, random airdrop gift packs, weapon shop + weapon system, shield + armor system, automatic walking AI, and various obstacle detection.

Guess you like

Origin blog.csdn.net/s445320/article/details/131881734