Как говорят, лень - двигатель прогресса. И именно лень товарища Kian Ni (и моя тоже) привела меня в составлению этой статьи.
Как известно, VXACE всем хорош по сравнению с XP, но одна деталь таки подкачала - маппинг.
Ограниченность количества слоев и размера графики для тайлсетов в асе становится ощутимой, если хочется сделать более-менее приличную карту.
Конечно, люди выкручиваются, собирая карты в фотошопе и применяя технику параллакс-маппинга, но... лень и неудобно, и долго, и вообще!
До недавнего времени я выкручивался при помощи event-маппинга (см. соответствующую статью), но это тоже долгая, нудная и, к тому же, ограниченная техника.
Но - теперь наступило счастье! Киан нашел годный скрипт от товарища LittleDrago (снимаю и грызу шляпу перед этим неизвестным кодером - у него в блоге столько скриптовых вкусностей), который называется Drago - Multi Layer и делает то, что и обозначено в названии: позволяет создавать множество слоев для одной карты
клац.
Сейчас я на картинках объясню, как им пользоваться.
0) Добавляем скрипты от драго в проект.
Кроме самого Multi Layer и тех скриптов, что описаны на странице в его блоге (XP Map Loader v1.10 or later; Drago - Core Engine v1.42), нам пригодится его
скрипт скриншота .
Ставим первым ядро, потом XP Map Loader, далее Multi Layer и скриншотоделалку.
1) Создаем базовую карту и её слои.
Это простой шаг - тут мы добавляем карту, которая будет основной, и, как её подкарты - дочерние слои. Есть определенное правило именования дочерних слоев - в названии его должна быть подстрока "[join]".
Смотрите скриншот:
Дочерних слоев может быть столько, сколько потянет player, штуки 3-4 - легко.
2) Маппим карты.
Теперь можно маппить карту послойно. Отмечу, что слой А маппится только на основной карте, а остальные - где угодно. Порядок наложения слоев определяется порядком расположения дочерних "карт" в мейкере.
То есть, на скрине, сначала отрисуется Map001, потом Map002, и, наконец, поверх - Map003.
Чтобы маппить было удобно - Киан предложил и доработал для удобства скрипт скриншота (в аттач проекте его версия).
Маппим послойно, и каждый раз после формирования слоя, запускаем карту, скриним (F7), скрин попадает в папку Parallaxes, и его прицепляем как картинку параллакса (не забыть "показать в редакторе") для дочерних карт - и нам будет видна сборка из предыдущих слоев. Более-менее удобно.
Вот так оно проставляется
А тут показаны две дочерние карты без включенной подложки
Тут уже с подложкой
3) Тестируем, подправляем, играем
Один очень важный момент. Как бы я ни расхваливал Драго, скрипт его не так крут, как я описываю, так как он не умеет работать с разными тайлсетами (вернее, не умеет вычислять проходимости и звезды).
По идее Kian Ni, я его подпилил и устранил этот недостаток; теперь все карты (и родительская, и дочерние) могут иметь разные тайлсеты. Картинки и проходимости при этом корректно обрабатываются.
Вот так выглядит конечная версия (мапа не претендует на конкурсы, просто за 5 минут набросал из найденных тайлсетов три слоя.
Измененный скрипт multilayer
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:
# Drago - Multi Layer
# Version: 2.02
# Author : LiTTleDRAgo
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:
($imported ||= {})[:drg_multi_layer] = 2.02
core = "This script needs Drago - Core Engine ver 1.43 or above"
rmvx = "This script not for RMVX"
$imported[:drg_core_engine] || raise(core)
LiTTleDRAgo::XP || LiTTleDRAgo::VXA || raise(rmvx)
#==============================================================================
# ** Spriteset_Map
#------------------------------------------------------------------------------
# This class brings together map screen sprites, tilemaps, etc. It's used
# within the Scene_Map class.
#==============================================================================
class Spriteset_Map
#--------------------------------------------------------------------------
# * Public Instance Variables
#--------------------------------------------------------------------------
attr_sec_reader :tilemap_layer, 'Array.new'
#--------------------------------------------------------------------------
# * Alias Listing
#--------------------------------------------------------------------------
alias_method :initialize_multi_layer_cr, :initialize
alias_sec_method :update_multi_layer_cr, :update
alias_sec_method :dispose_multi_layer_cr, :dispose
#--------------------------------------------------------------------------
# * New Method : create_multi_layer
#--------------------------------------------------------------------------
def create_multi_layer
return if @tilemap.not.is_a?(Tilemap)
tilemap_layer.dispose
tilemap_layer.clear
@layer_data = $game_map.layer_data
$game_map.layer_data.each_with_index do |data,index|
temp_map = Game_Map.new
temp_map.setup($game_map.layer_subs.at(index))
tileset = LiTTleDRAgo.cache.tileset(temp_map.tileset_name)
tilemap_layer[index] = Tilemap.new(@viewport1)
tilemap_layer[index].map_data = temp_map.data
if $game_map.map_type == 'XP'
tilemap_layer[index].tileset = tileset
for i in 0..6
autotile_name = LiTTleDRAgo.cache.autotile(temp_map.autotile_names[i])
tilemap_layer[index].autotiles[i] = autotile_name
end
tilemap_layer[index].priorities = temp_map.priorities
else
load = "This script needs Drago - XP Map Loader ver 1.10 or above"
$imported[:drg_xp_map_loader] || raise(load)
temp_map.tileset.tileset_names.each_with_index do |name, i|
@tilemap_layer[index].bitmaps[i] = LiTTleDRAgo.cache.tileset(name)
end
@tilemap_layer[index].flags = temp_map.tileset.flags
end
end
end
#--------------------------------------------------------------------------
# * Aliased Method : update
#--------------------------------------------------------------------------
def update
update_multi_layer_cr
create_multi_layer if @layer_data != $game_map.layer_data
tilemap_layer.ox = @tilemap.ox
tilemap_layer.oy = @tilemap.oy
tilemap_layer.update
end
#--------------------------------------------------------------------------
# * Aliased Method : dispose
#--------------------------------------------------------------------------
def dispose
tilemap_layer.dispose
dispose_multi_layer_cr
end
#-------------------------------------------------------------------------
# * Drago - Custom Resolution
#-------------------------------------------------------------------------
if $imported[:drg_custom_resolution] && LiTTleDRAgo::XP
#-------------------------------------------------------------------------
# * Alias Listing
#-------------------------------------------------------------------------
alias_sec_method :create_tilemap_ex_multi_layer, :create_tilemap_ex
alias_sec_method :update_new_tilemap_multi_layer, :update_tilemap_ex
alias_sec_method :dispose_tilemap_ex_multi_layer, :dispose_tilemap_ex
#-------------------------------------------------------------------------
# * Aliased method: create_tilemap_ex
#-------------------------------------------------------------------------
def create_tilemap_ex
create_tilemap_ex_multi_layer
create_multi_layer_ex
end
#-------------------------------------------------------------------------
# * New method: create_multi_layer_ex
#-------------------------------------------------------------------------
def create_multi_layer_ex
@tilemap_layer_ex && @tilemap_layer_ex.flatten.dispose
@tilemap_layer_ex = []
@extra_res.each_index do |a|
@tilemap_layer_ex[a] ||= []
$game_map.layer_data.each_with_index do |d,b|
temp_map = Game_Map.new
temp_map.setup($game_map.layer_subs.at(b))
tileset = LiTTleDRAgo.cache.tileset(temp_map.tileset_name)
@tilemap_layer_ex[a][b] = Tilemap.new(@viewport_ex[a])
@tilemap_layer_ex[a][b].tileset = tileset
for i in 0..6
autotile = LiTTleDRAgo.cache.autotile(temp_map.autotile_names[i])
@tilemap_layer_ex[a][b].autotiles[i] = autotile
end
@tilemap_layer_ex[a][b].map_data = temp_map.data
@tilemap_layer_ex[a][b].priorities = temp_map.priorities
end
end
end
#-------------------------------------------------------------------------
# * Aliased method: update_new_tilemap
#-------------------------------------------------------------------------
def update_tilemap_ex
update_new_tilemap_multi_layer
@tilemap_ex && @extra_res.each_index do |a|
$game_map.layer_data.each_index do |b|
if @tilemap_layer_ex && @tilemap_layer_ex[a] &&
@tilemap_layer_ex[a][b] && @tilemap_layer_ex[a][b].not.disposed?
@tilemap_ex[a].not.disposed?
@tilemap_layer_ex[a][b].ox = @tilemap_ex[a].ox
@tilemap_layer_ex[a][b].oy = @tilemap_ex[a].oy
@tilemap_layer_ex[a][b].update
end
end
end
end
#-------------------------------------------------------------------------
# * Aliased method: dispose_tilemap_ex
#-------------------------------------------------------------------------
def dispose_tilemap_ex
@tilemap_layer_ex && @tilemap_layer_ex.flatten.dispose
dispose_tilemap_ex_multi_layer
end
end
end
#==============================================================================
# ** Game_Map
#------------------------------------------------------------------------------
# This class handles maps. It includes scrolling and passage determination
# functions. The instance of this class is referenced by $game_map.
#==============================================================================
class Game_Map
#--------------------------------------------------------------------------
# * Public Instance Variables
#--------------------------------------------------------------------------
attr_sec_accessor :layer_data, :layer_subs, 'Array.new'
attr_reader :layer_tilesets
attr_reader :map_type
#--------------------------------------------------------------------------
# * Alias Method
#--------------------------------------------------------------------------
alias_sec_method :setup_multilayer, :setup
#--------------------------------------------------------------------------
# * Aliased Method : setup
#--------------------------------------------------------------------------
def setup(*args)
setup_multilayer(*args)
@map_type ||= 'XP'
@is_load = true # hot fix
decide_map_layer(*args)
end
#--------------------------------------------------------------------------
# * Overwriten Method : decide_map_layer
#--------------------------------------------------------------------------
def decide_map_layer(*args)
return if layer_subs.include?(@map_id)
return if @is_load == nil
@layer_data, @layer_subs = [], []
@layer_tilesets = []
child_ids.each do |submap_id|
map_info = mapInfo[submap_id]
unless map_info.nil?
if map_info.parent_id == map_id &&
map_info.name.include?("[" + 'join' + "]")
then
submap = load_map_data(submap_id, @map_type=='XP' ? :rxdata : :rvdata2)
unless submap
print "Map #{submap_id} doesn't exist"
next
end
if [submap.width,submap.height] != [width,height]
raise "Dimension of child maps must be same with parent's dimension"
end
# p sprintf("tileset=%d, cur_size=%d", submap.tileset_id, data.zsize)
@layer_tilesets << submap.tileset_id
@layer_data << data
@layer_subs << submap_id
old_zsize = data.zsize
data.resize(data.xsize, data.ysize, old_zsize + 3)
for x in 0...data.xsize
for y in 0...data.ysize
if @map_type == 'XP'
data[x, y, old_zsize] = submap.data[x, y, 0]
data[x, y, old_zsize + 1] = submap.data[x, y, 1]
data[x, y, old_zsize + 2] = submap.data[x, y, 2]
else
data[x, y, old_zsize] = submap.data[x, y, 1]
data[x, y, old_zsize + 1] = submap.data[x, y, 2]
end
end
end
end
@need_redraw = true
end
end
end
# for separate passability
def check_passage(x, y, bit)
pass = true
# проверяем первую карту
all_tiles1(x, y).each do |tile_id|
flag = tileset.flags[tile_id]
if flag & 0x10 != 0 # [?]: No effect on passage
if flag & bit == 0 # [0] : Passable but star
next
end
if flag & bit == bit # [?] : Impassable
pass = false
break
end
else
if flag & bit == 0 # [0] : Passable
pass = true
break
end
if flag & bit == bit # [?] : Impassable
pass = false
break
end
end
end
if pass == false # Impassable
return false
end
idx = 0
@layer_tilesets.each do |tileset_n|
all_tiles_n(x, y, idx).each do |tile_id|
flag = $data_tilesets[tileset_n].flags[tile_id]
if flag & 0x10 != 0 # [?]: No effect on passage
if flag & bit == 0 # [0] : Passable but star
next
end
if flag & bit == bit # [?] : Impassable
pass = false
break
end
else
if flag & bit == 0 # [0] : Passable
pass = true
break
end
if flag & bit == bit # [?] : Impassable
pass = false
break
end
end
end
if pass == false # Impassable
return false
end
idx = idx +1
end
return pass
end
def all_tiles1(x, y)
tile_events_xy(x, y).collect {|ev| ev.tile_id } + layered_tiles1(x, y)
end
def layered_tiles1(x, y)
_z_data = z_data.reject {|s| s >= 3 }
_z_data.collect {|z| tile_id(x, y, z) }
end
def all_tiles_n(x, y, idx)
tile_events_xy(x, y).collect {|ev| ev.tile_id } + layered_tiles_n(x, y, idx)
end
def layered_tiles_n(x, y, idx)
idx1 = 3 + idx*3
idx2 = 5 + idx*3
# p sprintf("idx1=%d,idx2=%d",idx1,idx2)
_z_data = z_data.reject {|s| s <= idx1 || s > idx2 }
_z_data.collect {|z| tile_id(x, y, z) }
end
#--------------------------------------------------------------------------
# * If RMXP
#--------------------------------------------------------------------------
if LiTTleDRAgo::XP
#--------------------------------------------------------------------------
# * Overwriten Method : passable?
#--------------------------------------------------------------------------
def passable?(x, y, d, s = nil)
return false unless valid?(x, y)
bit = (1 << (d / 2 - 1)) & 0x0f
(event_tiles(x, y, s) + layered_tiles(x, y)).each do |tile_id|
return false if tile_id.nil?
if @passages[tile_id] & bit != 0
return false
elsif @passages[tile_id] & 0x0f == 0x0f
return false
elsif @priorities[tile_id] == 0
return true
end
end
return true
end
#--------------------------------------------------------------------------
# * Overwriten Method : bush?
#--------------------------------------------------------------------------
def bush?(x, y)
return false if @map_id == 0
layered_tiles(x, y).each do |tile_id|
return false if tile_id.nil?
passage = @passages[tile_id]
return true if passage & 0x40 == 0x40
end
return false
end
#--------------------------------------------------------------------------
# * Overwriten Method : counter?
#--------------------------------------------------------------------------
def counter?(x, y)
return false if @map_id == 0
layered_tiles(x, y).each do |tile_id|
return false if tile_id.nil?
passage = @passages[tile_id]
return true if passage & 0x80 == 0x80
end
return false
end
#--------------------------------------------------------------------------
# * Overwriten Method : terrain_tag
#--------------------------------------------------------------------------
def terrain_tag(x, y)
return 0 if @map_id == 0
layered_tiles(x, y).each do |tile_id|
return 0 if tile_id.nil?
tag = @terrain_tags[tile_id]
return tag if tag > 0
end
return 0
end
#--------------------------------------------------------------------------
# * New Method : layered_tiles
#--------------------------------------------------------------------------
def layered_tiles(x, y)
z_data.collect {|z| tile_id(x, y, z) }
end
#--------------------------------------------------------------------------
# * New Method : z_data
#--------------------------------------------------------------------------
unless method_defined?(:z_data)
def z_data
(0...data.zsize).to_a.reverse
end
end
#--------------------------------------------------------------------------
# * New Method : event_tiles
#--------------------------------------------------------------------------
def event_tiles(x, y, s=nil)
return [] unless self.respond_to?(:events_xy_nt)
e = events_xy_nt(x,y).select {|e| e.tile_id >= 0 && e != s }
e.collect {|event| event.tile_id }
end
#--------------------------------------------------------------------------
# * New Method : tile_id
#--------------------------------------------------------------------------
unless method_defined?(:tile_id)
def tile_id(x, y, z)
data[x, y, z]
end
end
end
end
И, наконец, демка, где собраны все скрипты и есть пример карты.
тут
Резюмируя все вышесказанное,
- теперь XP уходит на задний план, он со своими тремя слоями и километровыми тайлсетами уже не так крут.
- обычный маппинг и событийный маппинг в асе туда же
- за параллакс, конечно, будут люди... которым не лень маппить все в фотошопах и проставлять каждый раз ручками проходимости - но это не я
Спасибо LittleDrago и Kian Ni за помощь в создании этого способа и статьи.