地图之战争迷雾/地图算法/自动导航(一)

news2024/11/27 12:54:17

战争迷雾

TiledMap 创建黑色覆盖块,然后使用碰撞组件,控制黑色块的显示和隐藏

地图算法

在有些游戏中,地图需要随机生成,比如游戏中的迷宫等,这就需要地图生成的算法;在角色扮演类游戏中,角色需要在地图中找到一条合适的路径,这就需要寻路算法,最常用的寻路算法就是A星路径搜索算法

Roguelike算法(地图生成)

Roguelike是角色扮演游戏(RPG)的一个子类(Roguelike-RPG),其原型——《Rogue》是20世纪80年代初,由Michael Toy和Glenn Wichman两位软件工程师共同在UNIX系统上开发,并在大型机上运行的游戏,Roguelike是角色扮演游戏(RPG)的一个子类(Roguelike-RPG),其原型——《Rogue》是20世纪80年代初,由Michael Toy和Glenn Wichman两位软件工程师共同在UNIX系统上开发,并在大型机上运行的游戏,在2008年的国际Roguelike发展会议上Roguelike游戏有了明确的定义,它的特点包括:

1)生成随机性。每一次新开局游戏都会随机生成游戏场景、敌人、宝物等不同事物。这样玩家的每一次冒险历程也都将是独一无二,不可复制的。

2)进程单向性。存档功能的唯一作用就是记录你当前的游戏进度,每当存档被读取时,对应的进度就会被清空,直到你进行下一次存档。

3)不可挽回性。在大多数Roguelike游戏中,每一个角色只有一次生命,一个角色的死亡意味着玩家将永远失去该角色。无论你是主角、敌人、物品还是场景。在很多玩家眼中,这正是Roguelike的乐趣所在。

4)游戏非线性。严谨而不失灵活性的游戏规则,使游戏具备了很高的自由度,在这类游戏中,玩家可以发挥想象力,利用各种方法实现任何他们想做的事情,或合乎常理,或匪夷所思,目的只在于解决他们在游戏中遇到的问题。

5)系统复杂性。可能会在一款游戏中包括多到无法估量的元素,例如地质、气候和生物分布,以及精细到皮肤、肌肉、血液、骨骼和脂肪的战斗系统,甚至战损痊愈后会留下伤疤以及后遗症。在有些游戏里则可能包括数百种的死亡原因,数千种的生物,数万种的物品。

Roguelike 地图生成算法

地图生成算法是这样一个黑盒,它需要你输入地图的限制规则和大小等信息,它的输出是具体的地图数据,具体到Roguelike游戏的地图生成算法,它有如下特点:

1)要同时有开放的房间和走廊,房间在Roguelike游戏中起着至关重要的作用,开放的空间可以让玩家有空间进行战斗,同时房间也可以通过不同的装饰风格来增强游戏场景的表现力。同时,这个地牢不应该完全由房间组成,玩家需要在游戏过程中有不同的感受,走廊会让他们有封闭感,同时增加游戏的策略性。

2)地图生成中部分参数是可调的,由于关卡的难度要有梯度,所以生成规则应该是可调的,理想的做法是将生成器的一些参数设置成可调,可以通过同一套代码生成不同风格和感觉的地牢。

3)地图不是完美的,完美的地图意味着两点之间只有唯一的一条通路,这样玩起来缺少乐趣。当玩家遇到一个死胡同的时候,必须要回溯到之前的路线去,然后寻找新的可探索的地方。游戏是一个决定和做出不同选择的过程。因此,过于完美的地图不能让游戏变得更有趣。

生成地图的具体步骤包括:

1)随机生成房间,保证房间之间不相互覆盖。

2)计算如何连接各个房间。

3)把房间之外的空地用迷宫填满,移除掉死胡同。

4)连接相连的迷宫和房间,增加少量连接。首先是生成房间,这个过程需要注意的是要检查房间之间不相互重叠,每一个房间的生成过程包括随机生成房间左下角坐标和尺寸,判断重叠与否,创建房间。需要注意的是横纵方向个数要保证为奇数。然后是生成迷宫,生成迷宫的过程可以抽象为树的生成,生成的过程为连接每一个节点。首先判断起点上下左右是否在一个方向有连接,不存在就需要将节点放入列表中,如果第一步完成后列表不为空,则将节点向列表中的某一个方向移动两格并将移动后的坐标压入栈中,重复第一步。如果列表为空,则弹出栈顶元素,直到栈为空时。对于每一个走廊的块,如果其四个方向中有3个为空,则把它删除,就可以移除死胡同了。连接迷宫和房间,需要把每个区域联系起来,首先随机找到一个点,连通合并两个区域,然后删除p以外所有能连通两个区域的点,继续第一步,直到所有区域连通,为所有连通区域创建走廊,使所有房间可以连通。

