Keeping it Small and Simple

2007.12.20

Rubygame tutorial #2: pixels

Filed under: Rubygame, Rubygame Tutorial — Tags: , , , , — Lorenzo E. Danielsson @ 13:10

Welcome to my second rubygame tutorial. This time we will look at pixels, which are the smallest of the building blocks available to you. We will also have a short introduction to timing. At the end of this tutorial you will have a framework for most of the applications that we will be writing over the next few tutorials.

Pixels

A pixel is the smallest possible unit that can be displayed on the screen. There are three attributes of a pixel that you always need to keep in mind: its x-coordinate, its y-coordinate and its color.

In contrast to pygame and just about every single game development library on the planet, Rubygame does not have a method to plot individual pixels. It used to but it was removed. Its not really a problem, because we can easily create our own, but it is a little annoying that something that you need so frequently is not there. Oh, well, let us not worry about such details now.

I normally learn best by looking at examples, so here we go. The following program plots random pixels to the screen.


 1 #! /usr/bin/ruby
 2
 3 # Plot random pixels.
 4
 5 require rubygame
 6
 7 Width = 640
 8 Height = 400
 9
10 module Rubygame
11   class Surface
12     def put_pixel(point, color)
13       self.draw_box point, point, color
14     end
15   end
16 end
17
18 class RandomPixels
19   include Rubygame
20
21   def initialize
22     @screen = Screen.new [Width, Height]
23     @events = EventQueue.new
24
25     @screen.fill [0, 0, 0]
26     @screen.update
27   end
28
29   def event_loop
30     loop do
31       @events.each { |event|
32         case event
33         when QuitEvent
34           return
35         end
36       }
37
38       draw
39       @screen.update
40     end
41   end
42
43   def draw
44     point = [rand(Width), rand(Height)]
45     color = [rand(255), rand(255), rand(255)]
46     @screen.put_pixel point, color
47   end
48 end
49
50 Rubygame.init
51 RandomPixels.new.event_loop
52 Rubygame.quit

The first thing we do is inject a little extra functionality into Rubygame’s Surface class. I chose to call it put_pixel (reminds me of Turbo Pascal’s graph library), but feel free to call it anything you want. The put_pixel method simple draws a box which is a single pixel high and a single pixel wide. The result is that I’ve plotted a single pixel.

Most of the rest of the program should be familiar to you, at least if you have already read tutorial #1. What is new is that there is a call to the draw method inside the event loop. The draw method creates a random point (x, y) and a random color (red, green, blue). Then it calls our Surface#putpixel method, passing in the point and color.

Notice that since the draw method is called inside the event loop, this program will continuously plot randomly colored pixels and random coordinates. There is also a call to Surface#update in the event loop. More about what that does below.

Exercises

  1. Modify the above program to only plot pixels that are random shades of blue.
  2. Modify the above program to only plot pixels of a single color, such as orange.
  3. Modify the above program to keep count of how many pixels have been plotted.
  4. Modify the program to only plot pixels in the top half of the screen.

Timing

Try running the above program. While it is running, open up a terminal and start ‘top’. You can also use any other utility that lets you see your system’s resource usage (such as Gnome system monitor). You may notice that CPU usage is quite high. What is happening?

Our program is actually very busy all the time, it is plotting points and checking if any event have occured, and its doing this constantly. Because of that, the program signals the kernel that “Hey, I’ve got a heavy workload over here. Better help me out!”

The fact is that the program isn’t really that busy. There is time for it to just relax a bit once in a while. If we can let our program take a break, it means there is more time available for the kernel to help out other tasks that are running on your system, and the whole system ends up being happier.

That is obvioulsy grossly over-simplified, but i think you get the point. We need to let our program “go to sleep” every so often. This can be achieved with the help of a clock. The following program looks like the previous one, but we have added a clock to it.


 1 #! /usr/bin/ruby
 2
 3 # Plot random pixels.
 4
 5 require rubygame
 6
 7 Width = 640
 8 Height = 400
 9
