SLIP INTO RUBY - UNDER THE HOOD PART 14: SPRITESETS

In which we collect sprites together in neat little rows.

  • Trihan
  • 01/27/2017 02:55 PM
  • 3186 views
Fans of sport! You join me on a most auspicious day: the day we finish sifting through the classes responsible for all the wonderful RPG things you see with your eye-stalks. It's time, yet again, for



Today we're going to round off the sprite class breakdown by looking at the three Spriteset classes. These combine sprites together into various effects: namely weather, the map, and battle displays. Let's do it.

Spriteset_Weather

This class is, as the name suggests, for weather effects. In VX Ace there are three kinds of weather: rain, storm, and snow. This spriteset is used in Spriteset_Map, and has four public instance variables:

attr_accessor :type                     # Weather type
  attr_accessor :ox                       # X coordinate of origin
  attr_accessor :oy                       # Y coordinate of orgin
  attr_reader   :power                    # Intensity


We've got three attr_accessors: :type for the weather type, :ox for the x origin, and :oy for the y origin. We also have an attr_reader :power, which is the intensity of the effect.

def initialize(viewport = nil)
    @viewport = viewport
    init_members
    create_rain_bitmap
    create_storm_bitmap
    create_snow_bitmap
  end


The constructor takes a parameter viewport which defaults to nil. First, we set @viewport to the passed-in value. We call init_members, which initialises the member variables as we'll see shortly. We also call create_rain_bitmap, create_storm_bitmap and create_snow_bitmap, which creates the sprites for each effect.

def init_members
    @type = :none
    @ox = 0
    @oy = 0
    @power = 0
    @sprites = []
  end


The init_members method initialises instance variables. @type is set to :none, @ox, @oy and @power are set to 0, and @sprites is initialised as an empty array, which will hold the list of sprites used to achieve the effects.

def dispose
    @sprites.each {|sprite| sprite.dispose }
    @rain_bitmap.dispose
    @storm_bitmap.dispose
    @snow_bitmap.dispose
  end


The dispose method iterates through each element of @sprite with the iteration variable "sprite" and calls its dispose method. Then, @rain_bitmap, @storm_bitmap and @snow_bitmap are disposed to free up the memory their associated objects were occupying.

def particle_color1
    Color.new(255, 255, 255, 192)
  end


This method defines the first colour used for weather particles, and returns a new Color object with 255 on R, G and B (white) and 192 alpha, so it will be partially transparent.

def particle_color2
    Color.new(255, 255, 255, 96)
  end


This method defines the second colour used for weather particles, and returns a new Color objects with 255 on R, G and B (white) and 96 alpha, so it will be very transparent.

def create_rain_bitmap
    @rain_bitmap = Bitmap.new(7, 42)
    7.times {|i| @rain_bitmap.fill_rect(6-i, i*6, 1, 6, particle_color1) }
  end


The method for creating a rain bitmap first sets @rain_bitmap to a new 7x42 Bitmap object. Then, we have a 7-iteration loop using the iteration variable "i" and calling fill_rect to draw a "raindrop" in the first particle colour. A drop in the rain effect is just 7 vertical 6-pixel lines which start at the top right, with each line being drawn 1 pixel further left than the one before it, ending at the bottom left. The result of each loop iteration is shown in the image below.



def create_storm_bitmap
    @storm_bitmap = Bitmap.new(34, 64)
    32.times do |i|
      @storm_bitmap.fill_rect(33-i, i*2, 1, 2, particle_color2)
      @storm_bitmap.fill_rect(32-i, i*2, 1, 2, particle_color1)
      @storm_bitmap.fill_rect(31-i, i*2, 1, 2, particle_color2)
    end
  end


Creating a storm bitmap is pretty similar to rain, but the line is drawn slightly differently since the raindrops are angled more. The bitmap we create is 34x64, and our drawing loop is 32 iterations. The drop still starts at the top right and ends at the bottom left.

There are three calls to fill_rect per iteration, as storm drops are thicker than rain and we need to draw 3 small lines per "section". Each section consists of a vertical line of 2 pixels in the second particle colour, then a vertical line of 2 pixels in the first particle colour, then another vertical line of 2 pixels in the second colour. The result of this is shown below.



def create_snow_bitmap
    @snow_bitmap = Bitmap.new(6, 6)
    @snow_bitmap.fill_rect(0, 1, 6, 4, particle_color2)
    @snow_bitmap.fill_rect(1, 0, 4, 6, particle_color2)
    @snow_bitmap.fill_rect(1, 2, 4, 2, particle_color1)
    @snow_bitmap.fill_rect(2, 1, 2, 4, particle_color1)
  end


This method creates the bitmaps for the snow effect. Each snowflake is a 6x6 slightly shaded circle and is drawn via four calls to fill_rect. The process for this is shown below.



def power=(power)
    @power = power
    (sprite_max - @sprites.size).times { add_sprite }
    (@sprites.size - sprite_max).times { remove_sprite }
  end


This method sets the intensity of the weather, and takes one parameter, power. First, @power is set to the passed-in value. Then, we call add_sprite a number of times equal to the maximum number of sprites minus the number we already have. Finally, we call remove_sprite a number of times equal to the number of sprites we have minus the maximum number. remove_sprite will probably never actually be called, since we'll only ever be adding as many sprites as it takes to reach the maximum anyway.

You may not remember from one of the previous articles, but this method is called by Game_Screen using an algorithm to gradually ramp up the power value proportional to the duration. That's why weather starts off with just a few particles and builds up to whatever power you chose on the slider.

def sprite_max
    (@power * 10).to_i
  end


This method determines the maximum number of sprites, and is currently hardcoded as @power multiplied by 10. In other words, each point of power on the slider will allow for an additional 10 weather particles, making the absolute maximum 90.

def add_sprite
    sprite = Sprite.new(@viewport)
    sprite.opacity = 0
    @sprites.push(sprite)
  end


This method adds a particle sprite. Creates a new instance of Sprite passing in the spriteset's viewport, sets its opacity to 0, and pushes it to the @sprites array.

def remove_sprite
    sprite = @sprites.pop
    sprite.dispose if sprite
  end


This method deletes a particle sprite. Removes and returns the last element of @sprites, storing it in the local variable "sprite", then calls its dispose method if it is not nil.

def update
    update_screen
    @sprites.each {|sprite| update_sprite(sprite) }
  end


The frame update method calls update_screen, which we'll look at in a sec, and then iterates through each element of @sprites with the iteration variable "sprite", using it as an argument when calling update_sprite.

def update_screen
    @viewport.tone.set(-dimness, -dimness, -dimness)
  end


This method updates the screen, and simply sets the R, G and B values of the viewport's tone to the negative of the value returned by calling the dimness method, which we're about to look at.

def dimness
    (@power * 6).to_i
  end


This method gets the dimness for the screen's RGB values, and returns @power multiplied by 6 as an integer. Effectively this means that dimness will start at (-0, -0, -0) and decrease by 6 for every point of power until reaching (-54, -54, -54) at 9 power, which will result in the screen darkening slightly over time as the weather effect gets more intense.

def update_sprite(sprite)
    sprite.ox = @ox
    sprite.oy = @oy
    case @type
    when :rain
      update_sprite_rain(sprite)
    when :storm
      update_sprite_storm(sprite)
    when :snow
      update_sprite_snow(sprite)
    end
    create_new_particle(sprite) if sprite.opacity < 64
  end


The update_sprite method takes one parameter, "sprite". We set the sprite's ox to the ox of the spriteset, and the sprite's oy to the oy of the spriteset. Then we have a case statement for @type. When :rain, we call update_sprite_rain, passing in the sprite. When :storm, we do the same with update_sprite_storm. When :snow, we do the same with update_sprite_snow. Finally, we call create_new particle, passing in sprite, if the sprite's opacity is less than 64 (in other words, it's nearly disappeared).

def update_sprite_rain(sprite)
    sprite.bitmap = @rain_bitmap
    sprite.x -= 1
    sprite.y += 6
    sprite.opacity -= 12
  end


This method updates rain sprites and takes one parameter, "sprite". Sets the sprite's bitmap to @rain_bitmap (which will essentially make it a 7x42 angled line), decrements its x coordinate by 1, adds 6 to its y, and reduces its opacity by 12. This means that every frame the raindrop will move 1 pixel left, 6 pixels downwards, and become slightly more transparent.

def update_sprite_storm(sprite)
    sprite.bitmap = @storm_bitmap
    sprite.x -= 3
    sprite.y += 6
    sprite.opacity -= 12
  end


This method updates storm sprites and takes one parameter, "sprite". Sets the sprite's bitmap to @storm_bitmap (making it a 34x64 line), decrements its x coordinate by 3, adds 6 to its y, and reduces its opacity by 12. More or less the same as the rain sprite update except in this case we're moving 3 pixels to the left per frame instead of 1.

