[JavaScript游戏开发] 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示)

系列文章目录

第一章 2D二维地图绘制、人物移动、障碍检测
第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示)



前言

带大家回顾下第一章的内容。

  • 使用JavaScript绘制简单的二维地图
    采用二维数组存储地图信息,使用表格绘制地图,每个td单元格存储数据
  • 键盘上下左右控制
    使用JavaScript keyPress键盘事件监听WASD键,按键触发时人物做出相应操作
  • 障碍物碰撞检测(采用格子碰撞检测)
    人物下一步碰撞到石头时,提示遇到障碍,终止人物运动

一、本章节效果图

请添加图片描述

二、介绍

游戏界面分2个区域,左边有小地图、英雄坐标、自动寻径路径,右边是大地图区域
在这里插入图片描述

2.1、左边区域

  • 小地图
    最左边顶部区域,会等比缩放大地图数据,英雄格使用红点替代;
    点击小地图也会触发自动寻径
  • 英雄坐标
    显示英雄的实时坐标地址,包括
  • 自动寻径的坐标
    采用ChatGpt生成JavaScript版本aStart算法,里面存放的是从起点坐标到终点坐标的所有路径数据,是[[x,y],[x,y],[x,y]]这样的数据,坐标格式正好跟第一章里的坐标相反(因为要先渲染y轴,也就是tr数据)
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、右边区域

  • 大地图
    右边的大区域,渲染地图、英雄信息、障碍信息、英雄自动寻径路径、英雄移动地图动态跟随绘制等

三、列计划

3.1、目标

3.1.1、完成跟随人物二维动态地图绘制(本期只完成高度动态)

3.12、自动寻径

3.13、小地图显示(人物红点显示)

3.2、步骤

  • 固定画布高度(本期只完成高度动态)
    根据人物英雄中心点位置,确定二维地图渲染的内容
    需要判断上边距、下边距
  • 自动寻径
    采用ChatGpt生成的JavaScript版本aStart算法,计算出从起点坐标到终点坐标的所有路径数据,通过JavaScript定时器每100毫秒去获取路径数组的头部数据(访问即删除),并实时更新地图信息(包括实时英雄坐标点、自动寻径数据)
  • 小地图显示(人物红点显示)
    等比缩放大地图,英雄采用红点替代,小地图也可触发自动寻径

四、实际作业流程

4.1、固定画布高度

4.1.1、地图绘制(地图数据、英雄初始数据、物品数据)

         /**
         * 加载地图数据
         * 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、设置地图最大高度、英雄与上下边框的距离

		 // 设定地图最大的高度
        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、根据人物英雄中心点位置,确定二维地图渲染的内容

1、判断上边距
英雄的y坐标点,如果在头几行,地图从头开始加载
如果不在头几行,需要从 [英雄的y坐标点-向下取整(固定长度/2)] 开始加载数据
2、判断下边距
英雄的y坐标点,如果在尾部几行,地图从 [地图数据长度 - 固定距离] 到地图数据长度开始渲染数据
如果不在尾部几行,需要从 [英雄的y坐标点-向下取整(固定长度/2)] 开始加载数据
3、中间部分直接走[英雄的y坐标点-向下取整(固定长度/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、自动寻径

采用ChatGpt生成的JavaScript版本aStart算法,计算出从起点坐标到终点坐标的所有路径数据。其中需要用到曼哈顿距离、从起点到终点其中每步距离终点的代价计算

  • 曼哈顿距离(有兴趣可以研究下,为:两点在南北方向上的距离加上在东西方向上的距离)
    Math.abs(this.x - target.x) + Math.abs(this.y - target.y)

  • 总代价(g + h)
    g:累计移动代价(从起点到当前节点的实际代价)
    h:启发式评估代价(当前节点到目标节点的估算代价)
    f:总代价(g + h)
    在这里插入图片描述

4.2.1、采用ChatGpt生成的JavaScript版本aStart算法,计算出从起点坐标到终点坐标的所有路径数据


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、通过JavaScript定时器每100毫秒去获取路径数组的头部数据(访问即删除),并实时更新地图信息(包括实时英雄坐标点、自动寻径数据)

	// 定时任务,每隔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);

总结

以上就是今天要讲的内容,本文仅仅简单介绍了地图y轴的动态绘制、自动寻径、小地图显示,后续还有血量、陷阱、大礼包、随机空投大礼包、武器店+武器系统、护盾+防具系统、自动行走AI、多种障碍物检测等多种玩法。

猜你喜欢

转载自blog.csdn.net/s445320/article/details/131881734