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

news2024/11/24 13:56:40

系列文章目录

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


文章目录

  • 系列文章目录
  • 前言
  • 一、本章节效果图
  • 二、介绍
    • 2.1、左边区域
    • 2.2、右边区域
  • 三、列计划
    • 3.1、目标
      • 3.1.1、完成跟随人物二维动态地图绘制(本期只完成高度动态)
      • 3.12、自动寻径
      • 3.13、小地图显示(人物红点显示)
    • 3.2、步骤
  • 四、实际作业流程
    • 4.1、固定画布高度
      • 4.1.1、地图绘制(地图数据、英雄初始数据、物品数据)
      • 4.1.2、设置地图最大高度、英雄与上下边框的距离
      • 4.1.3、根据人物英雄中心点位置,确定二维地图渲染的内容
    • 4.2、自动寻径
      • 4.2.1、采用ChatGpt生成的JavaScript版本aStart算法,计算出从起点坐标到终点坐标的所有路径数据
      • 4.2.2、通过JavaScript定时器每100毫秒去获取路径数组的头部数据(访问即删除),并实时更新地图信息(包括实时英雄坐标点、自动寻径数据)
  • 总结


前言

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

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

一、本章节效果图

请添加图片描述

二、介绍

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

2.1、左边区域

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/781276.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL知识2

十、数据库表的6种约束 建表同时添加约束&#xff1a; 主键约束和唯一&#xff1a; 默认值&#xff1a; 检查约束&#xff1a; 未能生效&#xff0c;原因&#xff1a;版本是5.7 在8.0之后&#xff1a;支持检查约束 主键自增可以写null&#xff0c;可以写default&#xff0c;可以…

音视频开发-ffmpeg介绍-系列一

目录 一.简介 FFmpeg框架的基本组成包含: 二. FFmpeg框架梳理音视频的流程​编辑 基本概念&#xff1a; 三.ffmpeg、ffplay、ffprobe区别 4.1 ffmpeg是用于转码的应用程序 4.2 fffplay是用于播放的应用程序 4.3 ffprobe是用于查看文件格式的应用程序 4.4 ffmpeg是用于转…

LocalDateTime、OffsetDateTime、ZonedDateTime之间的关系

什么是LocalDateTime&#xff1f; ISO-8601日历系统中不带时区的日期时间。 该类不存储时区&#xff0c;所以适合日期的描述&#xff0c;比如用于生日、deadline等等。 但是如果没有偏移量/时区等附加信息&#xff0c;一个时间是不能表示时间线上的某一时刻的。 什么是Offset…

【网络编程】网络套接字udp通用服务器和客户端

1.预备知识 认识端口号 端口号(port)是传输层协议的内容&#xff1a; 端口号是一个2字节16位的整数(uint16)端口号用来标识主机上的一个进程IP地址port能够标识网络上的某一台主机和某一个进程一个端口号只能被一个进程占用 认识TCP协议 此处我们先对TCP(Transmission Con…

day34-Animated Countdown(动画倒计时)

50 天学习 50 个项目 - HTMLCSS and JavaScript day34-Animated Countdown&#xff08;动画倒计时&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport&q…

自动驾驶感知系统-超声波雷达

超声波雷达&#xff0c;是通过发射并接收40kHz的超声波&#xff0c;根据时间差算出障碍物距离。其测距精度是1~3cm.常见的超声波雷达有两种&#xff1a;第一种是安装在汽车前后保险杠上的&#xff0c;用于测量汽车前后障碍物的驻车雷达或倒车雷达&#xff0c;称为超声波驻车辅助…

易班开放应用授权重定向,出现跨域的解决方案

问题描述 今天开发H5网站需要接入易班&#xff0c;经过易班授权然后重定向&#xff08;code: 302&#xff09;&#xff0c;使用axios发请求&#xff0c;但是前后端均配置跨域的情况下&#xff0c;不管怎么弄都是一直跨域 但是我们看network&#xff0c;network中对应请求的res…

nvm安装(win/linux)

win安装nvm Win安装nvm1、下载nvm2、直接安装nvm-setup.exe3、cmd运行查看安装情况 Linux安装nvm1、下载nvm安装包2、安装及配置环境变量3、查看安装情况 前端开发多个工程&#xff0c;node版本需要时不时的进行切换&#xff0c;如果重新下载安装nodejs会导致浪费很多无用的时间…

人群异常聚集检测告警算法 yolov5

