修改VX ACE寻路脚本以支持单次移动

我一直在努力寻找一种路径寻找脚本,该脚本在每次调用事件时只触发一次移动。经过大量的试错,以及不幸的是,在聊天机器人的帮助下,我成功地将这个 2011 年的经典寻路器改造成我想要的样子。

感谢原始脚本作者 cozziekuns(以及 Modern Albegra)。此外,大部分描述由 cozziekuns 编写,并在适用处进行了少量编辑。

特别感谢 johantnjl,他在 2012 年 1 月请求了此功能/脚本修改。希望这能对您的项目有所帮助 :slightly_smiling_face:

版本历史


  • <版本 1.0> 2011.12.31 - 原始发布 (myhgis 于 2026.07.19 进行了少量修改)

描述
此脚本允许您使用最短路径将任何角色移动到另一个图块。在大型地图上,这需要 significantly 更长的时间。

功能

  • 极快且准确的路径寻找

  • 简单的脚本调用

  • 几乎无需配置

说明

要按原意使用脚本而不是一步一步地移动,请创建一个移动路线事件,并作为脚本命令使用:

find_path(target_x, target_y)

此外,可以通过以下调用强制通过脚本进行路径规划,这是经过编辑以在每次调用脚本时仅移动一次的版本:

force_path(target_x, target_y)

*Modern Algebra 的脚本中存在一个已知错误,涉及找到路径后的重新计算。由于此脚本的某些部分仅仅是从他 VX 版本的转换(仅算法不同,稍慢且更粗糙),因此此脚本中也存在该错误。

致谢


  • cozziekuns
  • Modern Algebra,感谢他的 VX 版本脚本

感谢

  • Patrick Lester,以及他著名的 A* 教程,这对我们非常有帮助。

#===============================================================================
# [VXA] 路径寻找
#-------------------------------------------------------------------------------
# 版本:1.0
# 作者:cozziekuns (rmrk)
# 最后更新日期:2011/12/31 (MM/DD/YYYY)
#===============================================================================
# 描述:
#-------------------------------------------------------------------------------
# 此脚本允许您使用最短路径将任何角色移动到另一个图块。在大型地图上,
# 这需要 significantly 更长的时间。
#===============================================================================
# 更新
# ------------------------------------------------------------------------------
# o 2011/12/31 - 开始编写脚本
#===============================================================================
# 待办事项
#-------------------------------------------------------------------------------
# o 允许动态重新计算,如果出于任何原因想重复路径寻找过程。
#===============================================================================
# 说明
#-------------------------------------------------------------------------------
# 要使用,请创建一个移动路线事件,并作为脚本命令使用:
#
# find_path(target_x, target_y)
#
# 此外,可以通过以下调用强制通过脚本进行路径规划:
#
# force_path(target_x, target_y)
#
# Modern Algebra 的脚本中存在一个已知错误,涉及找到路径后的重新计算。由于
# 此脚本的某些部分仅仅是从他 VX 版本的转换(仅算法不同,稍慢且更粗糙 :P),
# 因此此脚本中也存在该错误。
#===============================================================================

#==============================================================================
# ** Game_Map
#==============================================================================

