Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

[VX] Pathfinding Question

Hey guys. Just another little question I've got regarding the new project FOR VX.

I've got npc's who just wander the streets during the day, but at certain times, they have things to do, and places to be. Because I want Rome to be a living and breathing world, I've developed a backstory for all 27 npc's so far. Each of them uses a time variable to allow them to complete daily tasks... I could have used a 7 day system as well, but I think it would get too complicated. Just doing a daily chore list for 27 different events was a big enough pain. They all do different things at different times. And that deosn't even include the storyline npc's that you have to meet to complete core story points and ultimately reach ONE of several endings....

Anyway... I kinda just went off bragging about the game... I'm proud, what can I say?

I need to know if there's a sort of pathfinding script, or another evented way of selecting a map and a coordinate for the event to travel to. The most difficult part of this, is that they'd have to walk toward the map exits of my choosing, then teleport, or just activate another event, at their destination... I'm not sure which would be easiest yet. Each of the events will not only have random conversations with you, but they'll ask questions, or allow you to choose a greeting style, so they'll have multiple event pages for each time of day. That'll make multiple events much more difficult.

In essence: I need a simple pathfinding solution that allows me to input coordinates for an event's current map and they're able to travel to that location without using a custom made move route... Since the events will be wandering randomly about the maps, even if I did make them all a custom move route for certain times, they'd all end up in strange places and then just disappear.

Is this possible?
 
Pathfinding can be done with events / common events, but it's a pain. And if you have lot's of events, it's gonna get laggy.
This is certainly more suited to a pathfinding script.

Unfortunately, I don't have much experience with them. I just haven't needed it yet. So I'll move this to Script Request (where someone who's familiar with pathfinding will see it.)

Be Well
 
I'm going to play around with a way to do it using events for the demo, which I plan to release by the end of today or tomorrow, but after that, I'm going to really dig in for a script.

Thanks again, Brew. You're a huge help. I'm glad you're done with your 'admin' things for the time being.
 
Here's a Pathfinding system I made for the MACL (still working on the node pathfinding bug before that "mode" is complete) and haven't released yet. The idea for this system is to allow multiple "modes" (aka algorithms) for finding the best path.

[rgss]#==============================================================================
# ** Systems.Pathfinding (V1.0)
#------------------------------------------------------------------------------
# SephirothSpawn
# Version 1.0
# 2008-08-17
#------------------------------------------------------------------------------
# Description:
# ------------
# This system allows the player and events draw a path from one point to
# another in the shortest route possible.
#
# This also can serve as a base for other pathfinding algorithms.
#  
# Method List:
# ------------
#
#   Pathfinding
#   -----------
#   find_path
#   retry_path
#
#   Pathfinding::Node
#   -----------------
#   clear_nodes
#   x, x=
#   y, y=
#   g, g=
#   h, h=
#   parent_id, parent_id=
#   parent
#   cost
#   ==
#  
#   Pathfinding::Seph_A_Star
#   ------------------------
#   find_path
#   retry_path
#   find_next_node (private)
#   adjacent_nodes (private)
#   add_node (private)
#   update_node (private)
#   passable? (private)
#   skip_node? (private)
#
#   Game_Character
#   --------------
#   find_path
#   clear_pathfinding
#   update_pathfinding
#   pathfinding
#   pathfinding_c_proc
#   pathfinding_c_frames
#------------------------------------------------------------------------------
# * Syntax :
#
#   Make Player find path
#    - $game_player.find_path(x, y, special = {}, mode = Pathfinding::Mode)
#==============================================================================
 
MACL::Loaded << 'Systems.Pathfinding'
 
#==============================================================================
# ** Pathfinding
#==============================================================================
 
module Pathfinding
  #--------------------------------------------------------------------------
  # * Constant
  #
  #   Limit: Number of steps a path will tried to be found before quitting
  #          (-1 for infinity)
  #    - Used in: Seph-A*
  #
  #   Fail Proc: Proc called when path cannot be found
  #    - Used in: Seph-A*
  #
  #   Complete Proc: Proc called when path finished
  #    - Used in: Seph-A*
  #
  #   Collision Frames: Number of frames to wait to retry when failure
  #--------------------------------------------------------------------------
  Defaults = {}
  Defaults['limit']     = -1              # Limit
  Defaults['f_proc']    = nil             # Fail Proc
  Defaults['c_proc']    = nil             # Complete Proc
  Defaults['c_frames']  = 20              # Collision Frames
  Mode                  = 'Seph-A*'
  Prevent_Script_Hang   = 200
  #--------------------------------------------------------------------------
  # * Find Path
  #--------------------------------------------------------------------------
  def self.find_path(character, end_x, end_y, special = {}, mode = Mode)
    # Setup Default Specials
    Defaults.each {|k, v| special[k] = v unless special.has_key?(k)}
    # Save Mode
    @mode = mode
    # Branch By Mode
    if mode == 'Seph-A*'
      # Seph-A*
      Seph_A_Star.find_path(character, end_x, end_y, special)
    end
  end
  #--------------------------------------------------------------------------
  # * Retry Path
  #--------------------------------------------------------------------------
  def self.retry_path
    # Branch By Mode
    if @mode == 'Seph-A*'
      # Seph-A*
      Seph_A_Star.retry_path
    end
  end
