RubyMotion Blog

Avatar

This is the official blog of RubyMotion, a toolchain for iOS, OS X and Android development that lets you do iPhone, iPad, Mac and Android apps in Ruby.

Follow us on Twitter to stay tuned with everything that's happening in the community!

Not a RubyMotion user yet? Give it a spin today!

Create an Asteroids Game for iOS in 15 Minutes… with Joybox 1.1.0 and RubyMotion!

This is a guest post from Juan Jose Karam, author of the Joybox gaming framework.

The idea behind Joybox has always been to help you focus on the things that matter while creating a game, such as the game play or the game mechanics, instead of dealing with collisions logic or ray casts. At the time it was introduced I knew that for this to become a reality, a good documentation had to be created.

We are launching today a totally new website with complete documentation, examples and learning tracks. Today, Joybox 1.1.0 is also released with a lot of new features like the Physics Debug Draw, support for Tile Maps and Physics Sprite Actions. You can read more about it in the Joybox 1.1.0 release announcement.

The following article is a preview of the easy-mode learning track. It will step-by-step guide you through the basics of game development as you build your first game!

Joybox Asteroids

The game we will be building is a remake of the Asteroids video game created in 1979 by Atari. However, it will have its particular differences that you will notice as we move forward.

I assume that you have already installed Joybox on your computer. If this is not the case you can visit the Getting Started guide on our website.

Creating a new Joybox project

Lets start by creating a new Joybox project. Note that we are using thejoybox-ios project template.

$ motion create --template=joybox-ios asteroids

You can then copy the following sprites into the resources folder. Since we are building an iPad game, we have to add the following lines into the Rakefile.

app.name = 'Asteroids'
app.interface_orientations = [:landscape_left]
app.device_family = [:ipad]
app.icons = ['icon.png', 'icon@2x.png']
app.prerendered_icon = true

Creating the game layer

Now we are ready to start developing our game! The first thing we need to do is create a Layer subclass, in which all the actions will happen. To accomplish this let’s create a new file named game_layer.rb into the app folder, and write the following into it:

class GameLayer < Joybox::Core::Layer
  scene

  def on_enter
    background = Sprite.new file_name: 'background.png',
        position: Screen.center
    self << background

    @rocket = Sprite.new file_name: 'rocket.png', position: Screen.center,
        alive: true
    self << @rocket
  end
end

In this code we are creating a new subclass of Layer, named GameLayer. In it we define the on_enter method which be called when our new layer is presented on the screen, which is a perfect timing for adding two sprites. The first sprite will be the background image of the game, and the other one the user’s rocket.

As you may notice the rocket sprite is being created with another option: alive. This is a custom value - you can pass custom parameters to Sprite#initialize and they will be stored along with the object. This value will help us determine if the rocket has collided with an asteroid or not.

Creating the director

The next step is tell the director to present our layer. You can think of the director object as the person who directs the making of a films: it manages everything that happens in the game: from what is being presented on the screen, to the game loop, a concept that we will explain a little further.

You can open the app_delegate.rb file and add the following lines:

def application(application, didFinishLaunchingWithOptions:launchOptions)

  @director = Joybox::Configuration.setup do
    director display_stats: true
  end

  @navigation_controller =
      UINavigationController.alloc.initWithRootViewController(@director)
  @navigation_controller.navigationBarHidden = true

  @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
  @window.setRootViewController(@navigation_controller)
  @window.makeKeyAndVisible

  # This line tells the director to present our Layer when the game starts
  @director << GameLayer.scene
  true
end

What you are seeing in this method is all the necessary configuration needed for our game to run. For the purpose of this tutorial we won’t be focusing much on it, but if you are curious you can look at the director page on our documentation section.

First run!

Now, let’s run the project for the first time! You should see the following on your iOS Simulator:

Controlling the Rocket

The basic idea of the game consists of the following points:

  • A lot of asteroids will fly through the screen in different directions, from different locations.
  • The user controls a rocket and should dodge the asteroids using his/her finger to move the rocket.
  • When an asteroid hits the rocket the game should be over.