def update_sprite_snow(sprite)
    sprite.bitmap = @snow_bitmap
    sprite.x -= 1
    sprite.y += 3
    sprite.opacity -= 12
  end


This method updates snow sprites and takes one parameter, "sprite". Sets the sprite's bitmap to @snow_bitmap (making it a 6x6 shaded circle), decrements its x coordinate by 1, adds 3 to its 6, and reduces its opacity by 12. This will cause it to move slowly left and slightly downwards, slower than rain or storm. Finally, we reduce opacity by 12.

def create_new_particle(sprite)
    sprite.x = rand(Graphics.width + 100) - 100 + @ox
    sprite.y = rand(Graphics.height + 200) - 200 + @oy
    sprite.opacity = 160 + rand(96)
  end


This method creates a new particle and takes one parameter, "sprite". Sets the sprite's x coordinate to a random number between 0 and 100 more than the screen width, minus 100, plus @ox. Then sets the y coordinate to a random number between 0 and 200 more than the screen height, minus 200, plus @oy. This basically allows particles to start a bit off-screen. Opacity is set to 160 plus a random number between 0 and 96, giving a range of 160-255. This is why some raindrops appear more faint than others, and is what gives the weather a pseudo-3D effect.

Spriteset_Map

This class brings together all of the sprites you see on screen, the tilesets, the pictures, shadows, timers, parallax backgrounds...basically it's responsible for creating the entire appearance of each map. That's a lot of responsibility for one class, but hopefully once we're done you'll have a full understanding of everything it's doing behind the scenes.

def initialize
    create_viewports
    create_tilemap
    create_parallax
    create_characters
    create_shadow
    create_weather
    create_pictures
    create_timer
    update
  end


As you should be used to seeing by now, the constructor for this class is essentially just a wrapper for calls to a bunch of other creation methods and the update method.

def create_viewports
    @viewport1 = Viewport.new
    @viewport2 = Viewport.new
    @viewport3 = Viewport.new
    @viewport2.z = 50
    @viewport3.z = 100
  end


This method creates the 3 viewports that make up a map. When no argument is specified in the call to Viewport.new, as is the case here, its dimensions are the size of the screen. We set the z of @viewport2 to 50, and the z of @viewport3 to 100, meaning the viewports are "layered"; viewport1 is on the bottom, viewport2 in the middle, and viewport3 on top.

Note that the z coordinate of a viewport takes precedence over that of a sprite being displayed on that viewport, so even if you set the z coordinate of something in viewport3 to, say, 25, it'll still appear above everything in viewport2.

def create_tilemap
    @tilemap = Tilemap.new(@viewport1)
    @tilemap.map_data = $game_map.data
    load_tileset
  end


In the method for creating the Tilemap (a class containing all of the map data), we can see that it's using viewport1. Its map_data property, which is a 3-dimensional Table, is set to $game_map.data, which contains the data from the map's rvdata2 file. Finally, we call load_tileset, which we'll look at now.

def load_tileset
    @tileset = $game_map.tileset
    @tileset.tileset_names.each_with_index do |name, i|
      @tilemap.bitmaps[i] = Cache.tileset(name)
    end
    @tilemap.flags = @tileset.flags
  end


The method for loading a tileset is essentially just plugging values into instance variables. @tileset is set to the tileset property of $game_map, which if you'll remember from that lesson returns $data_tilesets[@tileset_id].

We iterate through the tileset_names property with an index, using the iteration variable "name" and index variable "i". In each iteration, we set the bitmaps property of the element in tilemap with index i to the cached tileset with a name corresponding to that of the current tileset.

Essentially this goes through the various tileset graphics from the database (A1, A2, A3, A4, A5, B, C, D and E) and loads up the graphic files you chose.

Following this each loop, we set the flags property of @tilemap to the flags property of @tileset. Flags denote passability, ladder tile, bush tile, damaging floor, terrain tag etc. and the possible values are outlined in the help file.

def create_parallax
    @parallax = Plane.new(@viewport1)
    @parallax.z = -100
  end


This method creates the canvas for a parallax background, by creating a new Plane on viewport1. As per the help file, Planes are special sprites which tile across the entire screen. Its z is set to -100 so that it will appear below the tilemap.

