Report abuse


			
In part 2 of the Rubygame tutorial series we will be learning how to control a Rubygame application's CPU resource usage as well as writing a sprite system for our player.

For a little word of caution, part 2 is much longer than part 1. Since it is a really long article, I suggest you to print it out and read it later if you need to.

If you did not complete part 1 of this tutorial, it is highly recommended that you do so before continuing this tutorial. Otherwise you will miss a critical proportion of knowledge required to write video games with Rubygame. You may not also understand newer parts of this tutorial.

We'll jump right in learning how to control CPU usage so your Rubygame applications doesn't suck your computer's CPU resource dry. By controlling CPU usage, we also get the added advantage of consistent frame-rate.

Before we go any further, I request you to run dodgeball.rb and record the CPU usage of the game. Please record it in a place where you can refer back to(Not your brain, a paper is a much better place). This data will be used to determine if whether our code actually work or not. You'll need to look at the ruby process. Please make sure the ruby process is actually dodgeball.rb and that application alone.

My data show me that dodgeball.rb uses about 99.9% of CPU.

Now that we're done recording the data, we can start writing some code.

Modify the file named loop.rb in the rubies_dodgeball's lib directory:

	class GameLoop
		def initialize
			@screen = Screen.new([800,600],0,[Rubygame::HWSURFACE, Rubygame::DOUBLEBUF])
			@q = Rubygame::EventQueue.new()
			@clock = Rubygame::Clock.new
			@clock.target_frametime= 40
		end
		def run
			loop do
				@clock.tick
				@q.each do |ev|
					case ev
					when Rubygame::QuitEvent
						Rubygame.quit
						return
					end
				end
			end
		end
	end

The Rubygame::Clock class is used primary for delaying executions of Rubygame applications. It can also tracks running time. You must specify the frame-rate of your application before executing the main game loop( in our code, we used @clock.target_frametime= ). The method tick for @clock is used for frame-rate limiting to a specified frame-rate.

When you run this program, you should notice a great reduction in CPU usage. It should be a lower than your recorded CPU usage before you modified the code. My data show me that dodgeball.rb uses about a mere 6.7% of CPU. So it work like a charm.

The next step is writing out our own sprite system for the player. We'll required a few new files. First, create the data directory in rubies_dodgeball. This is where we put all our images in and as well future data files.

Please download the compressed files for images. You should extract all of them into rubies_dodgeball's data directory.

After that, create a file in the rubies_dodgeball/lib: read.rb

	class Read
		#Read the file
		def yaml_read filename
			yaml_string = File.read filename; YAML:: load yaml_string
		end
	end

This will read a yaml file for a list of images to load. You will use this later. Please create a yaml file in the data directory
as images.yml
	
	--- 
	up:
	- 0
	- 1
	- 2
	right:
	- 3
	- 4
	- 5
	down:
	- 6
	- 7
	- 8
	left:
	- 9
	- 10
	- 11
	images: 
	- data/player0.png
	- data/player1.png
	- data/player2.png
	- data/player3.png
	- data/player4.png
	- data/player5.png
	- data/player6.png
	- data/player7.png
	- data/player8.png
	- data/player9.png
	- data/player10.png
	- data/player11.png

Once you're finished with creating the yaml file, we'll create another file in rubies_dodgeball's lib directory: player.rb

	class Player
	attr_accessor :move , :image , :rect
	include Rubygame::Sprites::Sprite
	def initialize
		super
		@rect = rect
		@image = image
		@rect = Rubygame::Rect.new(300,0,50,50)
		@move = move
		@move = Move.new(self)
	end
	def update
		@rect.centerx = @move.x
		@rect.centery = @move.y
		@rect.centerx %= 800
		@rect.centery %= 600
	end
	def undraw(dest, background)
		background.blit(dest,@rect,@rect)
	end
end

Once we include Rubygame::Sprite::Sprite, the player class will need an image and as well a location. We set up the image later in this tutorial. The location is the @rect. The update method update x and y to latest calculated x and y position of @move.x and @move.y. It also render the image too. Undraw method is a method that undraw the images so we don't leave behind trailing images.

Also, we create animate.rb in the directory of rubies_dodgeball/lib too.

	class Animation
		def initialize
		end
	end

This placeholder class handles images loading as well the animation. We also created another placeholder class Move to handle Player's movement. Save the Move class in the rubies_dodgeball/lib directory as move.rb.

	class Move
		attr_accessor :x , :y , :animate
		def initialize player
			@x = x
			@x = player.rect.x
			@y = y
			@y = player.rect.y
			@animate = animate
			@animate = Animation.new
		end
	end

Lastly, Modify dodgeball.rb in the rubies_dodgeball directory.

	require"rubygame"
	require "yaml"
	require"lib/animate.rb"
	require"lib/loop.rb"
	require"lib/move.rb"
	require"lib/player.rb"
	require"lib/read.rb"
	include Rubygame

	game = GameLoop.new
	game.run()

That is some amount of code we're writing. Let stop and catch our breath and I'll explaining what we're trying to do. Basically Animation class will loads all the images. It animate the player sprite as it is loading the correct necessary files. This correspond to Move class, which determine the movement of the sprite. The Player class instruct the Move classs based on events in EventQueue that are triggered by the user.

Let modify animate.rb to load images so we can display them.

	class Animation
	attr_accessor :data , :state , :player , :n , :image
	def initialize player
		@player = player
		@data = data
		@image = image
		@state = state
		@n = n
		@n = 0
		datarip()
	end
	def datarip
		file = Read.new
		data = file.yaml_read("data/images.yml")
		@image = data['images']
		@data = data
		@state = @image[0]
		display()
	end
	def display
		@player.image = Rubygame::Surface.load_image(@state)
	end

