需求是做史莱姆传奇手游的挖矿玩法,一个主要难点是地图的配置和生成,这是一篇尝试使用gas解决的记录

一、

44291-xvukoa6cpb.png

首先定义好基础地格类型、奖励类型、地格生成权重等参数

这里为了插入图片方便,随便找了个免费的图床,使用时会频繁读取、加载,效果一般,应该可以调用googleDrive内文件来优化
对于权重的算法是之前钓鱼小项目里直接从gpt拿来的

随机生成的实现很简单,而生成后的效果对于数值策划来说需要有一个准确的期望,以此来裁定基础道具的产出等。于是需要引入一个带权重的寻路算法。大致了解一下感觉 A 要比 dijkstra 好一些(都是启发式算法,没求甚解,只记得大学时候光学过后者,A第一次知道还是透过畅游的实习生同事的毕业论文- -)

依旧是gpt产出代码如下:

class Grid {
  constructor(width, height) {
    this.width = width;
    this.height = height;
    this.grid = new Array(width);
    for (let i = 0; i < height; i++) {
      this.grid[i] = new Array(width);
      for (let j = 0; j < width; j++) {
        this.grid[i][j] = new Node(i, j,0);
      }
    }
  }
  
  getNode(x, y) {
    return this.grid[x][y];
  }
}

// 创建一个表示节点的类
class Node {
  constructor(x, y,weight) {
    this.x = x;
    this.y = y;
    this.weight=weight;
    this.g = 0; // 从起点到该节点的移动代价
    this.h = 0; // 从该节点到目标节点的估算代价(启发式函数)
    this.f = 0; // 总代价,f = g + h
    this.parent = null; // 用于回溯路径
  }
}

// A* 寻路算法
function astar(startX, startY, targetX, targetY, grid) {
  const openList = [];
  const closedList = [];
  
  const startNode = grid.getNode(startX, startY);
  const targetNode = grid.getNode(targetX, targetY);
  
  openList.push(startNode);
  
  while (openList.length > 0) {
    // 在 openList 中找到 f 值最小的节点
    let currentNode = openList[0];
    let currentIndex = 0;
    for (let i = 1; i < openList.length; i++) {
      if (openList[i].f < currentNode.f) {
        currentNode = openList[i];
        currentIndex = i;
      }
    }
    
    // 将当前节点从 openList 中移除,并添加到 closedList 中
    openList.splice(currentIndex, 1);
    closedList.push(currentNode);
    
    // 找到目标节点,构建路径并返回
    if (currentNode === targetNode) {
      const path = [];
      let current = currentNode;
      while (current !== null) {
        path.push(current);
        current = current.parent;
      }
      return path.reverse();
    }
    
    // 遍历当前节点的邻居节点
    const neighbors = getNeighbors(currentNode, grid);
    for (let i = 0; i < neighbors.length; i++) {
      const neighbor = neighbors[i];
      
      // 如果邻居节点已经在 closedList 中,则忽略
      if (closedList.includes(neighbor)) {
        continue;
      }
      
      // 计算邻居节点的新的移动代价
      const gScore = currentNode.g + neighborWeight(neighbor);
      
      // 如果邻居节点不在 openList 中,则添加进去
      if (!openList.includes(neighbor)) {
        openList.push(neighbor);
      } else if (gScore >= neighbor.g) {
        // 如果邻居节点已经有更好的路径,则忽略当前路径
        continue;
      }
      
      // 更新邻居节点的代价和父节点
      neighbor.g = gScore;
      neighbor.h = heuristic(neighbor, targetNode);
      neighbor.f = neighbor.g + neighbor.h;
      neighbor.parent = currentNode;
    }
  }
  
  // 没有找到路径
  return [];
}

// 获取一个节点的邻居节点(上、下、左、右)
function getNeighbors(node, grid) {
  const neighbors = [];
  const { x, y } = node;
  
  if (x > 0) {
    neighbors.push(grid.getNode(x - 1, y));
  }
  if (x < grid.width - 1) {
    neighbors.push(grid.getNode(x + 1, y));
  }
  if (y > 0) {
    neighbors.push(grid.getNode(x, y - 1));
  }
  if (y < grid.height - 1) {
    neighbors.push(grid.getNode(x, y + 1));
  }
  
  return neighbors;
}

// 计算节点之间的启发式估算代价(曼哈顿距离)
function heuristic(node, targetNode) {
  const dx = Math.abs(node.x - targetNode.x);
  const dy = Math.abs(node.y - targetNode.y);
  return dx + dy;
}