def create_characters
    @character_sprites = []
    $game_map.events.values.each do |event|
      @character_sprites.push(Sprite_Character.new(@viewport1, event))
    end
    $game_map.vehicles.each do |vehicle|
      @character_sprites.push(Sprite_Character.new(@viewport1, vehicle))
    end
    $game_player.followers.reverse_each do |follower|
      @character_sprites.push(Sprite_Character.new(@viewport1, follower))
    end
    @character_sprites.push(Sprite_Character.new(@viewport1, $game_player))
    @map_id = $game_map.map_id
  end


This method creates the character sprites that represent the player, followers, and events.

First, @character_sprites is initialised as an empty array.

We iterate through each value in the events hash of $game_map, with the iteration variable "event" and in this loop we push a new instance of Sprite_Character to the @character_sprites array, passing in @viewport1 and the event value (this is an instance of Game_Event). We do the same with the vehicles property, which creates the sprites for the boat, ship and airship. There's another each loop for followers, but this one pushes them in reverse order so that followers closer to the player will appear on top (if we didn't have this, because the z coordinate of the "last" party member would be higher than the others, they would appear on top, causing the follower display to look weird). The last sprite we push to the array is, of course, $game_player. Finally, @map_id is set to the map_id property of $game_map.

def create_shadow
    @shadow_sprite = Sprite.new(@viewport1)
    @shadow_sprite.bitmap = Cache.system("Shadow")
    @shadow_sprite.ox = @shadow_sprite.bitmap.width / 2
    @shadow_sprite.oy = @shadow_sprite.bitmap.height
    @shadow_sprite.z = 180
  end


This method creates the shadow for the airship.

First, we create a new Sprite in @viewport1 and assign it to @shadow_sprite. We set its bitmap property to the Cached system graphic called "Shadow", and set its origin to bottom centre. Its z coordinate is set to 180, meaning it will appear above everything else in viewport1.

def create_weather
    @weather = Spriteset_Weather.new(@viewport2)
  end


This method creates the spriteset for the weather, using @viewport2. This means that weather effects will appear above the tiles and characters.

def create_pictures
    @picture_sprites = []
  end


This method creates the picture sprites, or rather it initialises @picture_sprites as an empty array. The actual creation is done in the picture update method, as we'll see later.

def create_timer
    @timer_sprite = Sprite_Timer.new(@viewport2)
  end


This method creates the sprite for the in-game timer on @viewport2, meaning it appears above tiles and characters.

def dispose
    dispose_tilemap
    dispose_parallax
    dispose_characters
    dispose_shadow
    dispose_weather
    dispose_pictures
    dispose_timer
    dispose_viewports
  end


The dispose method for the spriteset is simply a wrapper for calls to the methods that dispose of everything else.

def dispose_tilemap
    @tilemap.dispose
  end


Disposes the tilemap object and frees up the memory from it.

def dispose_parallax
    @parallax.bitmap.dispose if @parallax.bitmap
    @parallax.dispose
  end


First disposes the parallax bitmap if one exists, then disposes the parallax object.

def dispose_characters
    @character_sprites.each {|sprite| sprite.dispose }
  end


Disposes characters by iterating through each element of @character_sprites with "sprite" and calling its dispose method.

def dispose_shadow
    @shadow_sprite.dispose
  end


Disposes the airship shadow sprite.

def dispose_weather
    @weather.dispose
  end


Disposes weather effects.

def dispose_pictures
    @picture_sprites.compact.each {|sprite| sprite.dispose }
  end


This method disposes of the game's pictures. Iterates through each element of @picture_sprites (.compact removes nil elements) and disposes it.

def dispose_timer
    @timer_sprite.dispose
  end


This method disposes the timer sprite.

def dispose_viewports
    @viewport1.dispose
    @viewport2.dispose
    @viewport3.dispose
  end


This method disposes the three viewports.

def refresh_characters
    dispose_characters
    create_characters
  end


This method refreshes characters, by first disposing them and then creating them again.

def update
    update_tileset
    update_tilemap
    update_parallax
    update_characters
    update_shadow
    update_weather
    update_pictures
    update_timer
    update_viewports
  end


The frame update method is, as usual, a wrapper for calls to other update methods.

def update_tileset
    if @tileset != $game_map.tileset
      load_tileset
      refresh_characters
    end
  end


This method updates the tileset. If @tileset is different from that of $game_map, we call load_tileset and refresh_characters. That's about it really.

def update_tilemap
    @tilemap.map_data = $game_map.data
    @tilemap.ox = $game_map.display_x * 32
    @tilemap.oy = $game_map.display_y * 32
    @tilemap.update
  end