The display method contain a method that load image depending on the image name in @state. Datarip read the images.yml to prepare for loading files. The end result for the initialization of move is that it loaded an image for @player's image instance variable. Now that we have all the required data for Player class, let draw them in loop.rb.

	class GameLoop
		def initialize
			@screen = Screen.new([800,600],0,[Rubygame::HWSURFACE, Rubygame::DOUBLEBUF])
			@background = Rubygame::Surface.new(@screen.size)
			@q = Rubygame::EventQueue.new()
			@clock = Rubygame::Clock.new
			@clock.target_frametime= 40
			@player = Player.new
		end
		def run
			loop do
				@clock.tick
				@q.each do |ev|
					case ev
					when Rubygame::QuitEvent
						Rubygame.quit
						return
					end
				end
				@player.undraw(@screen,@background)
				@player.draw(@screen)
				@screen.flip()
			end
		end
	end

OK, you will probably don't know why there is a new surface called @background or where @method draw come from. You see, the background's purpose is to be a copy of the original state of the display screen before we draw anything. We use this copy in the undraw method of Player class to provide a screen that doesn't seem to have graphical problems. Without the undraw method, a bunch of copy of an image will get left behind as that original image move around. The draw method is included when we written include Rubygame::Sprites::Sprite earlier. This is the method that actually draw the image to the display screen.

When you run this program, you should see a picture in the middle at the bottom of the display screen. It should look like this:

(screenshot)

Next on the checklist: animation and movement.

We're going to modify the Move class first in move.rb.
	
	class Move
		attr_accessor :x , :y , :animate , :move , :direct
		def initialize player
			@x = x
			@x = player.rect.x
			@y = y
			@y = player.rect.y
			@animate = animate
			@animate = Animation.new(player)
			@move = move
			@move = false
			@direct = direct
		end
		def right
			@x += 8
		end
		def left
			@x -= 8
		end
		def up
			@y -= 8
		end
		def down
			@y += 8
		end
		def action
			if @move == true
				case @direct
				when 1
					down()
				when 2
					up()
				when 3
					left()
				when 4
					right()
				end
			end
		end
	end

For value @x, it represent the location of the sprite horztiontally. Bigger number mean more to the right, while smaller numbers meant more to the left. @y represent the location of the image vertically. The higher number mean the image is lower on the screen, while less mean higher on the screen. When @move become true, the action method when activated will go whatever the direction that the player chosen. If @move become any other values other than the numbers it is looking for, then method action will not provoke any direction methods(left, right, down, and up) to move the image's location.

Modify the run method GameLoop class in loop.rb. We're going to make the player sprite movable by human beings

	def run
		loop do
			@clock.tick
			@q.each do |ev|
				case ev
				when Rubygame::QuitEvent
					Rubygame.quit()
					return
				when Rubygame::KeyDownEvent
					@player.move.move = true
					case ev.key
					when Rubygame::K_DOWN
						@player.move.direct = 1
					when Rubygame::K_UP
						@player.move.direct = 2
					when Rubygame::K_LEFT
						@player.move.direct = 3
					when Rubygame::K_RIGHT
						@player.move.direct = 4
					end
				when Rubygame::KeyUpEvent
					@player.move.move = false
				end
			end
			@player.move.action()
			@player.undraw(@screen,@background)
			@player.update()
			@player.draw(@screen)
			@screen.flip()
		end
	end

This add the ability to control the movement of your sprite. The action method Move class execute a direction method corresponding with one of the arrow keys. These movement continues until the player let go one of the arrow key. This allowed us to create continous movement by holding the key without having the need to repeating pressing certain buttons.

When you run the program, you should be able to move the sprite left using the left arrows and so on. Animation is now the only item left to code.

Modify the Animation class to add these methods in animate.rb.

	def change 
		@n += 1
		if @n == 3
			@n = 0
		end
		@state = @data[@n]
	end
	def down
		data = @data['down'][@n]
		@state = @image[data]
		display()
	end
	def right
		data = @data['right'][@n]
		@state = @image[data]
		display()
	end
	def left
		data = @data['left'][@n]
		@state = @image[data]
		display()
	end
	def up
		data = @data['up'][@n]
		@state = @image[data]
		display()
	end

Method change cycles though 0 to 3 so it can get the corresponding number for @image in these direction's array element. Each direction hashes in @data have four array elements that correspond to a particular files specified in the hash "images" of @data which is also in @image. The images are than loaded in display, making them ready for rendering.

Modify the direction methods of Move class in move.rb.

	def right
		@x += 8
		@animate.change()
		@animate.right()
	end
	def left
		@x -= 8
		@animate.change()
		@animate.left()
	end
	def up
		@y -= 8
		@animate.change()
		@animate.up()
	end
	def down
		@y += 8
		@animate.change()
		@animate.down()
	end

When right and other direction methods are executed, the animations should follow. Since rendering is already done, new images should come up automatically.

When you run this program, you should see that there are differing images cycling, making an animation for specific directions you choose to take with the arrow keys.

We're finally done with part 2!


The final products of the code should look like this:





The next article, Part 3 will deals with a new gameplay elements called balls. It will provide the first look at the gameplay design of Rubies Dodgeball.


Congraduation for completing part 2 of the tutorial series. It is the most lengthy article so far. There is also possiblity that this part is the longest article I ever written. I bet you that part 3 might even be longer and funner(since we're finally getting into real gameplay).

Anyway, happy hacking!

Kiba