// 获取节点的权重(示例中使用随机数作为权重)
function neighborWeight(node) {
  return node.weight;
}

简单的调试,然后加入赋予权重的方法就可以使用了,最终效果如下图:

50098-37rtqyytzlj.png

到此部前期的调研就结束了,基本可以确定:使用纯随机+手动修改部分区域是可以实现地图需求的。

二、

然而。。到了实际开发时需要面对配置导出的诸多问题,无疑还是得回归vba

直接呼唤gpt要求全部给我转成vba代码!

代码转换完出现的一个棘手问题是:vba对于类的支持不如js这么方便,模块之间的调用很多阻碍。而尝试使用自定义结构(type)定义同样是各种报错。

跟ai和excel斗智斗勇俩小时后果断决定放弃,性价比太低了,而且gsheet的脚本跟vba并不冲突啊!于是删掉所有a*寻路代码,开始专注配置。

分析得到以下需求:

  • 能够随机生成地格、奖励,并“序列化”到表格内
  • 能够操作修改已生成的地图内容
  • 能够反序列化,将修改后的地图导出为配置

因为表格作为存储方式很方便,所以没有抽象类型或数组来存储数据,单元格颜色即用来标识类型、内容文本用来标识奖励类型,配置表格如下:

38807-nq29brc8n2b.png

01606-nnrfdo9fcu.png

地格方面,因为装饰格比较特殊,在颜色代码上递增来实现视觉看不出单导出数据不同的效果

奖励方面,这里考虑到不同层数的奖励内容密度可能需要差异化,因此增加区间不同权重,数据在权重计算前先进行字符串的split(),然后判断层数去使用。

生成的效果:

29247-dwobxpoxt8t.png

关于编辑地格,直接使用vba自带的 Worksheet_SelectionChang 过程,在选定单元格时判断是否在地图范围内

如果是则调出编辑地格对话框:

    Dim selectedRange As Range
    Dim targetRange As Range
    
    ' 指定目标范围,这里假设目标范围为"A1:D10"
    Set targetRange = ThisWorkbook.Sheets("gen").Range("B2:H10086")
    
    ' 获取当前选中的单元格范围
    On Error Resume Next
    Set selectedRange = Selection
    On Error GoTo 0
    
    ' 判断选中的范围是否在目标范围内
    If Not selectedRange Is Nothing Then
        If Not Intersect(selectedRange, targetRange) Is Nothing Then
            Dim userInput As Variant
            Dim cellColor As Long
            Dim cellValue As String
            Dim cellFontColor As Long
            Dim selectName As String
            selectName = "要设置地块 (" & selectedRange.Column - 1 & "," & selectedRange.Row - 1 & ") "
            ' 弹出输入框
            userInput = Application.InputBox(selectName & "类型为:" & Chr(10) & "1-土块,2-石头,3-空气,4-5-6-7-奖励装饰↖↗↙↘", "设置地块类型", Type:=1)
            
            ' 判断用户选择的颜色
            If userInput = 1 Then
                cellColor = rgb(139, 103, 56) ' 土
            ElseIf userInput = 2 Then
                cellColor = rgb(134, 130, 120) ' 石
            ElseIf userInput = 3 Then
                cellColor = rgb(87, 88, 83) ' 空
            ElseIf userInput = 4 Then
                cellColor = 8388736 ' 奖励格
            ElseIf userInput = 5 Then
                cellColor = 8388736 ' 奖励格
            ElseIf userInput = 6 Then
                cellColor = 8388736 ' 奖励格
            ElseIf userInput = 7 Then
                cellColor = 8388736 ' 奖励格
            Else
                'MsgBox "无效的颜色选择。"
                Exit Sub
            End If
            
    
            
            If userInput = 2 Then

28931-mzjlsef9er.png

Application.InputBox 的方法包含很多有用的参数需要注意,根据地格类型再添加二级判断进行奖励的弹窗处理,这里略去。

最终将调整满意的地图导出为配置即可:

78371-04uo5syghko9.png

三、

最后反思,这次的开发准备过程耗时不长,效果还不错

需要注意的还是模块化的思维,以及扩展性的考虑。有些部分的设计无法兼容配置类型的增加,应该更多使用一些VBA Range.End (xlDown, xlUp, xlToRight, xlToLeft)之类的方法。

尤其值得注意的是耗费了太多时间在a*的代码移植上,行动前应首先根据性价比决定投入的时间上限。

标签: Google, JS

添加新评论