10 module Rubygame
11   class Surface
12     def put_pixel(point, color)
13       self.draw_box point, point, color
14     end
15   end
16 end
17
18 class RandomPixels
19   include Rubygame
20
21   def initialize
22     @screen = Screen.new [Width, Height]
23     @events = EventQueue.new
24     @clock = Clock.new
25     @clock.target_framerate = 120
26
27     @screen.fill [0, 0, 0]
28     @screen.update
29   end
30
31   def event_loop
32     loop do
33       @events.each { |event|
34         case event
35         when QuitEvent
36           return
37         end
38       }
39
40       draw
41       @clock.tick
42       @screen.update
43     end
44   end
45
46   def draw
47     point = [rand(Width), rand(Height)]
48     color = [rand(255), rand(255), rand(255)]
49     @screen.put_pixel point, color
50   end
51 end
52
53 Rubygame.init
54 RandomPixels.new.event_loop
55 Rubygame.quit

There are two things that are important here. The first happens on lines 24 and 25, where we initialize a clock and set a desired framerate for it. I’m going to let you experiment a little with the target_framerate. Set it to different values, high as well as low ones. Each time you do, run the program and see how “fast” the program itself seems to run. Also check the CPU usage. What do you notice?

The second thing that you should notice is that in the event loop, we added an extra line. On line 41 we call Clock#tick. This is the line that puts our program to rest for a short while. This call is inside the event loop so it gets called frequently.

Timing the program has other uses than just to lower its CPU usage, but we will look at that more in a later tutorial.

Exercises

  1. Set Clock#target_framerate to 10. What happens to your program? What happens to CPU usage while the program is running?
  2. Set Clock#target_framerate to 1000. What happens to your program? What happens to CPU usage while the program is running?

Doing more than one thing at once

Our program plots a single pixel every iteration of the event loop. Plotting just a single pixel is an expensive operation since @clock.tick and @screen.update take significant time (relatively speaking). In order to speed things up, it is better that we perform more than a single drawing operation at a time. In the following program we plot one hundred pixels each cycle of the event loop.


 1 #! /usr/bin/ruby
 2
 3 # Plot random pixels.
 4
 5 require rubygame
 6
 7 Width = 640
 8 Height = 400
 9
10 module Rubygame
11   class Surface
12     def put_pixel(point, color)
13       self.draw_box point, point, color
14     end
15   end
16 end
17
18 class RandomPixels2
19   include Rubygame
20
21   def initialize
22     @screen = Screen.new [Width, Height]
23     @events = EventQueue.new
24     @clock = Clock.new
25     @clock.target_framerate = 120
26
27     @screen.fill [0, 0, 0]
28     @screen.update
29   end
30
31   def event_loop
32     loop do
33       @events.each { |event|
34         case event
35         when QuitEvent
36           return
37         end
38       }
39
40       draw
41       @clock.tick
42       @screen.update
43     end
44   end
45
46   def draw
47     100.times do
48       point = [rand(Width), rand(Height)]
49       color = [rand(255), rand(255), rand(255)]
50       @screen.put_pixel point, color
51     end
52   end
53 end
54
55 Rubygame.init
56 RandomPixels2.new.event_loop
57 Rubygame.quit

Normally we want to assemble our whole frame before displaying it. If we were to draw directly to the screen, we would get a lot of flicker. So instead we perform all our drawing on a hidden buffer. When we have drawn the entire frame, we call Surface#update to make the new frame visible.

This happens at a very high speed, so our eyes don’t see that what we are doing is switching buffers all the time. But updating the screen is relatively time-consuming. At a later stage we will see how we can update only those parts of our surface that have changed since the previous frame.

Since we only call Surface#update once our whole frame is assembled, it also means we have a limited time to perform all our drawing. If it takes a minute to draw a frame then our framerate will be 1 frame per minute. Any gamer who plays your game for more than a minute deserves a reward, for patience or stupidity, whichever comes first.

Exercises

  1. In the last program, experiement with the number of pixels to plot in the draw method. Try a relatively low number such as 10. Also try higher number such as 1000 or 10000. What do you think would happen to the program if you choose a very large number?
  2. In the Surface#set_pixel method we are calling Surface#draw_box with two points that are the same. Modify the method so that the second point is 5×5 points away from the first. What happens to the program.

Conclusion

Now you are able to create a Rubygame screen and plot pixels onto it. There is actually quite a bit we can do armed with only that knowledge. We will continue plotting pixels for a little longer, while we look at some interesting events that we can let our program respond to.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: