|
> |
| Home > Articles > Tutorials > Ruby Game Scripting System (RGSS) > RGSS For Dummies Tutorial 5: Object Oriented Programming and More!
|
|
RGSS For Dummies Tutorial 5: Object Oriented Programming and More! |
|
Author: RPG
Updated: September 04, 2007
|
|
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:
my_string = 'cow'
p "The string in caps is: "my_string.upcase!
p "And the size of the string is #{my_string.size}"
|
|
|
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:
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:
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:
# 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.
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.
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:
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:
joe = Hero.new(9999, 999, 500)
|
You could also provide it with fewer arguments because we used default values
(remember the last tutorial?)
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:
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
|
|
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:
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.
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:
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:
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:
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:
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:
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:
# 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:
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:
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:
my_square = Square.new(4)
p my_square.height * 4
my_square.print_dimensions
my_square.area
|
The program output would be:
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.
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:
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:
funny_triangle = Triangle.new(10, 20)
p funny_triangle.width + 2
funny_triangle.print_dimensions
funny_triangle.area
|
The program output would be:
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:
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:
"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.
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:
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:
class Cows
COWS_NUMBER = 10
def print_number
print COWS_NUMBER
end
end
|
|
| 9.2. Class Methods (Singleton Methods) |
|
|
| 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:
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:
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.
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:
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.
|
 |
More in the 'RGSS For Dummies' series:
• RGSS For Dummies Tutorial 1: The Basics
• RGSS For Dummies Tutorial 2: Variables
• RGSS For Dummies Tutorial 3: Control Flow
• RGSS For Dummies Tutorial 4: Containers and Methods
• RGSS For Dummies Tutorial 5: Object Oriented Programming and More!
• RGSS For Dummies Tutorial 6: Game Programming 101
• RGSS For Dummies Tutorial 7: Windows for Dummies |
|