DragonRuby: Render Targets

Next up in my notes on DragonRuby: render targets!

Weirdly, the documentation on DragonRuby’s render targets is limited to example code. Personally, I prefer prose when I am trying to learn… so here we are!

In DragonRuby, a render target is like an infinite canvas you can render as many regular sprites onto as you want, then manipulate the whole thing as if it is one sprite.

This is especially good for things like tiled backgrounds that are built once and do not change.

Let’s take an example.

Clouds!

Let’s start off very simple and build up.

First, here’s all the code to render a single 250x250 pixel image to the screen:

def tick args
  clouds = {
    x: 0,
    y: 0,
    h: 250,
    w: 250,
    path: 'sprites/cloud.png'
  }

  args.outputs.sprites << clouds
end

Single cloud tile

More Clouds

Cool, but I’d like to fill the whole window with clouds, so I’m going to tile them.

The code below makes a 6x3 grid of the cloud image.

(In DragonRuby, the screen is always 1280x720. Our grid is 1500x750 but I’m not trying to be too precise with the numbers here.)

def tick args
  clouds = []

  6.times do |x|
    3.times do |y|
      clouds << {
        x: x * 250,
        y: y * 250,
        h: 250,
        w: 250,
        path: 'sprites/cloud.png'
      }
    end
  end

  args.outputs.sprites << clouds
end

Screen full of blue clouds

Ah… blue clouds. Nice.

On every tick, the code builds up an array of 18 sprites (images) and renders it out to the screen.

(There are a number of ways to make this more efficient - check out the previous posts in this series for different ways to “cache” the sprite information.)

Render Targets

But in this post we are talking about render targets - which is a way of rendering a bunch of sprites (or any other renderable thing) just once, and then treating the whole group of sprites as a single sprite. This is faster, simpler, and enables some neat effects.

The code only needs minor changes to switch the cloud grid to using a render target instead:

# Move cloud grid creation into a helper method
def make_clouds(args)
  clouds = []

  6.times do |x|
    3.times do |y|
      clouds << {
        x: x * 250,
        y: y * 250,
        h: 250,
        w: 250,
        path: 'sprites/cloud.png'
      }
    end
  end

  # Similar to `args.outputs`,
  # render targets have `.sprites`, `.solids`, etc.
  # The argument will be used as the path below
  args.render_target(:clouds).sprites << clouds
end

def tick(args)
  # Set up the render target on the first tick
  if args.tick_count == 0
    make_clouds(args)
  end

  # Output a single sprite
  # located at 0,0 and the size of the whole grid
  # created in `make_clouds`
  args.outputs.sprites << {
    x: 0,
    y: 0,
    w: 250 * 6,
    h: 250 * 3,
    path: :clouds # Name of the render target!
  }
end

For convenience, the code above moves the creation of the cloud grid and the render target into a helper method which gets called on the first tick of the game.

args.render_target(:clouds) automatically creates a new render target named :clouds if it does not already exist. Then we can render things to it just as if it were args.outputs.

Interestingly, render targets do not seem to have an innate width or height. In order to avoid unintentional scaling, you will need to “know” how big the render target is. In this case, we know it is a 6x3 grid of 250x250 images, so the size is fairly straightforward. I left the math in to make it clearer.

Finally, we reference the render target similarly to an image file, but pass in the name of the render target as the :path instead of an actual file path.

Static Sprites, Too!

As explored in a different post, we can use static_sprites to “render” the sprite once.

# No changes here
def make_clouds(args)
  clouds = []

  6.times do |x|
    3.times do |y|
      clouds << {
        x: x * 250,
        y: y * 250,
        h: 250,
        w: 250,
        path: 'sprites/cloud.png'
      }
    end
  end

  args.render_target(:clouds).sprites << clouds
end

def tick(args)
  if args.tick_count == 0
    make_clouds(args)

    # Create the clouds sprite once
    # and keep it in `args.state`.
    args.state.clouds = {
      x: 0,
      y: 0,
      w: 250 * 6,
      h: 250 * 3,
      path: :clouds
    }

    # Add the clouds sprite just once
    # as a "static" sprite
    args.outputs.static_sprites << args.state.clouds
  end