This method updates the tilemap (not to be confused with the tileset). We set @tilemap's map_data property to that of $game_map, its ox to the map's display x coordinate multiplied by 32, the same for oy and the y coordinate, and then call update on the tilemap. Updating the origins is what allows the map to "scroll" as the player moves; without updating these values, the characters would appear to be walking but the tiles would stay in place. As per the help file, calling the update method updates autotile animations and the like.

def update_parallax
    if @parallax_name != $game_map.parallax_name
      @parallax_name = $game_map.parallax_name
      @parallax.bitmap.dispose if @parallax.bitmap
      @parallax.bitmap = Cache.parallax(@parallax_name)
      Graphics.frame_reset
    end
    @parallax.ox = $game_map.parallax_ox(@parallax.bitmap)
    @parallax.oy = $game_map.parallax_oy(@parallax.bitmap)
  end


This method updates the parallax background.

If the parallax name is different from the one in $game_map, we update its value. If the parallax has a bitmap, we dispose it, then we get the named parallax from Cache and assign it to the bitmap. We call the frame_reset method of Graphics to reset game refresh timing.

Whether the name has changed or not, we update the ox and oy properties of the parallax to those of $game_map, passing in the bitmap as an argument; this updates the position of the background appropriately.

def update_characters
    refresh_characters if @map_id != $game_map.map_id
    @character_sprites.each {|sprite| sprite.update }
  end


This method updates characters. Calls refresh_characters if the map ID has changed from the one in $game_map, then iterates through each element of @character_sprites and calls its update method.

def update_shadow
    airship = $game_map.airship
    @shadow_sprite.x = airship.screen_x
    @shadow_sprite.y = airship.screen_y + airship.altitude
    @shadow_sprite.opacity = airship.altitude * 8
    @shadow_sprite.update
  end


This method updates the airship's shadow.

The local variable airship is set to the airship property of $game_map, then its x is set to the screen x of the airship. Its y is set to the airship's screen y plus the airship's altitude (so that the shadow moves down at the same rate as the airship moves up, or in other words stays still as the airship is taking off). Its opacity is set to the altitude multiplied by 8, so the higher the airship is the fainter the shadow. Finally, we call the update method on the sprite.

def update_weather
    @weather.type = $game_map.screen.weather_type
    @weather.power = $game_map.screen.weather_power
    @weather.ox = $game_map.display_x * 32
    @weather.oy = $game_map.display_y * 32
    @weather.update
  end


This method updates weather effects.

The type and power properties of @weather are set to the same values in $game_map.screen. Its ox and oy are set to 32 times their display values from $game_map, then we call @weather's update method.

def update_pictures
    $game_map.screen.pictures.each do |pic|
      @picture_sprites[pic.number] ||= Sprite_Picture.new(@viewport2, pic)
      @picture_sprites[pic.number].update
    end
  end


This method updates the picture sprites. We iterate through each picture in $game_map.screen with "pic" then return either the element of @picture_sprites with the index of the picture's number, or a new instance of Sprite_Picture passing in @viewport2 and pic. ||= is shorthand for "return the object on the left if it exists or the object on the right otherwise". Then we update the element.

def update_timer
    @timer_sprite.update
  end


This method updates the timer sprite, and simply calls the update method of @timer_sprite.

def update_viewports
    @viewport1.tone.set($game_map.screen.tone)
    @viewport1.ox = $game_map.screen.shake
    @viewport2.color.set($game_map.screen.flash_color)
    @viewport3.color.set(0, 0, 0, 255 - $game_map.screen.brightness)
    @viewport1.update
    @viewport2.update
    @viewport3.update
  end


This method updates the viewports. @viewport1's tone is set to that of $game_map.screen, and its ox is set to the shake property of $game_map.screen (which is what actually shakes the screen in the first place. Without this, all the other stuff related to screen shake does literally nothing). @viewport2 has its colour set to the flash_color property of $game_map.screen, @viewport3 has its colour set to black with an alpha equal to 255 minus the screen brightness, then all three viewports are updated (viewport 3 is used for fade in and fade out effects).

Spriteset_Battle

This class deals with all the sprites used in battle, and is used, funnily enough, in Scene_Battle.

def initialize
    create_viewports
    create_battleback1
    create_battleback2
    create_enemies
    create_actors
    create_pictures
    create_timer
    update
  end


The constructor is just your standard wrapper for calls to methods that create the various objects, and then a call to update.

