Extended Map Sprite Tiles & Autotiles (EMSTA) allows inserting tile or autotile sprites on the map via script without layer limitations. It's basically like when you use an event as a tile, but in this case there's no event, so it causes much less lag.
It doesn't affect the passability, but apart from that it is like a completely normal tile. In addition, in terms of passability, it can be easily solved by assigning an empty but not passable tile.
So far I know it doesn't seem like a big deal, but what advantages does this have then?
More than 3 tiles per map coordinate without having to use an empty event (i.e. less lag)
More than 4 tiles per map coordinate; now it's unlimited.
More tile priorities; now unlimited, and it also accepts negative values.
More than 7 autotiles per tileset/map. [Check a tutorial about thathere]
The Cliff Bugfix can be used with EMSTA for more complex cliff interactions.
Battle animations applied on tile and autotile sprites, and other similar stuff.
Engine shortcuts. Assign a parameter to EMSTA sprites and apply changes on them by group.
Authors: Wecoc & Eron
First Release: February 2018
Last Version: June 2020
Terms of use
- Giving credits (Wecoc & Eron) for using this script is optional, but appreciated.
- You can repost this code.
- You can edit this code freely, and distribute the edits as well.
- This code can be used on commercial games.
Code
Ruby:
#==============================================================================# ** [XP] Extended Map Sprite Tiles & Autotiles (EMSTA)#------------------------------------------------------------------------------# Authors: Wecoc & Eron (credits are optional)# Version: 1.1#==============================================================================# This script allows inserting tile and autotile sprites on the map.# They will look like normal tiles, but won't affect the passability.# Its advantage is you can define as many as you want per coordinate, and you# can get them from another tileset, so you can break the limit of autotiles# per map. Even with that it's a bit complicated to use so I recommend limiting# it to those cases where it's really necessary.#------------------------------------------------------------------------------# Note: When placing EMSTA sprites an ID is assigned, like with events. You can# use it as reference to directly control that sprite, for example removing it.#------------------------------------------------------------------------------# 1) Define a tile sprite on the map# $game_map.set_sprite_tile(x, y, tile_id)# $game_map.set_sprite_tile(x, y, tileset_id, tile_id)# $game_map.set_sprite_tile(x, y, tileset_id, tile_id, z)# tile_id - Tile ID (position inside the tileset)# tileset_id - Tileset ID on the Database (current tileset when non-defined)# z - Tile priority (when non-defined uses default priority on the tileset)# Example: $game_map.set_sprite_tile(4, 10, 3, 384)# Note: You can also define a tag for this sprite# Example: $game_map.set_sprite_tile(4, 10, 3, 384, :tree)#------------------------------------------------------------------------------# 1) Define an autotile sprite on the map# $game_map.set_sprite_autotile(x, y, autotile_id, tile_id)# $game_map.set_sprite_autotile(x, y, tileset_id, autotile_id, tile_id)# $game_map.set_sprite_autotile(x, y, tileset_id, autotile_id, tile_id, z)# autotile_id - Autotile index on the tileset, from 1 to 7# tile_id - Subtile ID, from 0 to 47# tileset_id - Tileset ID on the Database (current tileset when non-defined)# z - Tile priority (when non-defined uses default priority on the tileset)# Example: $game_map.set_sprite_autotile(4, 10, 3, 1, 24)# Note: You can also define a tag for this sprite# Example: $game_map.set_sprite_autotile(4, 10, 3, 1, 24, :water)#------------------------------------------------------------------------------# 3) Change an autotile on the map for an EMSTA autotile sprite# (it will use the tile_id and Z of the reference autotile)# $game_map.change_autotile_sprite(x, y, layer, autotile_id)# $game_map.change_autotile_sprite(x, y, layer, tileset_id, autotile_id)# layer - Map layer, from 0 to 2# You can also define two extra arguments after the autotile_id:# clear_tile_id - ID of the tile used to erase the reference autotile# clear - Boolean defining if the reference autotile should be erased# By default their values are 0 and true respectively.# tag - Symbol used to categorize tiles, by default nil#------------------------------------------------------------------------------# 4) Change an autotile for an EMSTA sprite along the whole layer# $game_map.change_autotile_layer(autotile, layer, autotile_id)# $game_map.change_autotile_layer(autotile, layer, tileset_id, autotile_id)# autotile - ID of the reference autotile, from 1 to 7# It works exactly like in the previous case but along an entire layer#==============================================================================# FAQ## - How do I get an EMSTA sprite previously inserted?## Script call for a Tile:# $game_map.sprite_tiles[ID]# Script call for an Autotile:# $game_map.sprite_autotiles[ID]## - How do I get it if I don't know its ID but I know coordinate and tile ID?# This example was made with a Tile. Change X, Y and TILE_ID## tiles = $game_map.sprite_tiles.values# tile = tiles.select do |sprite|# sprite[0] == X# sprite[1] == Y# sprite[3] == TILE_ID# end[0]## If tile is nil that means there's no sprite with those parameters## - Once I get the tile how do I get its ID?# id = $game_map.sprite_tiles.key(tile)## - How do I get all the sprites that share a tag?# tiles = $game_map.sprite_tiles.values# tiles = tiles.select do |sprite|# sprite[-1] == TAG# end## - What incompatibilities does this script have?# It should be compatible with practically everything#==============================================================================#==============================================================================# ** EronAutotileCache#==============================================================================module EronAutotileCache @@hash = {} module_function def hash @@hash endend#==============================================================================# ** Hash#==============================================================================class Hash def key(value) for k in self.keys return k if self[k] == value end return nil endend#==============================================================================# ** Game_Map#==============================================================================class Game_Map #-------------------------------------------------------------------------- attr_accessor :sprite_tiles, :sprite_autotiles #-------------------------------------------------------------------------- # * Setup #-------------------------------------------------------------------------- alias sprite_tile_setup setup unless $@ def setup(*args) sprite_tile_setup(*args) @sprite_tiles = {} @sprite_autotiles = {} end #-------------------------------------------------------------------------- # * [Internal] Get the next non-used index for a Tile or Autotile sprite #-------------------------------------------------------------------------- def get_next_sprite_id(hash) return 1 if hash.size == 0 for i in 1..(hash.size + 1) return i if hash.nil? end end #-------------------------------------------------------------------------- # * Show a Tile sprite on the map #-------------------------------------------------------------------------- def set_sprite_tile(x, y, *args) tag = args.pop if args[-1].is_a?(Symbol) || args[-1].nil? case args.size when 1 # tile_id tileset_id = @map.tileset_id tile_id = args[0] z = nil else # tileset_id, tile_id[, z] tileset_id = args[0] tile_id = args[1] z = args[2] end id = get_next_sprite_id(@sprite_tiles) if z == nil tileset = $data_tilesets[tileset_id] z = tileset.priorities[tile_id] end return if @sprite_tiles.values.include?([x, y, tileset_id, tile_id, z, tag]) @sprite_tiles[id] = [x, y, tileset_id, tile_id, z, tag] return true end #-------------------------------------------------------------------------- # * Show an Auotitle sprite on the map #-------------------------------------------------------------------------- def set_sprite_autotile(x, y, *args) tag = args.pop if args[-1].is_a?(Symbol) || args[-1].nil? case args.size when 2 # autotile_id, tile_id tileset_id = @map.tileset_id autotile_id = args[0] tile_id = args[1] else # tileset_id, autotile_id, tile_id[, z] tileset_id = args[0] autotile_id = args[1] tile_id = args[2] z = args[3] end id = get_next_sprite_id(@sprite_autotiles) if z == nil tileset = $data_tilesets[tileset_id] z = tileset.priorities[autotile_id * 48 + tile_id] end ary = @sprite_autotiles.values return if ary.include?([x, y, tileset_id, autotile_id, tile_id, z, tag]) @sprite_autotiles[id] = [x, y, tileset_id, autotile_id, tile_id, z, tag] return true end #-------------------------------------------------------------------------- # * Change an autotile to an EMSTA sprite on a specific coordinate #-------------------------------------------------------------------------- def change_autotile_sprite(x, y, layer, *args) tag = args.pop if args[-1].is_a?(Symbol) || args[-1].nil? current_autotile = self.data[x, y, layer] return if current_autotile == 0 or current_autotile >= 384 clear = true clear_tile = 0 if args[-1].is_a?(TrueClass) or args[-1].is_a?(FalseClass) clear = args.pop clear_tile = args.pop end case args.size when 1 # autotile_id tileset_id = @map.tileset_id autotile_id = args[0] else # tileset_id, autotile_id tileset_id = args[0] autotile_id = args[1] end tile_id = current_autotile % 48 z = $data_tilesets[tileset_id].priorities[current_autotile] set_sprite_autotile(x, y, tileset_id, autotile_id, tile_id, z, tag) self.data[x, y, layer] = clear_tile if clear end #-------------------------------------------------------------------------- # * Change an autotile to an EMSTA sprite along a map layer #-------------------------------------------------------------------------- def change_autotile_layer(autotile, layer, *args) for iy in 0...self.height for ix in 0...self.width current = (self.data[ix, iy, layer].to_f / 48).floor if current == autotile change_autotile_sprite(ix, iy, layer, *args) end end end endend#==============================================================================# ** Sprite_Tile#==============================================================================class Sprite_Tile < RPG::Sprite #-------------------------------------------------------------------------- attr_accessor :real_x, :real_y, :real_z #-------------------------------------------------------------------------- # * Update #-------------------------------------------------------------------------- def update super self.x = (@real_x - $game_map.display_x + 3) / 4 + 16 self.y = (@real_y - $game_map.display_y + 3) / 4 + 32 self.z = @real_z + (@real_y - $game_map.display_y + 3) / 4 + 32 endend#==============================================================================# ** Sprite_Autotile#==============================================================================class Sprite_Autotile < Sprite_Tile #-------------------------------------------------------------------------- attr_accessor :complete_bitmap, :frame, :frames #-------------------------------------------------------------------------- # * Update #-------------------------------------------------------------------------- def update super if @frames > 1 && @current_frame != @frame @current_frame = @frame refresh_bitmap end end #-------------------------------------------------------------------------- # * Refresh Bitmap #-------------------------------------------------------------------------- def refresh_bitmap self.bitmap = Bitmap.new(32, 32) self.bitmap.blt(0, 0, @complete_bitmap, Rect.new(@frame * 32, 0, 32, 32)) endend#==============================================================================# ** Spriteset_Map#==============================================================================class Spriteset_Map #-------------------------------------------------------------------------- # * Initialize #-------------------------------------------------------------------------- alias sprite_tile_ini initialize unless $@ def initialize @sprite_tiles = [] @sprite_autotiles = [] @sprite_tile_copy = {} @sprite_autotile_copy = {} sprite_tile_ini end #-------------------------------------------------------------------------- # * Dispose #-------------------------------------------------------------------------- alias sprite_tile_dis dispose unless $@ def dispose sprite_tile_dis @sprite_tiles.each {|sprite| sprite.dispose} @sprite_autotiles.each {|sprite| sprite.dispose} end #-------------------------------------------------------------------------- # * Refresh Sprite Tiles #-------------------------------------------------------------------------- def refresh_sprite_tiles for sprite in @sprite_tiles sprite.dispose end @sprite_tiles.clear for i in $game_map.sprite_tiles.keys tile = $game_map.sprite_tiles sprite = Sprite_Tile.new(@viewport1) sprite.real_x = tile[0] * 128 sprite.real_y = tile[1] * 128 sprite.ox = 16 sprite.oy = 32 tileset = $data_tilesets[tile[2]] sprite.bitmap = RPG::Cache.tile(tileset.tileset_name, tile[3], 0) sprite.real_z = tile[4] * 32 @sprite_tiles.push(sprite) end end #-------------------------------------------------------------------------- DECONSTRUCT_AUTOTILE_PARTS = [27, 28, 5, 28, 27, 6, 5, 6, 27, 28, 5, 28, 27, 6, 5, 6, 33, 34, 33, 34, 33, 34, 33, 34, 33, 12, 33, 12, 33, 12, 33, 12, 27, 28, 5, 28, 27, 6, 5, 6, 27, 28, 5, 28, 27, 6, 5, 6, 11, 34, 11, 34, 11, 34, 11, 34, 11, 12, 11, 12, 11, 12, 11, 12, 25, 26, 25, 6, 25, 26, 25, 6, 15, 16, 15, 16, 15, 16, 15, 16, 31, 32, 31, 32, 31, 12, 31, 12, 21, 22, 21, 12, 11, 22, 11, 12, 29, 30, 29, 30, 5, 30, 5, 30, 39, 40, 5, 40, 39, 6, 5, 6, 35, 36, 11, 36, 35, 36, 11, 36, 45, 46, 45, 46, 45, 46, 45, 46, 25, 30, 15, 16, 13, 14, 13, 14, 17, 18, 17, 18, 41, 42, 5, 42, 31, 36, 45, 46, 19, 20, 19, 12, 23, 24, 11, 24, 47, 48, 47, 48, 37, 38, 37, 6, 13, 18, 13, 14, 37, 42, 17, 18, 13, 18, 1, 2, 43, 44, 43, 44, 19, 24, 43, 44, 43, 48, 47, 48, 43, 48, 7, 8] #-------------------------------------------------------------------------- # * Refresh Sprite Autotiles #-------------------------------------------------------------------------- def refresh_sprite_autotiles for sprite in @sprite_autotiles sprite.dispose end @sprite_autotiles.clear for i in $game_map.sprite_autotiles.keys autotile = $game_map.sprite_autotiles sprite = Sprite_Autotile.new(@viewport1) sprite.real_x = autotile[0] * 128 sprite.real_y = autotile[1] * 128 sprite.ox = 16 sprite.oy = 32 sprite.frame = 0 tileset = $data_tilesets[autotile[2]] autotile_name = tileset.autotile_names[autotile[3] - 1] bitmap = RPG::Cache.autotile(autotile_name) case bitmap.height when 32 sprite.complete_bitmap = bitmap sprite.frames = bitmap.width / 32 when 128 frames = [bitmap.width / 96, 1].max if EronAutotileCache.hash.include?(autotile_name) result = EronAutotileCache.hash[autotile_name] else result = Bitmap.new(256, 192 * frames) deconstruct_autotile(bitmap, result, frames) EronAutotileCache.hash[autotile_name] = result end bitmap = Bitmap.new(32 * frames, 32) for f in 0...frames ix = (autotile[4] % 8) * 32 iy = ((autotile[4].to_f / 8).floor) * 32 bitmap.blt(f * 32, 0, result, Rect.new(ix, iy + 192 * f, 32, 32)) end sprite.complete_bitmap = bitmap sprite.frames = frames end sprite.bitmap = sprite.complete_bitmap if sprite.frames == 1 sprite.real_z = autotile[5] * 32 @sprite_autotiles.push(sprite) end end #-------------------------------------------------------------------------- # * [Internal] Deconstruct Autotile #-------------------------------------------------------------------------- def deconstruct_autotile(image, result, frames) t = DECONSTRUCT_AUTOTILE_PARTS for f in 0...frames fx = 96 * f fy = 192 * f bitmaps = [nil] for iy in 0...8 for ix in 0...6 bitmap = Bitmap.new(16, 16) bitmap.blt(0, 0, image, Rect.new(fx + 16 * ix, 16 * iy, 16, 16)) bitmaps << bitmap end end for iy in 0...12 for ix in 0...16 index = ix + iy * 16 bitmap = bitmaps[t[index]] result.blt(ix * 16, fy + iy * 16, bitmap, bitmap.rect) end end end return result end #-------------------------------------------------------------------------- # * Update #-------------------------------------------------------------------------- alias sprite_tile_upd update unless $@ def update if @sprite_tile_copy != $game_map.sprite_tiles @sprite_tile_copy = $game_map.sprite_tiles.clone refresh_sprite_tiles end if @sprite_autotile_copy != $game_map.sprite_autotiles @sprite_autotile_copy = $game_map.sprite_autotiles.clone refresh_sprite_autotiles end @autotile_frame = 0 if @autotile_frame.nil? @autotile_frame += 1 if @autotile_frame % 16 == 0 @sprite_autotiles.each do |sprite| sprite.frame = (@autotile_frame / 16) % sprite.frames end end for sprite in @sprite_tiles + @sprite_autotiles sprite.update end sprite_tile_upd endend