end
 
#==============================================================================
# ** Pathfinding::Node
#==============================================================================
 
class Pathfinding::Node
  #--------------------------------------------------------------------------
  # * Saved Nodes
  #--------------------------------------------------------------------------
  @@saved_nodes = {}
  #--------------------------------------------------------------------------
  # * Clear Nodes
  #--------------------------------------------------------------------------
  def self.clear_nodes
    @@saved_nodes = {}
  end
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
  attr_accessor :x
  attr_accessor :y
  attr_accessor :g
  attr_accessor :h
  attr_accessor :parent_id
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize(x, y, g = 0, h = 0, parent_id = nil)
    # Set Instances
    @x, @y, @g, @h, @parent_id = x, y, g, h, parent_id
    # Save By Object
    @@saved_nodes[object_id] = self
  end
  #--------------------------------------------------------------------------
  # * Parent
  #--------------------------------------------------------------------------
  def parent
    return @@saved_nodes[@parent_id]
  end
  #--------------------------------------------------------------------------
  # * Node Cost
  #--------------------------------------------------------------------------
  def cost
    return @g + @h
  end
  #--------------------------------------------------------------------------
  # * ==
  #--------------------------------------------------------------------------
  def ==(other)
    return other.is_a?(Pathfinding::Node) && other.x == x && other.y == y
  end
end
 
#==============================================================================
# ** Pathfinding::Seph_A_Star
#==============================================================================
 
