Home > Tutorials > Ruby Game Scripting System > RGSS For Dummies Tutorial 5: Object Oriented Programming and More
RGSS For Dummies Tutorial 5: Object Oriented Programming and More
Introduction
Ruby (RGSS) is an object oriented programming language, and to really call yourself a Ruby programmer you need to understand the "object oriented" part. It's not really as hard as it might sound; it's designed to make programming easier and more fun. We've been writing object oriented programs all along but without noticing it. After all, everything is an object! Part 1 of this tutorial will introduce you to classes and objects, Part 2 will discuss classes further and introduce inheritance, and Part 3 will talk about modules, variable scope and something else. I tried to focus more on code rather than theory. After all, Ruby makes OOP so natural that you'll start thinking in an object oriented way without knowing it!
Contents
1. Everything is an object
2. Classes
3. Objects
4. Instance Variables
5. Accessing Instance Variables
6. Class Review
7. Inheritance
8. Scope Rules
9. Types of Variables
9.1. Constants
9.2. Class Methods (Singleton Methods)
9.3. Modules
10. Conclusion
11. Summary
1. Everything is an object
Any variable you create with Ruby is an object, but what's an object? An object contains data and methods to operate on the data. Let's take a string with the value "cow" for example, the data is the value "cow", but what are the methods that operate on it? Well, the string class (don't worry about classes yet) has many methods such as upcase! which capitalizes all the characters in a string, size which returns the number of characters in a string, sub which replaces a part of the string with another, to_i which converts the string to a number and so on. You could actually do this:
Code:
my_string = 'cow'
p "The string in caps is: "my_string.upcase!
p "And the size of the string is #{my_string.size}"
In other words, strings have methods that can be called by placing a dot after the string (or the variable holding it) and then the method's name (string.method). This is also true for integers, you can do this:
The abs method for numeric objects returns the absolute value of the object, it's like the abs method we wrote in the last tutorial (abs(-1) ), the next method returns the next number (3.next is 4, 4.next is 5, etc.).
So basically, all data types have special methods to operate on the data they represent. For a complete list of the methods you can check Ruby's official site or RMXP's help file (when it's translated to English...).
2. Classes
I have rewrote this section around 4 times... I think it's one of those things that you can understand but it's hard to teach to someone who knows nothing about it. So bear with me and hope that it'll become clearer with some examples...
A class defines a data type and the data type can contain variables and methods that operate on the variables. You can have a string to represent text, an integer to represent integers, a boolean to represent true or false, etc. As you saw earlier, data types have certain methods to be invoked on their variables (objects). Now, what about creating your own data type? For example, a data type for playable characters, each character has HP, MP, STR, Level, and other variables. Also, you can have methods to check if the character is affected by a status effect, calculate the damage taken when attacked, equip a certain item on the character, teach the player a certain skill, etc. Assuming we call our new data type (Hero), we can do:
Code:
alex = Hero.new
p alex.hp
p alex.mp
p alex.str
alex.equip("Iron Sword")
alex.learn_skill("Fire")
It's not that simple, but you get the point... don't worry about the Hero.new for now.
So, how do we create a new data type? You do it by defining a class, a class is just a block that includes methods and variables, like this:
Code:
numberz = [1, 2, 3, 4]
for c in numberz
p c * c
end
The output would be (1, 4, 9, 16). That's the squares of the array elements.
What happens is: the print statement would be executed 4 times (the size of the array), each time the variable c will hold an element from the array. First it'd hold the first element, which is 1, then it'd multiply it by itself resulting in 1, then it'd do the same thing for 2 and get 4, and so on. You think it was too complicated? Here's another example:
Code:
# he keyword class defines a class
class Hero
# attr_accessor allows us to change
# the values of the instance variables.
attr_accessor :hp
attr_accessor :mp
attr_accessor :str
# Initialize is the method called when
# you first create the object.
def initialize
@hp = 0
@mp = 0
@str = 0
end
# You can normally define other methods.
def equip(sword)
p "Hero has equipped #{sword}"!"
end
# Another method~
def learn_skill(skill)
p "Hero has learned #{skill}!"
end
# This 'end' belongs to the class block
end
Try to go through this class and see if you understand anything. It's nothing complicated, really; just a block that starts with class followed by the class name. Inside the block you can have as many methods as you like. Generally you'd like to have a method called initialize which will be called when an object (variable) is created, you can set the initial values for variables in the initialize method. attr_accessor allows us to access the instance variables outside the class, and that's all.
Not really, let me explain each part in details.
This is where we start; you use the keyword class and then the desired name. The name has to start with a capital letter (remember how names of variables and methods should start with a small letter). A good name would be general enough to apply to all objects of the class. A class is a representation of a type, so a name like Hero would be good to define playable characters in a game. Just like variable and method names, you can't have spaces as a part of the class' name.
Code:
attr_accessor :hp
attr_accessor :mp
attr_accessor :str
A class can have instance variables, variables that can be accessed any where in the class. In our example, we have 3 instance variables: @hp, @mp, @str. All instance variables start with an 'at' @ sign. However, having instance variables doesn't mean you can access them outside the class (that is, doing something like: alex.hp. It wouldn't work). To be able to access these variables we use the attribute accessor (attr_accessor) command which is followed by :variable_name, it might be confusing but I'll explain in more details later.
Code:
def initialize
@hp = 0
@mp = 0
@str = 0
end
We define a method called initialize, which is the method called when an object of the Hero class is created. To create an object of class Hero (a variable of the Hero data type) you write something like:
Whenever the compiler sees the (.new) it'll call the initialize method (if it exists). Most of the time, the initialize method is used to set the initial values of the instant variables, in this example I set the values to 0. Initialize is just like any normal method, it could accept arguments (but not return anything because it returns an instance by default), so we can rewrite the previous example like this:
Code:
def initialize(hp = 0, mp = 0, str = 0)
@hp = hp
@mp = mp
@str = str
end
Notice how @hp and hp are different, one is a local variable and the other is an instance variable. The names are different anyways (@str isn't str, it has an extra @). To create an object of this class you'd do:
Code:
joe = Hero.new(9999, 999, 500)
You could also provide it with fewer arguments because we used default values (remember the last tutorial?)
Code:
def equip(sword)
p "Hero has equipped #{sword}"!"
end
def learn_skill(skill)
p "Hero has learned #{skill}!"
end
Just two random methods that take strings and print them in some message. To access these methods you'd do something like:
Code:
goku = Hero.new
goku.equip("SUPER GUN")
goku.learn("SUPER SKILL")
You must always 'create' the object before using it, otherwise the Ruby wouldn't know the type of your variable.
The end of the class block, don't forget to add it or you might get an error! :P
3. Objects
Code:
Q: What's an object?
A: An instance of some class.
Q: Yeah, but... what's an instance of a class?
A: An object!
Q: ...
A: Ok ok, I'll explain!
In programming, people like to make things sound more complicated than they really are. Remember variables? Remember data types? An object is a variable of a certain data type. Like, if you have a string variable then you can say it's a string object. For example:
Code:
int = 5
string = "lol"
bool = true
int, string, and bool are all objects of different data types. So, an object is just a variable. In Ruby, there are some built-in classes that define the basic data types such as integers or strings, and when you say (string = "lol") you are creating an object of class String. Don't get it yet? Remember the Hero class? In the next example, alex, goku and snake are all objects of the Hero class, they are all Hero variables.
Code:
alex = Hero.new
goku = Hero.new
snake = Hero.new
In general, whenever I mention an object of some class, I mean a variable of that class.
4. Instance Variables
Each object has its own set of internal instance variables. These variables can be used in any of the object's methods. In the Hero example, each hero has his or her own HP, MP and STR. If you had a class for cars, each would have different speed, color, weight, etc. An instance variable always starts with a @ sign, and it should be initialized somewhere (maybe in the initialize method?) or else it would have a nil value (nil generally means 'not defined') which you can't operate on. Instance variables can be of any data type, they can even be objects of classes that you've created. An instance variable can be normally used in methods, let's add a new method to the Hero class, this method prints HP and MP:
Code:
def print
p "HP is: " + @hp.to_s
p "MP is: " + @mp.to_s
end
.to_s converts the HP and MP to strings and then print them (because you can't add a string and a number, so we turn the number to a string).
5. Accessing Instance Variables
Maybe you'd like to know the HP of a Hero object, or maybe the color of a Car object. Thing is, instance variable's scope is limited to the class only, so how would you do it? One way is creating your own set and get methods, like this:
Code:
def hp
return @hp
end
def hp=(number)
@hp = number
end
If you create an object called cow, you use cow.hp and the hp method will be called and would return the value of @hp (hp is called a get method), and whenever you set the value of cow with something like cow.hp = 5 the hp= method will be called to set the value of @hp to 5 (hp= is called a set method).
This is nice, but can be quite annoying if you had tons of instance variables. For that reason, Ruby offers some cool attribute commands. attr_accessor allows you to read (get) or write (set) the value of the instance variable, attr_reader allows you to read but not write the value, and attr_writer allows you to write but not read the value. Most of the time you'll use attr_accessor or attr_reader. But... how do you use them? Something like this:
Code:
attr_accessor :variable_name
attr_reader :variable_name
attr_writer :variable_name
variable_name is the name of your instance variable, it should be preceded by a : (colon) and not by @. Once you added the attr_ stuff, you can normally access the instance variables like this:
Code:
haha = Hero.new
haha.hp = 67
p haha.hp
6. Class Review
A class is used to define new data types, and data types are used to create objects. An object is a variable of a certain data type, an object encapsulates variables and operations on these variables. In Ruby, we use the 'class' keyword to define a new class, a simple class looks like:
Code:
class Healing_Item
# The instance variables:
attr_accessor :name # The item's name
attr_accessor :hp_increment #Amount of HP restored
attr_accessor :mp_increment #Amount of MP restored
# The methods:
# initialize: Called when an item is created,
#sets the values for the instance variables
def initialize(item_name, hp_increase, mp_increase)
@name = item_name
@hp_increment = hp_increase
@mp_increment = mp_increase
end
# use_item: 'Use' the item on player
# player : a Hero object from the previous tutorial
def use_item(player)
# Increase player's HP by the value of @hp_increment
player.hp += @hp_increment
player.mp += @mp_increment # Increase MP too!
end
# print : Prints all the instance variables
def print
p "The item's name is: " + @name
p "The amount of HP recovered when using a #{name} is: " +
@hp_increment.to_s
p "As for MP, the amount is: " + @mp_increment.to_s
end
end
This sample class represents a healing item in a game. A typical healing item restores HP or MP (or both) when used. Our healing item is simple, it has three instance variables: @name, @hp_increment, and @mp_increment. As you might remember, instance variables can only be used inside classes, and we have to use an attr_accessor to be able to change it from outside (like potion.hp = 5), you might also consider using an attr_reader instead (because the name, hp or mp increment of the item aren't likely to change, so you only need to get them rather than setting them). An item object can call 2 methods, use_item and print. The use_item method uses the item on a certain Hero object. Hero object have HP and MP, and the use_item method just increases them by adding the item's increment value. As for the print method, it simply displays the name, hp increment, and mp. to_s is a method that converts a number into a string, without it you can't mix strings and numbers (just like you can't mix apples and oranges). This sample class has many comments (stuff after #), which is a good thing. Comments help people who use your class to know how it works. You can now create healing items like this:
Code:
# Create a new item called Potion, this item restores 100 HP
potion = Healing_Item.new("Potion", 100, 0)
potion.print# Print various useful information about potion
# create a new hero with 200 HP and 100 MP
alex = Hero.new(200,100)
alex.hp = alex.hp - 150 # Decrease the hero's HP by 150 (now it's 50)
# Use the potion on the hero, increasing his HP by 100
potion.use(alex)
p Alex.hp# Print the hero's current HP (150)
7. Inheritance
In the real world, objects are related to eachother. A car is a vehicle and so is a bus or a truck. A monkey is an animal and so is a cow, a human is also an animal because we share some common traits found in all animals. A simpler example would be geometric shapes. In general, a 2D shape has 2 dimensions (width and height), and also has an area that could be computed. To illustrate inheritance, I'll use a shape class that is inherited by two classes, square and triangle. In other words, a square or a triangle share the common trait of being a shape, but they differ in the details (the way they look, the way the area is computed, etc.). But before we do that, let me introduce some new concepts. First of all, a class can inherit another class, any class that inherits another can be called a child or derived class, and the class which is inherited can be called a mother, super or base class. When a derived class inherits a base class, the derived class might inherit all of the methods and instance variables of the base class. For example, if you have a class representing game entities, be them heroes or enemies, and this class has instance variables HP, MP and a method called restore_hp, you can create two derived classes for heroes and enemies, and both of these classes have HP, MP and the restore_hp method. I think that made the whole thing sound more confusing... let's go back to the shape example. This is the 'base' shape class:
Code:
class Shape
attr_accessor :height
attr_accessor :width
def initialize(width, height)
@width = width
@height = height
end
def area
p 'I'll be overwritten!'
end
def print_dimensions
p "Height is #{@height}"
p "Width is #{@width}"
end
end
Simple stuff, we have two instance variables, @width and @height. We also have initialize, area and print_dimensions methods. The initialize method takes two arguments to set the initial values of @width and @height. Notice that height isn't the same as @height, @height is an instance variable (it starts with @), it's local to the whole class, any method can modify its value. However, height is a local variable to the method initialize, it can only be used in the method initialize and if you try to use it in the area method you'll get an error, more about variable scope later. area is a method that computes the area and prints it out. The problem is that we don't know what the shape is... and you can't compute the area unless you know what the shape is. Now, if Ruby didn't support inheritance you'd have to create a variable that tells us what the shape is, and then check the type of the shape every time you want to compute the area, something like: if shape == 'triangle' then p 'The area is' + (0.5 * width * height). But if you supported lots of shapes and lots of methods, your code will look ugly because it'll be full of if conditions and different ways to compute stuff. However, with inheritance you just derive shapes as child classes, these shapes have the same interface as their base class (Shape), but each shape computes it's area in a different way. For now, the area method in the Shape class does no computation and only prints "I'll be overwritten", the child classes will compute their own areas and print something like "the area is 15". Finally, the print_dimensions method just prints out the width and height values. Here's a derived square class:
Code:
class Square < Shape
def initialize(side)
@width = @height = side
end
def area
p 'The area is: ' + (@width * @height).to_s
end
end
Wow, that's small! As you can see, we don't need attr_accessor anymore, because when we inherited the shape class we basically got everything in that class hidden in the new square class. By hidden I mean it's there, but you don't have to type it again. So, our square class does have the attr_accessors from the Shape class. But... how did we inherit the Shape class? In Ruby you start the child's class definition with something like: class Derived < Base. In the Square example it's class Square < Shape, this tells Ruby that Square is a child class of the base class Shape. Think of the < as "Square is less than Shape" in age, because Shape is the mother, so she must be older! Moving on, the Square class overwrites the initialize and area methods of the Shape class. By overwrite, I mean that it hides the old methods and uses its own versions. Think of the base class as a box, and the derived child class as a bigger box. Now, when the child class inherits the base class, a copy of the small box is placed in the big box. So if the small box (base) had 2 methods, they will also be part of the bigger box. However, if the bigger box already has 2 methods with the same name, then the ones in the big box will overwrite or hide the ones in the small box. However, the methods from the base class still exist in the derived class (that's why I said 'hidden') and we'll see how to access them in the triangle example. Back to the Square class, the initialize method overwrite the Shape overwrite method, the Shape initialize method is never called. Instead we set both @height and @width to the value of side, also note that the Square initialization method only takes one argument (side) because in squares, all sides have the same length (height = width and both of them = side). As for the overwritten area method, the area is computed by multiplying height by width. We could achieve the same result by multiplying height by itself, or width by itself (since they have the same value), or even do something like @height**2 which means '@height to the power of 2'. Anyways, after @height * @width is calculated the result is converted to a string and printed with "The area is: ". The parenthesis ( ) are needed because we need to convert the result into a string, not one of the sides (not @height.to_s). Since the print_dimension class doesn't need to be changed, it is not overwritten and can be called as if you actually coded it. Here's an example of using the square class:
Code:
my_square = Square.new(4)
p my_square.height * 4
my_square.print_dimensions
my_square.area
The program output would be:
Code:
16
Height is 4
Width is 4
The area is: 16
And now, we move the the triangle example, which illustrates the way to call hidden base class methods.
Code:
class Triangle < Shape
def initialize(base, height)
super(base, height)
end
def area
the_area = @width * @height / 2
p 'The area is: ' + the_area.to_s
end
def print_dimensions
super
p "AND THIS IS A TRIANGLE!!"
end
end
The triangle overwrites all the methods from the base class. Instead of setting the height and width manually, the triangle calls the base (Shape) initialize method with the given arguments. the 'super' keyword tells Ruby to call the method with the same name from the base class. Remember when I said the base methods are hidden when overwritten? They aren't really lost (which is what overwrite implies), but rather hidden. The methods that covered them can still call these hidden methods and pass arguments to them. Moving on, the area method isn't changed a lot, the area is just calculated differently (0.5 * base * height). Now, the print_dimensions method is extended with a new string "AND THIS IS A TRIANGLE!!". super calls the print_dimensions method of the base class (which prints height and width), and then the new print statement is added. It's like typing:
Code:
def print_dimensions
p "Height is #{@height}"
p "Width is #{@width}"
p "AND THIS IS A TRIANGLE!!"
end
The super keyword is so useful, you can write your base classes so that they do the basic things and then let the child classes add stuff on their own. In some RMXP built-in classes the instance variables are initialized in the initialize method (eg. @mp = 0), and when a class inherits such a built-in class they'd just call the super method and initialize any variables unique to the child class. Here's an example of using the Triangle class:
Code:
funny_triangle = Triangle.new(10, 20)
p funny_triangle.width + 2
funny_triangle.print_dimensions
funny_triangle.area
The program output would be:
Code:
12
Height is 20
Width is 10
AND THIS IS A TRIANGLE!!
The area is: 100
8. Scope Rules
When discussing classes, I said that the scope of instance variables (ones starting with @ sign) is limited to the class only. Only methods inside the class may access those variables, but anything outside the class doesn't know of the existence of such variables. To solve this, we had to use attr_accessor to make the variable 'public' rather than 'private'. One way to think of scope is the area where variables could be used.
Another way to think of scope is the life time of a variable. Consider this example:
Code:
outer_var = 10
def lol(num)
inner_var = num
print "Printing outer_var inside the function lol: " + outer_var.to_s
print "Printing inner_var inside the function lol: " + inner_var.to_s
end
lol(999)
print "Printing outer_var outside the function lol: " + outer_var.to_s
#print "Printing inner_var outside the function lol: " + inner_var.to_s
The output would be:
Code:
"Printing outer_var inside the function lol: 10"
"Printing inner_var inside the function lol: 999"
"Printing outer_var outside the function lol: 10"
Which is what you'd expect. However, if you remove the # sign (comment) before the last line in the example (the one that prints inner_var outside the function lol), then you would get an error!
What happened? Remember blocks and functions? Remember how they are like boxes and stuff? The function lol is a block, anything inside a block is hidden from the outside world, but anything outside the block is visible to the block. In other words, the inner variables die once the block ends (with the keyword end), but outer variables die once the whole program ends. You can't talk to a dead person, and you can't access an inner variable outside its block.
To make it easier to understand, think of a block as a person who doesn't like giving private information to others, but doesn't mind knowing other people's private information. Another way to think about it is to look at variables that are outside a block as national news that everyone in the country knows. On the other hand, variables inside blocks are like local news, they are only known to people in a certain town.
So, scopes are only inside functions and classes? Nope, anything that ends with the keyword 'end' defines a scope. That means variables inside a for loop or an if statement are considered inner variables. Also, when a block is nested inside another, the inner block can access the variables of the outer block, but not the other way around. Back to the news example, local news in a small town might not be known to other parts of the region, although the region itself has local news that isn't known to the rest of the country. Generally, all control flow blocks define their own scope. Loops, conditional expressions, etc. Wherever you find an 'end' you find a scope!
9. Types of Variables
Local variables are variables that start with a small letter or an underscore _. Their scope is in their block. If they're outside all blocks, their scope is their file (or RMXP script editor section). They are like local news in a town, or even a country.
Global variables start with a $ sign. Their scope is the entire program, if you define a global variable inside a method, you can still use it anywhere else in your program. But you can't do that with a local variable, because their scope is limited to their blocks. You could think of global variables like global news, news that is known to everyone around the globe.
Instance variables start with @, they are used in classes. The scope of an instance variable is inside its class, any method in the class may access instance variable. By define attribute accessors (attr_accessor), you can access those variables outside their class. Each object of the class gets its own set of instance variables.
Class variables start with @@ (I think), they are also used in classes. They are similar to instance variables, except that there's one copy in all objects of the class. If one object changes a class variable value, it'll be changed in all other objects of the class.
Code:
var = 3 # local variable
$var = "hello" # global variable
class Meow
@@var = 0 # class variable
@var = 0 #instance variable
end
9.1. Constants
A constant is a variable that has a fixed value that you can't change (actually you can change it, but you'll get a warning). Constants are often used to add clarity to your program by using constants. They also save you some typing. For example, maybe you have a method that converts degrees to radians (units for measuring angles). The formula for this is n degrees = n * PI / 180 radians. Your method might look like this:
Code:
def deg_to_rad(degree)
return degree * 3.141592654 / 180
end
If you used the value PI in many other methods in your program, you might like to make your code more readable (less numbers) by defining a constant named PI, like this:
PI = 3.141592654
def deg_to_rad(degree)
return degree * PI / 180
end
Looks better now! A constant is like a variable, but it must start with a capital letter (like classes) while variables must start with a small letter (like methods). Another difference is that you'd get a warning (like an error, but it doesn't terminate your program) if you try to change the value of a constant. In general, you should try to use constants instead of plain numbers (like 3.141592654) whenever possible. You can also put constants in classes like this:
Code:
class Cows
COWS_NUMBER = 10
def print_number
print COWS_NUMBER
end
end
9.2. Class Methods (Singleton Methods)
All the classes we've been creating so far contains 'instance methods', instance methods can only be called by an object. If you have a class called File which represents an open file (text file for example), and this class has an instance method called path, which returns the location of the file (eg. "my files\text\file1.txt"), you call the method like this:
Code:
class File
def initialize(filename)
@filepath = filename
# some other initialization stuff
end
def path
return @filepath
end
end
my_file = File.new("my files\text\file1.txt")
print my_file.path
To call any instance method, you create an object (in this case my_file) and then call the method of that object. You can't do something like File.path because instance methods must be associated to an object. Why? Because every File object has its own path, so the path method must be different for all File objects.
Sometimes, however, you have methods that aren't really unique to each object. For example, your File class might have a method called rename which renames a file. You pass the original filename to the method, and the new name, and then it renames the file. Thing is, you don't need to open the file to rename it. Having this method as an instance method (as in, each File object has a copy of it) isn't that useful, because the same method can be applied on any file, open or not. One idea would be creating a 'global' (outside any class) method to do it, but then... renaming files is connected to the File class, it makes sense to have it as a method... if only we could call the method like this: File.rename("old.txt", "new.txt") instead of my_file.rename("old.txt", "new.txt). Maybe nothing seems wrong with the latter approach, but it's not a good way to design a class. An object has data and methods that operate on data. However, rename isn't operating on the object data; it could be used to rename any file, even if it wasn't the file represented by my_file.
Well, the best way to do it is use a class method, a method that isn't linked to a particular object. Something like:
Code:
class File
def initialize(filename)
@filepath = filename
# some other initialization stuff
end
def path
return @filepath
end
def File.rename(oldname, newname)
# some complicated code to rename files~
print "The file " + oldname + " has been successfully renamed to " + newname
end
end
my_file = File.new("my files\text\file1.txt")
print my_file.path
File.rename("BigBoss.txt", "Snake.txt")
The only changes are adding the rename method to the class, and calling it to change BigBox.txt to Snake.txt. Notice the definition of the class method rename:
Code:
File.rename(oldname, newname)
Without the "File." part, the method will be treated as an instance method; each object will have its own copy of rename (like the path method). However, by prefixing the method name with the class name and then a dot (File.rename) you are stating that the rename method isn't linked to any particular object and that it can be invoked (called) directly from the class itself. As you can see, the rename method was called directly from File (File.rename("BigBoss.txt", "Snake.txt")). Cool, isn't it? 8)
Just a note: The File class is actually part of the built-in classes provided by Ruby. You don't have to write it yourself. I just used it as an example. Built-in classes and modules provide many useful methods that you can call right away. (Like the print method we've been using all along)
9.3. Modules
A module is very similar to a class, except that you can't declare instances of a module (you can't create variables of the module type), and you can't use inheritance with modules (no subclasses and super classes). So, a module is simply a place to put methods and constants in. Why use modules?
1) A module provides a namespace to prevent name conflicts.
2) You can include modules in classes to extend the classes.
Preventing name conflict is the simplest case, say you have two methods named abs, one of them returns the absolute value of a number, and another sets up an action battle system (ABS). The problem is that both methods have the same name, and Ruby wouldn't know which method to call. The easiest way to fix this is just choosing another name for one of the methods, like action_battle_system for the battle method. But let's say you're writing a set of methods for other people to copy and paste and use them in their projects, and maybe one of these people already has a method called abs or action_battle_system that does something different. This is called a name conflict (or clash), two methods, variables or constants having the same name.
One way to fix this problem is wrapping these methods in different classes like a Math class to hold one method and a Battle class to hold the other method. The new problem is, you don't really need objects of the Math class, having a math object (like my_math = Math.new) makes little sense. What about using class methods? This way we don't have to create objects and can directly call Math.abs or Battle.abs. Well, people can still create math objects, and you don't want them to do it. Another problem is that the reason for using these classes is preventing name conflict, not defining a type. Classes define types, so it's a poor decision.
Do not fear, modules are here! As I mentioned earlier, a module is like a class, but you can't have objects of the module. It just provides a place to put methods and variables in. In the C++ programming language, modules are called namespaces, which reflects their use. Now, for something more practical:
Code:
module Math
PI = 3.141592654
def Math.square(n)
return n * n
end
def Math.cube(n)
return n * n * n
end
def Math.abs(n)
if n >= 0
return n
else
return -n
end
end
end
print "The value of PI is: "
print Math::PI
print "The value of square(3) is: "
print Math.square(3)
print "The absolute value of -1 is: "
print Math.abs(-1)
This is a simple module containing several mathematical methods, and a constant for PI. Ruby has a built-in module called Math, but it doesn't have these methods because they're too simple (or can be done in other ways, such as using the .abs method, or the power operator **), the built-in Math module has methods to calculate sines, logs, etc.
As you can see, modules are defined with the keyword 'module', and are closed with the keyword 'end'. Like classes, modules are open, you can add to a module by defining it again. Inside the module you can have constants, methods, classes, or whatever you like. To access constants outside the module, you use the :: operator (I think it's called the scope operator because it allows you to access the scope of the module, remember that a module defines a local scope like classes). So, to access PI you use Math::PI. As for methods, you need to declare them like Class methods to be able to access them from outside. If you declare them as instance methods (def method_name instead of def Module_name.method_name) you can't access them outside the module*.
*Maybe you can using the scope operator ::? I don't have a ruby compiler at the moment, so I don't know~
The other use of modules is called mixins, extending classes by including modules in them. When you include a module in a class, everything in the module becomes part of the classes. If you had instance methods inside the module, they become instance methods of that class. It's similar to inheriting a super class, as you get access to all methods and constants. However, you can include (mixin) more than one module, but you can only have one super class. Here's an example:
Code:
module Hello
def hello_world
print "Hello, World!"
end
end
class A
include Hello
# whatever~
end
class B
include Hello
# whatever~
end
aaa = A.new
bbb = B.new
aaa.hello_word # outputs: "Hello, World!"
bbb.hello_word # outputs: "Hello, World!"
As you can see, including the Hello module in both classes allowed them to use hello_word as if it was defined inside them.
Mixins are very useful, If you include the built-in Comparable module in your class you can extend the class to support comparison operator (>, <, >=, <=, ==). You only need to define a method called <=> and include the Comparable module. The <=> represents the <=> operator, which is a comparison operator that returns 1 if the first operand is bigger than the second, 0 if they're equal, and -1 if the first is less than the second. For example, 3 <=> 4 returns -1, 7 <=> 7 returns 0, and 5 <=> 2 returns 1. Remember the Hero class? I'll extend it by adding the comparable module to allow comparison between different hero objects depending on HP. In other words, hero1 > hero2 would translate to hero1.hp > hero2.hp.
Code:
class Hero
include Comparable
attr_accessor :hp
attr_accessor :mp
attr_accessor :str
def initialize
@hp = 0
@mp = 0
@str = 0
end
def <=>(other_hero)
if @hp > other_hero.hp
return 1
elsif @hp == other_hero.hp
return 0
else
return -1
end
end
end
hero1 = Hero.new
hero1.hp = 10
hero2 = Hero.new
hero2.hp = 5
if hero1 > hero2
print "hero1 has more hp than hero2!"
end
A better way to write the <=> method is:
Code:
def <=>(other_hero)
return @hp <=> other_hero.hp
end
10. Conclusion
I know the last three (including this one) tutorials might've been long and badly written, but I haven't been into Ruby recently. This is the last tutorial that'll deal with the language itself, future tutorials would focus on the use of Ruby (RGSS) in RMXP. Stuff like windows, sprites, etc. Frankly, I think that if you've been following this tutorial all along, you can start looking at the classes that come with RMXP, try to understand how they work, and try to change things a bit. I'd recommend http://www.phylomortis.com for an excellent coverage of the built-in classes. Good luck! Next tutorial will discuss general concepts in game programming, stuff like update loops, frames, sprites, graphics, etc.
11. Summary
- Everything is an object! An integer number is an instance of the Integer class and has access to its methods.
- You use classes to define new data types that have data and methods to operate on the data.
- The class keyword is used to define classes, classes names start with a CAPITAL letter.
- The initialize method is called when an object of the class is created.
- An object is an instance of a class, a variable of the data type represented by the class.
- Instance variables can be used anywhere inside the class and are preceded with @.
- To access instance variables outside classes, use methods or attr_ commands.
- There are many examples of inheritance in real world.
- Inheritance is where some objects share common traits.
- A class that inherits another is called a child or derived class, a class that is inherited is a base, super or mother class.
- When a class inherits another, all the methods and variables of the base class become part of the derived class (unless you specify otherwise).
- Methods in the derived class hide or overwrite the methods with the same names from the base class.
- When monkeys and cows fight, pirates win.
- The super keyword is used to call the method with the same name from the base class.
- Blocks define scopes for variables; local variables can't be accessed outside their scope.
- Their are various kinds of variables in Ruby. Local variables, global variable, instance variables, and class variables.
- Constants represent a constant value that shouldn't be changed.
- Constants start with capital letters, unlike variables.
- Class Methods can be called directly without creating instances (objects) of the class.
- Modules provide a way to prevent name conflict.
- Modules provide a way to extend classes by including modules inside classes.
- When you include a module in a class, the class has access to all the module's instance methods.
|
|
Details
|
|
Tutorial:
|
RGSS For Dummies Tutorial 5: Object Oriented Programming and More |
|
Date Listed:
|
2008-06-05 |
|
Author:
|
RPG
|
|
Total Hits:
|
5527 |
|
|