end

And now we can move the clouds around just by changing the attributes on the render target.

Adding a little bit of code at the end of tick:

  args.state.clouds.x = (Math.sin(args.tick_count / 20) * 100) - 100

(The calculation and numbers aren’t really important here, I just fiddled around until something looked decent.)

Clouds moving back and forth

Oh hey! Those extra pixels on the sides of the cloud grid actually came in handy.

What Else?

Remember, the entire render target is like one sprite now. That means all the regular sprite attributes (e.g. color, size, blending, flipping, rotation) can be applied to the entire thing at once.

Wait, did you say rotation?

Sure, let’s make ourselves dizzy.

  args.state.clouds.angle = (Math.sin(args.tick_count / 120) * 180)

Spinning clouds

Okay, that’s as deep as we’ll go on render targets in this post!

DragonRuby: Object-Oriented Starter

I enjoy playing with the DragonRuby Game Toolkit, but the documentation and many of the examples are very much intended for non-Rubyists. Additionally, as a game engine, it’s more data/functionally-oriented than most Rubyists are used to. For example, the main game loop in the tick method needs to be implemented as a top-level method.

This post walks through structuring a game in a way that is a little more familiar to Rubyists.

Caveat! I am new to DragonRuby myself and this is not meant to be the “correct” or “best” or even “great” way to organize your code. It’s just a pattern I’ve started using and it might be useful for you!

(By the way, while DragonRuby is a commercial product, you can often grab a free copy. Keep an eye out for sales!)

Starting Example

First, let’s start with some code that isn’t using any class definitions at all. Everything happens inside tick:

def tick(args)
  # Set up player object
  args.state.player ||= {
    x: args.grid.w / 2,
    y: args.grid.h / 2,
    w: 20,
    h: 20,
  }

  # Move player based on keyboard input
  if args.inputs.keyboard.left
    args.state.player.x -= 10
  elsif args.inputs.keyboard.right
    args.state.player.x += 10
  elsif args.inputs.keyboard.down
    args.state.player.y -= 10
  elsif args.inputs.keyboard.up
    args.state.player.y += 10
  end

  # Render the "player" as a square to the screen
  args.outputs.solids << args.state.player
end

DragonRuby Example - Moving a black square around

As a reminder, DragonRuby automatically loads up code from a file called mygame/app/main.rb and then calls tick 60 times per second.

args.state is like an OpenStruct where you can add on whatever attributes you want and store whatever you would like. In this case, we add a hash that we name player.

The code then checks for keyboard input and adjusts the position of the “player”. (To keep things very simple, we don’t worry about keeping the player on the screen.)

Finally, we render the “player” as a solid square.

This is simple enough and the code isn’t too complicated. But, just for fun, let’s slowly transform it to be a little more “object-oriented”.

Game Object

First, let’s create a main “game” object to hold our logic, instead of putting it all in tick.

class MyGame
  # Adds convenience methods for args, gtk, keyboard, etc.
  attr_gtk

  def initialize(args)
    args.state.player = {
      x: args.grid.w / 2,
      y: args.grid.h / 2,
      w: 20,
      h: 20,
    }
  end

  def tick
    if keyboard.left
      state.player.x -= 10
    elsif keyboard.right
      state.player.x += 10
    elsif keyboard.down
      state.player.y -= 10
    elsif keyboard.up
      state.player.y += 10
    end

    outputs.solids << state.player
  end
end

def tick args
  $my_game ||= MyGame.new(args)
  $my_game.args = args
  $my_game.tick
end

Now the tick method only sets up the global $my_game on the first tick, then sets args on each tick and calls the game’s tick method.

(Tangent alert! Is it necessary to set args on every tick? Not strictly - you could set self.args = args in initialize and it will work okay. But if you want to use DragonRuby’s unit test framework, it may cause problems because each test has a fresh copy of args.)

Using attr_gtk allows the code to be a bit shorter. args, state, keyboard, and more now have convenience methods for them.

Instance Variables Instead of State

