Jump to content
Sign in to follow this  
kal

Random Ruby and RGSS3 Tutorials

Recommended Posts

Hello everyone! In this topic I will post tutorials concerning general Ruby programming and RGSS3 scripting. The time between posts may vary a lot. ;) Most of these tutorials will probably be in the intermediate to advanced range, but I encourage everyone who wants to attempt them and please ask any questions you may have. Also, I will take requests, which can be on any Ruby or RGSS3 topic you want a tutorial on. With that said, here is the first tutorial!

 

Tutorial 1 - Exploding Arrays

 

First, a little bit of background: I was a recently writing a snippet to convert a hexadecimal RGB color value in to three decimal RGB color values for use in the RGSS3 Color class. The color class is used to specify the color for a font object, which in turn is used to render the text when drawing text with the Bitmap#draw_text method.

 

For example, in order to draw some red text you would do something like this:

# bitmap is an instance of Bitmap
bitmap.font.color = Color.new(255, 0, 0)
bitmap.draw_text(0, 0, 100, 40, "I will be red")

The details of this code are not important for this tutorial, just how the Color.new call works.

 

In my script I wanted to represent the color as a hexadecimal RGB string instead of the three integers that the Color constructor takes.

Here is a color picker site that has both hexadecimal and decimal color values: http://www.colorpicker.com/

For example, I want to use the string "FF0000" to represent the color red, instead of using the three different numbers 255, 0, 0. This makes it easier to change the color quickly.

 

So the problem is, how do you turn a string like "FF0000" in to the numbers 255, 0, 0 and pass those numbers to the Color.new call?

 

Because the way hex numbers work, every two hex digits (in this case FF, 00 and 00) translates to our Red, Green and Blue values, respectively. If you convert the hexadecimal number FF to decimal you get 255 (and hex 00 is 0 of course ;)).

 

So my instinct was to split the string "FF0000" in to three different strings "FF", "00" and "00" and convert each of them to their decimal representation. There are many ways to do this in Ruby, but which is the best one? 

 

To find out, my first thought was to consult the excellent standard library documentation, more specifically the String class: http://www.ruby-doc.org/core-1.9.3/String.html

 

The string class has a handy method, String#scan which takes a regular expression  that is matched against the string and returns an array of all the regular expression matches. So if we construct a regex that says "match any two characters" and pass it to the scan method we will get, in this case, an array that looks like this: 

["FF", "00", "00"] 

Regular expressions can be a bit tricky at first, but luckily the regex "match any two characters" is really easy:

/../
 

The two forward slashes identify a regex literal (like " and " identify a string literal) and in regex language a dot means "any one character". So two dots mean "any one character followed by any one character".

 

Now that we have split the string in to the individual numbers that it represents, we can figure out how to turn a string like "FF" into a number like 255. After some quick googling it turns out this is really easy in Ruby! Simply do this:

"FF".to_i(16) # => 255

to_i is a method that turns a string into a number. If you don't give an argument to the to_i call it assumes you mean decimal numbers. So

 

"10".to_i  # => 10

just as you would expect. But if you give an argument to to_i it will use that value as the base when converting the string. So if we pass in 16 (which is the base of hexadecimal numbers) we get the right result! Yay!

 

So now we know how to split the string and how to turn the individual strings into decimal numbers. What's next?

 

Well, we now have an array 

["FF", "00", "00"] 

where we want to transform each element so that we get a different array

[255, 0, 0]

when you see a pattern like this (transforming each element of an array in to something else) your mind should immediately jump to the Array#map method.

 

This method iterates over every element in the array (like the each method does) and returns a new array where each element is the value returned by the block passed to the map method for that element. If that's confusing look at the example here http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-map and it should be clearer.

 

So we simply need to call map on our array, and pass in a block that converts the hexadecimal number to a decimal number, like this:

def hex_to_rgb(string)                  # pretend the string "FF0000" is passed in
  hex_nums = string.scan(/../)          # hex_nums == ["FF", "00", "00"]
  decimals = hex_nums.map do |two_hex|  # decimals == [255, 0, 0]
    two_hex.to_i(16)
  end
  return decimals
end
if we remove the variables and the return statement (in Ruby the last expression of a method is automatically returned) we can simplify the code to this:
def hex_to_rgb(string)
  string.scan(/../).map do |two_hex|
    two_hex.to_i(16)
  end
end

Woot! So now we are done, right? Well, not quite yet! We still need to get our values out of the array, and in to the Color.new call.

 

So let's do it like this:

rgb_value = "FF0000"
rgb = hex_to_rgb(rgb_value)
red = rgb[0]
green = rgb[1]
blue = rgb[2]
Color.new(red, green, blue)

this certainly works, but the code is kinda ugly. Can we do better than this? Yes, we can!

rgb_value = "FF0000"
red, green, blue = hex_to_rgb(rgb_value)
Color.new(red, green, blue)

So what is going on here? Here we see a somewhat obscure (but very neat) feature of Ruby, namely that if you assign an array (remember, the call to hex_to_rgb returns an array) to a comma separated list of variables the elements of the array will be pulled out of the array and assigned to the variables in the order that they appear! So in this case red will be assigned the first element of the array, green the second, and blue the third. The result is exactly the same as the previous example, only Ruby takes care of all the ugly details!

 

But hold on, it turns out we can do even better than that! Which brings me to the title of this tutorial, array exploding:

rgb_value = "FF0000"
Color.new(*hex_to_rgb(rgb_value))

If you add a * before an array that you pass in to a method (we pass the array returned by hex_to_rgb to the Color.new method) Ruby will take each element of the array and pass it in as a parameter to the method. So the call

Color.new(*hex_to_rgb(rgb_value))

gets translated to

Color.new(255, 0, 0)

pretty neat! This process of automatically extracting the elements of an array and passing them as arguments to a method is known as array exploding and it's a nice feature to keep in mind. That concludes the first tutorial. I hope you learned something and remember to ask questions and make request if you want.

 

  • Like 4

Share this post


Link to post
Share on other sites

Tutorial 2 - Fun with ObjectSpace

 

Today we're gonna look at a cool module called ObjectSpace that was added to Ruby 1.9. ObjectSpace allows you to see how many objects have been created by the Ruby interpreter and what their types are. With ObjectSpace you can also iterate through every object known to the Ruby interpreter like if they were in one big array which can sometimes be handy as we'll see later on in this tutorial.

 

First here here's how to see how many objects have been created in total:

 

ObjectSpace.count_objects 
=> {:TOTAL=>49106, :FREE=>6590, :T_OBJECT=>5543, :T_CLASS=>807, :T_MODULE=>38, :
T_FLOAT=>744, :T_STRING=>13174, :T_REGEXP=>27, :T_ARRAY=>13512, :T_HASH=>71, :T_
STRUCT=>24, :T_BIGNUM=>1, :T_FILE=>4, :T_DATA=>5889, :T_MATCH=>12, :T_COMPLEX=>1, 
:T_NODE=>2650, :T_ICLASS=>19}

 

TOTAL is how many object have been created during the entire run-time of the program and FREE is how many have been garbage collected.

 

Then there is the ObjectSpace#each_object method which can be used to iterate over every alive object in the program. For example:

 

ObjectSpace.each_object do |object|
  puts object
end

 

You can pass a class name to each_object to only iterate over objects that inherit from that class. For example to iterate over all the strings in the program do this:

 

ObjectSpace.each_object(String) do |string|
  puts string
end

 

Now let's say we want to store a reference to each string in the program in an array for future use. One way to do it is this:

 

strings = []
ObjectSpace.each_object(String) do |object|
  strings << string
end

 

But there's an easier way to do this because calling each_object without a block it returns an enumerator. I won't go in to exactly what enumerators are and how to use them in this tutorial, but in short they are external iterators. If you have programmed in Java they are similar to Java's Iterator class. Anyway, an enumerator can be directly converted to an array with to_a, so we can simplify the above code like this:

 

strings = ObjectSpace.each_object(String).to_a

 

Now that you know the basics of ObjectSpace let's build something that we can use in Ace with it! What I had in mind was a class that can get Window instances given a coordinate (an X and Y value). So the class should work like this:

 

window_info = WindowInfo.new
some_window = window_info.window_at(100, 100) # the values here are the X and Y coordinate, respectively, of a point that is within the bounds of the window

 

When you start to write a class it's sometimes helpful to use "scripting by assumption" which means that you start at some level of abstraction and write code as if you had already implemented all the lower level methods. Using this approach, we can write the window_at method like this:

 

class WindowInfo

  def window_at(x, y)
    windows = get_windows_at_position(x, y)
    sorted = windows.sort_by { |window| window.z }
    sorted.first
  end

end 

 

The window_at method first gets an array of all window objects at the given coordinate using the helper method get_windows_at_position. After that is done we need to return one window only (because there could be more than one window at a given screen coordinate) so we want to return the window that has the highest Z coordinate i.e. the one that is "on top". We can do this by sorting the windows using their Z value and then simply returning the first element of the sorted array.

 