实现

1、地图参数初始化
        //计算房间数量范围
        calculateRoomSize(size, cell)
        {
            var max = Math.floor((size/cell) * 0.8);
            var min = Math.floor((size/cell) * 0.25);
            if (min < 2) {
                min = 2;
            }
            if (max < 2) {
                max = 2;
            }
            return [min, max];
        },
        //初始化地图
        initMap()
        {
            //地图宽高的格子
            this._width = 96
            this._height = 64
            this._options = {
                cellWidth: 10,     //单元格宽
                cellHeight: 10,    //单元格高
                roomWidth: [2,10], //房间个数范围
                roomHeight: [2,7], //房间个数范围};
            if (! this._options.hasOwnProperty("roomWidth")) {
                this._options["roomWidth"] = this.calculateRoomSize(
                    this._width, this._options["cellWidth"]);
            }
                if (! this._options.hasOwnProperty("roomHeight")) {
                    this._options["roomHeight"]=this.calculateRoomSize(
                        this._height, this._options["cellHeight"]);
                }
        },
        //入口函数
        onLoad()
        {
                //初始化
                this.initMap()
                //地图生成
                this.mapGenerate()
                //绘制地图
                this.drawMap()
        },

地图生成主要根据如上三步进行,每一步都有一些需要注意的地方。当然,在做这些之前,首先需要进行地图数据的初始化,在这里首先初始化map数组,这是一个二维数组。用来存储最后地图表示的数据。首先把这个数组中的每一个值都初始化为0,也就是所有的位置都是空地,然后初始化房间和房间联通的数组

2、地图的初始化和生成步骤
        //设置map数值,初始化为一个值
        fillMap(value) {
            var map = [];
            for (var i = 0; i < this._width; i ++){
                  map.push([]);
                  for (var j = 0; j < this._height; j ++)
                  {
                      map[i].push(value);
                }
            }
            return map;
        },
        //初始化房间的数量
        initRooms() {
            for (var i = 0; i < this._options.cellWidth; i++) {
                  this.rooms.push([]);
                  for(var j = 0; j < this._options.cellHeight; j++) {
                      this.rooms[i].push({"x":0, "y":0, "width":0, "height":0,
                      "connections":[], "cellx":i, "celly":j});
                }
            }
        },
        //地图生成过程
        mapGenerate(){
            this.map = this.fillMap(0); //初始化地图数据
            this.rooms = [];              //房间
            this.connectedCells = [];    //连通的房间
            //初始化房间
            this.initRooms()
            //连接房间
            this.connectRooms()
            this.connectUnconnectedRooms()
            //创建房间
            this.createRooms()
            //创建走廊
            this.createCorridors()
        },

在initRooms函数里,只是初始化了房间数组,并没有创建房间的数据。生成房间的过程从connectRooms开始,首先连接房间,然后遍历一下房间,看看有没有“被遗忘”的角落,一定要确保所有房间都是连通的,这样才能避免死角的出现,最后才生成房间的数据,调用createRooms生成房间数据

3、生成房间和连接房间
        //创建房间
        createRooms() {
            var w = this._width;
            var h = this._height;

            var cw = this._options.cellWidth;
            var ch = this._options.cellHeight;

            var cwp = Math.floor(this._width / cw);
            var chp = Math.floor(this._height / ch);
            //房间属性
            var roomw;
            var roomh;
            var roomWidth = this._options["roomWidth"];
            var roomHeight = this._options["roomHeight"];
            var sx;
            var sy;
            var otherRoom;
            //遍历房间中每一个点
            for (var i = 0; i < cw; i++) {
                for (var j = 0; j < ch; j++) {
                      sx = cwp * i;
                      sy = chp * j;

                    if (sx == 0) {
                        sx = 1;
                    }
                    if (sy == 0) {
                        sy = 1;
                    }
                    //房间宽高,随机获得
                    roomw = GlobalHandle.getRandomInt(roomWidth[0], roomWidth[1]);
                    roomh = GlobalHandle.getRandomInt(roomHeight[0], roomHeight[1]);
                    if (j > 0) {
                        otherRoom = this.rooms[i][j-1];
                        while (sy - (otherRoom["y"] + otherRoom["height"] ) < 3) {
                            sy++;
                        }
                    }
                    if (i > 0) {
                        otherRoom = this.rooms[i-1][j];
                        while(sx - (otherRoom["x"] + otherRoom["width"]) < 3) {
                            sx++;
                        }
                    }
                    var sxOffset = Math.round(GlobalHandle.getRandomInt(
                    0, cwp - roomw)/2);
                    var syOffset = Math.round(GlobalHandle.getRandomInt(
                    0, chp - roomh)/2);
                    while (sx + sxOffset + roomw >= w) {
                        if(sxOffset) {
                            sxOffset--;
                        } else {
                            roomw--;
                        }
                    }
                    while (sy + syOffset + roomh >= h) {
                        if(syOffset) {
                            syOffset--;
                        } else {
                              roomh--;
                        }
                    }
                    sx = sx + sxOffset;
                    sy = sy + syOffset;
                    this.rooms[i][j]["x"] = sx;
                    this.rooms[i][j]["y"] = sy;
                    this.rooms[i][j]["width"] = roomw;
                    this.rooms[i][j]["height"] = roomh;
                    //设置地图
                    for (var ii = sx; ii < sx + roomw; ii++) {
                          for (var jj = sy; jj < sy + roomh; jj++) {
                                this.map[ii][jj] = 1;
                        }
                    }
                }
            }
        },

地图生成结果

小方块组成的即“房间”。可以发现,房间之间都互相独立,并不能互相连通,这就是后续我们要做的,即生成走廊。首先在数据上,参考之前生成的房间连通数据,生成走廊,随后在绘制走廊的函数里更新map数据。

        //绘制走廊,设置map值
        drawCorridor(startPosition, endPosition) {
            var xOffset = endPosition[0] - startPosition[0];
            var yOffset = endPosition[1] - startPosition[1];

            var xpos = startPosition[0];
            var ypos = startPosition[1];

            var tempDist;
            var xDir;
            var yDir;

            var move;
            var moves = [];

            var xAbs = Math.abs(xOffset);
            var yAbs = Math.abs(yOffset);

            var percent = Math.random();
            var firstHalf = percent;
            var secondHalf = 1- percent;

            xDir = xOffset > 0 ? 2 : 6;
            yDir = yOffset > 0 ? 4 : 0;

            if (xAbs < yAbs) {
                tempDist = Math.ceil(yAbs * firstHalf);
                moves.push([yDir, tempDist]);
                moves.push([xDir, xAbs]);
                tempDist = Math.floor(yAbs * secondHalf);
                moves.push([yDir, tempDist]);
            } else {
                tempDist = Math.ceil(xAbs * firstHalf);
                moves.push([xDir, tempDist]);
                moves.push([yDir, yAbs]);
                tempDist = Math.floor(xAbs * secondHalf);
                moves.push([xDir, tempDist]);
            }

            this.map[xpos][ypos] = 2;

            while (moves.length > 0) {
                move = moves.pop();
                while (move[1] > 0) {
                    xpos += GlobalHandle.DIRS[8][move[0]][0];
                    ypos += GlobalHandle.DIRS[8][move[0]][1];
                    this.map[xpos][ypos] = 2;
                    move[1] = move[1] -1;
                }
            }
        },
        createCorridors() {
            //创建走廊
            var cw = this._options.cellWidth;
            var ch = this._options.cellHeight;
            var room;
            var connection;
            var otherRoom;
            var wall;
            var otherWall;
            for (var i = 0; i < cw; i++) {
                for (var j = 0; j < ch; j++) {
                    room = this.rooms[i][j];
                    for (var k = 0; k < room["connections"].length; k++) {
                        connection = room["connections"][k];
                        otherRoom = this.rooms[connection[0]][connection[1]];
                        //获得墙体数量
                        if (otherRoom["cellx"] > room["cellx"]) {
                            wall = 2;
                            otherWall = 4;
                        } else if (otherRoom["cellx"] < room["cellx"]) {
                            wall = 4;
                            otherWall = 2;
                        } else if(otherRoom["celly"] > room["celly"]) {
                            wall = 3;
                            otherWall = 1;
                        } else if(otherRoom["celly"] < room["celly"]) {
                            wall = 1;
                            otherWall = 3;
                        }
                        this.drawCorridor(this.getWallPosition(room, wall),
                        this.getWallPosition(otherRoom, otherWall));
                    }
                }
            }
        },

生成地图数据后,接下来的任务就是把这个地图绘制出来,在drawMap中绘制,根据map里每个元素值对应的不同类型渲染不同颜色的方块。

        //绘制地图
        drawMap()
        {
            for (var i = 0; i < this._width; i ++){
                for (var j = 0; j < this._height; j ++) {
                    if(this.map[i][j] == 1){ //房间地图格
                        var ctx = this.mapLayer.getComponent(cc.Graphics)
                        ctx.fillColor.fromHEX('#FF0000');
                        ctx.rect((i) * this._options.cellWidth, (j) *
                        this ._options.cellHeight, this._options.cellWidth, this._options.
                            cellHeight)
                        ctx.fill()
                        ctx.stroke()
                    }
                    else if(this.map[i][j] == 2){ //门口地图格
                        var ctx = this.mapLayer.getComponent(cc.Graphics)
                        ctx.fillColor.fromHEX('#7B68EE');
                        ctx.rect((i)  *  this._options.cellWidth, (j)  *  this._options.
                            cellHeight, this._options.cellWidth, this._options.cellHeight)
                        ctx.fill()
                    }
                    else if(this.map[i][j] == 3) {//走廊地图格
                        var ctx = this.mapLayer.getComponent(cc.Graphics)
                        ctx.fillColor.fromHEX('#00FF00');
                        ctx.rect((i)  *  this._options.cellWidth, (j)  *  this._options.
                            cellHeight, this._options.cellWidth, this._options.cellHeight)
                        ctx.fill()
                    }
                }
            }
        },

结果

A星算法

,A星搜索算法用来实现敌人的智能运动,比如敌人巡逻或者角色寻径

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

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

相关文章

【AIGC】基于大模型+知识库的Code Review实践

一、背景描述 一句话介绍就是&#xff1a;基于开源大模型 知识库的 Code Review 实践&#xff0c;类似一个代码评审助手&#xff08;CR Copilot&#xff09;。信息安全合规问题&#xff1a;公司内代码直接调 ChatGPT / Claude 会有安全/合规问题&#xff0c;为了使用 ChatGPT…

华为云服务器-云容器引擎 CCE环境构建及项目部署

1、切换地区 2、搜索云容器引擎 CCE 3、购买集群 4、创建容器节点 通过漫长的等待(五分钟左右)&#xff0c;由创建中变为运行中&#xff0c;则表明容器已经搭建成功 购买成功后&#xff0c;返回容器控制台界面 5、节点容器管理 6、创建redis工作负载 7、创建mysql工作负载 8、…

C#使用GDI对一个矩形进行任意角度旋转

C#对一个矩形进行旋转GDI绘图&#xff0c;可以指定任意角度进行旋转 我们可以认为一张图片Image&#xff0c;本质就是一个矩形Rectangle,旋转矩形也就是旋转图片 在画图密封类 System.Drawing.Graphics中&#xff0c; 矩形旋转的两个关键方法 //设置旋转的中心点 public v…

MySQL-相关日志

官方文档 1、MySQL支持的日志 MySQL有不同类型日志文件&#xff0c;用来存储不同类型的日志&#xff0c;分别为 二进制日志、错误日志、通用查询日志、慢查询日志、中继日志、数据定义语句日志 慢查询日志&#xff1a;记录所有执行时间超过 long_query_time的所有查询&#xf…

单元测试覆盖率

什么是单元测试覆盖率 关于其定义&#xff0c;先来看一下维基百科上的一段描述&#xff1a; 代码覆盖&#xff08;Code coverage&#xff09;是软件测试中的一种度量&#xff0c;描述程序中源代码被测试的比例和程度&#xff0c;所得比例称为代码覆盖率。 简单来理解&#xff…

【微信小程序】事件绑定和事件对象

文章目录 1.什么是事件绑定2.button组件3.事件绑定4.input组件 1.什么是事件绑定 小程序中绑定事件与在网页开发中绑定事件几乎一致&#xff0c;只不过在小程序不能通过on的方式绑定事件&#xff0c;也没有click等事件&#xff0c;小程序中 绑定事件使用bind方法&#xff0c;c…

反转链表 (oj题)

一、题目链接 https://leetcode.cn/problems/reverse-linked-list/submissions/538124207 二、题目思路 1.定义三个指针&#xff0c;p1先指向NULL p2指向头结点 p3指向第二个结点 2.p2的next指向p1。然后移动指针&#xff0c;p1来到p2的位置&#xff0c;p2来到p3的位置&…

双列集合基础知识

package exercise;import java.util.HashMap; import java.util.Map;public class Demo1 {public static void main(String[] args) {Map<String, String> map new HashMap<>();//在添加数据的时候&#xff0c;如果键不存在&#xff0c;那么直接把键值对对象添加到…

小主机折腾记25

10.买了惠普光驱&#xff0c;想给880g5twr安装上&#xff0c;结果发现卡扣不对 880g5twr的卡扣更长一些&#xff0c;比光驱本身长一些&#xff0c;各位如果想买的注意擦亮眼睛&#xff0c;看看卡扣跟你的主机一致与否 后续在闲鱼上买了个卡扣&#xff0c;加邮费12块钱…… 1…

手搓文件格式转换

最初目标&#xff1a; 自己搞一个免费的pdf文件转换 根据现有的开源jar 项目实现思路&#xff1a; 1. 项目原因a. 我想转换文件b. wps 文件转换 2. 最初的状态a. jar运行的b. main,输入文件路径c. 一定的编程能力的人才能得 3. 开始构思项目a. 网页版本b. 想着大家一起用 4. …

Go 编程风格指南 - 最佳实践

Go 编程风格指南 - 最佳实践 原文&#xff1a;https://google.github.io/styleguide/go 概述 | 风格指南 | 风格决策 | 最佳实践 注意&#xff1a; 本文是 Google Go 风格 系列文档的一部分。本文档是 规范性(normative) 但不是强制规范(canonical)&#xff0c;并且从属于Goo…

2003NOIP普及组真题 3. 数字游戏

线上OJ 地址&#xff1a; 【03NOIP普及组】数字游戏 此题考察的是 区间DP 前缀和 核心思想&#xff1a; 1、这道题主要考查了动态规划的思想。通过分析题目&#xff0c;可以发现需要 枚举环上所有划分为m组 的不同方案&#xff0c;来求得最大或最小值。属于 环上动态规划 问…

IEDA 默认集成依赖概述

IEDA 默认集成依赖概述 目录概述需求&#xff1a; 设计思路实现思路分析 1.Developer Tools:GraalVM Native supportGraphQL DGs Code GenerationSpring Boot DevToolsLombokSpring Configuration ProcessorDocker Compose supportSpring Modulith 2.WebWebSpring WebSpring Re…

安卓约束性布局学习

据说这个布局是为了解决各种布局过度前套导致代码复杂的问题的。 我想按照自己想实现的各种效果来逐步学习&#xff0c;那么直接拿微信主页来练手&#xff0c;用约束性布局实现微信首页吧。 先上图 先实现顶部搜索框加号按钮 先实现 在布局中添加一个组件&#xff0c;然后摆放…

java web:springboot mysql开发的一套家政预约上门服务系统源码:家政上门服务系统的运行流程

java web&#xff1a;springboot mysql开发的一套家政预约上门服务系统源码&#xff1a;家政上门服务系统的运行流程 家政上门服务系统的优势 服务质量更稳定&#xff1a;由专业的家政人员提供服务&#xff0c;经过严格的培训和筛选。 价格更透明&#xff1a;采用套餐式收费&…

java的核心机制:JVM

JVM&#xff08;java virtual machine&#xff0c;java虚拟机&#xff09;&#xff1a;是一个虚拟的计算机&#xff0c;是java程序的运行环境。JVM具有指令集并使用不同的存储区域&#xff0c;负责执行指令&#xff0c;管理数据、内存、寄存器。 JVM功能1&#xff1a;实现java程…

以sqlilabs靶场为例,讲解SQL注入攻击原理【54-65关】

【Less-54】 与前面的题目不同是&#xff0c;这里只能提交10次&#xff0c;一旦提交超过十次&#xff0c;数据会重新刷新&#xff0c;所有的步骤需要重来一次。 解题步骤&#xff1a; 根据测试&#xff0c;使用的是单引号闭合。 # 判断字段的数量 ?id1 order by 3 -- aaa# …

力扣 有效的括号 栈

Problem: 20. 有效的括号 文章目录 思路复杂度&#x1f49d; Code 思路 &#x1f468;‍&#x1f3eb; 参考地址 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) &#x1f49d; Code class Solution {static Map<Character, Character> m…

香橙派 Orange AIpro 测评记录视频硬件解码

香橙派 Orange AIpro 测评记录视频硬件解码 香橙派官网&#xff1a;http://www.orangepi.cn/ 收到了一块Orange Pi AIpro开发板&#xff0c;记录一下我的测评~测评简介如下&#xff1a;1.连接网络2.安装流媒体进行硬件解码测试3.安装IO测试 简介 Orange Pi AI Pro 是香橙派联合…

i.MX8MP平台开发分享(RDC软件配置篇)

Uboot中已经将RDC的配置写入到了OCRAM中&#xff0c;NXP在ATF中预设了SIP服务&#xff0c;SIP服务下有厂商自定义的smc命令ID。例如下面的DDR、GPC、SRC和HAB的smc回调函数。 在SRC中断处理函数中&#xff0c;对于SRC_M4_START指令&#xff0c;先读取OCRAM中的配置&#xff0c;…