实现之前,先了解一些事实:
RGSS3中,事件的移动都是有方向的(废话),在Game_Character类中的move_straight等一系列方法中都用数字表示方向,具体对应如下图:
判断某个位置是否可以通行可以通过
$game_map.passable?(x, y, dir)(?)来做到,大约是这个意思:当某角色站在(x, y)这个点的时候 向dir方向是否可以行走
这是一点 另外 如果要判断“某一个点(x, y)是否可以*抵达*” 就需要稍微处理一下:
- 4个方向都试一试
- d = 方向
- x = x + (d == 4 ? -1 : d == 6 ? 1 : 0)
- y = y + (d == 8 ? -1 : d == 2 ? 1 : 0)
- $game_map.passable?(x, y, 10 - d)
复制代码拿上面的图打比方,我要判断
5这个位置是否可以抵达,我就先退到
8,然后朝
2这个方向走,如果不行,我就退到
2,朝
8走一走;再不行从
4到
6;从
6到
4。只要有一个可以通行,我们就认为
5这个位置是可以到达的。代码暂时看不懂不要紧,有点绕,把条件运算符拆开写就很好懂了。
好 现在难点都说完了 开始动工
为了简便 我们只考虑四方向行走
[pre lang="ruby" line="1"]module AStar
# 新建一个模块 我们将在这里开始
end[/pre]
[pre lang="ruby" line="1"]module AStar
class Point
# 新建节点类 它的实例即节点
attr_accessor :x, :y, :g, :h, :d
# 节点类保存着节点的各种属性:
# 横纵坐标
# G值
# H值
# 之所以没有F 是因为F可以通过 g + h 得到
# 父节点相对于自己的方向
# 用一个数组进行初始化 数组包含的信息是它的横纵坐标
# 比如 Point.new([64, 75])
def initialize(*point)
self.x, self.y = point
end
end
end[/pre]
[pre lang="ruby" line="1"]module AStar
class Point
attr_accessor :x, :y, :g, :h, :d
def initialize(*point)
self.x, self.y = point
end
end
module_function
def make_route(origin, target)
# 寻路的方法
# 接受两个数组作为参数
# 分别代表出发点和终止点的横纵坐标
# 调用示例:
# AStar.make_route([34, 75], [66, 32])
end
end[/pre]
[pre lang="ruby" line="1"]module AStar
class Point
attr_accessor :x, :y, :g, :h, :d
def initialize(*point)
self.x, self.y = point
end
end
module_function
def make_route(origin, target)
target = Point.new(*target)
# 建立终止点的节点对象
return [] unless [2, 4, 6, 8].any? do |d|
x = target.x + (d == 4 ? -1 : d == 6 ? 1 : 0)
y = target.y + (d == 8 ? -1 : d == 2 ? 1 : 0)
$game_map.passable?(x, y, 10 - d)
end
# 上面这一段可以拆成两部分
#
# flag = [2, 4, 6, 8].any? do |d|
# x = target.x + (d == 4 ? -1 : d == 6 ? 1 : 0)
# y = target.y + (d == 8 ? -1 : d == 2 ? 1 : 0)
# $game_map.passable?(x, y, 10 - d)
# end
# # 2, 4, 6, 8四个方向(即上下左右)是否可以到达终止点?
#
# return [] unless flag
# # 如果一个都不能到达 就返回一个空路径 因为根本不可能找到路径
# # 毕竟终止点是不可到达的
end
end[/pre]
[pre lang="ruby" line="1"]module AStar
class Point
attr_accessor :x, :y, :g, :h, :d
def initialize(*point)
self.x, self.y = point
end
end
module_function
def make_route(origin, target)
target = Point.new(*target)
return [] unless [2, 4, 6, 8].any? do |d|
x = target.x + (d == 4 ? -1 : d == 6 ? 1 : 0)
y = target.y + (d == 8 ? -1 : d == 2 ? 1 : 0)
$game_map.passable?(x, y, 10 - d)
end
origin = Point.new(*origin)
# 建立出发点节点
target.g = 0
# 初始化目标点的G值
target.h = (target.x - origin.x).abs + (target.y - origin.y).abs
# 以及H值
open = [target]
# 初始化open列表 只有一个元素 即 target
close = []
# 初始化close列表 因为还没有访问任何东西 所以是空的
include_open = ->(x, y){ !open.find{|pt| pt.x == x && pt.y == y}.nil? }
include_close = ->(x, y){ !close.find{|pt| pt.x == x && pt.y == y}.nil? }
# 建立两个方法,可以判断“坐标为x, y的点是否在open/close表内”
# 这两句看不懂不要紧 明白是干什么的就可以了
end
end[/pre]
[pre lang="ruby" line="1"]module AStar
class Point
attr_accessor :x, :y, :g, :h, :d
def initialize(*point)
self.x, self.y = point
end
end
module_function
def make_route(origin, target)
target = Point.new(*target)
return [] unless [2, 4, 6, 8].any? do |d|
x = target.x + (d == 4 ? -1 : d == 6 ? 1 : 0)
y = target.y + (d == 8 ? -1 : d == 2 ? 1 : 0)
$game_map.passable?(x, y, 10 - d)
end
origin = Point.new(*origin)
target.g = 0
target.h = (target.x - origin.x).abs + (target.y - origin.y).abs
open = [target]
close = []
include_open = ->(x, y){ !open.find{|pt| pt.x == x && pt.y == y}.nil? }
include_close = ->(x, y){ !close.find{|pt| pt.x == x && pt.y == y}.nil? }
# 开始循环 如果open表不是空的
until open.empty?
nod = open.shift
# 从open表中取出第一个节点 作为当前节点
# 然后把它从open表的名单中删除
# 至于为什么是第一个 下面说明
# 遍历四个方向
[2, 4, 6, 8].each do |d|
next unless $game_map.passable?(nod.x, nod.y, d)
# 如果我站在当前节点 向d方向不能通行的话 就忽略这个方向
# 否则继续
x = nod.x + (d == 4 ? -1 : d == 6 ? 1 : 0)
y = nod.y + (d == 8 ? -1 : d == 2 ? 1 : 0)
# 获取 “如果我向这个方向走一步的话所站的位置” 的坐标
if include_open.(x, y) # 如果坐标已经在open表中了
# 也就是说 该坐标的节点是另一个已知节点的子节点
nex = open.find{|pt| pt.x == x && pt.y == y}
# 那么 找到这个坐标的节点
if nod.g + 10 < nex.g
# 如果 通过当前节点到达这个节点的消耗 比原来的消耗更小
nex.d = 10 - d
# 就把这个节点的父节点设为当前节点
nex.g = nod.g + 10
# 并更新G值(G值就是到达该节点的消耗)
# 为什么不更新H值?
end
# 如果坐标不在open表中
elsif !include_close.(x, y) # 而且也不在close表中
# 说明这个位置还没有节点
child = Point.new(x, y)
# 于是我们在这里建立一个节点
child.d = 10 - d
# 把它的父节点设为当前节点
child.g = nod.g + 10
# 计算G值
child.h = (x - origin.x).abs + (y - origin.y).abs
# 计算H值 因为节点的H值是固定的 所以之前没有必要更新H值
open.unshift child
# 把子节点插入到open表的头部 简单地说 就是把子节点添加到open表中
open.sort_by!{|pt| pt.g + pt.h}
# 把open表按照F值(G+H)从小到大排序
# 现在 open表的第一个节点肯定是F最小的
# 这也就是为什么一开始我们把open表的第一个节点作为当前节点
end
# 如果坐标不在open表中 也并非不在close表中 说明它在close表中
# 那么我们什么都不需要做 跳过它就行了
end # 循环遍历
close.push nod
# 把当前节点加入到close表中 表明它已经被搜索了
break if include_close.(origin.x, origin.y)
# 如果出发点已经在close表中了 中断循环 没有必要继续下去了
end
# 已经在循环外了
# 因为结束循环有两个可能
# 1.找到了出发点 中断循环
# 2.open表空了 找不到出发点 路径不存在
# 所以 这里进行判断
# 如果close表中没有出发点 那么路径不存在 返回一个空路径
return [] unless include_close.(origin.x, origin.y)
# 否则继续
end
end[/pre]
[pre lang="ruby" line="1"]module AStar
class Point
attr_accessor :x, :y, :g, :h, :d
def initialize(*point)
self.x, self.y = point
end
end
module_function
def make_route(origin, target)
target = Point.new(*target)
return [] unless [2, 4, 6, 8].any? do |d|
x = target.x + (d == 4 ? -1 : d == 6 ? 1 : 0)
y = target.y + (d == 8 ? -1 : d == 2 ? 1 : 0)
$game_map.passable?(x, y, 10 - d)
end
origin = Point.new(*origin)
target.g = 0
target.h = (target.x - origin.x).abs + (target.y - origin.y).abs
open = [target]
close = []
include_open = ->(x, y){ !open.find{|pt| pt.x == x && pt.y == y}.nil? }
include_close = ->(x, y){ !close.find{|pt| pt.x == x && pt.y == y}.nil? }
until open.empty?
nod = open.shift
[2, 4, 6, 8].each do |d|
next unless $game_map.passable?(nod.x, nod.y, d)
x = nod.x + (d == 4 ? -1 : d == 6 ? 1 : 0)
y = nod.y + (d == 8 ? -1 : d == 2 ? 1 : 0)
if include_open.(x, y)
nex = open.find{|pt| pt.x == x && pt.y == y}
if nod.g + 10 < nex.g
nex.d = 10 - d
nex.g = nod.g + 10
end
elsif !include_close.(x, y)
child = Point.new(x, y)
child.d = 10 - d
child.g = nod.g + 10
child.h = (x - origin.x).abs + (y - origin.y).abs
open.unshift child
open.sort_by!{|pt| pt.g + pt.h}
end
end
close.push nod
break if include_close.(origin.x, origin.y)
end
return [] unless include_close.(origin.x, origin.y)
# 初始化路径表
routes = []
# 从出发点出发 沿着方向走回去就是路径了
# 找到出发点 作为当前节点
nod = close.find{|pt| pt.x == origin.x && pt.y == origin.y}
# 开始循环 直到抵达目标点
until nod.x == target.x && nod.y == target.y
# 把当前节点的方向添加到路径表的尾部
# 这样做 完成以后 把路径表从头到尾读一遍就是移动方向了
# 类似于“上右上右右上右右下”这样
routes.push nod.d
# 记录下来以后 向那个方向移动一步
x = nod.x + (nod.d == 4 ? -1 : nod.d == 6 ? 1 : 0)
y = nod.y + (nod.d == 8 ? -1 : nod.d == 2 ? 1 : 0)
# 然后获取移动后所在位置的节点 作为当前节点
nod = close.find{|pt| pt.x == x && pt.y == y}
# 回去循环
end
routes
# 最后返回路径
# 基础部分 完
end
end[/pre]
写在一起,就是这样:
…………
等等,因为在某些地图通行度应该循环修正(我在说什么)所以用
$game_map.passable?(x, y, dir)判断通行度是不好的……
合适的判定在Game_CharacterBase中有(如2L所说)
但是不能直接用 需要修改一下才能适合我们
注意is_passable那部分:
[pre lang="ruby" line="1"]#==============================================================================
# ** RGSS3-Based A-Star Find Path
# * Shadow Momo
# * Base Pack
#==============================================================================
module AStar
class Point
attr_accessor :x, :y, :g, :h, :d
def initialize(*point)
self.x, self.y = point
end
end
module_function
def make_route(origin, target, *characters)
target = Point.new(*target)
is_passable = ->(x, y, d){
x = $game_map.round_x_with_direction(x, d)
y = $game_map.round_y_with_direction(y, d)
$game_map.valid?(x, y) && $game_map.passable?(x, y, d) &&
$game_map.events_xy_nt(x, y).all? do |event|
!event.normal_priority? || characters.any?{|c| c.id == event.id}
end
}
return [] unless [2, 4, 6, 8].any? do |d|
x = target.x + (d == 4 ? -1 : d == 6 ? 1 : 0)
y = target.y + (d == 8 ? -1 : d == 2 ? 1 : 0)
is_passable.(x, y, 10 - d)
end
origin = Point.new(*origin)
target.g = 0
target.h = (target.x - origin.x).abs + (target.y - origin.y).abs
open = [target]
close = []
include_open = ->(x, y){ !open.find{|pt| pt.x == x && pt.y == y}.nil? }
include_close = ->(x, y){ !close.find{|pt| pt.x == x && pt.y == y}.nil? }
until open.empty?
nod = open.shift
[2, 4, 6, 8].each do |d|
x = nod.x + (d == 4 ? -1 : d == 6 ? 1 : 0)
y = nod.y + (d == 8 ? -1 : d == 2 ? 1 : 0)
next unless is_passable.(x, y, 10 - d)
if include_open.(x, y)
nex = open.find{|pt| pt.x == x && pt.y == y}
if nod.g + 10 < nex.g
nex.d = 10 - d
nex.g = nod.g + 10
end
elsif !include_close.(x, y)
child = Point.new(x, y)
child.d = 10 - d
child.g = nod.g + 10
child.h = (x - origin.x).abs + (y - origin.y).abs
open.unshift child
open.sort_by!{|pt| pt.g + pt.h}
end
end
close.push nod
break if include_close.(origin.x, origin.y)
end
return [] unless include_close.(origin.x, origin.y)
routes = []
nod = close.find{|pt| pt.x == origin.x && pt.y == origin.y}
until nod.x == target.x && nod.y == target.y
routes.push nod.d
x = nod.x + (nod.d == 4 ? -1 : nod.d == 6 ? 1 : 0)
y = nod.y + (nod.d == 8 ? -1 : nod.d == 2 ? 1 : 0)
nod = close.find{|pt| pt.x == x && pt.y == y}
end
routes
end
end[/pre]
好了 完成了
这部分的代码如果有更新 不会再编辑这个帖子了 最新版请见Github
但是,目前为止,我们最多调用一下AStar.make_route(...)然后获得一个数组,还是不怎么方便——当然,AStar的实现部分已经结束了,现在我们要应用。