class Game_Map
 
  def find_path(target_x, target_y, sx, sy, passable, char)
    path = []
    max_elements = width * height + 2
    checked_items = 0
    @open_list_items = 0
    @open_list = Table.new(max_elements)
    @nodes = Table.new(max_elements, 2)
    open = Table.new(width, height)
    closed = Table.new(width, height)
    parent = Table.new(width, height, 3)
    @f_cost = Table.new(width, height)
    @open_list[0] = 0
    @nodes[0, 0] = sx
    @nodes[0, 1] = sy
    next_point = [sx, sy]
    closed[sx, sy] = 1

    loop do
      next_point = delete_from_heap if not next_point == [sx, sy]
      
      # 目标不可达 — 取消搜索并保持空闲
      return [] if next_point.nil?
      
      open[next_point[0], next_point[1]] = 0
      closed[next_point[0], next_point[1]] = 2 if not next_point == [sx, sy]
      parent_x, parent_y = next_point[0], next_point[1]

      for i in 1..4
        dir = i * 2
        x, y = case dir
        when 2; [parent_x, parent_y + 1]
        when 4; [parent_x - 1, parent_y]
        when 6; [parent_x + 1, parent_y]
        when 8; [parent_x, parent_y - 1]
        end

        next unless $game_map.valid?(x, y)
        next if closed[x, y] == 2
        next unless custom_passable?(char, parent_x, parent_y, dir, x, y, target_x, target_y)

        if not open[x, y] == 1
          open[x, y] = 1
          parent[x, y, 0] = parent_x
          parent[x, y, 1] = parent_y
          parent[x, y, 2] = parent[parent_x, parent_y, 2] + 10
          g = parent[x, y, 2] + 10
          h = ((target_x - x).abs + (target_y - y).abs) * 10
          @f_cost[x, y] = g
          checked_items += 1
          @open_list_items += 1
          @nodes[checked_items, 0] = x
          @nodes[checked_items, 1] = y
          add_to_heap(checked_items)
        else
          old_g = parent[x, y, 2] + 10
          new_g = parent[parent_x, parent_y, 2] + 20
          next if old_g < new_g
          parent[x, y, 0] = parent_x
          parent[x, y, 1] = parent_y
          parent[x, y, 2] = new_g
          g = parent[x, y, 2] + 10
          h = ((target_x - x).abs + (target_y - y).abs) * 10
          @f_cost[x, y] = g
        end
      end

      next_point = nil
      break if closed[target_x, target_y] == 2
    end   

    # 再次检查是否到达目的地
    return [] unless closed[target_x, target_y] == 2

    path_x, path_y = target_x, target_y
    loop do   
      parent_x = parent[path_x, path_y, 0]
      parent_y = parent[path_x, path_y, 1]

      dx = path_x - parent_x
      dy = path_y - parent_y

      if dx == 1
        code = 6 # 右
      elsif dx == -1
        code = 4 # 左
      elsif dy == 1
        code = 2 # 下
      elsif dy == -1
        code = 8 # 上
      else
        code = 0
      end
        
      path.push(RPG::MoveCommand.new(code))
      path_x, path_y = parent_x, parent_y     
      break if path_x == sx and path_y == sy
    end

    return path
  end

  def custom_passable?(char, x, y, d, nx, ny, target_x, target_y)
    return false unless char.map_passable?(x, y, d)
    return true if nx == target_x && ny == target_y
    return !char.collide_with_characters?(nx, ny)
  end

  def add_to_heap(value)
    m = @open_list_items
    @open_list[m] = value
    while m != 1
      if fcost(@open_list[m]) < fcost(@open_list[m / 2])
        temp = @open_list[m / 2]
        @open_list[m / 2] = @open_list[m]
        @open_list[m] = temp
        m /= 2
      else
        break
      end
    end
  end
 
  def delete_from_heap
    return nil if @open_list_items <= 0
    
    next_point = @open_list[0]
    @open_list[0] = @open_list[@open_list_items]
    @open_list_items -= 1
    v = 1
    loop do
      u = v
      w = 2 * u
      if w + 1 <= @open_list_items
        v = w if fcost(@open_list[u - 1]) >= fcost(@open_list[w - 1])
        v = w + 1 if fcost(@open_list[v - 1]) >= fcost(@open_list[w])
      elsif w <= @open_list_items
        v = w if fcost(@open_list[u - 1]) >= fcost(@open_list[w - 1])
      end
      if u != v
        temp = @open_list[u - 1]
        @open_list[u - 1] = @open_list[v - 1]
        @open_list[v - 1] = temp
      else
        break
      end
    end
    
    return nil if next_point.nil?
    return @nodes[next_point, 0], @nodes[next_point, 1]
  end
 
  def fcost(point)
    x = @nodes[point, 0]
    y = @nodes[point, 1]
    return @f_cost[x, y]
  end

end

#==============================================================================
# ** Game_CharacterBase
#==============================================================================
class Game_CharacterBase

  def find_path(target_x, target_y)
    path = $game_map.find_path(target_x, target_y, @x, @y, false, self)
    path = path[0, 1] || []

    @move_route.list.delete_at(@move_route_index)
    path.each { |cmd| @move_route.list.insert(@move_route_index, cmd) }
    @move_route_index -= 1
  end

  def force_path(target_x, target_y)
    return if @x == target_x && @y == target_y
    
    path = $game_map.find_path(target_x, target_y, @x, @y, false, self)
    return if path.nil? || path.empty?

    next_step = path.last
    move_straight(next_step.code)
  end

  def count_iterations(target_x, target_y)
    path = $game_map.find_path(target_x, target_y, @x, @y, true, self)
    return path.size
  end

end
1 个赞