SLIP INTO RUBY PART 4: MAKING A SCENE CONTINUED.

In this edition of Slip into Ruby, we finish our bestiary and talk about the future.

  • Trihan
  • 03/10/2015 07:08 PM
  • 5796 views


Hello class! Today we're going to finish our bestiary projects. What you should have so far is this:

class Scene_Bestiary < Scene_MenuBase
  def start
    super
    @list_window = Window_EnemyList.new(0, 0, 200)
    create_battler_window
    @list_window.set_handler(:cancel,  method(:return_scene))
  end
  
  def create_battler_window
    @battler_window = Window_EnemyBattler.new(@list_window, 200, 0, Graphics.width - 200, Graphics.height - 175)
    @battler_window.viewport = @viewport
  end
end

class Window_EnemyBattler < Window_Base
  def initialize(enemy_window, x, y, width, height)
    super(x, y, width, height)
    @enemy_window = enemy_window
    refresh
  end
  
  def draw_battler(x, y)
    bmp = setup_battler_graphic(x, y)
    src_rect = Rect.new(0, 0, bmp.rect.width, bmp.rect.height)
    contents.blt(x, y, bmp, src_rect, 150)
    bmp.dispose
  end
  
  def setup_battler_graphic(x, y)
    bmp = get_battler
    rect = Rect.new(x, y, contents_width, contents_height)
    dest_rect, fits = battler_rect(bmp, rect)
    dummy_bmp = Bitmap.new(contents_width, contents_height)
    if fits
      dummy_bmp.blt(dest_rect.x, dest_rect.y, bmp, bmp.rect)
    else
      src_rect = Rect.new((bmp.rect.width - dest_rect.width) / 2, 
      (bmp.rect.height - dest_rect.height) / 2, dest_rect.width, dest_rect.height)
      dummy_bmp.blt(dest_rect.x, dest_rect.y, bmp, src_rect)
    end
    dummy_bmp
  end
  
  def battler_rect(bmp, rect)
    dest_rect = bmp.rect.dup
    fits = dest_rect.width <= rect.width && dest_rect.height <= rect.height
    if !fits
      dest_rect.width = rect.width if dest_rect.width > rect.width
      dest_rect.height = rect.height if dest_rect.height > rect.height
    end
    dest_rect.x = ((rect.width - dest_rect.width) / 2)
    dest_rect.y = ((rect.height - dest_rect.height) / 2)
    return dest_rect, fits
  end
  
  def get_battler
    Cache.battler(@enemy.battler_name, @enemy.battler_hue)
  end
  
  def update
    super
    refresh
  end
  
  def refresh
    contents.clear
    @enemy = @enemy_window.item
    draw_battler(1, 1)
  end
end

class Window_EnemyList < Window_Selectable
  def initialize(x, y, width)
    super(x, y, width, Graphics.height - y)
    data = []
    self.index = 0
    activate
    refresh
  end
  
  def item_max
    @data ? @data.size : 1
  end
  
  def item
    @data && index >= 0 ? @data[index] : nil
  end
    
  def make_item_list
    @data = $data_enemies.compact
  end
  
  def draw_item(index)
    item = @data[index]
    if item
      rect = item_rect_for_text(index)
      draw_text(rect, item.name)
    end
  end
  
  def refresh
    make_item_list
    create_contents
    draw_all_items
  end
end


Which should look like this:


NOTE: After writing this part I noticed that I had incorrectly used Graphics.width when determining the height of the enemy window. I have now amended this in the code, so anyone who was following up until now will have to do the same or copy/paste the code above. Sorry about that!

Now you may be dismaying at that massive blank space at the bottom right where by all laws of goodness and sense monster stats should be! Let's fix that.

First of all we're going to create a new window to hold the information, which fills up the space. You remember how to create a window, right? We need a new class:

class Window_EnemyInfo < Window_Base
  def initialize(enemy_window, x, y, width, height)
    super(x, y, width, height)
    @enemy_window = enemy_window
    refresh
  end
  
  def update
    super
    refresh
  end
  
  def refresh
    contents.clear
    @enemy = @enemy_window.item
  end
end


If I've been doing my job up until now I shouldn't have to explain what this all does, as it's pretty much exactly the same as the code for creating the battler window.

And then to show it, we go back to the start method of Scene_Bestiary:

create_info_window


And as this method doesn't exist yet, we should probably create it.

def create_info_window
    @info_window = Window_EnemyInfo.new(@list_window, 200, Graphics.height - 175, Graphics.width - 200, 175)
  end


We should now have this.



Let's add some stats here. We could be all fancy and use icons and stuff, but for now let's stick with simplicity, using standard game terms:

Add the following line to the end of the refresh method in Window_EnemyInfo.

draw_parameters(0, 0)


And the new method draw_parameters.

def draw_parameters(x, y)
    6.times {|i| draw_enemy_param(@enemy, x, y + line_height * i, i) }
  end


And the new method draw_enemy_param!

def draw_enemy_param(enemy, x, y, param_id)
    change_color(system_color)
    draw_text(x, y, 120, line_height, Vocab::param(param_id))
    change_color(normal_color)
    draw_text(x + 120, y, 56, line_height, enemy.params[param_id], 2)
  end


Playtest, and lo and behold suddenly you have this.



Looking pretty nice, huh? Let's take a look at the new stuff line by line:

6.times {|i| draw_enemy_param(@enemy, x, y + line_height * i, i) }


.times is a lovely little iteration method Ruby has; it's basically a shorthand version of a for loop. You put [number of times to loop].times {|name of index variable| stuff to loop through }
So basically this is calling draw_enemy_param 6 times. Note the parameters y + line_height * i and i. Basically this means each successive loop will be placed at a Y equal to the index multiplied by the height required to show one line. The end result speaks for itself. :P