args.state is essentially a global variable space. This is a big convenience when a game is all top-level methods - otherwise you would have to figure out where to stash all your game state yourself.

However, it’s not required to use it.

The code below uses @player to store the player hash, instead of args.state.

class MyGame
  attr_gtk
  attr_reader :player

  def initialize(args)
    @player = {
      x: args.grid.w / 2,
      y: args.grid.h / 2,
      w: 20,
      h: 20,
    }
  end

  def tick
    if keyboard.left
      player.x -= 10
    elsif keyboard.right
      player.x += 10
    elsif keyboard.down
      player.y -= 10
    elsif keyboard.up
      player.y += 10
    end

    outputs.solids << player
  end
end

def tick args
  $my_game ||= MyGame.new(args)
  $my_game.args = args
  $my_game.tick
end

One thing that has thrown me off with DragonRuby is understanding just how much “regular” Ruby I can use. For the most part, other than how the tick method is used as the main game loop, you can use the Ruby language constructs you are comfortable with.

Splitting Things Up

No big change here, but as a game grows it’s easier to split the steps of each game loop into different methods.

class MyGame
  attr_gtk
  attr_reader :player

  def initialize(args)
    @player = {
      x: args.grid.w / 2,
      y: args.grid.h / 2,
      w: 20,
      h: 20,
    }
  end

  def tick
    handle_input
    render
  end

  def handle_input
    if keyboard.left
      player.x -= 10
    elsif keyboard.right
      player.x += 10
    elsif keyboard.down
      player.y -= 10
    elsif keyboard.up
      player.y += 10
    end
  end

  def render
    outputs.solids << player
  end
end

def tick args
  $my_game ||= MyGame.new(args)
  $my_game.args = args
  $my_game.tick
end

Player Class

Final step in this post - let’s move the “player” out to a separate class.

In this example, it might not make a lot of sense. But in most games there will be a lot of state and logic you might want to associate with the “player” or any other objects in the game. Having it be its own class helps keep the logic in one place.

class MyGame
  attr_gtk
  attr_reader :player

  def initialize(args)
    @player = Player.new(args.grid.w / 2, args.grid.h / 2)
  end

  def tick
    handle_input
    render
  end

  def handle_input
    if keyboard.left
      player.x -= 10
    elsif keyboard.right
      player.x += 10
    elsif keyboard.down
      player.y -= 10
    elsif keyboard.up
      player.y += 10
    end
  end

  def render
    outputs.solids << player
  end
end

class Player
  attr_sprite

  def initialize(x, y)
    @x = x
    @y = y
    @w = 20
    @h = 20
  end
end

def tick args
  $my_game ||= MyGame.new(args)
  $my_game.args = args
  $my_game.tick
end

Here the code uses another DragonRuby convenience. attr_sprite adds a bunch of helper methods that allow you to use any object as a sprite/solid/border, etc. (Note that the code still passes player into outputs.solids and DragonRuby treats it as a solid. If it were passed into outputs.sprites then it would be treated like a sprite instead!)

Separate Files?

For the sake of a blog post, all the code is together. But there is no reason not to start splitting the code across separate files.

But! In DragonRuby there is one weirdness with require: you must include the file extension (usually .rb), while in regular Ruby that is usually omitted.

Wrapping Up

Did our code become longer and less straight-forward? Yes, for this small example, definitely.

But as a game (or any project) grows, pulling bits out into modular pieces is going to be an advantage. Personally I fall back on this structure pretty quickly when I start a new DragonRuby project.

Hopefully this post is useful to Rubyists trying to get into DragonRuby!

DragonRuby: Static Outputs

In a previous post we looked at different ways to render outputs (sprites, rectangles, lines, etc.) in the DragonRuby Game Toolkit.

The post ended by hinting at a more efficient way to render outputs instead of adding them to e.g. args.outputs.solids or args.outputs.sprites each tick.

This post explores the world of “static outputs”!

Static What?

First of all, we should address the most confusing part of all this.

“Static” does not mean the images don’t move or change. Instead, it means that render “queue” is not cleared after every tick.

Normally, one would load up the queue each tick, like this:

def tick args

  # Render a black rectangle
  args.outputs.solids << {
    x: 100,
    y: 200,
    w: 300,  # width
    h: 400   # height
  }
end

But this is kind of wasteful. We are creating a new hash table each tick (60 ticks/second) and then throwing it away. Also each tick we are filling up the args.outputs.solids queue and then emptying it.

Instead, why not create the hash table once, load up the queue once, and then re-use them?

That’s the idea of static outputs!

There are static versions for each rendered type:

  • args.outputs.static_borders
  • args.outputs.static_labels
  • args.outputs.static_primitives
  • args.outputs.static_solids
  • args.outputs.static_sprites

Going Static

Starting Out

Here’s an example with comments explaining what the code is doing. This “game” simply moves a square back and forth across the screen. This is the entire program!

def tick args
  # Initialize the x location of the square
  args.state.x ||= 0

  # Initialize the direction/velocity
  args.state.direction ||= 10

  # If we hit the sides, change direction
  if args.state.x > args.grid.right or args.state.x < args.grid.left
    args.state.direction = -args.state.direction
  end

  # Update the x location
  args.state.x += args.state.direction

  # Build the square
  square = {
    x: args.state.x,
    y: 400,
    w: 40,
    h: 40,
  }

  # Add the square to the render queue
  args.outputs.solids << square
end

The resulting output looks like:

Image description

This example introduces args.state. This is basically a persistent bag you can throw anything into. (For Rubyists - this is like OpenStruct.)

x and direction are not special, they are just variables we are defining. We use ||= to initialize them because we only want to set the values on the first tick.

This example illustrates the point from above - every tick it creates a new square and adds it to the queue. The queue is emptied out and then the code starts all over again.

Seems wasteful, right?

Caching the Objects

First thing I think of is - “why not create the square once, then just update the object each tick? Does that work?” Yes! It does.

def tick args
  args.state.direction ||= 10
  args.state.square ||= {
    x: 0,
    y: 400,
    w: 40,
    h: 40,
  }

  if args.state.square[:x] > args.grid.right or args.state.square[:x] < args.grid.left
    args.state.direction = -args.state.direction
  end

  args.state.square[:x] += args.state.direction

  args.outputs.solids << args.state.square
end

In this code, we create the square only once and then store it in args.state.square.

Instead of having a separate x variable, the code updates the x property on the square directly.

This is better, but we are still updating args.outputs.solids each tick.

Full Static

def tick args
  args.state.direction ||= 10
  args.state.square ||= {
    x: 0,
    y: 400,
    w: 40,
    h: 40,
  }

  # On the first tick, add the square to the render queue
  if args.tick_count == 0
    args.outputs.static_solids << args.state.square
  end

  if args.state.square[:x] > args.grid.right or args.state.square[:x] < args.grid.left
    args.state.direction = -args.state.direction
  end

  args.state.square[:x] += args.state.direction
end

In this code, we use the fact that the first args.tick_count is 0 to add the square to args.outputs.static_solids just once. It will continue to be rendered on each tick.

Performance

Intuitively, since the code is doing less, it should be faster. But does it really make a difference?

It depends on your game, how much it’s doing per tick, how many sprites you are rendering, and what platform/hardware it’s running on.

The examples above? Not going to see any difference using static_solids.

But DragonRuby contains two examples that directly compare args.outputs.sprites vs. args.outputs.static_sprites (here and here).

In these examples, you can play with the number of “stars” rendered to see different performance. On my ancient laptop, I do not see a performance difference until around 3,000 stars.

Your mileage may vary, though!

Should I Always Use the Static Versions?

It depends! Probably not?

If your code mainly manipulates the same objects around the screen and always renders them in the same order, then using the static_ approach might be simpler and faster.

But in many cases it might be easier to simply set up the render queues each tick, especially if the objects rendered or their ordering change regularly. Otherwise, managing the state of the rendering queues can become cumbersome. (We haven’t even talked about clearing the static queues, for example.)

Some of this comes down to personal preference and how you would like to structure your code. But hopefully this post has helped explain how to use the args.outputs.static_* methods in your game!