Let’s start with the number two of the list: moving the rocket around when the user touches the screen. For this we need to add the following code into our game_layer.rb file:

class GameLayer < Joybox::Core::Layer
  scene

  def on_enter
    background = Sprite.new file_name: 'background.png',
        position: Screen.center
    self << background

    @rocket = Sprite.new file_name: 'rocket.png', position: Screen.center, 
        alive: true
    self << @rocket

    # The following block will be called when the user touches the screen
    on_touches_began do |touches, event|
      # We retrieve the touch location and assign it to the rocket position
      touch = touches.any_object
      @rocket.position = touch.location
    end
  end
end

In this code we are updating the position of the rocket based on the touch location generated by the user, but if you run it you will notice that something weird is happening:

Oops! The rocket moves instantaneously and it does look like it is being teleported instead of naturally moving. To fix this we can introduce a Move action to update its position:

on_touches_began do |touches, event|
  touch = touches.any_object
  # The move action will update the rocket position frame by frame until it
  # arrives at the desired location
  @rocket.run_action Move.to position: touch.location
end

If we run the game again the rocket should now move through the screen instead of being teleported.

Asteroid rain!

Now is time to focus on the first point of the game: make those asteroids fly through the screen. This is the biggest part of the tutorial and is divided into two main characteristics:

  • Define where the asteroid should be spawned and where they should be moved to.
  • Maintain a constant number of asteroids flying on the screen: not too many, because it will make the game impossible to play, and not too few, because it will not be challenging at all.

Creating asteroids

Let’s begin with the first part, which is defining an asteroid starting and ending position. For this, it is better to isolate that part of the code in another class named AsteroidSprite which should be a subclass of Sprite. Let’s add the following into a new file named asteroid_sprite.rb:

class AsteroidSprite < Joybox::Core::Sprite
  def initialize
  end
end

Now if you look at our resources folder, you should see four graphic files for ou asteroids (they are numbered from 1 to 4). They are different in size and shape, in order to add a bit of complexity into the game while keeping it simple. It will not be practical to have four different classes of asteroids to manage only a different image, so it is better to implement a more simple solution inside the initialize method:

def initialize
  @random = Random.new

  # We define what kind of asteroid by using a random number
  kind = @random.rand(1..4)
  # We can now get the appropriate image
  file_name = "asteroid_#{kind}.png"
end

Well, that should be it! Using a random number to retrieve the image takes the multi-class problem away. Now we can think about the starting and ending positions of our sprite.

We can use a random number to get a starting position as well, but they would spawn everywhere (like teleporting). We need the asteroids to spawn outside the screen and move to the other side, for this first we need to know on which side of the screen they should appear:

def initialize
  @random = Random.new

  kind = @random.rand(1..4)
  file_name = "asteroid_#{kind}.png"

  # Using a random number we can define in which side of the screen
  # it should appear. 1 for left, 2 for top, 3 for right and 4 for bottom.
  screen_side = @random.rand(1..4)
end

Now we can create a method that will generate the exact initial position of the asteroid based on the side of the screen:

# This is the maximum size of any of our asteroids, because their images are
# rectangles only one value is needed.
# We need this value to make sure that the asteroid will not appear on the edge
# of the screen.
MaximumSize = 96.0

def initial_position(screen_side)
  case screen_side
  when 1
    # In case it spawns on the Left:
    # The X axis should be outside of the screen
    # The Y axis can be any point inside the height
    [-MaximumSize, @random.rand(1..Screen.height)]
  when 2
    # In case it spawns on the Top:
    # The X axis can be any point inside the width
    # The Y axis must be higher than the screen height
    [@random.rand(1..Screen.width), Screen.height + MaximumSize]
  when 3
    # In case it spawns on the Right:
    # The X axis must be greater than the entire screen width
    # The Y axis can by any value inside the total height
    [Screen.width + MaximumSize, @random.rand(1..Screen.height)]
  else
    # In case it spawns on the Bottom:
    # The X axis can be any value of the screen width
    # The Y axis should be lower than the total height
    [@random.rand(1..Screen.width), -MaximumSize]
  end