So next let's implement get_windows_at_position (again employing scripting by assumption):

 

class WindowInfo

  def window_at(x, y)
    windows = get_windows_at_position(x, y)
    sorted = windows.sort_by { |window| window.z }
    sorted.first
  end
  
  private
  
  def get_windows_at_position(x, y)
    windows = get_all_windows
    windows.find_all do |window|
      !window.disposed? && within_bounds?(window, x, y)
    end
  end

end 

 

Here we delegate the job of getting all the window objects to another method, get_all_windows. Next once we have all the windows we want to filter out the ones that are disposed and the ones that are not within the bounds of the coordinate. We can do that by calling the find_all method on the window array. find_all will return an array containing only the elements for which the block returned true. In the block we return true only if the window is not disposed, and if the window is within the bounds of the coordinate.

 

Now let's code our two remaining helpers, get_all_windows and within_bounds?

 

class WindowInfo
 
  def window_at(x, y)
    windows = get_windows_at_position(x, y)
    sorted = windows.sort_by { |window| window.z }
    sorted.first
  end
 
  private
 
  def get_windows_at_position(x, y)
    windows = get_all_windows
    windows.find_all do |window|
      !window.disposed? && within_bounds?(window, x, y)
    end
  end
 
  def get_all_windows
    ObjectSpace.each_object(Window).to_a
  end
 
  def within_bounds?(window, x, y)
    x_range = (window.x)..(window.x + window.width)
    y_range = (window.y)..(window.y + window.height)
   
    x_range.include?(x) && y_range.include?(y)
  end
 
end

 

As you can see, get_all_windows simply uses the ObjectSpace module's each_object method to return an array of all Window objects (that is, all Window objects and all objects which inherits from Window).

 

within_bounds? is a little more involved. It starts by creating two range (http://ruby-doc.org/core-1.9.3/Range.html) objects using the .. syntax. These ranges make up the rectangle of the window as it is drawn on the screen. The window.x and window.y values is where the upper left corner of the window is placed on the screen, so if we start at that point and add the width and height we get the window's rectangle. Then it's simply a matter of checking if the x and y coordinates are withing the x and y ranges.

 

Now our WindowInfo class is complete! However, it's kind of hard to try it out right now. Sure, we can use it inside a script in the editor but it's not that useful. To make it more interesting we'll use Alex, an interactive console I wrote some time ago. You can get Alex here: https://gist.github.com/kl/2044957

 

Alex works like a poor man's version of the awesome Pry library: http://pryrepl.org/

It has a few features you can read about in the script comments but it basically allows you to pause script execution at any time and type in code that will be execute in the current context.

 

Once you have Alex installed, you can start an Alex session by either calling binding.alex from anywhere inside any script, or by pressing the D key on the keyboard when the game is running. We'll use the latter approach in this tutorial.

 

So with Alex and our newly created WindowInfo class ready to go, start the game and go into the menu screen (and make sure that you enabled the RGSS console window). Now if you press D you should see that the game freezes and the console window should print out alex:

 

Now you can start typing in some code and it will evaluate and print out the return value. For example:

 

alex: 2 + 2
=> 4

 

so let's use Alex to get a reference to the Window_MenuCommand window (the window in the upper left corner).

 

alex: $wmc = WindowInfo.new.window_at(100, 100)
=> #<Window_MenuCommand:0x83c5e1c>

 

So we give the coordinate (100,100) and our WindowInfo class gives back the Window_MenuCommand object that occupies that coordinate! Yay! Note that I am using a global variable here, something which is not normally advisable, and that's i because it's an easy way to make sure we can get to the object later on. I wouldn't do this in production code, but for simple testing like this it's fine.

 

Now that we have a reference to the window object, we can play around with it a bit. For example we can make it invisible:

 

alex: $wmc.visible = false
 => false

 

Now when you type this in you won't see a change in the game window because the game is currently in a frozen state. To switch back to the game exit out of Alex by typing exit:

 

alex: exit

 

Now the window should disappear. Or what about changing it's position (to get back to Alex just press D again):

 

alex: $wmc.visible = true
=> true
alex: $wmc.y = 150
=> 150

 

That concludes this tutorial on ObjectSpace and how it can be used to hold of object references! You could extend this example a lot further if you wanted, for exampling if you have a mouse script installed you could create a debugging system where you could click on the screen to select windows and even re-position windows with the mouse.

Edited by kal
  • Like 3

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
Top ArrowTop Arrow Highlighted