旧VX ACE経路探索スクリプトを単一移動用に編集

イベントが呼び出されるたびに1回だけ移動を発動する経路探索スクリプトを見つけるのに苦労していました。多くの試行錯誤と、残念ながらチャットボットによるある種の支援を経て、2011年のこの古典的なパスファインダーを私が求めていた形に仕上げることができました。

元のスクリプト作者である cozziekuns(および Modern Algebra)に全クレジットを贈ります。また、説明文の大部分は cozziekuns によって書かれており、必要に応じて微調整を加えています。

2012年1月にこの機能/修正をリクエストしていただいた johantnjl にも感謝申し上げます。あなたのプロジェクトが役に立つことを願っています :slightly_smiling_face:

バージョン履歴


  • <Version 1.0> 2011.12.31 - 初版リリース (myhgis による軽微な修正 2026.07.19)

説明
このスクリプトを使用すると、最短経路で任意のキャラクターを別のタイルに移動させることができます。ただし、広大なマップでは処理にかなり時間がかかります。

機能

  • 驚くほど高速で正確な経路探索

  • スクリプト呼び出しが簡単

  • 設定がほぼ不要

使用方法

スクリプトを本来の意図通り、1歩ずつ移動せずに使用するには、移動ルートイベントを作成し、スクリプトコマンドとして以下を使用します:

find_path(target_x, target_y)

また、以下の呼び出しにより、スクリプト経由で経路を強制することもできます。これは、スクリプトが呼び出されるたびに1回だけ移動するように編集されたものです:

force_path(target_x, target_y)

Modern Algebraのスクリプトには、経路発見後の再計算に関する既知のバグがあります。このスクリプトの一部は彼の VX 版からの単純な変換であり(アルゴリズムのみがわずかに遅く、雑である点が異なります)、そのためこのバグもこのスクリプトに含まれています。

クレジット


  • cozziekuns
  • 彼の VX 版スクリプトを提供してくれた Modern Algebra

謝辞


#===============================================================================
# [VXA] Pathfinding
#-------------------------------------------------------------------------------
# Version: 1.0
# Author: cozziekuns (rmrk)
# Last Date Updated: 12/31/2011 (MM/DD/YYYY)
#===============================================================================
# Description:
#-------------------------------------------------------------------------------
# This script allows you to move any character to another tile using the 
# shortest path possible. It takes a significantly longer time on large maps.
#===============================================================================
# Updates
# ------------------------------------------------------------------------------
# o 12/31/2011 - Started Script
#===============================================================================
# To-do List
#-------------------------------------------------------------------------------
# o Allow for dynamic recalculation if one wants to repeat the pathfinding
#   process for any reason.
#===============================================================================
# Instructions
#-------------------------------------------------------------------------------
# To use, create a move route event and as a script command use:
#
# find_path(target_x, target_y)
#
# Additionally, one can force a path through a script using the following call:
#
# force_path(target_x, target_y)
#
# There is a known bug that is present in Modern Algebra's script regarding the
# recalculation of a path once found. Since some of this script is simply
# a convert from his VX version (only the algorithm is different by being
# slightly slower and sloppier :P), the bug is also found in this script.
#===============================================================================

#==============================================================================
# ** 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]
      
      # Target is unreachable — cancel search and stay idle
      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   

    # Double-check that destination was reached
    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 # right
      elsif dx == -1
        code = 4 # left
      elsif dy == 1
        code = 2 # down
      elsif dy == -1
        code = 8 # up
      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