end

Using the initial_position method we can obtain the first location point of the asteroid according to the side of the screen it should be spawned. Please take notice that the code can be greatly optimized, but we will keep it simple to make to easier to follow.

Now, we need to do the same logic for the final location point. Wait! Why don’t we just add the entire width or height? Well! If we do that then the asteroids will only fly in straight paths, which will reduce the difficulty and make them look like they are on rails instead of flying naturally. It’s better to take a similar approach as for the initial position:

def final_position(screen_side)
  case screen_side
  when 1
    # In case it spawns on the Left:
    # The X axis must be bigger than the total width
    # The Y axis can be any point inside the height
    [Screen.width + MaximumSize, @random.rand(1..Screen.height)]
  when 2
    # In case it spawns on the Top:
    # The X axis can be any point inside the width
    # The Y axis must be lower than the initial screen height
    [@random.rand(1..Screen.width), -MaximumSize]
  when 3
    # In case it spawns on the Right:
    # The X axis must be lower than the start of the width
    # The Y axis can by any value inside the total height
    [-MaximumSize, @random.rand(1..Screen.height)]
  else
    # In case it spawns on the Bottom:
    # The X axis can be any value of the screen width
    # The Y axis should be higher than the total height
    [@random.rand(1..Screen.width), Screen.height + MaximumSize]
  end
end

As well as for the initial_position method, this code can be optimized but we will keep it this way for simplicity sake.

Now that we can obtain an initial and ending position for our asteroid, let’s use these methods to initialize the sprite:

# We will need access the final position later on our GameLayer
attr_accessor :end_position

def initialize
  @random = Random.new

  kind = @random.rand(1..4)
  file_name = "asteroid_#{kind}.png"

  screen_side = @random.rand(1..4)
  # Let's create the start and end position for our asteroid
  start_position = initial_position(screen_side)
  @end_position = final_position(screen_side)

  # Initialize the Sprite using the asteroids file image and the start position
  # This is the same as do Sprite.new file_name:, position:
  super file_name: file_name, position: start_position
end

The code will first generate the starting and ending point for the asteroid, and more importantly it will initialize the sprite using the appropriate graphic file which was obtained by using the kind of asteroid.

After doing this, we can now say the code in the AsteroidSprite class is complete!

Launching asteroids

Now it’s time to launch these asteroids through the space! But if you remember, there is a condition we need to meet: maintain a constant number of them on the screen.

The easier way to do this is to check at certain intervals of time if they are enough of them on the screen, or we need to launch more. The concept of the Game Loop was created for such scenarios. It will make periodic calls to our classes (in this case the GameLayer) so we can update the logic of the game. To make sense, let’s implement it in the game_layer.rb file:

# Class GameLayer
def on_enter

  ...

  # The game loop will call the following block every time it passes, regularly
  # sixty times per second.
  schedule_update do |dt|
    # The following method is not implemented yet
    launch_asteroids
  end
end

If we take a look at the previous code: We are calling the schedule_update method with a block, this will set up the Game Loop for the layer. The block we are passing will get called every time the game loop runs, which means that the launch_asteroids method will be called a lot of times, allowing us to validate the number of asteroids on the screen.

Now it’s time to implement the launch_asteroids method:

# Defines the max. number of asteroids that can be on the screen at the same time.
MaximumAsteroids = 10

def launch_asteroids
  # In the following array we will contain the asteroids
  @asteroids ||= Array.new

  # Checks if we need to launch more asteroids
  if @asteroids.size <= MaximumAsteroids
    missing_asteroids = MaximumAsteroids - @asteroids.size

    # Let's create a new asteroid for each missing one
    missing_asteroids.times do
      # Create a new instance of our AsteroidSprite class
      asteroid = AsteroidSprite.new

      # Like the rocket, we move the asteroid using the Move class
      asteroid.run_action Move.to position: asteroid.end_position,
          duration: 4.0

      # Add it to the array and the layer to present it on screen
      self << asteroid
      @asteroids << asteroid
    end
  end
