第一章 2D二维地图绘制、人物移动、障碍检测
第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示)
带大家回顾下第一章的内容。
游戏界面分2个区域,左边有小地图、英雄坐标、自动寻径路径,右边是大地图区域
x | x1 | x2 | |
---|---|---|---|
y | 0, 0 | 0,1 | 0,2 |
y1 | 1,0 | 1,1 | 1,2 |
y2 | 2,0 | 2,1 | 2,2 |
/**
* 加载地图数据
* 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 = [];
// 设定地图最大的高度
var maxRow = 7;
// 地图的行
var row = mapData.length > maxRow ? maxRow : mapData.length;
// 英雄与上下边框的距离
var heroMargin = Math.floor(row / 2);
//地图的列
var column = mapData[0].length;
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 += "";
for (var j = 0; j < column; j++) {
if (mapData[i][j] == item.empty) { //只有点击路,才能自动寻径
mapHTML += " + i + "," + j + ")'> ";
} else {
mapHTML += '+ items[mapData[i][j]] +'" style="width: 100%; height: 100%; border-radius: 0%;" > ';
}
}
mapHTML += " ";
}
// 渲染大地图
map.innerHTML = mapHTML;
//渲染小地图数据
var smallMapHTML = "";
for (var i = 0; i < mapData.length; i++) {
smallMapHTML += "";
for (var j = 0; j < column; j++) {
if (mapData[i][j] == item.empty) { //只有点击路,才能自动寻径
smallMapHTML += " + i + "," + j + ")'> ";
} else if (mapData[i][j] == item.stone) {
smallMapHTML += '+ items[mapData[i][j]] +'" style="width: 100%; height: 100%; border-radius: 0%;" > ';
} else if (mapData[i][j] == item.hero || mapData[i][j] == item.heroHasPath) {
smallMapHTML += ' ';
}
}
smallMapHTML += " ";
}
// 渲染小地图
smallMap.innerHTML = smallMapHTML;
// 渲染英雄坐标信息
heroPointElement.innerText = heroPoint[1] + " , " + heroPoint[0]
}
采用ChatGpt生成的JavaScript版本aStart算法,计算出从起点坐标到终点坐标的所有路径数据。其中需要用到曼哈顿距离、从起点到终点其中每步距离终点的代价计算
曼哈顿距离(有兴趣可以研究下,为:两点在南北方向上的距离加上在东西方向上的距离)
Math.abs(this.x - target.x) + Math.abs(this.y - target.y)
总代价(g + h)
g:累计移动代价(从起点到当前节点的实际代价)
h:启发式评估代价(当前节点到目标节点的估算代价)
f:总代价(g + h)
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
}
// 定时任务,每隔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、多种障碍物检测等多种玩法。