My Assistant
Supernatural (tv show)"Bela: You know, when this is over, we should really have angry sex.
Dean: (after thinking hard) Don't objectify me.
"
| Entertainment
| Game Making
| Creative Forum
| Technology & Computers
| Translations
Aug 10 2008, 08:15 PM
Post
#1
|
||
![]() Look what the Khat dragged in... CelestialMember No.: 54 Posts Liked: 0 * Joined: 3-December 04 Posts: 2,115 --> From: Narnia! (Just north of Lantern Waste) RPG Maker Level: Good Class Title: combat muskrat |
Chapper 2 (Being the second of a series of chapters which number greater than or equal to two.)
Okey doke. In chapter one we covered some basic sprite operations and made a sword, a crazy woman and a cat. Now with our great sense of accomplishment we can proceed to get even more funky than before. Go ahead and create a new project because this time around we're going to work with classes so we can animate our sprites. In fact, once your new project is created, go into the script editor and delete everything. That's right. Everything. Delete all of the script sections and empty the last one out of all its code. We're going to start from scratch here and write a program purely in RGSS that will do some basic grahpics operations. Since all of the default script was removed there's no maps or characters or anything to deal with. We're not making an RPG any more. We're writing our own program. Just so you can see that this will work, go ahead and let's test out some basic commands here. Enter the following script: RUBY print "This is a test using the 'print' command." p "This is a test using the 'p' command." Save it and run it. That's it. That's your whole game. This should help to free you from the idea that some people seem to get that the scripts are an additional part of a game created by the editor. This is not the case. The fact is that the scripts themselves are the whole program. They merely load information from files created by the editor in order to create your RPGs. That may sound scary to an RGSS noob, but when you think about it what it really means is that RMXP games are 100% customizable. There is no aspect of your game that cannot be modified to do whatever you can dream up. The only thing you can't change, in fact, are the built-in functions which are hard-coded in C into the game, and even those can be replaced. They just can't be directly modified. So now we've closed the world and we're ready to open the next. Let's get started. First let's talk about classes. If you know anything about Ruby/RGSS then you've probably heard the saying, 'Everything in Ruby is an object." This is true. Ruby is what's called and 'object oriented' language. That means that when you declare a variable of any kind the underlying system handles that variable as and object rather than a value. The difference is that an object has properties and methods. For instance, in C you can declare a number variable like this: CODE int mynum = 4; The computer will reserve four bytes of memory (on a 32-bit system) and set its value to '0x00000004'. That's all that variable is. It's just the number 4 sitting there in memory and the program will remember its location until 'mynum' loses scope (becomes unreachable). In Ruby/RGSS (I'm tired of doing that, so I'm just going to call it Ruby from now on.) it's different. If we say: RUBY mynum = 4 The underlying system creates an instance of the class 'Object' and puts it into 'mynum'. The Object class has several methods and properties that are now all attached to 'mynum'. Once the Object object is created Ruby adds in the methods and properties of the 'Numeric' class, then the 'Integer' class, then finally the 'Fixnum' class. Then the actual value of the object gets set to '4'. Obviously this is a much more complicated process, but it allows us to do things to our number a lot more easily than we could in C. For instance, if we wanted to convert our C number into a string (some text) and then print it out we would have to do this: CODE #include <stdlib.h> //have to include this to use sprintf. int mynum = 4; char buffer[2]; sprintf(buffer, "%i", mynum); printf(buffer); We would have to create another variable and convert the value of our number into our new variable, then print it out. In Ruby we can just do this: RUBY mynum = 4 mynum = mynum.to_s print mynum Because 'mynum' is an instance of the 'Fixnum' class it has a method called '.to_s', or 'to string'. The code is similar in both cases but the Ruby code is a lot easier to understand and gets the job done in fewer lines. So that's how objects work. They aren't just values, they're actually structures in memory that have access to their own special methods and properties. So how do we make our own objects? Easy! We just define a class. A class is just the recipe for a new type of object. We can create a new class like this: RUBY class Animal def initialize @furry = false @smelly = false @age = 0 @name = "-no name-" end def showinfo report = "" report << "This animal is " #when you use '<<' on a string it adds stuff to the end of the string. if @furry report << "furry.\n" #the '\n' makes it move to the next line (like pressing enter) else report << "not furry.\n" end report << "The animal is also " if @smelly report << "smelly.\n" else report << "not smelly.\n" end report << "The animal is " + @age.to_s + " years old.\n" report << "The animal's name is " + @name + "." print report end end Then we can create an object based on that class by saying (put this after the class): RUBY myanimal = Animal.new But it doesn't do anything yet. Let's make it do that reporting stuff that's in the 'showinfo' method. RUBY myanimal = Animal.new myanimal.showinfo Okay, that's great. Now let's add a new method that will let us set the properties of the animal. We can add this inside the class: RUBY def setup(fur, smell, age, name) @furry = fur @smelly = smell @age = age @name = name end Now we just add one more thing to our commands: RUBY myanimal = Animal.new myanimal.setup(true, false, 5, "Yogi") myanimal.showinfo And now if we run it... Our animal has properties! I know this isn't very exciting, but it's very important to understand these concepts. This is the cornerstone upon which all of Ruby is built. If you understand these simple concepts then you're halfway to becoming a Ruby programmer. Just to make sure you're not confused yet here's what your full script should look like so far: scriptNow let's cover one final aspect of classes that you'll need to know. Inheritance. Remember how I said that when we declare a '4' it's actually more than one type of class? (Object > Numeric > Integer > Fixnum) That's because of a principal called inheritance that allows you to create a new class without having to rewrite a whole bunch of crap that's already in a similar class. For instance, we already have an animal there. Let's get more specific and make a dog. A dog is an animal, so it can inherit from the animal class. Clear out the commands after your Animal class and add this code where they were: RUBY class Dog < Animal #the '< Animal' here causes the inheritance def initialize(age, name) super() setup(true, true, age, name) end def action print @name + " barks!" end end Pretty short class there, right? Now add these commands: RUBY myanimal = Dog.new(8, "Rover") myanimal.showinfo myanimal.action Now go ahead and run that. As you can see, since Dog inherits from Animal, it can use all of the methods and properties that we set up in Animal. That can save you a lot of time and trouble when making new or similar classes, and it's important for understanding the default scripts. You'll notice that there's an 'initialize' method in both the Animal class and the Dog class. The one from the Dog class overrides the Animal one when we create a Dog object. However, you can also see that I used the command 'super' in there. That causes Dog's initialize to look at the class it's inheriting from and call the method with the same name. So: RUBY def initialize(age, name) super() setup(true, true, age, name) end Is kind of the same as: RUBY def initialize(age, name) @furry = false @smelly = false @age = 0 @name = "-no name-" setup(true, true, age, name) end Which can also save a lot of time and trouble when you're writing classes. Okay. Last thing, then we'll move on to more graphics stuff. Once you've written a class you can declare as many different instances of it as you want. Set your commands like this: RUBY one = Animal.new one.setup(false, false, 4, "Tom") two = Dog.new(8, "Rover") three = Dog.new(9, "Killer") two.setup(true, false, 8, "Joe-bob") one.showinfo two.showinfo three.showinfo And go ahead and run it. As you can see, we have one instance of Animal and two instances of Dog all operating independently of one another. Each instance is its own object, and does not share its variables with other objects created by the same class. So "Rover/Joe-bob" can be not-smelly and "Killer" can be smelly. There is a way to make a variable that all instances of a class share, though. You'll probably never use this, but I'll show you anyway, just so you can be a Ruby whiz. Change your Dog class to this: RUBY class Dog < Animal @@numdogs = 0 def initialize(age, name) super() setup(true, true, age, name) @@numdogs += 1 end def action print @name + " barks!" print "There are a total of " + @@numdogs.to_s + " dogs here." end end As you can see, I added a variable with @@ before its name. This is a 'class variable'. It's shared by all instances of that class. I declared it outside of the method definitions so that its value doesn't get reset by any of the methods. Now add one command to the end of your command list: RUBY two.action When you run it you'll see that you now can tell how many Dogs you've got running around. Mostly useless, but good to know. Now on to graphics! Go ahead and clear out all that script or else start a new project and remove its scripts. We're going to create a class to act as a scene for us. Let's make just the bare essentials of it like so and then pause to look at what we're doing: RUBY class MyScene def main while $scene == self Graphics.update Input.update update end end def update #nothing yet end end Okay, so our class is named 'MyScene'. It has two methods. One of them is 'main'. Our 'main' method starts a loop that keeps running as long as the global variable $scene is equal to this instance of MyScene. As long as that's the case our loop executes three commands: 'Graphics.update' draws the sprite objects onto the screen and waits for the frame-timer to be done (to keep the program at 40 fps). This method must be called at least once every 10 seconds. If it's not then the game will assume that it's stuck in a loop and it will crash itself. 'Input.update' captures the current state of the keyboard/control pad input. You call this once per frame and then check the state of the 'Input' module to see what keys were pressed when this was called (we'll get into this a little). 'update' just calls the update method we define below. We cam make it do whatever we want. Our update method, as you can see, doesn't do anything yet. There's a few problems here, though: How does our scene object get declared? How does the 'main' method get called? Where is this $scene variable declared? And our solution is 'Main'. That's right. That script section that you always pasted your scripts just above before. It does all of those things. Let's write our own really quickly. Create a new script section. This time it needs to be the very bottom section. Put this in it: RUBY begin #start an error-control block $scene = MyScene.new while $scene != nil $scene.main end rescue #if there's an error p $! #print the error message out (before quitting the program) end #end the error-control block You may have to create the new section above the existing one and transfer 'MyScene' into it and then put 'Main' into the bottom one, but it's important that this code be at the very bottom of all other script. Okay. So a quick look at that and we can see what's happening here. Ignoring the error-control stuff, you can see that we're not inside any method definition, so any commands we put here will get executed when we hit 'play' in RMXP. The first thing we do is make a global variable called $scene and put a new instance of our class into it. Our class doesn't have an 'initialize' method, so it won't do anything special when it gets created. Next we enter a loop. As long as $scene is not 'nil' (empty) we'll keep running $scene.main. In this case that means MyScene.main will get run. This is the same method that the default scripts use to control the program. When you're on a map, for instance, $scene has a copy of Scene_Map in it. When you hit the menu key the code in Scene_Map sets $scene to be a new instance of Scene_Menu. Since $scene is no longer equal to Scene_Map, the loop in Scene_Map.main gets broken and the program drops back to the loop in Main. $scene does not equal nil, so $scene.main gets executed. Since we just put Scene_Menu in $scene that ends up being Scene_Menu.main, and the new scene gets started. That sounds really complicated, but take a little time to understand it and you should have no more trouble following the flow of the program within the default scripts. It's all just loops inside of loops. Nothing more and nothing less. Great. So now let's make our program create a sprite. So far in our scene we have two methods. 'main' will get called once and holds the loop that refreshes the grahpics and controls and then calls 'update'. Because of this setup we should create and destroy our persistent objects in 'main'. We'll create them before the loop and destroy them after the loop. Then we can monkey with them in 'update'. That way we don't have to re-create our object every frame. So let's make a simple sprite. Let's make our sword lady come back. I sorta miss her. (I'm a sucker for crazy chicks.) Set up your 'MyScene.main' like this: RUBY def main #create my sprites here @swordgirl = Sprite.new @swordgirl.bitmap = Bitmap.new("Graphics/Battlers/008-Fighter08") @swordgirl.x = 100 @swordgirl.y = 100 while $scene == self #this is our 'control loop' Graphics.update Input.update update end #destroy my sprites here @swordgirl.dispose end You can run the program and see that indeed sword-girl has returned. Congratulations. You've made your own fully functional RMXP scene! Right. You may be wondering why we can create 'Sprite' class objects since we deleted all of the scripts. 'Sprite' is actually a 'built-in' class in RGSS. It's part of the RGSS 'library'. This class is handled by C code that's built in to Game.exe. RGSS is actually just Ruby with some extra classes and modules added to it, such as 'Sprite'. Now that we have our scene, let's set it up so we can move sword-girl around on the screen. Set up your 'update' method like this: RUBY def update if Input.press?(Input::DOWN) @swordgirl.y += 1 end if Input.press?(Input::LEFT) @swordgirl.x -= 1 end if Input.press?(Input::RIGHT) @swordgirl.x += 1 end if Input.press?(Input::UP) @swordgirl.y -= 1 end if Input.trigger?(Input::B) $scene = nil end end As you can see, this looks at the Input module to see what buttons were pressed when our 'main' method called 'Input.update'. If one of the directional buttons was pressed then it moves the sprite by 1 pixel in the appropriate direction. If the 'B' button (the cancel button) is pressed it sets $scene to nil. In that case $scene will no longer equal this copy of MyScene, so our loop will break and we'll go back to the loop in the 'Main' section. $scene will equal 'nil', so that loop will break too and the program won't have any more commands to execute, so it will exit. You can mess around with that for a minute if you like. Now let's do something a little more complicated. Remember our cat spritesheet? Let's animate it! Get rid of @swordgirl and let's prepare our sprite like this: RUBY def main @cat = Sprite.new @cat.bitmap = Bitmap.new("Graphics/Characters/152-Animal02") @cat.src_rect.set(0, 0, @cat.bitmap.width / 4, @cat.bitmap.height / 4) @cat.x = 100 @cat.y = 100 while $scene == self Graphics.update Input.update update end @cat.dispose end Then remember to change all of the @swordgirl references in 'update' to @cat. If you run that you can move the cat around on screen, but it's not animated. First let's get it to face the proper direction. We can use a different method of checking our input to make this easier. Let's use Input.dir4. Set up the update method like this: RUBY def update case Input.dir4 when 2 #down @cat.y += 1 when 4 #left @cat.x -= 1 when 6 #right @cat.x += 1 when 8 #up @cat.y -= 1 end if Input.trigger?(Input::B) $scene = nil end end If you're not familiar with 'case' then this is a good example. Input.dir4 returns a number that indicates which direction is being pressed. The number it returns corresponds to the numbers on the numeric keypad of your keyboard. You may remember that I mentioned something like that in lesson 1. This is going to make things a little easier for us. First, though, you can see the case tree there. It accepts Input.dir4 as its input and then has a branch for each possibility. If Input.dir4 returns '2' then the branch starting with 'when 2' gets executed. Pretty simple. So now we have to determine how to change the src_rect of our @cat to make him face a new direction. We know that the cat faces different directions along the Y dimension. So we can adjust our Y value in the src_rect to change the direction the cat is facing. If you'll remember in lesson 1 I talked about how the directions correspond to the order of the numbers on the keypad. Since our Input checks are returning those numbers we can use a little math to calculate our Y value based on the input. The formula will look something like this: ((input number / 2) - 1) * (height of src_rect) = Y value Since our Y values start at 0 we can convert the input value to the direction by dividing it by 2 and subtracting 1. When we divide by 2 the values become 1,2,3,4 respectively. Since Y starts at 0 we subtract 1 and get 0,1,2,3 as possible outcomes. Each sub-image has the same height, which is the height of our src_rect, so we multiply our facing value by that number to get our Y value (it's called the 'y offset' in graphics programming). The Ruby code ends up looking like this: RUBY @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) In fact, we can just add that line to our program in the blank line after our case tree: RUBY def update case Input.dir4 when 2 #down @cat.y += 1 when 4 #left @cat.x -= 1 when 6 #right @cat.x += 1 when 8 #up @cat.y -= 1 end @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) if Input.trigger?(Input::B) $scene = nil end end You can test that out and see that there's a problem. Our cat only appears when we press a direction key! That's because Input.dir4 returns a 0 when no direction keys are being pressed. That means we're setting our Y value to a negative number and our cat is being invisible. As cool as invisible cats may be, let's fix this. The simplest way to fix it is like so: RUBY def update case Input.dir4 when 2 #down @cat.y += 1 @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) when 4 #left @cat.x -= 1 @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) when 6 #right @cat.x += 1 @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) when 8 #up @cat.y -= 1 @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) end if Input.trigger?(Input::B) $scene = nil end end That way the cat's direction will only be set when a direction key is pressed. Try it out. No more invisible cats for us. But our cat still doesn't move its feet while it's walking. We have all those animation images. Let's put them to use. But how? Well, the simplest thing we could do is make the cat always move his feet. In fact, let's do that first and then do it the right way. We'll create a counter that we can use to cycle through the cat's steps and have it increment (go up by one) every frame. Let's try it. First add a new variable to the class. Do that in 'main' so we don't have to re-declare the variable every frame. (The 'main' method, not the 'Main' script section.) Add this line to the 'main' method of 'MyScene': RUBY @counter = 0 Now we want the counter to go up by 1 every frame, so in 'update' add this line at the beginning: RUBY @counter += 1 Now we just need to adjust the src_rect of @cat to match our counter. The animation frames are along the X dimension, so we'll use a technique similar to the one we used in our Y dimension. RUBY @cat.src_rect.x = @counter * @cat.src_rect.width Put that anywhere in 'update'. There's a problem here, though. The @counter will start at 0 and keep going up forever. We only have 4 frames of animation for our cat, which correspond to 0, 1, 2, and 3 on the @counter. So we need to make the counter go back to 0 whenever it hits 4. We could make a simple 'if' statement to do this, but there's actually an easier way. We can use the 'modulo' operator. Modulo is a mathematics operator, just like * or +. It's actually like division ('/') except that it returns the remainder of the division instead of the quotient. You may have noticed that in Ruby if you divide an integer you always get an integer back, and it never rounds up. For instance, 3 / 2 == 1. This is normal in computer programming. The answer in math should be 1.5, which rounds to 2. Computers don't round numbers for you. They just drop the 'out-of-range' information. Since 3 and 2 are integers (whole numbers) the answer they give is an integer and doesn't contain any information after the decimal point. Likewise, 3 / 4 = 0. However, we can use modulo to get the remainder like this: 3 % 4 = 3. That's because 3 divided by 4 is 0 with a remainder of 3. Get it? So what in the hairball does this have to do with our @cat? Well, it turns out that modulation is a great way to create a looping counter. Try to work through this loop in your mind: RUBY num = 0 loop do num += 1 num = num % 4 end What will the result be? 'num' will start at 0 and go 0,1,2,3... 0! Because when 'num' becomes 4 the remainder of 4 / 4 is 0. This way our 'num' will go 0,1,2,3,0,1,2,3,0,1,2,3.... forever. Let's apply that understanding to our @cat by adding the line: RUBY @counter = @counter % 4 Add it just after your '@counter += 1' line. So your 'update' method should look something like this: RUBY def update @counter += 1 @counter = @counter % 4 @cat.src_rect.x = @counter * @cat.src_rect.width case Input.dir4 when 2 #down @cat.y += 1 @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) when 4 #left @cat.x -= 1 @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) when 6 #right @cat.x += 1 @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) when 8 #up @cat.y -= 1 @cat.src_rect.y = ((Input.dir4 / 2) - 1) * (@cat.src_rect.height) end if Input.trigger?(Input::B) $scene = nil end end Go ahead and test it out. WHOA! Look at the little guy go! He's moving way too fast! His animation is getting update 40 times every second, so he looks like he's on crack or something. Let's slow him down with a little bit of division. Set your modulo value to 16 instead of 4, then when you use @counter to determine the src_rect.x divide it by 4 like this: RUBY @counter = @counter % 16 @cat.src_rect.x = (@counter / 4) * @cat.src_rect.width That way the animation is only 1/4 the speed it was before. Since we're working with integer division we don't have to worry about fractions messing up our x value and getting 2 halves of a cat or anything weird like that. In this case 8 / 4 = 2 and 9 / 4 = 2 as well, so we're okay because we'll never have animation frame # 1.5 or anything like that. Now that was easy, but it's not right. We only want the cat's feet to move when it's walking. This cat is starting to get a little bit complicated. In a real program we're going to need all sorts of more important stuff in that update loop that doesn't have anything to do with the cat. So let's turn the cat into its own class, shall we? So far the cat is a Sprite. It also has some setup instructions, some update instructions that need to be called every frame and it also has two properties: a direction of facing and an animation counter. So let's make a new class that inherits from Sprite and give it some of those things. Create a new script section on the top of the list and put this in it: RUBY class Kitty < Sprite def initialize super self.bitmap = Bitmap.new("Graphics/Characters/152-Animal02") self.src_rect.set(0, 0, self.bitmap.width / 4, self.bitmap.height / 4) self.x = 100 self.y = 100 @counter = 0 end def update super @counter += 1 @counter = @counter % 16 self.src_rect.x = (@counter / 4) * self.src_rect.width case Input.dir4 when 2 #down self.y += 1 self.src_rect.y = ((Input.dir4 / 2) - 1) * (self.src_rect.height) when 4 #left self.x -= 1 self.src_rect.y = ((Input.dir4 / 2) - 1) * (self.src_rect.height) when 6 #right self.x += 1 self.src_rect.y = ((Input.dir4 / 2) - 1) * (self.src_rect.height) when 8 #up self.y -= 1 self.src_rect.y = ((Input.dir4 / 2) - 1) * (self.src_rect.height) end end end Then replace your MyScene with this one: RUBY class MyScene def main @cat = Kitty.new while $scene == self Graphics.update Input.update update end @cat.dispose end def update @cat.update if Input.trigger?(Input::B) $scene = nil end end end Take a moment to look at what I've done there. Our cat behaves exacty like before, but now the code is much cleaner and more organized. Things that only have to do with the cat are now all handled by the cat itself. All we have to do is update the cat once per frame and it internally handles all of its business. This is a great example of how Ruby can keep things organized very easily. Now if I want to modify my cat's behaviour I don't have to hunt through all the scene update code and find it. I can just look in the cat and there it is. You'll notice that in the cat's methods I changed references to '@cat' into references to 'self'. Since the cat is its own class now instead of just a sprite in our scene we need to refer to it that way. 'self' just refers to the object created by the class it appears in. That's a crappy explanation, but if you look at the code for a second you'll understand what I mean. You'll also notice that I made a call to 'super' at the beginning of both of the cat's methods. That's because 'Sprite' has both an 'initialize' and an 'update' method that we want our cat to still be able to use. We'll go over those later. For now let's modify our cat so he only steps when he's walking. Basically we want our animation frame to be 0 unless the cat is in motion. The cat is only in motion when Input.dir4 is not equal to 0, so let's use that information to apply our fix: RUBY if Input.dir4 == 0 @counter = 0 else @counter += 1 end Use that to replace the line that used to say '@counter += 1'. (This is all going on in Kitty.update.) Now test it and... Viola! Intelligent cat movement. Well now... That's a lot of text for one lesson, so I'm gonna go ahead and break here. In the next session we'll look at some of the other methods and properties of the Sprite class and see what all we can make our crazy little cat do. Save this project for next time as we'll continue using it then. Ciao! This post has been edited by Khatharr: Aug 12 2008, 11:06 PM
|
|
|
|
||
Khatharr RGSS Graphics for Noobs - Part 2 Aug 10 2008, 08:15 PM
alwayzconfuzed Yet again, another very helpful tutorial. First tu... Aug 14 2008, 06:15 AM
Khatharr Okay, I'll reveal my secret plan. This is act... Aug 15 2008, 10:39 PM
alwayzconfuzed GENIUS AND DEVIOUS! Though I don't get why... Aug 16 2008, 12:51 AM
Khatharr Everything is data manipulation. That's part ... Aug 20 2008, 09:20 PM
alwayzconfuzed
Ok, I should have said "mathematic manipulat... Aug 21 2008, 05:21 AM
Khatharr Like my secret formula for potato chip dip! X... Aug 31 2008, 03:05 PM![]() ![]() |
The Staff Team |
Lo-Fi Version | Time is now: 13th May 2022 - 08:55 PM |