end

What we are doing here is first checking if more asteroids are required, if so we create a new instance of our AsteroidSprite class. Then we add it to the layer so it can be rendered and to the array so we can keep control of how many asteroids we have.

Also, we move the asteroid through the screen using the Move action, as you remember this is needed because we don’t want the movement to be instantaneous.

Finally we can run our project!

Yeah, it works! But… the asteroids only appear once!

To fix this, we need to determine when the asteroid movement is finished so that we can remove it from the array. We can use two more actions to perform this. The first one is named Callback and its function is to call a block, and the other one is Sequence, which will help us to run first the move action, and when it finishes, the callback action.

Let’s update the launch_asteroids method accordingly:

def launch_asteroids
  @asteroids ||= Array.new

  if @asteroids.size <= MaximumAsteroids
    missing_asteroids = MaximumAsteroids - @asteroids.size

    missing_asteroids.times do
      asteroid = AsteroidSprite.new

      # First we need to create the Move action
      move_action = Move.to position: asteroid.end_position, duration: 4.0

      # Second using the Callback action we create a block that will remove the
      # asteroid from the array. As you may notice the block will receive as
      # parameter the sprite that run the action
      callback_action = Callback.with { |asteroid| @asteroids.delete asteroid }

      # Finally we run both actions in sequence first the Move action and then
      # the Callback one.
      asteroid.run_action Sequence.with actions: [move_action, callback_action]

      self << asteroid
      @asteroids << asteroid
    end
  end
end

Finally, if we run it this time the asteroids should appear over and over again! Yeepee!

Crashing the rocket

We are almost there, it’s now time to implement the third and final point: detect when the rocket crashes with an asteroid. The Game Loop will help us here too, we can create a method to check if the rocket’s bounds are being intersected by any of the asteroids bounds.

To begin with the collision detection we should update the game loop block as following:

# Class GameLayer
# Method on_enter
schedule_update do |dt|
  launch_asteroids

  # The following method is not implemented yet
  # We only need to call this method if the Rocket is still alive, its value
  # was defined when the @rocket sprite was created
  check_for_collisions if @rocket[:alive]
end

Final step! We need to implement the check_for_collisions method, that will check if the bounds of the rocket intersects with the bounds of any asteroid. In order to get the bounds of a sprite, we will use the bounding_box method, which will return the minimum box that can contain the sprite image.

Let’s add the following method to the game_layer.rb file:

def check_for_collisions
  # Iterate through every asteroid in the screen
  @asteroids.each do |asteroid|
    # Check if it's bounding box intersects with the rocket
    if CGRectIntersectsRect(asteroid.bounding_box, @rocket.bounding_box)
      # If true finish the game
      # First stop the movement on all of the asteroids
      @asteroids.each(&:stop_all_actions)

      # Set the alive property of the rocket to false, this to avoid that the
      # collision is checked again
      @rocket[:alive] = false
      # Give the rocket a nice retro blink!
      @rocket.run_action Blink.with times: 20, duration: 3.0
      break
    end
  end
end

In this method, we first check if any of the asteroids are intersecting with the rocket, and if we find one, we stop the movement on all asteroids and give a little feedback to the user using the Blink action. It’s our way to show that the game is over.

The game is now complete! Let’s run it.

We hope you enjoyed this tutorial! You can download the full code here.

Let’s improve the game!

As you may have noticed they are lots of things that can be improved in the game, some of them will be added in the Easy Level on the Joybox site. But until that time, you are welcome to experiment, polish or create your own version of the game!

RubyMotion + games = happiness

Using Ruby for creating games is a great experience, but what makes it amazing is the ability to deploy them to great platforms. Thanks to the RubyMotion team for making it possible!


Interested in giving RubyMotion a try? We offer a 30 days money back guarantee. Download it today!
Follow us on Twitter to stay tuned with everything that is happening in the community.

By Laurent Sansonetti.