[Script] [ACE] [Movement] SES Event Movement 1.0

in RPG Maker
[align=center][size=large]SES Event Movement 1.2[/size][/align]
[chapter]Giới thiệu[/chapter]
Trong các game kinh dị, thì rượt đuổi chính là một chiêu thức khiến cho tim người chơi thon thót thon thót, tạo cảm giác mất bình tĩnh. Nhưng làm sao để một NPC (event) đuổi theo người chơi khi hệ thống "bám đuôi người chơi" của nó không được thông minh lắm? Trong RPG Maker, và chi tiết hơn là cái hệ thống "bám đuôi/đuổi theo người chơi", chỉ cần gặp phải một chướng ngại vật là người "bám đuôi" sẽ dừng lại ngay => không thông minh. Ví dụ, bạn cho người A đuổi theo nhân vật chính, nhưng đang đuổi thì người A đụng phải một cái bàn, thay vì tránh nó và đuổi theo nhân vật chính tiếp thì người A đó lại dừng lại và đứng nhìn nhân vật chính chạy đi. Vậy làm sao để người A này "thông minh hơn"?
Đó chính là script SES Event Movement. Với SES Event Movement, bạn chỉ cần 1 dòng gọi script và mọi thứ sẽ tự động thực hiện một cách thông minh nhất.
[chapter]Script[/chapter]
Core:
SES Event Movement:
[chapter]Credit[/chapter]
Solistra (Tác giả)
Near_Fantastica
[chapter]Giới thiệu[/chapter]
Trong các game kinh dị, thì rượt đuổi chính là một chiêu thức khiến cho tim người chơi thon thót thon thót, tạo cảm giác mất bình tĩnh. Nhưng làm sao để một NPC (event) đuổi theo người chơi khi hệ thống "bám đuôi người chơi" của nó không được thông minh lắm? Trong RPG Maker, và chi tiết hơn là cái hệ thống "bám đuôi/đuổi theo người chơi", chỉ cần gặp phải một chướng ngại vật là người "bám đuôi" sẽ dừng lại ngay => không thông minh. Ví dụ, bạn cho người A đuổi theo nhân vật chính, nhưng đang đuổi thì người A đụng phải một cái bàn, thay vì tránh nó và đuổi theo nhân vật chính tiếp thì người A đó lại dừng lại và đứng nhìn nhân vật chính chạy đi. Vậy làm sao để người A này "thông minh hơn"?
Đó chính là script SES Event Movement. Với SES Event Movement, bạn chỉ cần 1 dòng gọi script và mọi thứ sẽ tự động thực hiện một cách thông minh nhất.
[chapter]Script[/chapter]
Core:
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ § SES Core (v1.2) by Solistra and Enelvon [License: CC BY-SA 3.0]
# ║ <RMVX Ace>
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Change Log
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ v1.0 (December ???, 2012) - Initial release
# ║ v1.1 (February 26th, 2013) - Enelvon fixed an issue with scanning comments,
# ║ but took forever to remember to upload it
# ║ v1.2 (March 15, 2013) - Ditto the above
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Summary
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ This serves as a base script required by some SES scripts released by both
# ║ Solistra and Enelvon. It contains some utility methods and methods to help
# ║ facilitate the use of tags in note boxes and event comments.
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Installation
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ For best compatibility, place this script below Materials, but above all
# ║ other custom scripts (especially SES scripts).
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § New Methods
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ ● Game_Event
# ║ comments
# ║ ● Game_Interpreter
# ║ this
# ║ event
# ║ comments
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ ▼ module SES
#═╩═════════════════════════════════════════════════════════════════════════════
module SES
# Includes SES in the data structures used by RMVX Ace by default.
def self.insert
RPG.constants.each do |s|
(s = RPG.const_get(s)).send(:include, self)
s.constants.each{|c| s.const_get(c).send(:include, self)}
end
end
# Used in Game_Event and Game_Interpreter to collect comments.
def self.comment_proc
Proc.new do
@list.select{|c| c.code == 108 || c.code == 408}.map{|c|c.parameters[0]}
end
end
# An extended version of Kernel.rand. Also takes ranges, arrays, or hashes.
def self.rand(arg = nil)
if arg.is_a?(Range) then Kernel.rand(1 + arg.max - arg.min) + arg.min
elsif arg.kind_of?(Array) then arg[Kernel.rand(arg.size)]
elsif arg.is_a?(Hash) then self.rand(arg.values)
else Kernel.rand(arg) end
end
# Note tag scanning method.
def scan_ses_notes(tags = {})
return unless defined?(self.note)
self.note.split(/[\r\n+]/).each do |line|
tags.each{|regex, code| eval(code) if line =~ regex}
end
end
# Allows scan_ses_comments to work.
def comments
com = []
if self.is_a?(RPG::Event::Page) || self.is_a?(RPG::CommonEvent)
@list.each{|c|com.push(c.parameters[0]) if c.code == 108 || c.code == 408}
end
com
end
# Event comment scanning method. May be used in common events as well.
def scan_ses_comments(tags = {})
self.comments.each do |comment|
tags.each{|regex, code| eval(code) if comment =~ regex}
end
end
# Method definitions are complete; include SES in the RPG data structures.
self.insert
end
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▲ module SES
#═╩═════════════════════════════════════════════════════════════════════════════
($imported ||= {})["SES - Core"] = 1.1
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▼ class Game_Event
#═╩═════════════════════════════════════════════════════════════════════════════
class Game_Event < Game_Character
def comments() @list.nil? ? [] : self.instance_eval(&SES.comment_proc) end
end
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▲ class Game_Event
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ ▼ class Game_Interpreter
#═╩═════════════════════════════════════════════════════════════════════════════
class Game_Interpreter
def event(id) $game_map.events[id] end
def this() @event_id > 0 ? $game_map.events[@event_id] : self end
def comments() this.page ? self.instance_eval(&SES.comment_proc) : [] end
end
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▲ class Game_Interpreter
#═╩═════════════════════════════════════════════════════════════════════════════
SES Event Movement:
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ § Event Movement (1.0) by Solistra [License: CC BY-SA 3.0]
# ║ Original path-finding code by Near_Fantastica <RMVX Ace>
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Change Log
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ v1.0 (December 30th, 2012) - Initial release
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Summary
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ This script adds a number of new movement types for events, including simple
# ║ path finding (originally written by Near_Fantastica for RMXP), easy patrol
# ║ routes, and movement types that can move events closer to other characters
# ║ and positions on the map. There is also a simple script call available for
# ║ determining a character's proximity to any other character (the player or
# ║ another event, for example).
# ║
# ║ NOTE: Make sure to credit Near_Fantastica as well as me (Solistra) if you
# ║ use this script!
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Required Scripts
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ This script requires the SES Core script version 1.0 or higher.
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Known Incompatibilities
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ None.
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Installation
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ Place this script below Materials, but above Main -- preferably relatively
# ║ high in your list of custom scripts. This script must be below the SES Core
# ║ script and the SES Event Memory script (if you are using it).
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Configuration
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ There is only one configuration option: PATH_FINDING_DEPTH. The value of
# ║ this option is important, though: it defines how large the path-finding tree
# ║ may be when trying to determine a path. High values are more computationally
# ║ expensive than low values, but a value that is too low may make the path
# ║ finder relatively useless (as it may not be able to find paths that exist).
# ║ Generally, a value from 50-150 is quite safe, but if you are experiencing
# ║ lag in-game, you should try lowering this value as much as you reasonably
# ║ can.
# ║
# ║ With that in mind, here is some explanation regarding patrol routes: in
# ║ order to use patrol routes, you will have to "tag" the event you want to
# ║ patrol with a comment. This comment is in the format of "<patrol [route]>".
# ║ Patrol routes must be written as a series of comma-separated x and y values
# ║ enclosed in brackets (these: []). The event will then move from its current
# ║ position to the first position in the patrol route, then the next, until it
# ║ reaches the end of the route and goes back to the first position in the
# ║ route, effectively looping it.
# ║
# ║ Example of how to set up the comment:
# ║ <patrol [0,0], [10,5], [5,10]>
# ║
# ║ This will cause the event to move to 0, 0 on the map, then 10, 5, then 5,
# ║ 10, and finally back to 0, 0 -- looping through these patrol points.
# ║
# ║ NOTE: Patrol routes use path finding to get from one destination to another.
# ║ This makes for very efficient patrols, but inefficient processing. Try not
# ║ to use too many events with patrols on the same map -- you may find that
# ║ your map begins to lag otherwise. Consider yourself informed.
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Tags
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ ● Event Comment
# ║ <patrol !route!>
# ║ ► Replacements:
# ║ !route! with a comma-separated list of x and y values written as
# ║ Ruby arrays. Example: <patrol [0,0], [10,5], [5,10]>
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Script Calls
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ This script features a number of script calls relating to event movement
# ║ types (which should be used in an event's Move Route). If you wish to use
# ║ any of these movement types as the default movement type for an event page,
# ║ simply use the Custom movement type and insert the appropriate script call
# ║ in that Move Route.
# ║
# ║ There are a few script calls that you will likely want to make use of if
# ║ you plan to use the path finder: find_path, find_path_to, and reset_path.
# ║ The find_path method finds a path to a given x and y value on the map, while
# ║ the find_path_to method finds the path to a given character (the player or
# ║ another event). This is how to use them:
# ║ find_path([x], [y]) - Finds a path to [x] and [y] on the map.
# ║ find_path_to($game_player) - Finds a path to the player.
# ║ find_path_to(event([id])) - Finds a path to the event with ID # [id].
# ║ reset_path - Cancels path finding by removing the tree.
# ║
# ║ There are also script calls for movement types that can move an event
# ║ towards a specific x and y position on the map or another character (the
# ║ player or another event). These do not use path finding, and are very
# ║ efficient (but also easily confused on complicated maps). To use them, make
# ║ use of these script calls:
# ║ move_type_toward_position([x], [y])
# ║ move_type_toward_character(event([id])
# ║
# ║ In addition to this, there are also path-finding movement types -- but be
# ║ warned: they are NOT efficient at all, and may cause serious lag if abused!
# ║ With that in mind, these movement types will recalculate the path each time
# ║ the character moves, meaning it will actively avoid obstacles. If you wish
# ║ to use them, make use of these script calls:
# ║ move_type_path([x], [y])
# ║ move_type_path_to($game_player)
# ║ move_type_path_to(event([id]))
# ║
# ║ You can also use a script call to determine whether or not a character is
# ║ near the calling event (very useful in Conditional Branches). It will return
# ║ "true" if the character is in proximity, "false" otherwise. Use it like so:
# ║ near_character?($game_player, [range in tiles])
# ║ in_proximity?(event([id]), [range in tiles])
# ║
# ║ If you leave out the range, it will default to 20 tiles to be consistent
# ║ with RGSS3's default near_the_player? method in Game_Event.
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § Aliased Methods
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ ● Game_Map
# ║ setup_events
# ║ ● Game_Character
# ║ initialize
# ║ update
# ║ ● Game_Player
# ║ move_by_input
# ║ ● Game_Event
# ║ initialize
# ║ update_self_movement
# ║ refresh
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ § New Methods
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ ● Game_Character
# ║ move_toward_position
# ║ find_path
# ║ find_path_to
# ║ run_next_path_step
# ║ reset_path
# ║ determine_path_tree
# ║ moved?
# ║ on_movement
# ║ reset_movement_variables
# ║ move_failed?
# ║ ● Game_Event
# ║ move_type_toward_character
# ║ move_type_toward_position
# ║ move_type_path
# ║ move_type_path_to
# ║ move_type_patrol
# ║ near_character?
# ║ determine_patrol_points
# ║
#═╬═════════════════════════════════════════════════════════════════════════════
# ║ ▼ module SES::Event_Movement
#═╩═════════════════════════════════════════════════════════════════════════════
module SES module Event_Movement
#═╦═══════════════════════════════════════════════════════════════════════════
# ║ α BEGIN CONFIGURATION
#═╩═══════════════════════════════════════════════════════════════════════════
# This determines how large the path tree can be when trying to determine a
# path. Higher values require more computation while lower values may not
# find a path -- even if one exists. A modest amount from 50-150 works well.
PATH_FINDING_DEPTH = 100
#═╦═══════════════════════════════════════════════════════════════════════════
# ║ Ω END CONFIGURATION
#═╩═══════════════════════════════════════════════════════════════════════════
end end
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▲ module SES::Event_Movement
#═╩═════════════════════════════════════════════════════════════════════════════
($imported ||= {})["SES - Event Movement"] = 1.0
if $imported["SES - Core"].nil? || $imported["SES - Core"] < 1.0
raise("SES Event Movement requires SES Core 1.0 or higher.") end
if $imported["SES - Event Memory"]
module SES module Event_Memory; @event_memory[:patrol] = :patrol end end end
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▼ class Game_Map
#═╩═════════════════════════════════════════════════════════════════════════════
class Game_Map
if $imported["SES - Event Memory"]
# Aliased to remember patrol points if SES Event Memory is in use.
alias :ses_evmov_gm_setupev :setup_events
def setup_events(*args, &block)
ses_evmov_gm_setupev(*args, &block)
$game_system.events[@map_id].each{|i, v| @events[i].patrol = v[:patrol]
} if $game_system.events[@map_id]
end
end
end
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▲ class Game_Map
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ ▼ class Game_Character
#═╩═════════════════════════════════════════════════════════════════════════════
class Game_Character < Game_CharacterBase
# Initialize the new instance variables @old_x and @old_y.
alias :ses_evmov_gc_init :initialize
def initialize(*a, &b) ses_evmov_gc_init(*a, &b); @old_x, @old_y = @x, @y end
# Aliased to provide the ability to run code whenever an event moves via the
# on_movement method. (Scripters, alias on_movement if you need this.)
alias :ses_evmov_gc_upd :update
def update(*a, &b) ses_evmov_gc_upd(*a, &b); on_movement if moved? end
# Moves the character towards a specific x and y position on the map.
def move_toward_position(x, y)
sx, sy = @x - x, @y - y
if sx != 0 || sy != 0
if sx.abs > sy.abs
sx > 0 ? move_straight(4) : move_straight(6)
(sy > 0 ? move_straight(8) : move_straight(2)) if move_failed?
else
sy > 0 ? move_straight(8) : move_straight(2)
(sx > 0 ? move_straight(4) : move_straight(6)) if move_failed?
end
end
end
# Convenience method to quickly build a path tree for an event. Original code
# by Near_Fantastica, stripped and modified by Solistra.
def find_path(x, y)
result = determine_path_tree(@x, @y, x, y)
@path_tree = result[0].is_a?(Table) ? result[0] : nil
@path_tree[@x, @y] = result[1] unless @path_tree.nil?
end
# Convenience method to build a path tree to an ancestor of Game_Character.
def find_path_to(character) find_path(character.x, character.y) end
# Moves a character along their path tree. Original version was written by
# Near_Fantastica, stripped and modified by Solistra.
def run_next_path_step
return if moving?
step = @path_tree[@x, @y]
if step <= 1 then reset_path; return
else
move_straight(6) if @path_tree[@x + 1, @y] == step - 1
move_straight(2) if @path_tree[@x, @y + 1] == step - 1
move_straight(4) if @path_tree[@x - 1, @y] == step - 1
move_straight(8) if @path_tree[@x, @y - 1] == step - 1
end
end
# Reset the path tree. Original code by Near_Fantastica, modified by Solistra.
def reset_path() remove_instance_variable(:@path_tree) end
# Creates the path tree used for path finding. Original code written by
# Near_Fantastica, modified by Solistra.
def determine_path_tree(sx, sy, ex, ey)
map = Table.new($game_map.width, $game_map.height)
map[ex, ey], depth, old_positions, new_positions = 1, 2, [[ex, ey]], []
depth.upto(SES::Event_Movement::PATH_FINDING_DEPTH) do |step|
loop do
break if old_positions[0].nil?
x, y = old_positions.shift
return [map, step] if x == sx && y + 1 == sy
if passable?(x, y, 2) && map[x, y + 1] == 0
map[x, y + 1] = step
new_positions.push([x, y + 1])
end
return [map, step] if x - 1 == sx && y == sy
if passable?(x, y, 4) && map[x - 1, y] == 0
map[x - 1, y] = step
new_positions.push([x - 1, y])
end
return [map, step] if x + 1 == sx && y == sy
if passable?(x, y, 6) && map[x + 1, y] == 0
map[x + 1, y] = step
new_positions.push([x + 1, y])
end
return [map, step] if x == sx && y - 1 == sy
if passable?(x, y, 8) && map[x, y - 1] == 0
map[x, y - 1] = step
new_positions.push([x, y - 1])
end
end
old_positions, new_positions = new_positions, []
end
end
# Movement-related convenience methods -- certainly easy enough to figure out.
def moved?() @old_x != @x || @old_y != @y end
def on_movement() reset_movement_variables end
def reset_movement_variables() @old_x, @old_y = @x, @y end
def move_failed?() !@move_succeed end
end
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▲ class Game_Character
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ ▼ class Game_Player
#═╩═════════════════════════════════════════════════════════════════════════════
class Game_Player < Game_Character
# Aliased to move the player along their path tree if one exists before any
# movement by input is processed.
alias :ses_evmov_gp_move :move_by_input
def move_by_input(*args, &block)
if @path_tree
run_next_path_step
move_failed? ? reset_path : return
end
ses_evmov_gp_move(*args, &block)
end
end
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▲ class Game_Player
#─╫─────────────────────────────────────────────────────────────────────────────
# ║ ▼ class Game_Event
#═╩═════════════════════════════════════════════════════════════════════════════
class Game_Event < Game_Character
attr_accessor :patrol
# Aliased to initialize the new instance variable @patrol, which is used to
# determine the patrol points for an event.
alias :ses_evmov_ge_init :initialize
def initialize(*args, &block)
ses_evmov_ge_init(*args, &block)
@patrol = determine_patrol_points
end
# Aliased to move events along their patrol route if a route exists and they
# are not currently running a path-finding tree.
alias :ses_evmov_ge_updmov :update_self_movement
def update_self_movement(*args, &block)
ses_evmov_ge_updmov(*args, &block)
if near_the_screen? && @stop_count > stop_count_threshold
move_type_patrol if @patrol
run_next_path_step if @path_tree
end
end
# Aliased to reset the path when this event is refreshed and to get any new
# patrol routes (or remove them if necessary).
alias :ses_evmov_ge_ref :refresh
def refresh(*args, &block)
ses_evmov_ge_ref(*args, &block)
unless @erased
reset_path if @path_tree
@patrol = determine_patrol_points
end
end
# Self-explanatory enough, but this method provides a new movement type that
# will move this event towards any ancestor of Game_Character.
def move_type_toward_character(character)
if near_character?(character)
case rand(6)
when 0..3 then move_toward_character(character)
when 4 then move_random
when 5 then move_forward
end
else move_random end
end
# Also self-explanatory, but provides a movement type that will cause this
# event to move towards a specific x and y position on the map.
def move_type_toward_position(x, y)
case rand(6)
when 0..3 then move_toward_position(x, y)
when 4 then move_random
when 5 then move_forward
end
end
# Custom movement type that will move to a specified path, moving around any
# obstacles if they exist. Warning: this recalculates the path each step,
# which is NOT efficient! Do not abuse this!
def move_type_path(x, y)
find_path(x, y) if [@x, @y] != [x, y] || move_failed?
end
# Convenience method for move_type_path which moves to an ancestor of the
# Game_Character class.
def move_type_path_to(character) move_type_path(character.x, character.y) end
# The patrol movement type. Simply uses the path finder to move from one point
# in its patrol route to the next.
def move_type_patrol
return unless @patrol
[@x,@y] == @patrol[0] ? @patrol.push(@patrol.shift) : find_path(*@patrol[0])
end
# Method used by move_type_toward_character to keep it consistent with the
# behavior of the RGSS3 default move_type_toward_player method. Can also be
# used as a simple proximity-checking method.
def near_character?(character, range = 20)
distance_x_from(character.x).abs + distance_y_from(character.y).abs < range
end
alias :in_proximity? :near_character?
# Gets patrol points from an event's comments. (Note: patrol points can also
# be manually added with $game_map.events[event_id].patrol = [route]. The
# route is just an array of arrays containing x and y values.)
def determine_patrol_points
if comments.any?{|c| c =~ /\<patrol (.+)\>/} then eval("[#{$1}]") end
end
end
#═╦═════════════════════════════════════════════════════════════════════════════
# ║ ▲ class Game_Event
#═╩═════════════════════════════════════════════════════════════════════════════
[chapter]Credit[/chapter]
Solistra (Tác giả)
Near_Fantastica
Comments
THAM GIA GROUP CỦA TTC TRÊN FACEBOOK
find_path(20,17)
và
find_path_to($game_player)
đến lúc dùng toàn báo lỗi game_interpreter vậy ?
Phải call trong Move Route nhé, nếu không là không được đâu.
find_path(20,17)
và
find_path_to(event(9))
vẫn lỗi và ko hoạt động.
$game_map.events[9] nhé
còn vụ đi tới tọa độ nữa ?
@sanggameboy
Nó ko bị lỗi nữa rồi, nó chỉ ko làm theo thôi.
Đuổi theo nhân vật thì vẫn thực hiện chứ còn đuổi theo event hoặc đến 1 tọa độ thì ko.
Đây :
[align=center]
Nhỏ khoanh tròn đấy là NPC cần ra các lệnh find path
Tìm đến người chơi ở vị trí P thì nó làm rất tốt
nhưng bắt nó tìm đến lão già số 9 trong hình thì nó ko làm theo
Lệnh lôi event này sang chỗ event kia: find_path_to(X)
trong đó X là ID. VD: ID là 00001 thì ta sẽ có find_path_to(1)
rồi, còn vụ đến tọa độ thì sao ?
find_path(20,17) is for example