Creation Asylum Banner
X   Site Message
(Message will auto close in 2 seconds)

Welcome Guest ( Log In | Register )

"If a fat girl falls in the woods and there is no one else around, do the trees laugh?"

Unknown Author - T-Shirt
Quick Jump: The AsylumV | EntertainmentV | Game MakingV | Creative ForumV | Technology & ComputersV | TranslationsV

> RGSS Graphics for Noobs - Part 4, Twas brillig, and the slithy toves did gire and gimble in the wabe...
Khatharr
post Sep 16 2008, 11:18 PM
Post #1


Look what the Khat dragged in...
Group Icon


Celestial

Member 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



Veloci-Chaptor 4 (Being an amusing wordplay concerning a bipedal reptilian killing machine)

In this chapter we'll take a look at the Bitmap class. This chapter will mostly be explanations of the information found in the help file. The Viewport class will be covered in the next chapter since this chapter went longer than expected.

First off let's create a sprite and assign it a bitmap. Go ahead and make yourself a new project and remove all the scripts, then set up your sprite like so:

Simple Sprite Script


A simple enough program. It creates the sprite, gives it a bitmap, centers it and then updates until the B button (esc/keypad 0) is pressed.

Now let's go through the methods and properties listed in the Bitmap class.

Firstly it should be noted that a bitmap can be created in one of two ways. In the program we have now the bitmap is created by providing a file name. However, we can also create a new bitmap (a blank one) by passing a width and height instead. For instance,
RUBY
Bitmap.new(100, 200)

Would create a new bitmap that is 100 pixels wide and 200 pixels tall. The pixels would all be set to color 0,0,0,0 and would thus be invisible black.

In the help file we see two methods, "dispose" and "disposed?". The dispose method frees the bitmap's image data from memory. The bitmap object still exists but the image data that it may have contained does not. The "disposed?" method simply returns true or false depending on whether or not the bitmap is disposed. When a Sprite object is created its .bitmap property is set to nil, so using the "disposed?" method is not a good way to check and see if a Sprite has been assigned a Bitmap yet. In fact, as long as you don't get too crazy there's not really any reason to use these methods. The Garbage Collector will free any memory used by unreachable bitmaps, including the image data. (I tested it and monitored the RAM usage from Task Manager. It's fine.) If you do run into memory problems (usually an error saying "Can't create bitmap" or something like that) then it's better to use "GC.start" to solve the problem. That method will cause the Garbage Collector to aggressively free memory for several seconds, including unused bitmap data.

Next up we see "width" and "height". We've been using both of these already. They are simply used to check the width and height of the current bitmap. These values cannot be changed since they're only there to inform you about the properties of the loaded image.

Next is the "rect" method, which returns an object of type "Rect". A "Rect" in RGSS is just a convenient way to store (x, y, width, height). This really isn't useful because the x and y values of the bitmap's Rect should always be zero and the width and height can be referred to directly. These values cannot be changed, even by using Rect.set.

Next up are the two blitting functions, "blt" and "stretch_blt". In graphics manipulation "blitting" is slang for "block transferring", which refers to the process of taking a block of data from one image and transferring it to another image (or to the screen, though RGSS behave differently in that case). Let's experiment a little with blitting.

In your script, just before "loop do" place the following lines:
RUBY
bmp = Bitmap.new("Graphics/Icons/021-Potion01.png")
spr.bitmap.blt(100, 100, bmp, bmp.rect)
bmp.dispose
bmp = nil

When you run the program you can see that the potion bottle has been drawn onto the wing of our angel graphic. It appears even though we disposed of the potion bitmap. Move the angel around a little and you'll see that the potion has indeed been drawn onto it.

You may not use this technique much since it's so easy to have several sprites working together in RMXP, but it's good to know its there just in case.

"stretch_blt" is very similar except that instead of taking an x,y for the target location it takes a Rect. Because of this you can alter the size of the block being drawn onto the bitmap. Let's replace our "blt" with a "stretch_blt" and then play with it a little. Replace the "blt" line with this one:
RUBY
spr.bitmap.stretch_blt(Rect.new(100, 100, bmp.width * 2, bmp.height * 2), bmp, bmp.rect)