module Pathfinding::Seph_A_Star
  #--------------------------------------------------------------------------
  # * Retry Path
  #--------------------------------------------------------------------------
  def self.retry_path
    self.find_path(@character, @end_node.x, @end_node.y, @special)
  end
  #--------------------------------------------------------------------------
  # * Find Path
  #--------------------------------------------------------------------------
  def self.find_path(character, end_x, end_y, special = {})
    # Clear Nodes List
    Pathfinding::Node.clear_nodes
    # Setup default specials
    Pathfinding::Defaults.each {|k, v| special[k] = v unless special.has_key?(k)}
    # Saves character
    @character = character
    # Creates nodes
    start_node = Pathfinding::Node.new(character.x, character.y)
    @end_node   = Pathfinding::Node.new(end_x, end_y)
    # Saves specials
    @special  = special
    limit    = special['limit']
    f_proc   = special['f_proc']
    c_proc   = special['c_proc']
    c_frames = special['c_frames']
    # Create lists
    @open_list    = [start_node]
    @closed_list  = {}
    # Clear found flag
    found = false
    # Start count
    count = 1
    # If target can be reached
    if self.passable?(@end_node.x, @end_node.y)
      # As long as list has a node to check
      until @open_list.empty?
        # Add to count
        count += 1
        # Update Graphics (Prevents scripting hanging error)
        Graphics.update if count % Pathfinding::Prevent_Script_Hang == 0
        # Break pathfinding if limit reached
        break if limit != -1 && count > limit
        # Get next node
        next_node = self.find_next_node
        # Add node to closed list
        @closed_list[[next_node.x, next_node.y]] = next_node
        # If next node has same position as end node
        if next_node == @end_node
          # Replace start node
          @end_node = next_node
          # Set found flag
          found = true
          # Break pathfinding
          break
        end
        # Get adjacent nodes
        adj_nodes = self.adjacent_nodes(next_node)
        # Pass through adjacent nodes
        for node in adj_nodes
          # Skip node if node in list
          next if self.skip_node?(node)
          # Add Node to Open List
          @open_list << node
          # Update Node
          self.update_node(@open_list.size - 1)
        end
      end
    end
    # If path cannot be found, call fail proc
    f_proc.call if found == false && f_proc != nil
    # If path found
    if found
      # Create movement list
      mvt_list = []
      # Start from end node
      node = @end_node
      # Keep checking parent node
      while node.parent != nil
        # Get Direction
        if node.parent.x > node.x
          dir = 4
        elsif node.parent.x < node.x
          dir = 6
        elsif node.parent.y > node.y
          dir = 8
        elsif node.parent.y < node.y
          dir = 2
        end
        # Add direction to front of list
        mvt_list.unshift(dir) if dir != nil
        # Switch current node to parent node
        node = node.parent
      end
      # Set pathfinding
      @character.pathfinding = mvt_list
      @character.pathfinding_c_proc = c_proc
      @character.pathfinding_c_frames = c_frames
    # If no path found
    else
      # Clear Pathfinding
      @character.clear_pathfinding
    end
  end
  #--------------------------------------------------------------------------
  # * Find Next Node
  #--------------------------------------------------------------------------
  private
  def self.find_next_node
    # Return nil if no nodes
    return nil if @open_list.empty?
    # Remove Last Element
    last = @open_list.pop
    # Return Last if List Empty
    return last if @open_list.empty?
    # Get original first element
    first = @open_list.first
    # Replace first element with last
    @open_list[0] = last
    # V Counter
    v = 0
    # Loop
    loop do
      u = v
      # If both children exist
      if 2 * u + 1 < @open_list.size
        v = 2 * u     if @open_list[2 * u].cost     <= @open_list.cost
        v = 2 * u + 1 if @open_list[2 * u + 1].cost <= @open_list[v].cost
      # If only one child exists
      elsif 2 * u < @open_list.size
        v = 2 * u     if @open_list[2 * u].cost     <= @open_list.cost
      end
      # Break if same
      break if u == v
      # Swap
      @open_list, @open_list[v] = @open_list[v], @open_list
    end
    # Return first node
    return first
  end
  #--------------------------------------------------------------------------
  # * Get a List of Adjacent Nodes
  #--------------------------------------------------------------------------
  private
  def self.adjacent_nodes(node)
    # Creates list of nodes
    nodes = []
    nodes << self.add_node(node.x, node.y + 1, node)
    nodes << self.add_node(node.x - 1, node.y, node)
    nodes << self.add_node(node.x + 1, node.y, node)
    nodes << self.add_node(node.x, node.y - 1, node)
    # Return Nodes - nil elements
    return nodes.compact
  end
  #--------------------------------------------------------------------------
  # * Add Node
  #--------------------------------------------------------------------------
  private
  def self.add_node(x, y, parent)
    # If passable
    if self.passable?(x, y)
      # Add 1 to Part Cost
      g = parent.g + 1
      # Get heuristic distance (Manhattan distance)
      h = (x - @end_node.x).abs + (y - @end_node.y).abs
      # Create New Node
      return Pathfinding::Node.new(x, y, g, h, parent.object_id)
    end
    # Return nil
    return nil
  end
  #--------------------------------------------------------------------------
  # * Update Node (heap_update)
  #--------------------------------------------------------------------------
  private
  def self.update_node(i)
    # Loop
    while i > 0
      # Break if parents cost is greater than parents
      break if @open_list[i / 2].cost > @open_list.cost
      # Swap node and parent
      @open_list[i / 2], @open_list = @open_list, @open_list[i / 2]
      # Switch index to parent
      i /= 2
    end
  end
  #--------------------------------------------------------------------------
  # * Passable?
  #--------------------------------------------------------------------------
  private
  def self.passable?(x, y)
    return @character.passable?(x, y, 0)
  end
  #--------------------------------------------------------------------------
  # * Skip Node?
  #--------------------------------------------------------------------------
  private
  def self.skip_node?(node)
    # Clear skip flag
    skip_node = false
    # Gets node index
    index = @open_list.index(node)
    # If Node Found
    if index != nil
      # If current node cost less than node being tested
      if @open_list[index].cost <= node.cost
        # Set skip flag
        skip_node = true
      else
        # Replace node
        @open_list[index] = node
        # Update node
        self.update_node(index)
        # Set skip flag
        skip_node = true
      end
    end
    # If closed list has a node
    if @closed_list[[node.x, node.y]] != nil
      # If current node cost less than node being tested
      if @closed_list[[node.x, node.y]].cost <= node.cost
        # Set skip flag
        skip_node = true
      else
        # Replace Node
        @closed_list[[node.x, node.y]] = node
      end
    end
    # Return the result
    return skip_node
  end
end
 
#==============================================================================
# ** Game_Character
#==============================================================================
 