change_color(system_color)


change_color is a built-in method of Window_Base which changes the text colour. There are quite a few system presets which you can use, or you can even specify your own colours using Color.new(r, g, b, a)

draw_text(x, y, 120, line_height, Vocab::param(param_id))


We've used draw_text before, but I'd like to draw your attention to the last parameter. Vocab is a module containing all the terminology used in your game. If you look up param in that module you'll see it returns $data_system.terms.params which is whatever you set the "Parameters" terms to in the terms tab of the database.

draw_text(x + 120, y, 56, line_height, enemy.params[param_id], 2)

Because we have a looping index going from 0-5 (as we're looping 6 times, starting with 0) we can use this to get each successive parameter assigned to an enemy, which in order are Max HP, Max MP, Atk, Def, Mat and Mdf. We're simply displaying the value of that index of the parameters array.

And that's a basic bestiary! Be proud of what you've accomplished. Here's the final code.

class Scene_Bestiary < Scene_MenuBase
  def start
    super
    @list_window = Window_EnemyList.new(0, 0, 200)
    create_battler_window
    @list_window.set_handler(:cancel,  method(:return_scene))
    create_info_window
  end
  
  def create_battler_window
    @battler_window = Window_EnemyBattler.new(@list_window, 200, 0, Graphics.width - 200, Graphics.height - 175)
    @battler_window.viewport = @viewport
  end
  
  def create_info_window
    @info_window = Window_EnemyInfo.new(@list_window, 200, Graphics.height - 175, Graphics.width - 200, 175)
  end
end

class Window_EnemyInfo < Window_Base
  def initialize(enemy_window, x, y, width, height)
    super(x, y, width, height)
    @enemy_window = enemy_window
    refresh
  end
  
  def draw_parameters(x, y)
    6.times {|i| draw_enemy_param(@enemy, x, y + line_height * i, i) }
  end
  
  def draw_enemy_param(enemy, x, y, param_id)
    change_color(system_color)
    draw_text(x, y, 120, line_height, Vocab::param(param_id))
    change_color(normal_color)
    draw_text(x + 120, y, 56, line_height, enemy.params[param_id], 2)
  end
  
  def update
    super
    refresh
  end
  
  def refresh
    contents.clear
    @enemy = @enemy_window.item
    draw_parameters(0, 0)
  end
end

class Window_EnemyBattler < Window_Base
  def initialize(enemy_window, x, y, width, height)
    super(x, y, width, height)
    @enemy_window = enemy_window
    refresh
  end
  
  def draw_battler(x, y)
    bmp = setup_battler_graphic(x, y)
    src_rect = Rect.new(0, 0, bmp.rect.width, bmp.rect.height)
    contents.blt(x, y, bmp, src_rect, 150)
    bmp.dispose
  end
  
  def setup_battler_graphic(x, y)
    bmp = get_battler
    rect = Rect.new(x, y, contents_width, contents_height)
    dest_rect, fits = battler_rect(bmp, rect)
    dummy_bmp = Bitmap.new(contents_width, contents_height)
    if fits
      dummy_bmp.blt(dest_rect.x, dest_rect.y, bmp, bmp.rect)
    else
      src_rect = Rect.new((bmp.rect.width - dest_rect.width) / 2, 
      (bmp.rect.height - dest_rect.height) / 2, dest_rect.width, dest_rect.height)
      dummy_bmp.blt(dest_rect.x, dest_rect.y, bmp, src_rect)
    end
    dummy_bmp
  end
  
  def battler_rect(bmp, rect)
    dest_rect = bmp.rect.dup
    fits = dest_rect.width <= rect.width && dest_rect.height <= rect.height
    if !fits
      dest_rect.width = rect.width if dest_rect.width > rect.width
      dest_rect.height = rect.height if dest_rect.height > rect.height
    end
    dest_rect.x = ((rect.width - dest_rect.width) / 2)
    dest_rect.y = ((rect.height - dest_rect.height) / 2)
    return dest_rect, fits
  end
  
  def get_battler
    Cache.battler(@enemy.battler_name, @enemy.battler_hue)
  end
  
  def update
    super
    refresh
  end
  
  def refresh
    contents.clear
    @enemy = @enemy_window.item
    draw_battler(1, 1)
  end
end

class Window_EnemyList < Window_Selectable
  def initialize(x, y, width)
    super(x, y, width, Graphics.height - y)
    data = []
    self.index = 0
    activate
    refresh
  end
  
  def item_max
    @data ? @data.size : 1
  end
  
  def item
    @data && index >= 0 ? @data[index] : nil
  end
    
  def make_item_list
    @data = $data_enemies.compact
  end
  
  def draw_item(index)
    item = @data[index]
    if item
      rect = item_rect_for_text(index)
      draw_text(rect, item.name)
    end
  end
  
  def refresh
    make_item_list
    create_contents
    draw_all_items
  end
end


I had debated showing you all sorts of cool stuff, like listing enemy drops and how many times you'd killed a certain enemy, but ultimately I've decided that I'm here to show you the basics and that's going beyond scope. However, just to indulge the showoff in me, here's the kind of thing you could make with a bit more knowledge of Ruby:



Inspired yet?

Okay, so I'm thinking I'm going to update Slip into Ruby every week on a Tuesday, and from this point on I'm going to do a line-by-line analysis of each default script in RPG Maker VX Ace. As always feel free to comment, critique or insult and if there's anything specific you want me to talk about or something you're not quite sure how to script, let me know in the comments and I'll do my best to work it in.

Until next time.