人群异常聚集检测报警系统基于yolov5图像识别和数据分析技术&#xff0c;人群异常聚集检测告警算法通过在关键区域布设监控摄像头&#xff0c;实时监测人员的密集程度和行为动态,分析和判断人群密集程度是否超过预设阈值&#xff0c;一旦发现异常聚集&#xff0c;将自动发出信号…

Pytorch个人学习记录总结 03

目录 Transeforms的使用 常见的transforms Transeforms的使用 torchvision中的transeforms&#xff0c;主要是对图像进行变换&#xff08;预处理&#xff09;。from torchvision import transforms transeforms中常用的就是以下几种方法&#xff1a;&#xff08;Alt7可唤出…

FileNotFoundException:xxx(系统找不到指定的路径)

目录 前言 背景 解决方法 错误示例 前言 这次是有个两年前的项目吧&#xff0c;不知道为什么无法启动了。中间迭代了多个版本&#xff0c;现在另一个同事接手了&#xff0c;领导让看一下。因为时间间隔过长&#xff0c;问题处理比较费劲。其中有的是配置问题&#xff0c;比…

【Linux学习】超详细——进程(2)

三、进程状态 3.1 准备知识 进程阻塞&#xff1a;进程因为等待某种条件就绪&#xff0c;而导致的一种不推进的状态&#xff08;例如进程卡顿&#xff09;&#xff0c;因而阻塞一定是在等待某种资源。为什么阻塞&#xff1f;进程需要通过等待的方式&#xff0c;等具体的资源被别…

SpringCloudAlibaba微服务实战系列(二)Nacos配置中心

SpringCloudAlibaba Nacos配置中心 在java代码中或者在配置文件中写配置&#xff0c;是最不雅的&#xff0c;意味着每次修改配置都需要重新打包或者替换class文件。若放在远程的配置文件中&#xff0c;每次修改了配置后只需要重启一次服务即可。话不多说&#xff0c;直接干货拉…

java实现ffmpeg音频文件分割

项目中需要将视频会议中录入的音频文件通过阿里云语音识别为文件&#xff0c;但是阿里云语音识别对音频大小有限制&#xff0c;因此通过ffmpeg将大音频文件分割为几个短音频文件&#xff0c;并进行语音识别操作。 代码如下&#xff1a; package com.vion.utils; import java.i…

openGauss学习笔记-18 openGauss 简单数据管理-WHERE子句

文章目录 openGauss学习笔记-18 openGauss 简单数据管理-WHERE子句18.1 语法格式18.2 参数说明18.3 示例 openGauss学习笔记-18 openGauss 简单数据管理-WHERE子句 当我们需要根据指定条件从表中查询数据时&#xff0c;就可以在SELECT语句中添加WHERE子句&#xff0c;从而过滤…

Install Ansible on CentOS 8

环境准备&#xff1a; 1.至少俩台linux主机&#xff0c;一台是控制节点&#xff0c;一台是受控节点 2.控制节点和受控节点都需要安装Python36 3.控制节点需要安装ansible 4.控制节点需要获得受控节点的普通用户或root用户的权限&#xff0c;控制节点需要ssh客户端&#xff0c;…

24考研数据结构-——绪论

数据结构 引用文章第一章&#xff1a;绪论1.0 数据结构在学什么1.1 数据结构的基本概念1.2 数据结构的三要素1.3 算法的基本概念 引用文章 在此基础上增加自己的学习过程: 《王道》数据结构笔记整理2022 1.2数据结构三要素——逻辑结构和物理结构与数据运算之间的关系 1.3抽象…

JMeter+提取token变成全局变量

注&#xff1a;没打码&#xff0c;就代码乱写的接口&#xff0c;具体请按照你要跑的接口来输入值 一、创建线程组 二、配置HTTP请求默认值 IP地址一模一样&#xff0c;可以配置一个默认值&#xff0c;就不用每次都输入IP地址了 三、配置登陆ip 配置登陆地址&#xff0c;通过…

iOS 测试 iOS 端 Monkey 测试

说起 Monkey 测试&#xff0c;大家想到的是 monkey 测试只有安卓有&#xff0c;monkey 测试只针对安卓 app&#xff0c;今天给大家分享一下 Monkey 测试在 iOS 端也能跑&#xff01;iOS 端 app 也能使用 Monkey 测试来执行稳定性测试。 一、环境准备 1、准备 Mac 设备&#x…