All we really changed there is that the width and height of the block are multiplied by 2. This allows you to distort the block being drawn. Not very exciting, again, because we could just make a new Sprite on top of the angel and use zoom_x and zoom_y to change its dimensions. Of course it's easier to just blit to the image and then be able to move it around instead of having to move both sprites, but when we cover Viewports you'll see that they can easily be used for the same thing. It's a matter of taste, really.

As a side note, blitting is a very common graphics procedure in other libraries. In most 2D image manipulation settings you actually have to draw your graphics every frame, whereas in RGSS you only need to set up the properties of the Sprite and the engine takes care of the rest for you. RMXP is actually using Direct3D functions to perform its blitting manually every frame according to what Sprite objects exist and are visible.

Last note on blitting in RGSS, you can add an argument for opacity to either blt or stretch_blt. The range is 0 to 255 and the opacity argument comes at the end.

In addition to blitting you can also use the next method, "fill_rect" to draw your own bitmaps. This method simply draws a rectangle of the specified color onto your image. It is fully possible, using the available methods, to create simple graphics from script and not need to use images from files. For instance, HP/MP bars can be drawn this way and I've also used the fill_rect and set_pixel methods to create progress bars before. This method is very easy to use. You can either provide a Rect object and a Color object or you can provide x,y,width,height and a Color object. This is probably a good time for you to give it a shot on your own, since you can jsut take out the 'bmp' stuff and put in a .fill_rect call to see it work. Give it a shot.

The next method is probably the most straightforward of them all. The "clear" method simply sets all pixels in the Bitmap image to Color(0,0,0,0). That's invisible black. You can use this to clear an image and then redraw it with these other methods.

The next method, "get_pixel" is an interesting one. It simply returns a Color object representing the pixel at the given x,y location in the Bitmap. You can come up with your own uses for this. I can't think of anything right now that I would use it for apart from things that would require including dll files coded in C. (A little beyond the scope of this tutorial.)

"set_pixel", however, can be very useful if you're drawing your own graphics. You can use it to set a pixel at an x,y location in the image to a color based on the Color object you pass. In fact, let's experiment a little with this. We'll use two sprites, both of which we'll draw using only script, and we'll make a fake progress bar that we can control with the arrow keys. First we need to draw the 'container' for the progress bar. Clear everything out and try this:
Draw a simple box

Looks pretty plain... Let's adjust the corners to make it rounder. When you use this method a great way to cheat is to just open MS Paint and draw the image yourself using rectangles and the pencil. As you adjust the image in MS Paint just record every action you take in your script and you'll have a much easier time creating the desired results. (MS Paint shows the x,y location of the pixel that you're highlighting in the lower-right corner.) If you have Photoshop or the Gimp then you'll have an easier time with the Rects because they show the width and height of rectangles as you're creating them.

Using MS Paint (remember the zoom function!) I got this:
Draw a fancy box

That looks a little better. Now let's create another Sprite that will work as the actual colored bar inside the container. Looking in MS Paint I can see that I want my progress bar to take up the space from x,y = 5,5 to x,y = 94,26. A little subtraction there gives me width,height = 89,21. However, we need to reduce that by one pixel from both because we're including both the source and destination pixels in both dimensions.

...

Just trust me on this one.

Anyway, since the container we made is centered we can just create and then center the color bar and it should align itself correctly. Let's create it and then fill it with green to make sure it's in the right spot:
Check the location of the color bar

Now we can get to the part that actually does something. Using the functions we've seen so far we can clear the bar every frame and use fill_rect to fill it in to a certain length depending on a numeric variable. So first get rid of the line that calls '.fill_rect' for 'bar'. With that done let's create a variable called 'progress' that will be our percentage of progress for the bar to show. Just put 'progress = 50' where the line you just removed was. Then, just inside the loop, just before Graphics.update, put:
RUBY
  bar.bitmap.clear #gets rid of what's there
  barlength = (88 * progress) / 100 #calculate the number of pixels the bar should be
  bar.bitmap.fill_rect(0,0,barlength,20, Color.new(0,255,0)) #use .fill_rect to draw the bar

Then let's make a way to control the bar by adding a little section to our controls:
RUBY
  case Input.dir4
  when 4
    progress = [progress - 1, 1].max
  when 6
    progress = [progress + 1, 100].min
  end

Simple enough. Go ahead and test it out and you'll see that you can change the 'progress' with the arrow keys. Not too exciting until you realize that you can replace 'progress' with any value you want, including HP, MP or the progress of some process such as downloading a file. Let's take a brief look at some of the techniques I used there.

To get the length of the bar I multiplied 88 (the number of pixels in a 'full' bar) by 'progress', then divided by 100 (the highest value 'progress' can have). I used my braces to make sure that the multiplication took place first. Since we're working with integers the value being divided by 100 needs to be higher than 100 or else we'll always get zero. (Remember that 99/100 = 0 in integer math.) To make this bar work with a player's HP we would just multiply 88 by the current HP and then divide by the max HP. The formula is always:

pixel_length = (length_range * current_value) / maximum_value

Also, I used a spiffy little trick with the Array class to make sure our 'progress' value doesn't go too high or too low. The Array class has a pair of methods called "min" and "max". They cause an array to return the lowest or highest value respectively. For instance:
RUBY
[0,1,2,3].min #this is equal to 0
[0,1,2,3].max #this is equal to 3
[5,7,3,2].max #this is equal to 7

So basically when you press the left arrow an array is created that holds 'progress - 1' and '1', or '[progress - 1, 1]'. Then using '.max' will return whichever of the two is higher. That prevents 'progress' from going lower than 1. If it hit zero our program would abort with a 'division by zero' error because we divide by 'progress' to get the length of our color bar. I did the opposite with the right-arrow key by using the '.min' method.

Now that we have our bar we can also adjust the color based on the value it's representing(or any other value). For instance, let's make our bar red when it's low and green when it's high and cause it to adjust smoothly with yellow in-between. This will involve a little bit of creative math, but it's definately do-able.

Our color values are red, green and blue respectively. Fortunately we can get yellow by combining red and green, so we won't need to do anything with blue in this case. We know that we want red to be 255 and green to be 0 when 'progress' is low, and we know that we want the inverse when it's high. In the middle though, when 'progress' is 50, we want both of them to be 255. That will give us yellow. So this is what we know:

progress - red / green:
1 - 255 / 0
50 - 255 / 255
100 - 0 / 255

So we need to come up with a pair of formulas that will give us a red value and a green value based on 'progress'. The trick here is to make use of .min and .max again. Our green formula can look like this:

true_green = (255 * progress) / 50

Because we want green to be zero and then hit 255 at 50.

Our red can be:

true_red = 510 - ((255 * progress) / 50)

The formula is essentially the same except that we reverse it by subtracting it from 510, which is twice 255. We use double the value here because when 'progress' is 50 we want red to start to diminish, so it should be 255 at this point, higher below and lower after. 510-255 is 255. I may sound like some sort of mad genius at this point but it took me a minute to get the formulas right even though I've done this exact thing before, so don't feel like you're out of your depth.

Now we can create the color like so:

Color.new([true_red, 0].max, [true_green, 255].min, 0)

Let's try it:
Color changing bar script


That's nice, eh?
Let's move on though. Keep this project open because we'll be using it again in a minute.

Next in the help file is "hue_change". This method changes the hue of the bitmap image just like the slider bar in the database does. It accepts a value from 0 to 360. There's not really any way to properly describe how the hue works. You'll just need to play with it in your own time. When you use this method it changes the image directly. If you say ".hue_change(0)" and then look at the bitmap, then ".hue_change(0)" and look at it again you'll see that it has changed again even though you passed the same value. The image's hue is adjusted on-the-spot and it stays that way permanently. This is not like just setting a property that temporarily changes the way the image looks and then can be undone.

The last three methods, and the class' only listed property are all related, so we'll talk about them all together. They concern drawing text to your bitmap. I'll take them in reverse order since it makes the most sense that way.

The Bitmap class has a property called '.font'. This holds an instance of the RGSS Font class, which allows you to use MS Windows system fonts and set up their properties such as size, bold/italics/etc. Since the Font class is very simple we'll cover it briefly, then look at the text drawing methods and use them to modify our progress bar script.

To create a Font object we just use "Font.new". It accepts a font name as its argument. For instance:
RUBY
myfont = Font.new("Arial")

The font names are the MS Windows system font names. You can see them by browsing to your 'C:\Windows\Fonts' folder. They are listed by name there rather than by file-name. These are the values you can pass to Font.new. You can set the font size by passing an integer as a second argument, but this is optional.

Remember when creating your games that not everyone has the same fonts that you do. Some fonts, such as "Arial" or "Tahoma"(the default) come with Windows and are therefore pretty safe to use, but if you want to use other fonts there's a couple of ways to make sure that a font that exists is used.

The first is the "Font.exist?" method. This will return true or false indicating whether or not a font by that name exists on the PC running the program. You can use this to check and see if the font you want is avaiable and act accordingly. This is a class method, so you can call it without needing to create a Font object first.

Secondly, when providing a name for the font you can pass an array instead of a string and the game will cycle through the array (starting at the beginning) until it finds a font that exists and then use that one. So if you have a really cool font named "Dragon Letters" but you know that not many people have that one you can say:
RUBY
myfont = Font.new(["Dragon Letters", "Tahoma"], 16)

The program will check to see if "Dragon Letters" exists on the system and if not then it will use "Tahoma" instead. The font size will be 16. You can list as many font names as you like this way. Personally I find this method easier than using Font.exist?, but it's up to you.

If you want to change the font of a bitmap without changing the size or other properties you can use the '.name' method on the font object. For instance:
RUBY
myspri = Sprite.new
myspri.bitmap = Bitmap.new(100, 32)
myspri.bitmap.font = Font.new("Arial")
#write stuff
myspri.bitmap.font.name = "Tahoma"
#write other stuff

You can use an array here as well.

There are four settings for a font object as follows:
*size (an integer representing the font size)
*bold (a true/false value)
*italic (a true/false value)
*color (a Color object to set the color to draw the text with)

There are also four class properties that represent the default values assigned when creating a new font object:
*default_name
*default_size
*default_bold
*default_italic
*default_color

If you set the 'default_name' property then you don't need to pass anything when creating a new font object. For instance:
RUBY
Font.default_name = "Tahoma"
font1 = Font.new
font2 = Font.new

Both 'font1' and 'font2' will have "Tahoma" as their font.

It is worth noting that the font size is not in pixels. It is in 'points', which is a different system of measurement that I won't get into. You can play with it a little to get a feel for it. These 'points' are the same values use in programs like Microsoft Word and other text editors and word processors.

When you create a bitmap it automatically creates a Font object for itself and stores it in its '.font' property. This font will have all of the default values unless you change them.

When you want to write something with your font you use the 'draw_text' method of the Bitmap class. However, this method takes a Rect as an argument to determine where in the image to draw the text and what size to make it. If the rect is not as tall as your letters they will be cut off at the bottom. If the rect is narrower than your text then the text will be squeezed horizontally to fit. This only goes to a certain point though, (the helpfile says 60%) then the end will start to get cut off.

So how can you know what size to make the rect?

There's a method called '.text_size' that will return a rect indicating the width and height of a string drawn using the font of the current Bitmap. So you can do something like this:
RUBY
spr = Sprite.new
spr.bitmap = Bitmap.new(100, 100)
spr.bitmap.font.name = "Tahoma"
myrect = spr.bitmap.text_size("This is test text.")
myrect.x = 3
myrect.y = 5
spr.bitmap.draw_text(myrect, "This is text text.")

Let's try this in our progress bar script.
First find the line that says 'progress = 50' and change it to this:
RUBY
progress = 2
bar.bitmap.font.name = ["Centaur", "Tahoma"]

We'll start progress at 2 so it's easy to see the text. We set the font name before entering the loop so it doesn't happen every iteration and slow the program down. We want the text to appear on top of the color, so we place these lines after the call to '.fill_rect' in the loop:
RUBY
  thisrect = bar.bitmap.text_size("Test")
  thisrect.x = (bar.bitmap.width / 2) - (thisrect.width / 2)
  thisrect.y = (bar.bitmap.height / 2) - (thisrect.height / 2)
  bar.bitmap.draw_text(thisrect, "Test")

Now go ahead and test the program and sure enough the word "Test" appears in the progress bar. However, you'll notice that if you move the bar across it you can hardly read it when the color turns yellow.

Oftentimes it can be difficult to read text against a colored background. In order to fix this you can draw a border around the text to make it easier to read. Check this out:
RUBY
  textstring = "Text"
  thisrect = bar.bitmap.text_size(textstring)
  thisrect.x = (bar.bitmap.width / 2) - (thisrect.width / 2)
  thisrect.y = (bar.bitmap.height / 2) - (thisrect.height / 2)
  bar.bitmap.font.color = Color.new(0,0,0)
  for xo in [-1, 0, 1]
    for yo in [-1, 0, 1]
      bar.bitmap.draw_text(thisrect.x + xo, thisrect.y + yo, thisrect.width, thisrect.height, textstring)
    end
  end
  bar.bitmap.font.color = Color.new(255,255,255)
  bar.bitmap.draw_text(thisrect, textstring)

Now you'll see that the text can now be read easily regardless of the colors behind it. All we did there is set the font color to black and then draw the text 9 times around the location desired. The 'for' loops will draw the text at -1,-1 then -1,0 then -1,1 then 0,-1 and so on until all the iterations are done. Then the text color is set to white and we draw the text on target.

This is a complicated way to introduce the 'for' loop, so I'll explain its use. If I want to cycle through a series of numbers or the contents of an array and do something with each value I can use a for loop. For instance, this loop:
RUBY
for number in 1..10
  p number
end

Will print 1, then 2, then 3, etc. until it prints 10 and then it will stop. The ".." between 1 and 10 means 'all values from 1 to 10 including 10'. If I had said "..." instead it would mean 'all values from 1 to 10 but not including 10'. I can also use an array:
RUBY
for value in ["a", "b", "dog", "bear", 7, 8, 42.1]
  p value
end

The loop will cycle through each object in the array and print it out. So in the case of our text I used a for-loop inside of another for-loop. This is called a 'nested loop'. The loop that sets the 'yo' value is considered to be "nested" inside the loop that sets the 'xo' value. Because of this 'xo' will be set to -1 and then 'yo' will cycle through [-1, 0, 1]. Then 'xo' will change to 0 and 'yo' will cycle through [-1, 0, 1] again, and so forth. This gives us our nine 'offset' positions. I simply add 'xo' to the x location of the text and 'yo' to the y location and the text gets drawn all around the target.

A very complicated explanation for a fairly simple trick.

So let's change the text to be a percentage indicator so this thing looks like a real progress bar. This is very easy. All we have to do is change the line that sets the value of 'textstring'. Try this:
RUBY
  textstring = "#{progress}%"

Nice, huh?

You can 'embed' values in a string by using "#{value}". For instance:
RUBY
a_number = 5
mystring = "The number is #{a_number}.  How's that?"
p mystring

That will print out:

"The number is 5. How's that?"

Easy enough. Any values embedded this way automatically have their '.to_s' method called to convert them to a string. Very handy.

So that only leaves us with one last detail. When you use '.draw_text' there's an optional argument that can be placed at the end. This is for text-alignment. If it's omitted then the text will be left-justified, meaning that it will be drawn at the left edge of the rect. If a value of 1 is passed then the text will be centered in its rect, and a value of 2 will cause the text to be right-justified, or pressed against the right-edge of the rect. You can play with that if you like.

Next time we'll look at the Viewport class and possibly at the Window class, which is somewhat similar to the Sprite class. Finally we'll look at the Graphics module and that will wrap up our tutorials. Feel free to ask any questions in the help forums. See you next time.



Want to learn RGSS?
My secret identity


It's come to my attention that people still use scripts I wrote and forgot about a long time ago. If you have a question about a script that I wrote or participated in writing please send me a PM with a link to the script's topic and your question. I don't check the topics, so I won't see your question if you don't notify me. (This does not apply to the script-help forum as I check there often.)
Go to the top of the page
 
Thanks+Quote Post

Posts in this topic


Reply to this topicStart new topic
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:

 



- The Staff Team Lo-Fi Version Time is now: 13th May 2022 - 08:55 PM