class Game_Character
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
  attr_accessor :pathfinding
  attr_accessor :pathfinding_c_proc
  attr_accessor :pathfinding_c_frames
  #--------------------------------------------------------------------------
  # * Alias Listings
  #--------------------------------------------------------------------------
  alias_method :seph_pathfinding_gmchr_init,   :initialize
  alias_method :seph_pathfinding_gmchr_update, :update
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize
    # Clear pathfinding
    clear_pathfinding
    # Original Initialization
    seph_pathfinding_gmchr_init
  end
  #--------------------------------------------------------------------------
  # * Clear Pathfinding
  #--------------------------------------------------------------------------
  def clear_pathfinding
    @pathfinding = nil
    @pathfinding_c_proc = nil
    @pathfinding_c_frames = nil
  end
  #--------------------------------------------------------------------------
  # * Find Pathing
  #--------------------------------------------------------------------------
  def find_path(x, y, special = {}, mode = Pathfinding::Mode)
    # Run Pathfinding
    Pathfinding.find_path(self, x, y, special)
  end
  #--------------------------------------------------------------------------
  # * Frame Update
  #--------------------------------------------------------------------------
  def update
    # Run pathfinding if path defined
    update_pathfinding if @pathfinding != nil
    # Original Update
    seph_pathfinding_gmchr_update
  end
  #--------------------------------------------------------------------------
  # * Frame Update : Pathfinding
  #--------------------------------------------------------------------------
  def update_pathfinding
    # Return If Moving
    return if self.moving?
    # If Empty Pathfinding
    if @pathfinding.empty?
      # Call Complete Proc
      @pathfinding_c_proc.call unless @pathfinding_c_proc == nil
      # Clear Pathfinding
      clear_pathfinding
      return
    end
    # Get Next Direction
    dir = @pathfinding.shift
    # If Passable
    if passable?(x, y, dir)
      # Get Method Name
      m = 'move_'
      m += dir == 2 ? 'down' : dir == 4 ? 'left' : dir == 6 ? 'right' : 'up'
      # Call Next Move
      self.send(m.to_sym)
      return
    end
    # If retry
    if @pathfinding_c_frames != nil && @pathfinding_c_frames > 0
      # Set Wait Count
      @wait_count = @pathfinding_c_frames
      # Retry Path
      Pathfinding.retry_path
    # If don't retry
    else
      # Clear Pathfinding
      clear_pathfinding
    end
  end
end
 
#==============================================================================
# ** Game_Map
#==============================================================================
 
class Game_Map
  #--------------------------------------------------------------------------
  # * Alias Listings
  #--------------------------------------------------------------------------
  alias_method :seph_pathfinding_gmmap_setup, :setup
  #--------------------------------------------------------------------------
  # * Setup
  #--------------------------------------------------------------------------
  def setup(map_id)
    # Orignal Setup
    seph_pathfinding_gmmap_setup(map_id)
    # Clear Player Pathfinding
    $game_player.clear_pathfinding
  end
end
 
#==============================================================================
# ** Game_Player
#==============================================================================
 
class Game_Player
  #--------------------------------------------------------------------------
  # * Alias Listings
  #--------------------------------------------------------------------------
  alias_method :seph_pathfinding_gmplyr_update, :update
  #--------------------------------------------------------------------------
  # * Frame Update
  #--------------------------------------------------------------------------
  def update
    # Clear Path if Direciton Pressed
    clear_pathfinding if Input.dir4 != 0
    # Original Update
    seph_pathfinding_gmplyr_update
  end
end
 
[/rgss]

Just insert below above Main. Check the heading for instructions on make the event move to a position. Let me know how it preforms.
 
Okay.

1.) thank you so much. I'm sure this will work perfect, and from what I can see, it's nothing more or less than I need.

2.) I'm a noob scripter, and I don't care who knows it. I have no idea what your notes mean, and how to get an event to find a path...... Help?

Sorry... I'll go try a little harder, but I'm pretty hopeless when it comes to scripts... that's why I prefer not use them, mainly because people get mad when you ask stupid questions like this.
 
I don't get mad at all. I was a n00b at scripts once too.

To make an event find a path to a specific spot, use:
Code:
$game_map.events[event_id].find_path(x, y)

Replace event_id with your events id and x/y with your destination tile.
 
Okay, great. So I just need to use the "script" command on the event then? Then add what you listed there? Or... do I need to paste that snippet somewhere special? lol

I'm going to go test 'er out real quick before I head to bed.

Thanks, so so much SephirothSpawn. You're my favorite scripter in all of rmxp land. : )
 
Update:

Okay, this makes no sense whatsoever. I copy/pasted your script into my scripts, above main. I copy/pasted the snippet too, and fixed it, just as a test event.

But when I testplay, I get a syntax error. ..... for line 2 .... Which is just a comment. So I have no idea WHY I'm getting it. I'll paste it, just in case it can help you somehow.

Error: "Script 'Pathfinding' line 2: SyntaxError occurred.

I don't even need to paste the script, but I'm going to anyway, even though you could have just scrolled up six inches and seen it.

Code:
    1. #==============================================================================

   2. # ** Systems.Pathfinding (V1.0)

   3. #------------------------------------------------------------------------------

   4. # SephirothSpawn

   5. # Version 1.0

   6. # 2008-08-17

   7. #------------------------------------------------------------------------------

   8. # Description:
 
Okay... yes. I am a moron. But it's 7:00 AM here, and I haven't slept yet.... So there.

Anyway. I have another error: Script 'Pathfinding' line 61: NameError occurred. uninitialized constant MACL

Again, don't know what that means.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top