def create_viewports
    @viewport1 = Viewport.new
    @viewport2 = Viewport.new
    @viewport3 = Viewport.new
    @viewport2.z = 50
    @viewport3.z = 100
  end


As with the map, we create 3 viewports, with the second one at z 50 and the third at z 100.

def create_battleback1
    @back1_sprite = Sprite.new(@viewport1)
    @back1_sprite.bitmap = battleback1_bitmap
    @back1_sprite.z = 0
    center_sprite(@back1_sprite)
  end


This method creates the first battle background (floor). We set @back1_sprite to a new instance of Sprite passing in viewport 1, and set its bitmap to the result of calling battleback1_bitmap, which we'll see shortly. Its z is set to 0 so it appears on the bottom, and finally we call center_sprite passing in the new sprite as an argument.

def create_battleback2
    @back2_sprite = Sprite.new(@viewport1)
    @back2_sprite.bitmap = battleback2_bitmap
    @back2_sprite.z = 1
    center_sprite(@back2_sprite)
  end


This method creates the second battle background (wall). Pretty much identical to the method above except we set z to 1 instead of 0, so the walls appear above the floor.

def battleback1_bitmap
    if battleback1_name
      Cache.battleback1(battleback1_name)
    else
      create_blurry_background_bitmap
    end
  end


This method gets the bitmap for the floor background. If battleback1_name doesn't return nil, we return battleback1 from the Cache, passing in the battleback's filename. Otherwise, we call create_blurry_background_bitmap (basically, if a battle background floor hasn't been set, one will be created as a blurry view of the current map).

def battleback2_bitmap
    if battleback2_name
      Cache.battleback2(battleback2_name)
    else
      Bitmap.new(1, 1)
    end
  end


This method gets the bitmap for the wall background. More or less the same as above, but in the case that battleback2_name returns nil, we create a new 1x1 Bitmap.

def create_blurry_background_bitmap
source = SceneManager.background_bitmap
bitmap = Bitmap.new(640, 480)
bitmap.stretch_blt(bitmap.rect, source, source.rect)
bitmap.radial_blur(120, 16)
bitmap
end

This method creates a battle background bitmap as a blurry copy of the map the player is on. Sets a local variable called source to the return value of the background_bitmap method in SceneManager, creates a new 640x480 Bitmap, performed a stretched block transfer into the rect of the bitmap from the rect of the source (essentially, this copies a snapshot of the map into the new 640x480 canvas) then performs a radial blur on it with angle 120 degrees and division 16 (the exact nuances of radial blur are beyond the scope of this series, but suffice to say this makes a kind of circular blurry version of the map that ends up looking a bit like the view from a kaleidoscope). Finally, we return the bitmap.

This method will be called if the player is on a non-overworld map and didn't specify a battleback in the map settings.

def battleback1_name
    if $BTEST
      $data_system.battleback1_name
    elsif $game_map.battleback1_name
      $game_map.battleback1_name
    elsif $game_map.overworld?
      overworld_battleback1_name
    end
  end


This method gets the filename of the battle background being used as the floor. If we're in a test battle, we return battleback 1 from $data_system (basically, whatever background was chosen in the test battle dialog). If we're not, and the battleback1_name property of $game_map returns a value, we return that value. Otherwise, if field type is overworld (tileset mode has been set to 0), we return the result of calling overworld_battleback1_name.

def battleback2_name
    if $BTEST
      $data_system.battleback2_name
    elsif $game_map.battleback2_name
      $game_map.battleback2_name
    elsif $game_map.overworld?
      overworld_battleback2_name
    end
  end


This method does exactly the same thing as the previous method, but for the wall background.

def overworld_battleback1_name
    $game_player.vehicle ? ship_battleback1_name : normal_battleback1_name
  end


This method gets the filename of the battleback used on the floor in the overworld. If the player is riding a vehicle, we return the result of calling ship_battleback1_name, otherwise we return the result of calling normal_battleback1_name.

def overworld_battleback2_name
    $game_player.vehicle ? ship_battleback2_name : normal_battleback2_name
  end


Exactly the same but for battleback2 (wall).

def normal_battleback1_name
    terrain_battleback1_name(autotile_type(1)) ||
    terrain_battleback1_name(autotile_type(0)) ||
    default_battleback1_name
  end


This method gets the filename of the normal battleback used on the floor. Returns either the result of calling terrain_battleback1_name passing in autotile_type(1), terrain_battleback1_name passing in autotile_type(0), or default_battleback1_name (subsequent calls will be made if the previous one returned false or nil).

def normal_battleback2_name
    terrain_battleback2_name(autotile_type(1)) ||
    terrain_battleback2_name(autotile_type(0)) ||
    default_battleback2_name
  end


Exactly the same but for battleback2 (wall).

def terrain_battleback1_name(type)
    case type
    when 24,25        # Wasteland
      "Wasteland"
    when 26,27        # Dirt field
      "DirtField"
    when 32,33        # Desert
      "Desert"
    when 34           # Rocks
      "Lava1"
    when 35           # Rocks (lava)
      "Lava2"
    when 40,41        # Snowfield
      "Snowfield"
    when 42           # Clouds
      "Clouds"
    when 4,5          # Poisonous swamp
      "PoisonSwamp"
    end
  end


This method gets the filename of a floor background based on terrain, and takes one parameter, type.

We have a case statement for type, which returns the string corresponding to the terrain represented by the types in each case, which are just tile IDs from the "World_A1" tileset. If you look at the tiles in the editor and look at tiles 4 and 5 for example (bearing in mind it's 0-indexed) you'll see that those tiles do indeed look like poisonous swamp.

Because these values are hardcoded, if you ever use a different tileset for A1 with differing autotiles or autotiles in different places you'll have to update this method accordingly, or they won't match up.

def terrain_battleback2_name(type)
    case type
    when 20,21        # Forest
      "Forest1"
    when 22,30,38     # Low hill
      "Cliff"
    when 24,25,26,27  # Wasteland, dirt field
      "Wasteland"
    when 32,33        # Desert
      "Desert"
    when 34,35        #  Rocks
      "Lava"
    when 40,41        # Snowfield
      "Snowfield"
    when 42           # Clouds
      "Clouds"
    when 4,5          # Poisonous swamp
      "PoisonSwamp"
    end
  end


Same thing as before but with walls.

The calls to these two methods will result in a somewhat-dynamic battle background that matches as closely as possible the terrain of the tile the player is on when a battle occurs.

def default_battleback1_name
    "Grassland"
  end


This method gets the filename of the default floor battle background and is hardcoded to return "Grassland" meaning any tile that doesn't have another associated background will use it.

def default_battleback2_name
    "Grassland"
  end


Same thing for walls.

def ship_battleback1_name
    "Ship"
  end


This method gets the floor battle background when on a vehicle, and is hardcoded to return "Ship".

def ship_battleback2_name
    "Ship"
  end


Same for the wall background.

def autotile_type(z)
    $game_map.autotile_type($game_player.x, $game_player.y, z)
  end


This method gets the type of the autotile the player is standing on, and returns the result of calling $game_map's autotile_type method, passing in the player's x and y, and the passed-in z value. From the calls to the method from methods we looked at above, this means z will either be 1 or 0. We looked at this method of $game_map in the Game_Map episode, if you remember.

def center_sprite(sprite)
    sprite.ox = sprite.bitmap.width / 2
    sprite.oy = sprite.bitmap.height / 2
    sprite.x = Graphics.width / 2
    sprite.y = Graphics.height / 2
  end


This method moves a sprite to the centre of the screen, and takes one parameter, sprite, which is the sprite object to move. Its ox and oy are set to half the sprite's source bitmap's width and height, and its x/y are set to half the width and height of the screen.

def create_enemies
    @enemy_sprites = $game_troop.members.reverse.collect do |enemy|
      Sprite_Battler.new(@viewport1, enemy)
    end
  end


This method creates the enemy sprites. We iterate through a reverse collect on members from $game_troop, with the block creating a new Sprite_Battler passing in @viewport1 and the current enemy object, and assign the returned array of sprites to @enemy_sprites.

def create_actors
    @actor_sprites = Array.new(4) { Sprite_Battler.new(@viewport1) }
  end


This method creates actor sprites. By default in VX Ace actors are not actually displayed, but in order to treat enemies and allies the same in code dummy sprites are created for them.

Pretty simple really. @actor_sprites is set to a new 4-element Array, with each element initialised as a new instance of Sprite_Battler passing in @viewport1.

def create_pictures
    @picture_sprites = []
  end


This method creates the initial state for picture sprites. As with Spriteset_Map, it's created as an empty array and will be populated in the update method.

def create_timer
    @timer_sprite = Sprite_Timer.new(@viewport2)
  end


This method creates the timer sprite in viewport 2, same as on the map spriteset.

def dispose
    dispose_battleback1
    dispose_battleback2
    dispose_enemies
    dispose_actors
    dispose_pictures
    dispose_timer
    dispose_viewports
  end


The main dispose method is just a wrapper for calls to other dispose methods.

def dispose_battleback1
    @back1_sprite.bitmap.dispose
    @back1_sprite.dispose
  end


This method disposes the floor background. Disposes the sprite's bitmap if one is set, then disposes the sprite object to free up its memory.

def dispose_battleback2
    @back2_sprite.bitmap.dispose
    @back2_sprite.dispose
  end


Same thing for the wall background.

def dispose_enemies
    @enemy_sprites.each {|sprite| sprite.dispose }
  end


This method disposes enemy sprites. Iterates through each element of @enemy_sprites with "sprite" and calls its dispose method.

def dispose_actors
    @actor_sprites.each {|sprite| sprite.dispose }
  end


Same thing but for the actor sprites.

def dispose_pictures
    @picture_sprites.compact.each {|sprite| sprite.dispose }
  end


Same thing but for the picture sprites. Note the call to compact before each to remove nil elements.

def dispose_timer
    @timer_sprite.dispose
  end


This method disposes the timer sprite and just calls dispose on @timer_sprite.

def dispose_viewports
    @viewport1.dispose
    @viewport2.dispose
    @viewport3.dispose
  end


This method calls dispose for the 3 viewports.

def update
    update_battleback1
    update_battleback2
    update_enemies
    update_actors
    update_pictures
    update_timer
    update_viewports
  end


The frame update method is a wrapper for the calls to the other update methods.

def update_battleback1
    @back1_sprite.update
  end


This method updates the floor background sprite by calling the update method of @back1_sprite.

def update_battleback2
    @back2_sprite.update
  end


Same thing for the wall background sprite.

def update_enemies
    @enemy_sprites.each {|sprite| sprite.update }
  end


This method updates enemy sprites by iterating through each element of @enemy_sprites and calling its update method.

def update_actors
    @actor_sprites.each_with_index do |sprite, i|
      sprite.battler = $game_party.members[i]
      sprite.update
    end
  end


This method updates the actor sprites by iterating through each element of @actor_sprites with an index, then in the block setting the sprite's battler property to the party member with index i and calling the sprite's update method (if you'll recall, the update method of Sprite_Battler sets the bitmap property to nil if there's no associated battler, which is why we have to set one here first. I'm honestly not sure why this wasn't done by iterating through the party when creating the sprites in the first place, as with the enemies. It's not like the actor sprites actually do anything by default).

def update_pictures
    $game_troop.screen.pictures.each do |pic|
      @picture_sprites[pic.number] ||= Sprite_Picture.new(@viewport2, pic)
      @picture_sprites[pic.number].update
    end
  end


This method updates the pictures by iterating through each element of the pictures array of the screen property of $game_troop with "pic". In the block, we either get the element of @picture_sprites with an index of the picture's number, or a new instance of Sprite_Picture passing in @viewport2 and pic. Then we call the sprite's update method.

def update_timer
    @timer_sprite.update
  end


This method updates the timer sprite by calling the update method of @timer_sprite. The obvious explanations are starting to look silly, but I want to make sure I never completely lose people regardless of their programming knowledge so I keep pointing them out.

def update_viewports
    @viewport1.tone.set($game_troop.screen.tone)
    @viewport1.ox = $game_troop.screen.shake
    @viewport2.color.set($game_troop.screen.flash_color)
    @viewport3.color.set(0, 0, 0, 255 - $game_troop.screen.brightness)
    @viewport1.update
    @viewport2.update
    @viewport3.update
  end


The viewport update method is identical to the one from Spriteset_Map with the exception that we're using the screen property of $game_troop instead of $game_map.

def battler_sprites
    @enemy_sprites + @actor_sprites
  end


This method gets all of the battler sprites by adding the @enemy_sprites and @actor_sprites arrays together into one larger array.

def animation?
    battler_sprites.any? {|sprite| sprite.animation? }
  end


This method determines whether an animation is being displayed. Returns true if any of the elements in battler_sprites return true when their animation? method is called, false otherwise.

def effect?
    battler_sprites.any? {|sprite| sprite.effect? }
  end


Same thing but for effects.

And that's it! The classes and methods we've looked at over the past three or so articles comprise absolutely everything RPG Maker uses to make stuff appear on the screen, with the exception of the absolute baseline stuff in built-in classes like Bitmap and Sprite. Coming up next, we'll be breaking down the Window classes.

As always, comments are welcome.

Until next time.