RECIPIES: link

How To Determine What Frame You Are On link

There is a property on state called tick_count that is incremented by DragonRuby every time the tick method is called. The following code renders a label that displays the current tick_count.

def tick args
  args.outputs.labels << [10, 670, "#{args.state.tick_count}"]
end

How To Get Current Framerate link

Current framerate is a top level property on the Game Toolkit Runtime and is accessible via args.gtk.current_framerate.

def tick args
  args.outputs.labels << [10, 710, "framerate: #{args.gtk.current_framerate.round}"]
end

How To Render A Sprite Using An Array link

All file paths should use the forward slash / *not* backslash . Game Toolkit includes a number of sprites in the sprites folder (everything about your game is located in the mygame directory).

The following code renders a sprite with a width and height of 100 in the center of the screen.

args.outputs.sprites is used to render a sprite.

def tick args
  args.outputs.sprites << [
    640 - 50,                 # X
    360 - 50,                 # Y
    100,                      # W
    100,                      # H
    'sprites/square-blue.png' # PATH
 ]
end

More Sprite Properties As An Array link

Here are all the properties you can set on a sprite.

def tick args
  args.outputs.sprites << [
    100,                       # X
    100,                       # Y
    32,                        # W
    64,                        # H
    'sprites/square-blue.png', # PATH
    0,                         # ANGLE
    255,                       # ALPHA
    0,                         # RED_SATURATION
    255,                       # GREEN_SATURATION
    0                          # BLUE_SATURATION
  ]
end

Different Sprite Representations link

Using ordinal positioning can get a little unruly given so many properties you have control over.

You can represent a sprite as a Hash:

def tick args
  args.outputs.sprites << {
    x: 640 - 50,
    y: 360 - 50,
    w: 100,
    h: 100,

    path: 'sprites/square-blue.png',
    angle: 0,

    a: 255,
    r: 255,
    g: 255,
    b: 255,

    # source_ properties have origin of bottom left
    source_x:  0,
    source_y:  0,
    source_w: -1,
    source_h: -1,

    # tile_ properties have origin of top left
    tile_x:  0,
    tile_y:  0,
    tile_w: -1,
    tile_h: -1,

    flip_vertically: false,
    flip_horizontally: false,

    angle_anchor_x: 0.5,
    angle_anchor_y: 1.0,

    blendmode_enum: 1

    # labels anchor/alignment (default is nil)
    # if these values are provided, they will be used over alignment_enum and vertical_alignment_enum
    anchor_x: 0.5,
    anchor_y: 0.5
  }
end

The blendmode_enum value can be set to 0 (no blending), 1 (alpha blending), 2 (additive blending), 3 (modulo blending), 4 (multiply blending).

You can represent a sprite as an object:

# Create type with ALL sprite properties AND primitive_marker
class Sprite
  attr_accessor :x, :y, :w, :h, :path, :angle, :a, :r, :g, :b,
                :source_x, :source_y, :source_w, :source_h,
                :tile_x, :tile_y, :tile_w, :tile_h,
                :flip_horizontally, :flip_vertically,
                :angle_anchor_x, :angle_anchor_y, :blendmode_enum,
                :anchor_x, :anchor_y

  def primitive_marker
    :sprite
  end
end

class BlueSquare < Sprite
  def initialize opts
    @x = opts[:x]
    @y = opts[:y]
    @w = opts[:w]
    @h = opts[:h]
    @path = 'sprites/square-blue.png'
  end
end

def tick args
  args.outputs.sprites << (BlueSquare.new x: 640 - 50,
                                          y: 360 - 50,
                                          w: 50,
                                          h: 50)
end

How To Render A Label link

args.outputs.labels is used to render labels.

Labels are how you display text. This code will go directly inside of the def tick args method.

Here is the minimum code:

def tick args
  #                       X    Y    TEXT
  args.outputs.labels << [640, 360, "I am a black label."]
end

A Colored Label link

def tick args
  # A colored label
  #                       X    Y    TEXT,                   RED    GREEN  BLUE  ALPHA
  args.outputs.labels << [640, 360, "I am a redish label.", 255,     128,  128,   255]
end

Extended Label Properties link

def tick args
  # A colored label
  #                       X    Y     TEXT           SIZE  ALIGNMENT  RED  GREEN  BLUE  ALPHA  FONT FILE
  args.outputs.labels << [
    640,                   # X
    360,                   # Y
    "Hello world",         # TEXT
    0,                     # SIZE_ENUM
    1,                     # ALIGNMENT_ENUM
    0,                     # RED
    0,                     # GREEN
    0,                     # BLUE
    255,                   # ALPHA
    "fonts/coolfont.ttf"   # FONT
  ]
end

A SIZE_ENUM of 0 represents "default size". A negative value will decrease the label size. A positive value will increase the label's size.

An ALIGNMENT_ENUM of 0 represents "left aligned". 1 represents "center aligned". 2 represents "right aligned".

Rendering A Label As A Hash link

You can add additional metadata about your game within a label, which requires you to use a `Hash` instead.

If you use a Hash to render a label, you can set the label's size using either SIZE_ENUM or SIZE_PX. If both options are provided, SIZE_PX will be used.

def tick args
  args.outputs.labels << {
    x:                       200,
    y:                       550,
    text:                    "dragonruby",
    # size specification can be either size_enum or size_px
    size_enum:               2,
    size_px:                 22,
    alignment_enum:          1,
    r:                       155,
    g:                       50,
    b:                       50,
    a:                       255,
    font:                    "fonts/manaspc.ttf",
    vertical_alignment_enum: 0, # 0 is bottom, 1 is middle, 2 is top
    anchor_x: 0.5,
    anchor_y: 0.5
    # You can add any properties you like (this will be ignored/won't cause errors)
    game_data_one:  "Something",
    game_data_two: {
       value_1: "value",
       value_2: "value two",
       a_number: 15
    }
  }
end

Getting The Size Of A Piece Of Text link

You can get the render size of any string using args.gtk.calcstringbox.

def tick args
  #                             TEXT           SIZE_ENUM  FONT
  w, h = args.gtk.calcstringbox("some string",         0, "font.ttf")

  # NOTE: The SIZE_ENUM and FONT are optional arguments.

  # Render a label showing the w and h of the text:
  args.outputs.labels << [
    10,
    710,
    # This string uses Ruby's string interpolation literal: #{}
    "'some string' has width: #{w}, and height: #{h}."
  ]
end

Rendering Labels With New Line Characters And Wrapping link

You can use a strategy like the following to create multiple labels from a String.

def tick args
  long_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elitteger dolor velit, ultricies vitae libero vel, aliquam imperdiet enim."
  max_character_length = 30
  long_strings_split = args.string.wrapped_lines long_string, max_character_length
  args.outputs.labels << long_strings_split.map_with_index do |s, i|
    { x: 10, y: 600 - (i * 20), text: s }
  end
end

How To Play A Sound link

Sounds that end .wav will play once:

def tick args
  # Play a sound every second
  if (args.state.tick_count % 60) == 0
    args.outputs.sounds << 'something.wav'
  end
end

Sounds that end .ogg is considered background music and will loop:

def tick args
  # Start a sound loop at the beginning of the game
  if args.state.tick_count == 0
    args.outputs.sounds << 'background_music.ogg'
  end
end

If you want to play a .ogg once as if it were a sound effect, you can do:

def tick args
  # Play a sound every second
  if (args.state.tick_count % 60) == 0
    args.gtk.queue_sound 'some-ogg.ogg'
  end
end

Using args.state To Store Your Game State link

args.state is a open data structure that allows you to define properties that are arbitrarily nested. You don't need to define any kind of class.

To initialize your game state, use the ||= operator. Any value on the right side of ||= will only be assigned _once_.

To assign a value every frame, just use the = operator, but _make sure_ you've initialized a default value.

def tick args
  # initialize your game state ONCE
  args.state.player.x  ||= 0
  args.state.player.y  ||= 0
  args.state.player.hp ||= 100

  # increment the x position of the character by one every frame
  args.state.player.x += 1

  # Render a sprite with a label above the sprite
  args.outputs.sprites << [
    args.state.player.x,
    args.state.player.y,
    32, 32,
    "player.png"
  ]

  args.outputs.labels << [
    args.state.player.x,
    args.state.player.y - 50,
    args.state.player.hp
  ]
end

Accessing files link

DragonRuby uses a sandboxed filesystem which will automatically read from and write to a location appropriate for your platform so you don't have to worry about theses details in your code. You can just use gtk.read_file, gtk.write_file, and gtk.append_file with a relative path and the engine will take care of the rest.

The data directories that will be written to in a production build are:

The values in square brackets are the values you set in your app/metadata/game_metadata.txt file.

When reading files, the engine will first look in the game's data directory and then in the game directory itself. This means that if you write a file to the data directory that already exists in your game directory, the file in the data directory will be used instead of the one that is in your game.

When running a development build you will directly write to your game directory (and thus overwrite existing files). This can be useful for built-in development tools like level editors.

For more details on the implementation of the sandboxed filesystem, see Ryan C. Gordon's PhysicsFS documentation: https://icculus.org/physfs/

Troubleshoot Performance link

  1. If you're using Arrays for your primitives (args.outputs.sprites << []), use Hash instead (args.outputs.sprites << { x: ... }).
  2. If you're using Entity for your primitives (args.outputs.sprites << args.state.new_entity), use StrictEntity instead (args.outputs.sprites << args.state.new_entity_strict).
  3. Use .each instead of .map if you don't care about the return value.
  4. When concatenating primitives to outputs, do them in bulk. Instead of:
args.state.bullets.each do |bullet|
  args.outputs.sprites << bullet.sprite
end

do

args.outputs.sprites << args.state.bullets.map do |b|
  b.sprite
end

5. Use args.outputs.static_ variant for things that don't change often (take a look at the Basic Gorillas sample app and Dueling Starships sample app to see how static_ is leveraged.

6. Consider using a render_target if you're doing some form of a camera that moves a lot of primitives (take a look at the Render Target sample apps for more info).

Runtime link

The GTK::Runtime class is the core of DragonRuby. It is globally accessible via $gtk or inside of the tick method through args.

def tick args
  args.gtk # accessible like this
  $gtk # or like this
end

Indie and Pro Functions link

The following functions are only available at the Indie and Pro License tiers.

get_pixels link

Given a file_path to a sprite, this function returns a one dimensional array of hexadecimal values representing the ARGB of each pixel in a sprite.

See the following sample app for a full demonstration of how to use this function: ./samples/07_advanced_rendering/06_pixel_arrays_from_file

dlopen link

Loads a precompiled C Extension into your game.

See the sample apps at ./samples/12_c_extensions for detailed walkthroughs of creating C extensions.

Environment and Utility Functions link

The following functions will help in interacting with the OS and rendering pipeline.

calcstringbox link

Returns the render width and render height as a tuple for a piece of text. The parameters this method takes are:

  • text: the text you want to get the width and height of.
  • size_enum: number representing the render size for the text. This parameter is optional and defaults to 0 which represents a baseline font size in units specific to DragonRuby (a negative value denotes a size smaller than what would be comfortable to read on a handheld device postive values above 0 represent larger font sizes).
  • font: path to a font file that the width and height will be based off of. This field is optional and defaults to the DragonRuby's default font.
def tick args
  text = "a piece of text"
  size_enum = 5 # "large font size"

  # path is relative to your game directory (eg mygame/fonts/courier-new.ttf)
  font = "fonts/courier-new.ttf"

  # get the render width and height
  string_w, string_h = args.gtk.calcstringbox text, size_enum, font

  # render the label
  args.outputs.labels << {
    x: 100,
    y: 100,
    text: text,
    size_enum: size_enum,
    font: font
  }

  # render a border around the label based on the results from calcstringbox
  args.outputs.borders << {
    x: 100,
    y: 100,
    w: string_w,
    h: string_h,
    r: 0,
    g: 0,
    b: 0
  }
end

request_quit link

Call this function to exit your game. You will be given one additional tick if you need to perform any housekeeping before that game closes.

def tick args
  # exit the game after 600 frames (10 seconds)
  if args.state.tick_count == 600
    args.gtk.request_quit
  end
end

quit_requested? link

This function will return true if the game is about to exit (either from the user closing the game or if request_quit was invoked).

set_window_fullscreen link

This function takes in a single boolean parameter. true to make the game fullscreen, false to return the game back to windowed mode.

def tick args
  # make the game full screen after 600 frames (10 seconds)
  if args.state.tick_count == 600
    args.gtk.set_window_fullscreen true
  end

  # return the game to windowed mode after 20 seconds
  if args.state.tick_count == 1200
    args.gtk.set_window_fullscreen false
  end
end

window_fullscreen? link

Returns true if the window is currently in fullscreen mode.

set_window_scale link

This function takes in a float value and uses that to resize the game window to a percentage of 1280x720 (or 720x1280 in portrait mode). The valid scale options are 0.1, 0.25, 0.5, 0.75, 1.25, 1.5, 2.0, 2.5, 3.0, and 4.0. The float value you pass in will be floored to the nearest valid scale option.

platform? link

You can ask DragonRuby which platform your game is currently being run on. This can be useful if you want to perform different pieces of logic based on where the game is running.

The raw platform string value is available via args.gtk.platform which takes in a symbol representing the platform's categorization/mapping.

You can see all available platform categorizations via the args.gtk.platform_mappings function.

Here's an example of how to use args.gtk.platform? category_symbol:

def tick args
  label_style = { x: 640, y: 360, anchor_x: 0.5, anchor_y: 0.5 }
  if    args.gtk.platform? :macos
    args.outputs.labels << { text: "I am running on MacOS.", **label_style }
  elsif args.gtk.platform? :win
    args.outputs.labels << { text: "I am running on Windows.", **label_style }
  elsif args.gtk.platform? :linux
    args.outputs.labels << { text: "I am running on Linux.", **label_style }
  elsif args.gtk.platform? :web
    args.outputs.labels << { text: "I am running on a web page.", **label_style }
  elsif args.gtk.platform? :android
    args.outputs.labels << { text: "I am running on Android.", **label_style }
  elsif args.gtk.platform? :ios
    args.outputs.labels << { text: "I am running on iOS.", **label_style }
  elsif args.gtk.platform? :touch
    args.outputs.labels << { text: "I am running on a device that supports touch (either iOS/Android native or mobile web).", **label_style }
  elsif args.gtk.platform? :steam
    args.outputs.labels << { text: "I am running via steam (covers both desktop and steamdeck).", **label_style }
  elsif args.gtk.platform? :steam_deck
    args.outputs.labels << { text: "I am running via steam on the Steam Deck (not steam desktop).", **label_style }
  elsif args.gtk.platform? :steam_desktop
    args.outputs.labels << { text: "I am running via steam on desktop (not steam deck).", **label_style }
  end
end

production? link

Returns true if the game is being run in a released/shipped state.

platform_mappings link

These are the current platform categorizations (args.gtk.platform_mappings):

{
  "Mac OS X"   => [:desktop, :macos, :osx, :mac, :macosx], # may also include :steam and :steam_desktop run via steam
  "Windows"    => [:desktop, :windows, :win],              # may also include :steam and :steam_desktop run via steam
  "Linux"      => [:desktop, :linux, :nix],                # may also include :steam and :steam_desktop run via steam
  "Emscripten" => [:web, :wasm, :html, :emscripten],       # may also include :touch if running in the web browser on mobile
  "iOS"        => [:mobile, :ios, :touch],
  "Android"    => [:mobile, :android, :touch],
  "Steam Deck" => [:steamdeck, :steam_deck, :steam],
}

Given the mappings above, args.gtk.platform? :desktop would return true if the game is running on a player's computer irrespective of OS (MacOS, Linux, and Windows are all categorized as :desktop platforms).

open_url link

Given a uri represented as a string. This fuction will open the uri in the user's default browser.

def tick args
  # open a url after 600 frames (10 seconds)
  if args.state.tick_count == 600
    args.gtk.open_url "http://dragonruby.org"
  end
end

system link

Given an OS dependent cli command represented as a string, this function executes the command and puts the results to the DragonRuby Console (returns nil).

def tick args
  # execute ls on the current directory in 10 seconds
  if args.state.tick_count == 600
    args.gtk.system "ls ."
  end
end

exec link

Given an OS dependent cli command represented as a string, this function executes the command and returns a string representing the results.

def tick args
  # execute ls on the current directory in 10 seconds
  if args.state.tick_count == 600
    results = args.gtk.exec "ls ."
    puts "The results of the command are:"
    puts results
  end
end

show_cursor link

Shows the mouse cursor.

hide_cursor link

Hides the mouse cursor.

cursor_shown? link

Returns true if the mouse cursor is visible.

set_mouse_grab link

Takes in a numeric parameter representing the mouse grab mode.

  • 0: Ungrabs the mouse.
  • 1: Grabs the mouse.
  • 2: Hides the cursor, grabs the mouse and puts it in relative position mode accessible via args.inputs.mouse.relative_(x|y).

set_system_cursor link

Takes in a string value of "arrow", "ibeam", "wait", or "hand" and sets the mouse curosor to the corresponding system cursor (if available on the OS).

set_cursor link

Replaces the mouse cursor with a sprite. Takes in a path to the sprite, and optionally an x and y value representing the realtive positioning the sprite will have to the mouse cursor.

def tick args
  if args.state.tick_count == 0
    # assumes a sprite of size 80x80 and centers the sprite
    # relative to the cursor position.
    args.gtk.set_cursor "sprites/square/blue.png", 40, 40
  end
end

File IO Functions link

The following functions give you the ability to interact with the file system.

IMPORTANT: File access functions are sandoxed and assume that the dragonruby binary lives alongside the game you are building. Do not expect these functions to return correct values if you are attempting to run the dragonruby binary from a shared location. It's recommended that the directory structure contained in the zip is not altered and games are built using that starter template.

list_files link

This function takes in one parameter. The parameter is the directory path and assumes the the game directory is the root. The method returns an Array of String representing all files within the directory. Use stat_file to determine whether a specific path is a file or a directory.

stat_file link

This function takes in one parameter. The parameter is the file path and assumes the the game directory is the root. The method returns nil if the file doesn't exist otherwise it returns a Hash with the following information:

# {
#   path: String,
#   file_size: Int,
#   mod_time: Int,
#   create_time: Int,
#   access_time: Int,
#   readonly: Boolean,
#   file_type: Symbol (:regular, :directory, :symlink, :other),
# }

def tick args
  if args.inputs.mouse.click
    args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at #{args.state.tick_count}."
  end

  file_info = args.gtk.stat_file "last-mouse-click.txt"

  if file_info
    args.outputs.labels << {
      x: 30,
      y: 30.from_top,
      text: file_info.to_s,
      size_enum: -3
    }
  else
    args.outputs.labels << {
      x: 30,
      y: 30.from_top,
      text: "file does not exist, click to create file",
      size_enum: -3
    }
  end
end

read_file link

Given a file path, a string will be returned representing the contents of the file. nil will be returned if the file does not exist. You can use stat_file to get additional information of a file.

write_file link

This function takes in two parameters. The first parameter is the file path and assumes the the game directory is the root. The second parameter is the string that will be written. The method **overwrites** whatever is currently in the file. Use append_file to append to the file as opposed to overwriting.

def tick args
  if args.inputs.mouse.click
    args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at #{args.state.tick_count}."
  end
end

append_file link

This function takes in two parameters. The first parameter is the file path and assumes the the game directory is the root. The second parameter is the string that will be written. The method appends to whatever is currently in the file (a new file is created if one does not alread exist). Use write_file to overwrite the file's contents as opposed to appending.

def tick args
  if args.inputs.mouse.click
    args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at #{args.state.tick_count}."
  end
end

delete_file link

This function takes in a single parameters. The parameter is the file path that should be deleted. This function will raise an exception if the path requesting to be deleted does not exist.

Notes:

  • Use delete_if_exist to only delete the file if it exists.
  • Use stat_file to determine if a path exists.
  • Use list_files to determine if a directory is empty.
  • You cannot delete files outside of your sandboxed game environment.

Here is a list of reasons an exception could be raised:

- If the path is not found. - If the path is still open (for reading or writing). - If the path is not a file or directory. - If the path is a circular symlink. - If you do not have permissions to delete the path. - If the directory attempting to be deleted is not empty.

def tick args
  if args.inputs.mouse.click
    args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at #{args.state.tick_count}."
  end
end

delete_file_if_exist link

Has the same behavior as delete_file except this function does not throw an exception.

XML and JSON link

The following functions help with parsing xml and json.

parse_json link

Given a json string, this function returns a hash representing the json data.

sh = args.gtk.parse_json '{ "name": "John Doe", "aliases": ["JD"] }'
structure of hash: { "name"=>"John Doe", "aliases"=>["JD"] }

parse_json_file link

Same behavior as parse_json_file except a file path is read for the json string.

parse_xml link

Given xml data as a string, this function will return a hash that represents the xml data in the following recursive structure:


type: :element,
name: "Person",
children: [...]

parse_xml_file link

Function has the same behavior as parse_xml except that the parameter must be a file path that contains xml contents.

Network IO Functions link

The following functions help with interacting with the network.

http_get link

Returns an object that represents an http response which will eventually have a value. This http_get method is invoked asynchronously. Check for completion before attempting to read results.

def tick args
  # perform an http get and print the response when available
  args.state.result ||= args.gtk.http_get "https://httpbin.org/html"

  if args.state.result && args.state.result[:complete] && !args.state.printed
    if args.state.result[:http_response_code] == 200
      puts "The response was successful. The body is:"
      puts args.state.result[:response_data]
    else
      puts "The response failed. Status code:"
      puts args.state.result[:http_response_code]
    end
    # set a flag denoting that the response has been printed
    args.state.printed = true

    # show the console
    args.gtk.show_console
  end
end

http_post link

Returns an object that represents an http response which will eventually have a value. This http_post method is invoked asynchronously. Check for completion before attempting to read results.

  • First parameter: The url to send the request to.
  • Second parameter: Hash that represents form fields to send.
  • Third parameter: Headers. Note: Content-Type must be form encoded flavor. If you are unsure of what to pass in, set the content type to application/x-www-form-urlencoded
def tick args
  # perform an http get and print the response when available

  args.state.form_fields ||= { "userId" => "1693822467" }
  args.state.result ||= args.gtk.http_post "http://httpbin.org/post",
                                           args.state.form_fields,
                                           ["Content-Type: application/x-www-form-urlencoded"]


  if args.state.result && args.state.result[:complete] && !args.state.printed
    if args.state.result[:http_response_code] == 200
      puts "The response was successful. The body is:"
      puts args.state.result[:response_data]
    else
      puts "The response failed. Status code:"
      puts args.state.result[:http_response_code]
    end
    # set a flag denoting that the response has been printed
    args.state.printed = true

    # show the console
    args.gtk.show_console
  end
end

http_post_body link

Returns an object that represents an http response which will eventually have a value. This http_post_body method is invoked asynchronously. Check for completion before attempting to read results.

  • First parameter: The url to send the request to.
  • Second parameter: String that represents the body that will be sent
  • Third parameter: Headers. Be sure to populate the Content-Type that matches the data you are sending.
def tick args
  # perform an http get and print the response when available

  args.state.json ||= "{ "userId": "#{Time.now.to_i}"}"
  args.state.result ||= args.gtk.http_post_body "http://httpbin.org/post",
                                                args.state.json,
                                                ["Content-Type: application/json", "Content-Length: #{args.state.json.length}"]


  if args.state.result && args.state.result[:complete] && !args.state.printed
    if args.state.result[:http_response_code] == 200
      puts "The response was successful. The body is:"
      puts args.state.result[:response_data]
    else
      puts "The response failed. Status code:"
      puts args.state.result[:http_response_code]
    end
    # set a flag denoting that the response has been printed
    args.state.printed = true

    # show the console
    args.gtk.show_console
  end
end

start_server! link

Starts a in-game http server that can be process http requests. When your game is running in development mode. A dev server is started at http://localhost:9001

You can start an in-game http server in production via:

def tick args
  # server explicitly enabled in production
  args.gtk.start_server! port: 9001, enable_in_prod: true
end

Here's how you would responde to http requests:

def tick args
  # server explicitly enabled in production
  args.gtk.start_server! port: 9001, enable_in_prod: true

  # loop through pending requests and respond to them
  args.inputs.http_requests.each do |request|
    puts "#{request}"
    request.respond 200, "ok"
  end
end

Developer Support Functions link

The following functions help support the development process. It is not recommended to use this functions in "production" game logic.

version link

Returns a string representing the version of DragonRuby you are running.

version_pro? link

Returns true if the version of DragonRuby is NOT Standard Edition.

reset link

Resets DragonRuby's internal state as if it were just started. args.state.tick_count is set to 0 and args.state is cleared of any values. This function is helpful when you are developing your game and want to reset everything as if the game just booted up.

def tick args
end

# reset the game if this file is hotloaded/required
# (removes the need to press "r" when I file is updated)
$gtk.reset

NOTE: args.gtk.reset does not reset global variables or instance of classes you have have constructed.

reset_next_tick link

Has the same behavior as reset except the reset occurs before tick is executed again. reset resets the environment immediately (while the tick method is inflight). It's recommended that reset should be called outside of the tick method (invoked when a file is saved/hotloaded), and reset_next_tick be used inside of the tick method so you don't accidentally blow away state the your game depends on to complete the current tick without exceptions.

def tick args
  # reset the game if "r" is pressed on the keyboard
  if args.inputs.keyboard.key_down.r
    args.gtk.reset_next_tick # use reset_next_tick instead of reset
  end
end

# reset the game if this file is hotloaded/required
# (removes the need to press "r" when I file is updated)
$gtk.reset

reset_sprite link

Sprites when loaded are cached. Given a string parameter, this method invalidates the cache record of a sprite so that updates on from the disk can be loaded.

reset_sprites link

Sprites when loaded are cached. This method invalidates the cache record of all sprites so that updates on from the disk can be loaded.

calcspritebox link

Given a path to a sprite, this method returns the width and height of a sprite as a tuple.

NOTE: This method should be used for development purposes only and is expensive to call every frame. Do not use this method to set the size of sprite when rendering (hard code those values since you know what they are beforehand).

current_framerate link

Returns a float value representing the framerate of your game. This is an approximation/moving average of your framerate and should eventually settle to 60fps.

def tick args
  # render a label to the screen that shows the current framerate
  # formatted as a floating point number with two decimal places
  args.outputs.labels << { x: 30, y: 30.from_top, text: "#{args.gtk.current_framerate.to_sf}" }
end

framerate_diagnostics_primitives link

Returns a set of primitives that can be rendered to the screen which provide more detailed information about the speed of your simulation (framerate, draw call count, mouse position, etc).

def tick args
  args.outputs.primitives << args.gtk.framerate_diagnostics_primitives
end

warn_array_primitives! link

This function helps you audit your game of usages of array-based primitives. While array-based primitives are simple to create and use, they are slower to process than Hash or Class based primitives.

def tick args
  # enable array based primitives warnings
  args.gtk.warn_array_primitives!

  # array-based primitive elsewhere in code
  # an log message will be posted giving the location of the array
  # based primitive usage
  args.outputs.sprites << [100, 100, 200, 200, "sprites/square/blue.png"]

  # instead of using array based primitives, migrate to hashes as needed
  args.outputs.sprites << {
    x: 100,
    y: 100,
    w: 200,
    h: 200, path:
    "sprites/square/blue.png"
  }
end

benchmark link

You can use this function to compare the relative performance of blocks of code.

def tick args
  # press r to run benchmark
  if args.inputs.keyboard.key_down.r
    args.gtk.console.show
    args.gtk.benchmark iterations: 1000, # number of iterations
                       # label for experiment
                       using_numeric_map: -> () {
                         # experiment body
                         v = 100.map_with_index do |i|
                           i * 100
                         end
                       },
                       # label for experiment
                       using_numeric_times: -> () {
                         # experiment body
                         v = []
                         100.times do |i|
                           v << i * 100
                         end
                       }
  end
end

notify! link

Given a string, this function will present a message at the bottom of your game. This method is only invoked in dev mode and is useful for debugging.

An optional parameter of duration (number value representing ticks) can also be passed in. The default value if 300 ticks (5 seconds).

def tick args
  if args.inputs.mouse.click
    args.gtk.notify! "Mouse was clicked!"
  end

  if args.inputs.keyboard.key_down.r
    # optional duration parameter
    args.gtk.notify! "R key was pressed!", 600 # present message for 10 seconds/600 frames
  end
end

notify_extended! link

Has similar behavior as notify! except you have additional options to show messages in a production environment.

def tick args
  if args.inputs.mouse.click
    args.gtk.notify_extended! message: "message",
                              duration: 300,
                              env: :prod
  end
end

slowmo! link

Given a numeric value representing the factor of 60fps. This function will bring your simulation loop down to slower rate. This method is intended to be used for debugging purposes.

def tick args
  # set your simulation speed to (15 fps): args.gtk.slowmo! 4
  # set your simulation speed to (1 fps): args.gtk.slowmo! 60
  # set your simulation speed to (30 fps):
  args.gtk.slowmo! 2
end

Remove this line from your tick method will automatically set your simulation speed back to 60 fps.

show_console link

Shows the DragonRuby console. Useful when debugging/customizing an in-game dev workflow.

hide_console link

Shows the DragonRuby console. Useful when debugging/customizing an in-game dev workflow.

enable_console link

Enables the DragonRuby Console so that it can be presented by pressing the tilde key (the key next to the number 1 key).

disable_console link

Disables the DragonRuby Console so that it won't show up even if you press the tilde key or call args.gtk.show_console.

start_recording link

Resets the game to tick 0 and starts recording gameplay. Useful for visual regression tests/verification.

stop_recording link

Function takes in a destination file for the currently recording gameplay. This file can be used to replay a recording.

cancel_recording link

Function cancels a gameplay recording session and discards the replay.

start_replay link

Given a file that represents a recording, this method will run the recording against the current codebase.

You can start a replay from the command line also:

first argument: the game directory
--replay switch is the file path relative to the game directory
--speed switch is optional. a value of 4 will run the replay and game at 4x speed
cli command example is in the context of Linux and Mac, for Windows the binary would be ./dragonruby.exe
dragonruby ./mygame --replay ./replay.txt --speed 4

stop_replay link

Function stops a replay that is currently executing.

get_base_dir link

Returns the path to the location of the dragonruby binary. In production mode, this value will be the same as the value returned by get_game_dir. Function should only be used for debugging/development workflows.

get_game_dir link

Returns the location within sandbox storage that the game is running. When developing your game, this value will be your mygame directory. In production, it'll return a value that is OS specific (eg the Roaming directory on Windows or the Application Support directory on Mac).

Invocations of ~(write|append)_file will write to this sandboxed directory.

get_game_dir_url link

Returns a url encoded string representing the sandbox location for game data.

open_game_dir link

Opens the game directory in the OS's file explorer. This should be used for debugging purposes only.

write_file_root link

Given a file path and contents, the contents will be written to a directory outside of the game directory. This method should be used for development purposes only. In production this method will write to the same sandboxed location as write_file.

append_file_root link

Has the same behavior as write_file_root except that it appends the contents as opposed to overwriting them.

argv link

Returns a string representing the command line arguments passed to the DragonRuby binary. This should be used for development/debugging purposes only.

cli_arguments link

Returns a Hash for command line arguments in the format of --switch value (two hyphens preceding the switch flag with the value seperated by a space). This should be used for development/debugging purposes only.

download_stb_rb(_raw) link

These two functions can help facilitate the integration of external code files. OSS contributors are encouraged to create libraries that all fit in one file (lowering the barrier to entry for adoption).

Examples:

def tick args
end

# option 1:
# source code will be downloaded from the specified GitHub url, and saved locally with a
# predefined folder convension.
$gtk.download_stb_rb "https://github.com/xenobrain/ruby_vectormath/blob/main/vectormath_2d.rb"

# option 2:
# source code will be downloaded from the specified GitHub username, repository, and file.
# code will be saved locally with a predefined folder convension.
$gtk.download_stb_rb "xenobrain", "ruby_vectormath", "vectormath_2d.rb"

# option 3:
# source code will be downloaded from a direct/raw url and saved to a direct/raw local path.
$gtk.download_stb_rb_raw "https://raw.githubusercontent.com/xenobrain/ruby_vectormath/main/vectormath_2d.rb",
                         "lib/xenobrain/ruby_vectionmath/vectormath_2d.rb"

reload_history link

Returns a Hash representing the code files that have be loaded for your game along with timings for the events. This should be used for development/debugging purposes only.

reload_history_pending link

Returns a Hash for files that have been queued for reload, but haven't been processed yet. This should be used for development/debugging purposes only.

reload_if_needed link

Given a file name, this function will queue the file for reload if it's been modified. An optional second parameter can be passed in to signify if the file should be forced loaded regardless of modified time (true means to force load, false means to load only if the file has been modified). This function should be used for development/debugging purposes only.

args.state link

Store your game state inside of this state. Properties with arbitrary nesting is allowed and a backing Entity will be created on your behalf.

def tick args
  args.state.player.x ||= 0
  args.state.player.y ||= 0
end

args.state.*.entity_id link

Entities automatically receive an entity_id of type Fixnum.

args.state.*.entity_type link

Entities can have an entity_type which is represented as a Symbol.

args.state.*.created_at link

Entities have created_at set to args.state.tick_count when they are created.

args.state.*.created_at_elapsed link

Returns the elapsed number of ticks since creation.

args.state.*.global_created_at link

Entities have global_created_at set to Kernel.global_tick_count when they are created.

args.state.*.global_created_at_elapsed link

Returns the elapsed number of global ticks since creation.

args.state.*.as_hash link

Entity cast to a Hash so you can update values as if you were updating a Hash.

args.state.new_entity link

Creates a new Entity with a type, and initial properties. An option block can be passed to change the newly created entity:

def tick args
  args.state.player ||= args.state.new_entity :player, x: 0, y: 0 do |e|
    e.max_hp = 100
    e.hp     = e.max_hp * rand
  end
end

args.state.new_entity_strict link

Creates a new Strict Entity. While Entities created via args.state.new_entity can have new properties added later on, Entities created using args.state.new_entity_strict must define all properties that are allowed during its initialization. Attempting to add new properties after initialization will result in an exception.

args.state.tick_count link

Returns the current tick of the game. args.state.tick_count is 0 when the game is first started or if the game is reset via $gtk.reset.

args.events.resize_occured link

This property will be set to true if the window is resized.

args.inputs link

Access using input using args.inputs.

args.inputs.last_active link

This function returns the last active input which will be set to either :keyboard, :mouse, or :controller. The function is helpful when you need to present on screen instructions based on the input the player chose to play with.

def tick args
  if args.inputs.last_active == :controller
    args.outputs.labels << { x: 60, y: 60, text: "Use the D-Pad to move around." }
  else
    args.outputs.labels << { x: 60, y: 60, text: "Use the arrow keys to move around." }
  end
end

:mouse, or :controller. The function is helpful when you need to present on screen instructions based on the input the player chose to play with.

args.inputs.locale link

Returns the ISO 639-1 two-letter langauge code based on OS preferences (see https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). Defaults to "en" if locale can't be retrieved (args.inputs.locale_raw will be nil in this case).

args.inputs.up link

Returns true if: the up arrow or w key is pressed or held on the keyboard; or if up is pressed or held on controller_one; or if the left_analog on controller_one is tilted upwards.

args.inputs.down link

Returns true if: the down arrow or s key is pressed or held on the keyboard; or if down is pressed or held on controller_one; or if the left_analog on controller_one is tilted downwards.

args.inputs.left link

Returns true if: the left arrow or a key is pressed or held on the keyboard; or if left is pressed or held on controller_one; or if the left_analog on controller_one is tilted to the left.

args.inputs.right link

Returns true if: the right arrow or d key is pressed or held on the keyboard; or if right is pressed or held on controller_one; or if the left_analog on controller_one is tilted to the right.

args.inputs.left_right link

Returns -1 (left), 0 (neutral), or +1 (right) depending on results of args.inputs.left and args.inputs.right.

args.state.player[:x] += args.inputs.left_right * args.state.speed

args.inputs.up_down link

Returns -1 (down), 0 (neutral), or +1 (up) depending on results of args.inputs.down and args.inputs.up.

args.state.player[:y] += args.inputs.up_down * args.state.speed

args.inputs.text OR args.inputs.history link

Returns a string that represents the last key that was pressed on the keyboard.

args.inputs.mouse link

Represents the user's mouse.

args.inputs.mouse.has_focus link

Return's true if the game has mouse focus.

args.inputs.mouse.x link

Returns the current x location of the mouse.

args.inputs.mouse.y link

Returns the current y location of the mouse.

args.inputs.mouse.inside_rect? rect link

Return. args.inputs.mouse.inside_rect? takes in any primitive that responds to x, y, w, h:

args.inputs.mouse.inside_circle? center_point, radius link

Returns true if the mouse is inside of a specified circle. args.inputs.mouse.inside_circle? takes in any primitive that responds to x, y (which represents the circle's center), and takes in a radius:

args.inputs.mouse.moved link

Returns true if the mouse has moved on the current frame.

args.inputs.mouse.button_left link

Returns true if the left mouse button is down.

args.inputs.mouse.button_middle link

Returns true if the middle mouse button is down.

args.inputs.mouse.button_right link

Returns true if the right mouse button is down.

args.inputs.mouse.button_bits link

Returns a bitmask for all buttons on the mouse: 1 for a button in the down state, 0 for a button in the up state.

args.inputs.mouse.wheel link

Represents the mouse wheel. Returns nil if no mouse wheel actions occurred.

args.inputs.mouse.wheel.x link

Returns the negative or positive number if the mouse wheel has changed in the x axis.

args.inputs.mouse.wheel.y link

Returns the negative or positive number if the mouse wheel has changed in the y axis.

args.inputs.mouse.click OR .down, .previous_click, .up link

The properties args.inputs.mouse.(click|down|previous_click|up) each return nil if the mouse button event didn't occur. And return an Entity that has an x, y properties along with helper functions to determine collision: inside_rect?, inside_circle. This value will be true if any of the mouse's buttons caused these events. To scope to a specific button use .button_left, .button_middle, .button_right, or .button_bits.

args.inputs.controller_(one-four) link

Represents controllers connected to the usb ports.

args.inputs.controller_(one-four).active link

Returns true if any of the controller's buttons were used.

args.inputs.controller_(one-four).up link

Returns true if up is pressed or held on the directional or left analog.

args.inputs.controller_(one-four).down link

Returns true if down is pressed or held on the directional or left analog.

args.inputs.controller_(one-four).left link

Returns true if left is pressed or held on the directional or left analog.

args.inputs.controller_(one-four).right link

Returns true if right is pressed or held on the directional or left analog.

args.inputs.controller_(one-four).left_right link

Returns -1 (left), 0 (neutral), or +1 (right) depending on results of args.inputs.controller_(one-four).left and args.inputs.controller_(one-four).right.

args.inputs.controller_(one-four).up_down link

Returns -1 (down), 0 (neutral), or +1 (up) depending on results of args.inputs.controller_(one-four).up and args.inputs.controller_(one-four).down.

args.inputs.controller_(one-four).(left_analog_x_raw|right_analog_x_raw) link

Returns the raw integer value for the analog's horizontal movement (-32,000 to +32,000).

args.inputs.controller_(one-four).left_analog_y_raw|right_analog_y_raw) link

Returns the raw integer value for the analog's vertical movement (-32,000 to +32,000).

args.inputs.controller_(one-four).left_analog_x_perc|right_analog_x_perc) link

Returns a number between -1 and 1 which represents the percentage the analog is moved horizontally as a ratio of the maximum horizontal movement.

args.inputs.controller_(one-four).left_analog_y_perc|right_analog_y_perc) link

Returns a number between -1 and 1 which represents the percentage the analog is moved vertically as a ratio of the maximum vertical movement.

args.inputs.controller_(one-four).directional_up link

Returns true if up is pressed or held on the directional.

args.inputs.controller_(one-four).directional_down link

Returns true if down is pressed or held on the directional.

args.inputs.controller_(one-four).directional_left link

Returns true if left is pressed or held on the directional.

args.inputs.controller_(one-four).directional_right link

Returns true if right is pressed or held on the directional.

args.inputs.controller_(one-four).(a|b|x|y|l1|r1|l2|r2|l3|r3|start|select) link

Returns true if the specific button is pressed or held.

args.inputs.controller_(one-four).truthy_keys link

Returns a collection of Symbols that represent all keys that are in the pressed or held state.

args.inputs.controller_(one-four).key_down link

Returns true if the specific button was pressed on this frame. args.inputs.controller_(one-four).key_down.BUTTON will only be true on the frame it was pressed.

args.inputs.controller_(one-four).key_held link

Returns true if the specific button is being held. args.inputs.controller_(one-four).key_held.BUTTON will be true for all frames after key_down (until released).

args.inputs.controller_(one-four).key_up link

Returns true if the specific button was released. args.inputs.controller_(one-four).key_up.BUTTON will be true only on the frame the button was released.

args.inputs.keyboard link

Represents the user's keyboard

args.inputs.keyboard.active link

Returns true if any keys on the keyboard were pressed.

args.inputs.keyboard.has_focus link

Returns true if the game has keyboard focus.

args.inputs.keyboard.up link

Returns true if up or w is pressed or held on the keyboard.

args.inputs.keyboard.down link

Returns true if down or s is pressed or held on the keyboard.

args.inputs.keyboard.left link

Returns true if left or a is pressed or held on the keyboard.

args.inputs.keyboard.right link

Returns true if right or d is pressed or held on the keyboard.

args.inputs.keyboard.left_right link

Returns -1 (left), 0 (neutral), or +1 (right) depending on results of args.inputs.keyboard.left and args.inputs.keyboard.right.

args.inputs.keyboard.up_down link

Returns -1 (left), 0 (neutral), or +1 (right) depending on results of args.inputs.keyboard.up and args.inputs.keyboard.up.

keyboard properties link

The following properties represent keys on the keyboard and are available on args.inputs.keyboard.KEY, args.inputs.keyboard.key_down.KEY, args.inputs.keyboard.key_held.KEY, and args.inputs.keyboard.key_up.KEY:

  • alt
  • meta
  • control
  • shift
  • ctrl_KEY (dynamic method, eg args.inputs.keyboard.ctrl_a)
  • exclamation_point
  • zero - nine
  • backspace
  • delete
  • escape
  • enter
  • tab
  • (open|close)_round_brace
  • (open|close)_curly_brace
  • (open|close)_square_brace
  • colon
  • semicolon
  • equal_sign
  • hyphen
  • space
  • dollar_sign
  • double_quotation_mark
  • single_quotation_mark
  • backtick
  • tilde
  • period
  • comma
  • pipe
  • underscore
  • a - z
  • shift
  • control
  • alt
  • meta
  • left
  • right
  • up
  • down
  • pageup
  • pagedown
  • char
  • plus
  • at
  • forward_slash
  • back_slash
  • asterisk
  • less_than
  • greater_than
  • carat
  • ampersand
  • superscript_two
  • circumflex
  • question_mark
  • section_sign
  • ordinal_indicator
  • raw_key
  • left_right
  • up_down
  • directional_vector
  • truthy_keys

inputs.keyboard.keys link

Returns a Hash with all keys on the keyboard in their respective state. The Hash contains the following keys

  • :down
  • :held
  • :down_or_held
  • :up

args.inputs.touch link

Returns a Hash representing all touch points on a touch device.

args.inputs.finger_left link

Returns a Hash with x and y denoting a touch point that is on the left side of the screen.

args.inputs.finger_right link

Returns a Hash with x and y denoting a touch point that is on the right side of the screen.

args.outputs link

Outputs is how you render primitives to the screen. The minimal setup for rendering something to the screen is via a tick method defined in mygame/app/main.rb

def tick args
  args.outputs.solids     << [0, 0, 100, 100]
  args.outputs.sprites    << [100, 100, 100, 100, "sprites/square/blue.png"]
  args.outputs.labels     << [200, 200, "Hello World"]
  args.outputs.lines      << [300, 300, 400, 400]
end

Primitives are rendered first-in, first-out. The rendering order (sorted by bottom-most to top-most):

args.outputs.background_color link

Set args.outputs.background_color to an Array with RGB values (eg. [255, 255, 255] for the color white).

args.outputs.sounds link

Send a file path to this collection to play a sound. The sound file must be under the mygame directory.

args.outputs.sounds << "sounds/jump.wav"

args.outputs.solids link

Send a Primitive to this collection to render a filled in rectangle to the screen. This collection is cleared at the end of every frame.

args.outputs.static_solids link

Send a Primitive to this collection to render a filled in rectangle to the screen. This collection is not cleared at the end of every frame. And objects can be mutated by reference.

args.outputs.sprites, .static_sprites link

Send a Primitive to this collection to render a sprite to the screen.

args.outputs.primitives, .static_primitives link

Send a Primitive of any type and it'll be rendered. The Primitive must have a primitive_marker that returns :solid, :sprite, :label, :line, :border.

args.outputs.labels, .static_labels link

Send a Primitive to this collection to render text to the screen.

args.outputs.lines, .static_lines link

Send a Primitive to this collection to render a line to the screen.

args.outputs.borders, .static_borders link

Send a Primitive to this collection to render an unfilled rectangle to the screen.

args.outputs.debug, .static_debug link

Send any Primitive to this collection which represents things you render to the screen for debugging purposes. Primitives in this collection will not be rendered in a production release of your game.

args.easing link

A set of functions that allow you to determine the current progression of an easing function.

args.easing.ease start_tick, current_tick, duration, easing_functions link

Given a start, current, duration, and easing function names, ease returns a number between 0 and 1 that represents the progress of an easing function.

The built in easing definitions you have access to are :identity, :flip, :quad, :cube, :quart, and :quint.

This example will move a box at a linear speed from 0 to 1280.

def tick args
  start_time = 10
  duration = 60
  current_progress = args.easing.ease start_time,
                                      args.state.tick_count,
                                      duration,
                                      :identity
  args.outputs.solids << { x: 1280 * current_progress, y: 360, w: 10, h: 10 }
end

args.easing.ease_spline start_tick, current_tick, duration, spline link

Given a start, current, duration, and a multiple bezier values, this function returns a number between 0 and 1 that represents the progress of an easing function.

This example will move a box at a linear speed from 0 to 1280 and then back to 0 using two bezier definitions (represented as an array with four values).

def tick args
  start_time = 10
  duration = 60
  spline = [
    [  0, 0.25, 0.75, 1.0],
    [1.0, 0.75, 0.25,   0]
  ]
  current_progress = args.easing.ease_spline start_time,
                                             args.state.tick_count,
                                             duration,
                                             spline
  args.outputs.solids << { x: 1280 * current_progress, y: 360, w: 10, h: 10 }
end

args.string link

Useful string functions not included in Ruby core libraries.

args.string.wrapped_lines string, max_character_length link

This function will return a collection of strings given an input string and max_character_length. The collection of strings returned will split the input string into strings of length <= max_character_length.

The following example takes a string with new lines and creates a label for each one. Labels (args.outputs.labels) ignore newline characters \n.

def tick args
  long_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.
teger dolor velit, ultricies vitae libero vel, aliquam imperdiet enim."
  max_character_length = 30
  long_strings_split = args.string.wrapped_lines long_string, max_character_length
  args.outputs.labels << long_strings_split.map_with_index do |s, i|
    { x: 10, y: 600 - (i * 20), text: s }
  end
end

args.grid link

Returns the virtual grid for the game.

args.grid.name link

Returns either :origin_bottom_left or :origin_center.

args.grid.bottom link

Returns the y value that represents the bottom of the grid.

args.grid.top link

Returns the y value that represents the top of the grid.

args.grid.left link

Returns the x value that represents the left of the grid.

args.grid.right link

Returns the x value that represents the right of the grid.

args.grid.rect link

Returns a rectangle Primitive that represents the grid.

args.grid.origin_bottom_left! link

Change the grids coordinate system to 0, 0 at the bottom left corner.

args.grid.origin_center! link

Change the grids coordinate system to 0, 0 at the center of the screen.

args.grid.w link

Returns the grid's width (always 1280).

args.grid.h link

Returns the grid's height (always 720).

Geometry link

The Geometry module contains methods for calculations that are frequently used in game development. For convenience, this module is mixed into Hash, Array, and DragonRuby's Entity class. It is also available in a functional variant at args.geometry.

Many of the geometric functions assume the objects have a certain shape:

def tick args
  # Geometry is mixed into Hash, Array, and Entity

  # define to rectangles
  rect_1 = { x: 0, y: 0, w: 100, h: 100 }
  rect_2 = { x: 50, y: 50, w: 100, h: 100 }

  # call geometry method function from instance of a Hash class
  puts rect_1.intersect_rect?(rect_2)

  # OR

  # use the geometry methods functionally
  puts args.geometry.intersect_rect?(rect_1, rect_2)
end

intersect_rect? link

Invocation variants:

Given two rectangle primitives this function will return true or false depending on if the two rectangles intersect or not. An optional final parameter can be passed in representing the tolerence of overlap needed to be considered a true intersection. The default value of tolerance is 0.1 which keeps the function from returning true if only the edges of the rectangles overlap.

:anchor_x, and anchor_y is taken into consideration if the objects respond to these methods.

Here is an example where one rectangle is stationary, and another rectangle is controlled using directional input. The rectangles change color from blue to read if they intersect.

def tick args
  # define a rectangle in state and position it
  # at the center of the screen with a color of blue
  args.state.box_1 ||= {
    x: 640 - 20,
    y: 360 - 20,
    w: 40,
    h: 40,
    r: 0,
    g: 0,
    b: 255
  }

  # create another rectangle in state and position it
  # at the far left center
  args.state.box_2 ||= {
    x: 0,
    y: 360 - 20,
    w: 40,
    h: 40,
    r: 0,
    g: 0,
    b: 255
  }

  # take the directional input and use that to move the second rectangle around
  # increase or decrease the x value based on if left or right is held
  args.state.box_2.x += args.inputs.left_right * 5
  # increase or decrease the y value based on if up or down is held
  args.state.box_2.y += args.inputs.up_down * 5

  # change the colors of the rectangles based on whether they
  # intersect or not
  if args.state.box_1.intersect_rect? args.state.box_2
    args.state.box_1.r = 255
    args.state.box_1.g = 0
    args.state.box_1.b = 0

    args.state.box_2.r = 255
    args.state.box_2.g = 0
    args.state.box_2.b = 0
  else
    args.state.box_1.r = 0
    args.state.box_1.g = 0
    args.state.box_1.b = 255

    args.state.box_2.r = 0
    args.state.box_2.g = 0
    args.state.box_2.b = 255
  end

  # render the rectangles as border primitives on the screen
  args.outputs.borders << args.state.box_1
  args.outputs.borders << args.state.box_2
end

inside_rect? link

Invocation variants:

Given two rectangle primitives this function will return true or false depending on if the first rectangle (or self) is inside of the second rectangle.

Here is an example where one rectangle is stationary, and another rectangle is controlled using directional input. The rectangles change color from blue to read if the movable rectangle is entirely inside the stationary rectangle.

:anchor_x, and anchor_y is taken into consideration if the objects respond to these methods.

def tick args
  # define a rectangle in state and position it
  # at the center of the screen with a color of blue
  args.state.box_1 ||= {
    x: 640 - 40,
    y: 360 - 40,
    w: 80,
    h: 80,
    r: 0,
    g: 0,
    b: 255
  }

  # create another rectangle in state and position it
  # at the far left center
  args.state.box_2 ||= {
    x: 0,
    y: 360 - 10,
    w: 20,
    h: 20,
    r: 0,
    g: 0,
    b: 255
  }

  # take the directional input and use that to move the second rectangle around
  # increase or decrease the x value based on if left or right is held
  args.state.box_2.x += args.inputs.left_right * 5
  # increase or decrease the y value based on if up or down is held
  args.state.box_2.y += args.inputs.up_down * 5

  # change the colors of the rectangles based on whether they
  # intersect or not
  if args.state.box_2.inside_rect? args.state.box_1
    args.state.box_1.r = 255
    args.state.box_1.g = 0
    args.state.box_1.b = 0

    args.state.box_2.r = 255
    args.state.box_2.g = 0
    args.state.box_2.b = 0
  else
    args.state.box_1.r = 0
    args.state.box_1.g = 0
    args.state.box_1.b = 255

    args.state.box_2.r = 0
    args.state.box_2.g = 0
    args.state.box_2.b = 255
  end

  # render the rectangles as border primitives on the screen
  args.outputs.borders << args.state.box_1
  args.outputs.borders << args.state.box_2
end

scale_rect link

Given a Rectangle this function returns a new rectangle with a scaled size.

def tick args
  # a rect at the center of the screen
  args.state.rect_1 ||= { x: 640 - 20, y: 360 - 20, w: 40, h: 40 }

  # render the rect
  args.outputs.borders << args.state.rect_1

  # the rect half the size with the x and y position unchanged
  args.outputs.borders << args.state.rect_1.scale_rect(0.5)

  # the rect double the size, repositioned in the center given anchor optional arguments
  args.outputs.borders << args.state.rect_1.scale_rect(2, 0.5, 0.5)
end

scale_rect_extended link

The behavior is similar to scale_rect except that you can independently control the scale of each axis. The parameters are all named:

def tick args
  baseline_rect = { x: 640 - 20, y: 360 - 20, w: 40, h: 40 }
  args.state.rect_1 ||= baseline_rect
  args.state.rect_2 ||= baseline_rect.scale_rect_extended(percentage_x: 2,
                                                          percentage_y: 0.5,
                                                          anchor_x: 0.5,
                                                          anchor_y: 1.0)
  args.outputs.borders << args.state.rect_1
  args.outputs.borders << args.state.rect_2
end

anchor_rect link

Returns a new rect that is anchored by an anchor_x and anchor_y value. The width and height of the rectangle is taken into consideration when determining the anchor position:

def tick args
  args.state.rect ||= {
    x: 640,
    y: 360,
    w: 100,
    h: 100
  }

  # rect's center: 640 + 50, 360 + 50
  args.outputs.borders << args.state.rect.anchor_rect(0, 0)

  # rect's center: 640, 360
  args.outputs.borders << args.state.rect.anchor_rect(0.5, 0.5)

  # rect's center: 640, 360
  args.outputs.borders << args.state.rect.anchor_rect(0.5, 0)
end

angle_from link

Invocation variants:

Returns an angle in degrees from the end_point to the start_point (if you want the value in radians, you can call .to_radians on the value returned):

def tick args
  rect_1 ||= {
    x: 0,
    y: 0,
  }

  rect_2 ||= {
    x: 100,
    y: 100,
  }

  angle = rect_1.angle_from rect_2 # returns 225 degrees
  angle_radians = angle.to_radians
  args.outputs.labels << { x: 30, y: 30.from_top, text: "#{angle}, #{angle_radians}" }

  angle = args.geometry.angle_from rect_1, rect_2 # returns 225 degrees
  angle_radians = angle.to_radians
  args.outputs.labels << { x: 30, y: 60.from_top, text: "#{angle}, #{angle_radians}" }
end

angle_to link

Invocation variants:

Returns an angle in degrees to the end_point from the start_point (if you want the value in radians, you can call .to_radians on the value returned):

def tick args
  rect_1 ||= {
    x: 0,
    y: 0,
  }

  rect_2 ||= {
    x: 100,
    y: 100,
  }

  angle = rect_1.angle_to rect_2 # returns 45 degrees
  angle_radians = angle.to_radians
  args.outputs.labels << { x: 30, y: 30.from_top, text: "#{angle}, #{angle_radians}" }

  angle = args.geometry.angle_to rect_1, rect_2 # returns 45 degrees
  angle_radians = angle.to_radians
  args.outputs.labels << { x: 30, y: 60.from_top, text: "#{angle}, #{angle_radians}" }
end

distance link

Returns the distance between two points;

def tick args
  rect_1 ||= {
    x: 0,
    y: 0,
  }

  rect_2 ||= {
    x: 100,
    y: 100,
  }

  distance = args.geometry.distance rect_1, rect_2
  args.outputs.labels << {
    x: 30,
    y: 30.from_top,
    text: "#{distance}"
  }

  args.outputs.lines << {
    x: rect_1.x,
    y: rect_1.y,
    x2: rect_2.x,
    y2: rect_2.y
  }
end

point_inside_circle? link

Invocation variants:

Returns true if a point is inside of a circle defined as a center point and radius.

def tick args
  # define circle center
  args.state.circle_center ||= {
    x: 640,
    y: 360
  }

  # define circle radius
  args.state.circle_radius ||= 100

  # define point
  args.state.point_1 ||= {
    x: 100,
    y: 100
  }

  # allow point to be moved using keyboard
  args.state.point_1.x += args.inputs.left_right * 5
  args.state.point_1.y += args.inputs.up_down * 5

  # determine if point is inside of circle
  intersection = args.geometry.point_inside_circle? args.state.point_1,
                                                    args.state.circle_center,
                                                    args.state.circle_radius

  # render point as a square
  args.outputs.sprites << {
    x: args.state.point_1.x - 20,
    y: args.state.point_1.y - 20,
    w: 40,
    h: 40,
    path: "sprites/square/blue.png"
  }

  # if there is an intersection, render a red circle
  # otherwise render a blue circle
  if intersection
    args.outputs.sprites << {
      x: args.state.circle_center.x - args.state.circle_radius,
      y: args.state.circle_center.y - args.state.circle_radius,
      w: args.state.circle_radius * 2,
      h: args.state.circle_radius * 2,
      path: "sprites/circle/red.png",
      a: 128
    }
  else
    args.outputs.sprites << {
      x: args.state.circle_center.x - args.state.circle_radius,
      y: args.state.circle_center.y - args.state.circle_radius,
      w: args.state.circle_radius * 2,
      h: args.state.circle_radius * 2,
      path: "sprites/circle/blue.png",
      a: 128
    }
  end
end

center_inside_rect link

Invocation variants:

Given a target rect and a reference rect, the target rect is centered inside the reference rect (a new rect is returned).

def tick args
  rect_1 = {
    x: 0,
    y: 0,
    w: 100,
    h: 100
  }

  rect_2 = {
    x: 640 - 100,
    y: 360 - 100,
    w: 200,
    h: 200
  }

  centered_rect = args.geometry.center_inside_rect rect_1, rect_2
  # OR
  # centered_rect = rect_1.center_inside_rect rect_2

  args.outputs.solids << rect_1.merge(r: 255)
  args.outputs.solids << rect_2.merge(b: 255)
  args.outputs.solids << centered_rect.merge(g: 255)
end

ray_test link

Given a point and a line, ray_test returns one of the following symbols based on the location of the point relative to the line: :left, :right, :on

def tick args
  # create a point based off of the mouse location
  point = {
    x: args.inputs.mouse.x,
    y: args.inputs.mouse.y
  }

  # draw a line from the bottom left to the top right
  line = {
    x: 0,
    y: 0,
    x2: 1280,
    y2: 720
  }

  # perform ray_test on point and line
  ray = args.geometry.ray_test point, line

  # output the results of ray test at mouse location
  args.outputs.labels << {
    x: point.x,
    y: point.y + 25,
    text: "#{ray}",
    alignment_enum: 1,
    vertical_alignment_enum: 1,
  }

  # render line
  args.outputs.lines << line

  # render point
  args.outputs.solids << {
    x: point.x - 5,
    y: point.y - 5,
    w: 10,
    h: 10
  }
end

line_rise_run link

Given a line, this function returns a Hash with x and y keys representing a normalized representation of the rise and run of the line.

def tick args
  # draw a line from the bottom left to the top right
  line = {
    x: 0,
    y: 0,
    x2: 1280,
    y2: 720
  }

  # get rise and run of line
  rise_run = args.geometry.line_rise_run line

  # output the rise and run of line
  args.outputs.labels << {
    x: 640,
    y: 360,
    text: "#{rise_run}",
    alignment_enum: 1,
    vertical_alignment_enum: 1,
  }

  # render the line
  args.outputs.lines << line
end

rotate_point link

Given a point and an angle in degrees, a new point is returned that is rotated around the origin by the degrees amount. An optional third argument can be provided to rotate the angle around a point other than the origin.

def tick args
  args.state.rotate_amount ||= 0
  args.state.rotate_amount  += 1

  if args.state.rotate_amount >= 360
    args.state.rotate_amount = 0
  end

  point_1 = {
    x: 100,
    y: 100
  }

  # rotate point around 0, 0
  rotated_point_1 = args.geometry.rotate_point point_1,
                                               args.state.rotate_amount

  args.outputs.solids << {
    x: rotated_point_1.x - 5,
    y: rotated_point_1.y - 5,
    w: 10,
    h: 10
  }

  point_2 = {
    x: 640 + 100,
    y: 360 + 100
  }

  # rotate point around center screen
  rotated_point_2 = args.geometry.rotate_point point_2,
                                               args.state.rotate_amount,
                                               x: 640, y: 360

  args.outputs.solids << {
    x: rotated_point_2.x - 5,
    y: rotated_point_2.y - 5,
    w: 10,
    h: 10
  }
end

find_intersect_rect link

Given a rect and a collection of rects, find_intersect_rect returns the first rect that intersects with the the first parameter.

:anchor_x, and anchor_y is taken into consideration if the objects respond to these methods.

If you find yourself doing this:

collision = args.state.terrain.find { |t| t.intersect_rect? args.state.player }

Consider using find_intersect_rect instead (it's more descriptive and faster):

collision = args.geometry.find_intersect_rect args.state.player, args.state.terrain

find_all_intersect_rect link

Given a rect and a collection of rects, find_all_intersect_rect returns all rects that intersects with the the first parameter.

:anchor_x, and anchor_y is taken into consideration if the objects respond to these methods.

If you find yourself doing this:

collisions = args.state.terrain.find_all { |t| t.intersect_rect? args.state.player }

Consider using find_all_intersect_rect instead (it's more descriptive and faster):

collisions = args.geometry.find_all_intersect_rect args.state.player, args.state.terrain

find_intersect_rect_quad_tree link

This is a faster collision algorithm for determining if a rectangle intersects any rectangle in an array. In order to use find_intersect_rect_quad_tree, you must first generate a quad tree data structure using create_quad_tree. Use this function if find_intersect_rect isn't fast enough.

def tick args
  # create a player
  args.state.player ||= {
    x: 640 - 10,
    y: 360 - 10,
    w: 20,
    h: 20
  }

  # allow control of player movement using arrow keys
  args.state.player.x += args.inputs.left_right * 5
  args.state.player.y += args.inputs.up_down * 5

  # generate 40 random rectangles
  args.state.boxes ||= 40.map do
    {
      x: 1180 * rand + 50,
      y: 620 * rand + 50,
      w: 100,
      h: 100
    }
  end

  # generate a quad tree based off of rectangles.
  # the quad tree should only be generated once for
  # a given array of rectangles. if the rectangles
  # change, then the quad tree must be regenerated
  args.state.quad_tree ||= args.geometry.quad_tree_create args.state.boxes

  # use quad tree and find_intersect_rect_quad_tree to determine collision with player
  collision = args.geometry.find_intersect_rect_quad_tree args.state.player,
                                                          args.state.quad_tree

  # if there is a collision render a red box
  if collision
    args.outputs.solids << collision.merge(r: 255)
  end

  # render player as green
  args.outputs.solids << args.state.player.merge(g: 255)

  # render boxes as borders
  args.outputs.borders << args.state.boxes
end

create_quad_tree link

Generates a quad tree from an array of rectangles. See find_intersect_rect_quad_tree for usage.

args.audio link

Hash that contains audio sources that are playing.

Sounds that don't specify looping: true will be removed automatically from the hash after the playback ends. Looping sounds or sounds that should stop early must be removed manually.

When you assign a hash to an audio output, a :length key will be added to the hash on the following tick. This will tell you the duration of the audio file in seconds (float).

One-Time Sounds link

Here's how to play audio one-time (does not loop).

def tick args
  # play a one-time non-looping sound every second
  if (args.state.tick_count % 60) == 0
    args.audio[:coin] = { input: "sounds/coin.wav" }
    # OR
    args.outputs.sounds << "sounds/coin.wav"
  end
end

Looping Audio link

Here's how to play audio that loops (eg background music), and how to stop the sound.

def tick args
  if args.state.tick_count == 0
    args.audio[:bg_music] = { input: "sounds/bg-music.ogg", looping: true }
  end

  # stop sound if space key is pressed
  if args.inputs.keyboard.key_down.space
    args.audio[:bg_music] = nil
    # OR
    args.audio.delete :bg_music
  end
end

Setting Additional Audio Properties link

Here are additional properties that can be set.

def tick args
  # The values below (except for input of course) are the default values that apply if you don't
  # specify the value in the hash.
  args.audio[:my_audio] ||= {
    input: 'sound/boom.wav',  # file path relative to mygame directory
    gain:    1.0,             # Volume (float value 0.0 to 1.0)
    pitch:   1.0,             # Pitch of the sound (1.0 = original pitch)
    paused:  false,           # Set to true to pause the sound at the current playback position
    looping: true,            # Set to true to loop the sound/music until you stop it
    foobar:  :baz,            # additional keys/values can be safely added to help with context/game logic (ie metadata)
    x: 0.0, y: 0.0, z: 0.0    # Relative position to the listener, x, y, z from -1.0 to 1.0
  }
end

IMPORTANT: Please take note that gain and pitch must be given float values (eg gain: 1.0, not gain: 1 or game: 0).

Advanced Audio Manipulate (Crossfade) link

Take a look at the Audio Mixer sample app for a non-trival example of how to use these properties. The sample app is located within the DragonRuby zip file at ./samples/07_advanced_audio/01_audio_mixer.

Here's an example of crossfading two bg music tracks.

def tick args
  # start bg-1.ogg at the start
  if args.state.tick_count == 0
    args.audio[:bg_music] = { input: "sounds/bg-1.ogg", looping: true, gain: 0.0 }
  end

  # if space is pressed cross fade to new bg music
  if args.inputs.keyboard.key_down.space
    # get the current bg music and create a new audio entry that represents the crossfade
    current_bg_music = args.audio[:bg_music]

    # cross fade audio entry
    args.audio[:bg_music_fade] = {
      input:    current_bg_music[:input],
      looping:  true,
      gain:     current_bg_music[:gain],
      pitch:    current_bg_music[:pitch],
      paused:   false,
      playtime: current_bg_music[:playtime]
    }

    # replace the current playing background music (toggling between bg-1.ogg and bg-2.ogg)
    # set the gain/volume to 0.0 (this will be increased to 1.0 accross ticks)
    new_background_music = { looping: true, gain: 0.0 }

    # determine track to play (swap between bg-1 and bg-2)
    new_background_music[:input] = if current_bg_music.input == "sounds/bg-1.ogg"
                                     "sounds/bg-2.ogg"
                                   else
                                     "sounds/bg-2.ogg"
                                   end

    # bg music audio entry
    args.audio[:bg_music] = new_background_music
  end

  # process cross fade (happens every tick)
  # increase the volume of bg_music every tick until it's at 100%
  if args.audio[:bg_music] && args.audio[:bg_music].gain < 1.0
    # increase the gain 1% every tick until we are at 100%
    args.audio[:bg_music].gain += 0.01
    # clamp value to 1.0 max value
    args.audio[:bg_music].gain = 1.0 if args.audio[:bg_music].gain > 1.0
  end

  # decrease the volume of cross fade bg music until it's 0.0, then delete it
  if args.audio[:bg_music_fade] && args.audio[:bg_music_fade].gain > 0.0
    # decrease by 1% every frame
    args.audio[:bg_music_fade].gain -= 0.01
    # delete audio when it's at 0%
    if args.audio[:bg_music_fade].gain <= 0.0
      args.audio[:bg_music_fade] = nil
    end
  end
end

Audio encoding trouble shooting link

If audio doesn't seem to be working, try re-encoding it via ffmpeg:

# re-encode ogg
ffmpeg -i ./mygame/sounds/SOUND.ogg -ac 2 -b:a 160k -ar 44100 -acodec libvorbis ./mygame/sounds/SOUND-converted.ogg

# convert wav to ogg
ffmpeg -i ./mygame/sounds/SOUND.wav -ac 2 -b:a 160k -ar 44100 -acodec libvorbis ./mygame/sounds/SOUND-converted.ogg

Audio synthesis link

Instead of a path to an audio file you can specify an array [channels, sample_rate, sound_source] for input to procedurally generate sound. You do this by providing an array of float values between -1.0 and 1.0 that describe the waveform you want to play.

Sound source link

A sound source can be one of two things:

  • A Proc object that is called on demand to generate the next samples to play. Every call should generate enough samples for at least 0.1 to 0.5 seconds to get continuous playback without audio skips. The audio will continue playing endlessly until removed, so the looping option will have no effect.
  • An array of sample values that will be played back once. This is useful for procedurally generated one-off SFX. looping will work as expected

When you specify 2 for channels, then the generated sample array will be played back in an interleaved manner. The first element is the first sample for the left channel, the second element is the first sample for the right channel, the third element is the second sample for the left channel etc.

Example: link

def tick args
  sample_rate = 48000

  generate_sine_wave = lambda do
    frequency = 440.0 # A5
    samples_per_period = (sample_rate / frequency).ceil
    one_period = samples_per_period.map_with_index { |i|
      Math.sin((2 * Math::PI) * (i / samples_per_period))
    }
    one_period * frequency # Generate 1 second worth of sound
  end

  args.audio[:my_audio] ||= {
    input: [1, sample_rate, generate_sine_wave]
  }
end

args.easing link

This function will give you a float value between 0 and 1 that represents a percentage. You need to give the funcation a start_tick, current_tick, duration, and easing definitions.

This YouTube video is a fantastic introduction to easing functions: https://www.youtube.com/watch?v=mr5xkf6zSzk

Example link

This example shows how to fade in a label at frame 60 over two seconds (120 ticks). The :identity definition implies a linear fade: f(x) -> x.

def tick args
  fade_in_at   = 60
  current_tick = args.state.tick_count
  duration     = 120
  percentage   = args.easing.ease fade_in_at,
                                  current_tick,
                                  duration,
                                  :identity
  alpha = 255 * percentage
  args.outputs.labels << { x: 640,
                           y: 320, text: "#{percentage.to_sf}",
                           alignment_enum: 1,
                           a: alpha }
end

Easing Definitions link

There are a number of easing definitions availble to you:

:identity link

The easing definition for :identity is f(x) = x. For example, if start_tick is 0, current_tick is 50, and duration is 100, then args.easing.ease 0, 50, 100, :identity will return 0.5 (since tick 50 is half way between 0 and 100).

:flip link

The easing definition for :flip is f(x) = 1 - x. For example, if start_tick is 0, current_tick is 10, and duration is 100, then args.easing.ease 0, 10, 100, :flip will return 0.9 (since tick 10 means 100% - 10%).

:quad, :cube, :quart, :quint link

These are the power easing definitions. :quad is f(x) = x * x (x squared), :cube is f(x) = x * x * x (x cubed), etc.

The power easing definitions represent Smooth Start easing (the percentage changes slow at first and speeds up at the end).

Example

Here is an example of Smooth Start (the percentage changes slow at first and speeds up at the end).

def tick args
  start_tick   = 60
  current_tick = args.state.tick_count
  duration     = 120
  percentage   = args.easing.ease start_tick,
                                  current_tick,
                                  duration,
                                  :quad
  start_x      = 100
  end_x        = 1180
  distance_x   = end_x - start_x
  final_x      = start_x + (distance_x * percentage)

  start_y      = 100
  end_y        = 620
  distance_y   = end_y - start_y
  final_y      = start_y + (distance_y * percentage)

  args.outputs.labels << { x: final_x,
                           y: final_y,
                           text: "#{percentage.to_sf}",
                           alignment_enum: 1 }
end

Combining Easing Definitions link

The base easing definitions can be combined to create common easing functions.

Example

Here is an example of Smooth Stop (the percentage changes fast at first and slows down at the end).

def tick args
  start_tick   = 60
  current_tick = args.state.tick_count
  duration     = 120

  # :flip, :quad, :flip is Smooth Stop
  percentage   = args.easing.ease start_tick,
                                  current_tick,
                                  duration,
                                  :flip, :quad, :flip
  start_x      = 100
  end_x        = 1180
  distance_x   = end_x - start_x
  final_x      = start_x + (distance_x * percentage)

  start_y      = 100
  end_y        = 620
  distance_y   = end_y - start_y
  final_y      = start_y + (distance_y * percentage)

  args.outputs.labels << { x: final_x,
                           y: final_y,
                           text: "#{percentage.to_sf}",
                           alignment_enum: 1 }
end

Custom Easing Functions link

You can define your own easing functions by passing in a lambda as a definition or extending the Easing module.

Example - Using Lambdas

This easing function goes from 0 to 1 for the first half of the ease, then 1 to 0 for the second half of the ease.

def tick args
  fade_in_at    = 60
  current_tick  = args.state.tick_count
  duration      = 600
  easing_lambda = lambda do |percentage, start_tick, duration|
                    fx = percentage
                    if fx < 0.5
                      fx = percentage * 2
                    else
                      fx = 1 - (percentage - 0.5) * 2
                    end
                    fx
                  end

  percentage    = args.easing.ease fade_in_at,
                                   current_tick,
                                   duration,
                                   easing_lambda

  alpha = 255 * percentage
  args.outputs.labels << { x: 640,
                           y: 320,
                           a: alpha,
                           text: "#{percentage.to_sf}",
                           alignment_enum: 1 }
end

Example - Extending Easing Definitions

If you don't want to create a lambda, you can register an easing definition like so:

# 1. Extend the Easing module
module Easing
  def self.saw_tooth x
    if x < 0.5
      x * 2
    else
      1 - (x - 0.5) * 2
    end
  end
end

def tick args
  fade_in_at    = 60
  current_tick  = args.state.tick_count
  duration      = 600

  # 2. Reference easing definition by name
  percentage    = args.easing.ease fade_in_at,
                                   current_tick,
                                   duration,
                                   :saw_tooth

  alpha = 255 * percentage
  args.outputs.labels << { x: 640,
                           y: 320,
                           a: alpha,
                           text: "#{percentage.to_sf}",
                           alignment_enum: 1 }

end

Pixel Arrays link

A PixelArray object with a width, height and an Array of pixels which are hexadecimal color values in ABGR format.

You can create a pixel array like this:

w = 200
h = 100
args.pixel_array(:my_pixel_array).w = w
args.pixel_array(:my_pixel_array).h = h

You'll also need to fill the pixels with values, if they are nil, the array will render with the checkerboard texture. You can use #00000000 to fill with transparent pixels if desired.

gs.pixel_array(:my_pixel_array).pixels.fill #FF00FF00, 0, w * h

Note: To convert from rgb hex (like skyblue #87CEEB) to abgr hex, you split it in pairs pair (eg 87 CE EB) and reverse the order (eg EB CE 87) add join them again: #EBCE87. Then add the alpha component in front ie: FF for full opacity: #FFEBCE87.

You can draw it by using the symbol for :path

args.outputs.sprites << { x: 500, y: 300, w: 200, h: 100, path: :my_pixel_array) }

If you want access a specific x, y position, you can do it like this for a bottom-left coordinate system:

x = 150
y = 33
args.pixel_array(:my_pixel_array).pixels[(height - y) * width + x] = 0xFFFFFFFF

args.cvars link

Hash contains metadata pulled from the files under the ./metadata directory. To get the keys that are available type $args.cvars.keys in the Console. Here is an example of how to retrieve the game version number:

def tick args
  args.outputs.labels << {
    x: 640,
    y: 360,
    text: args.cvars["game_metadata.version"].value.to_s
  }
end

Each CVar has the following properties value, name, description, type, locked.

Outputs link

Outputs is how you render primitives to the screen. The minimal setup for rendering something to the screen is via a tick method defined in mygame/app/main.rb

def tick args
  args.outputs.solids     << [0, 0, 100, 100]
  args.outputs.sprites    << [100, 100, 100, 100, "sprites/square/blue.png"]
  args.outputs.labels     << [200, 200, "Hello World"]
  args.outputs.lines      << [300, 300, 400, 400]
end

Render Order link

Primitives are rendered first-in, first-out. The rendering order (sorted by bottom-most to top-most):

Solids link

Add primitives to this collection to render a solid to the screen.

Rendering a solid using an Array link

Creates a solid black rectangle located at 100, 100. 160 pixels wide and 90 pixels tall.

def tick args
  #                         X    Y  WIDTH  HEIGHT
  args.outputs.solids << [100, 100,   160,     90]
end

Rendering a solid using an Array with colors and alpha link

The value for the color and alpha is a number between 0 and 255. The alpha property is optional and will be set to 255 if not specified.

Creates a green solid rectangle with an opacity of 50%.

def tick args
  #                         X    Y  WIDTH  HEIGHT  RED  GREEN  BLUE  ALPHA
  args.outputs.solids << [100, 100,   160,     90,   0,   255,    0,   128]
end

Rendering a solid using a Hash link

If you want a more readable invocation. You can use the following hash to create a solid. Any parameters that are not specified will be given a default value. The keys of the hash can be provided in any order.

def tick args
  args.outputs.solids << {
    x:    0,
    y:    0,
    w:  100,
    h:  100,
    r:    0,
    g:  255,
    b:    0,
    a:  255
  }
end

Rendering a solid using a Class link

You can also create a class with solid properties and render it as a primitive. ALL properties must be on the class. *Additionally*, a method called primitive_marker must be defined on the class.

Here is an example:

# Create type with ALL solid properties AND primitive_marker
class Solid
  attr_accessor :x, :y, :w, :h, :r, :g, :b, :a, :anchor_x, :anchor_y

  def primitive_marker
    :solid # or :border
  end
end

# Inherit from type
class Square < Solid
  # constructor
  def initialize x, y, size
    self.x = x
    self.y = y
    self.w = size
    self.h = size
  end
end

def tick args
  # render solid/border
  args.outputs.solids  << Square.new(10, 10, 32)
end

Borders link

Add primitives to this collection to render an unfilled solid to the screen. Take a look at the documentation for Outputs#solids.

The only difference between the two primitives is where they are added.

Instead of using args.outputs.solids:

def tick args
  #                         X    Y  WIDTH  HEIGHT
  args.outputs.solids << [100, 100,   160,     90]
end

You have to use args.outputs.borders:

def tick args
  #                           X    Y  WIDTH  HEIGHT
  args.outputs.borders << [100, 100,   160,     90]
end

Sprites link

Add primitives to this collection to render a sprite to the screen.

Rendering a sprite using an Array link

Creates a sprite of a white circle located at 100, 100. 160 pixels wide and 90 pixels tall.

def tick args
  #                         X    Y   WIDTH   HEIGHT                      PATH
  args.outputs.sprites << [100, 100,   160,     90, "sprites/circle/white.png]
end

Rendering a sprite using an Array with colors and alpha link

The value for the color and alpha is a number between 0 and 255. The alpha property is optional and will be set to 255 if not specified.

Creates a green circle sprite with an opacity of 50%.

def tick args
  #                         X    Y  WIDTH  HEIGHT           PATH                ANGLE  ALPHA  RED  GREEN  BLUE
  args.outputs.sprites << [100, 100,  160,     90, "sprites/circle/white.png",     0,    128,   0,   255,    0]
end

Rendering a sprite using a Hash link

If you want a more readable invocation. You can use the following hash to create a sprite. Any parameters that are not specified will be given a default value. The keys of the hash can be provided in any order.

def tick args
  args.outputs.sprites << {
    x:                             0,
    y:                             0,
    w:                           100,
    h:                           100,
    path: "sprites/circle/white.png",
    angle:                         0,
    a:                           255,
    r:                             0,
    g:                           255,
    b:                             0
  }
end

Rendering a solid using a Class link

You can also create a class with solid/border properties and render it as a primitive. ALL properties must be on the class. *Additionally*, a method called primitive_marker must be defined on the class.

Here is an example:

# Create type with ALL sprite properties AND primitive_marker
class Sprite
  attr_accessor :x, :y, :w, :h, :path, :angle, :angle_anchor_x, :angle_anchor_y,  :tile_x, :tile_y, :tile_w, :tile_h, :source_x, :source_y, :source_w, :source_h, :flip_horizontally, :flip_vertically, :a, :r, :g, :b

  def primitive_marker
    :sprite
  end
end

# Inherit from type
class Circle < Sprite
# constructor
  def initialize x, y, size, path
    self.x = x
    self.y = y
    self.w = size
    self.h = size
    self.path = path
  end

  def serialize
    {x:self.x, y:self.y, w:self.w, h:self.h, path:self.path}
  end

  def inspect
    serialize.to_s
  end

  def to_s
    serialize.to_s
  end
end

def tick args
  # render circle sprite
  args.outputs.sprites  << Circle.new(10, 10, 32,"sprites/circle/white.png")
end

Labels link

Add primitives to this collection to render a label.

Rendering a label using an Array link

Labels represented as Arrays/Tuples:

def tick args
                         #        X         Y              TEXT   SIZE_ENUM
  args.outputs.labels << [175 + 150, 610 - 50, "Smaller label.",         0]
end

Here are all the properties that you can set with a label represented as an Array. It's recommended to move over to using Hashes once you've specified a lot of properties.

def tick args
  args.outputs.labels << [
    640,                   # X
    360,                   # Y
    "Hello world",         # TEXT
    0,                     # SIZE_ENUM
    1,                     # ALIGNMENT_ENUM
    0,                     # RED
    0,                     # GREEN
    0,                     # BLUE
    255,                   # ALPHA
    "fonts/coolfont.ttf"   # FONT
  ]
end
d

Rendering a label using a Hash link

def tick args
  args.outputs.labels << {
      x:                       200,
      y:                       550,
      text:                    "dragonruby",
      size_enum:               2,
      alignment_enum:          1, # 0 = left, 1 = center, 2 = right
      r:                       155,
      g:                       50,
      b:                       50,
      a:                       255,
      font:                    "fonts/manaspc.ttf",
      vertical_alignment_enum: 0  # 0 = bottom, 1 = center, 2 = top
  }
end

Screenshots link

Add a hash to this collection to take a screenshot and save as png file. The keys of the hash can be provided in any order.

def tick args
  args.outputs.screenshots << {
    x: 0, y: 0, w: 100, h: 100,    # Which portion of the screen should be captured
    path: 'screenshot.png',        # Output path of PNG file (inside game directory)
    r: 255, g: 255, b: 255, a: 0   # Optional chroma key
  }
end

Chroma key (Making a color transparent) link

By specifying the r, g, b and a keys of the hash you change the transparency of a color in the resulting PNG file. This can be useful if you want to create files with transparent background like spritesheets. The transparency of the color specified by r, g, b will be set to the transparency specified by a.

The example above sets the color white (255, 255, 255) as transparent.

Mouse link

The mouse is accessible via args.inputs.mouse:

def tick args
  # Rendering a label that shows the mouse's x and y position (via args.inputs.mouse).
  args.outputs.labels << [
    10,
    710,
    "The mouse's position is: #{args.inputs.mouse.x} #{args.inputs.mouse.y}."
  ]
end

The mouse has the following properties.

OpenEntity link

OpenEntity is accessible within the DragonRuby's top level tick function via the args.state property.

def tick args
  args.state.x ||= 100
  args.outputs.labels << [10, 710, "value of x is: #{args.state.x}."]
end

The primary benefit of using args.state as opposed to instance variables is that GTK::OpenEntity allows for arbitrary nesting of properties without the need to create intermediate objects.

For example:

def tick args
  # intermediate player object does not need to be created
  args.state.player.x ||= 100
  args.state.player.y ||= 100
  args.outputs.labels << [
    10,
    710,
    "player x, y is:#{args.state.player.x}, #{args.state.player.y}."
  ]
end

as_hash link

Returns a reference to the GTK::OpenEntity as a Hash. This property is useful when you want to treat args.state as a Hash and invoke methods such as Hash#each.

Example:

def tick args
  args.state.x ||= 100
  args.state.y ||= 100
  values = args.state
               .as_hash
               .map { |k, v| "#{k} #{v}" }

  args.outputs.labels << values.map.with_index do |v, i|
    [
      10,
      710 - (30 * i),
      v
    ]
  end
end

Array link

The Array class has been extend to provide methods that will help in common game development tasks. Array is one of the most powerful classes in Ruby and a very fundamental component of Game Toolkit.

map_2d link

Assuming the array is an array of arrays, Given a block, each 2D array index invoked against the block. A 2D array is a common way to store data/layout for a stage.

repl do
  stage = [
    [:enemy, :empty, :player],
    [:empty, :empty,  :empty],
    [:enemy, :empty,  :enemy],
  ]

  occupied_tiles = stage.map_2d do |row, col, tile|
    if tile == :empty
      nil
    else
      [row, col, tile]
    end
  end.reject_nil

  puts "Stage:"
  puts stage

  puts "Occupied Tiles"
  puts occupied_tiles
end

include_any? link

Given a collection of items, the function will return true if any of self's items exists in the collection of items passed in:

any_intersect_rect? link

Assuming the array contains objects that respond to left, right, top, bottom, this method returns true if any of the elements within the array intersect the object being passed in. You are given an optional parameter called tolerance which informs how close to the other rectangles the elements need to be for it to be considered intersecting.

The default tolerance is set to 0.1, which means that the primitives are not considered intersecting unless they are overlapping by more than 0.1.

repl do
  # Here is a player class that has position and implement
  # the ~attr_rect~ contract.
  class Player
    attr_rect
    attr_accessor :x, :y, :w, :h

    def initialize x, y, w, h
      @x = x
      @y = y
      @w = w
      @h = h
    end

    def serialize
      { x: @x, y: @y, w: @w, h: @h }
    end

    def inspect
      "#{serialize}"
    end

    def to_s
      "#{serialize}"
    end
  end

  # Here is a definition of two walls.
  walls = [
     [10, 10, 10, 10],
     { x: 20, y: 20, w: 10, h: 10 },
   ]

  # Display the walls.
  puts "Walls."
  puts walls
  puts ""

  # Check any_intersect_rect? on player
  player = Player.new 30, 20, 10, 10
  puts "Is Player #{player} touching wall?"
  puts (walls.any_intersect_rect? player)
  # => false
  # The value is false because of the default tolerance is 0.1.
  # The overlap of the player rect and any of the wall rects is
  # less than 0.1 (for those that intersect).
  puts ""

  player = Player.new 9, 10, 10, 10
  puts "Is Player #{player} touching wall?"
  puts (walls.any_intersect_rect? player)
  # => true
  puts ""
end

map link

The function given a block returns a new Enumerable of values.

Example of using Array#map in conjunction with args.state and args.outputs.sprites to render sprites to the screen.

def tick args
  # define the colors of the rainbow in ~args.state~
  # as an ~Array~ of ~Hash~es with :order and :name.
  # :order will be used to determine render location
  #  and :name will be used to determine sprite path.
  args.state.rainbow_colors ||= [
    { order: 0, name: :red    },
    { order: 1, name: :orange },
    { order: 2, name: :yellow },
    { order: 3, name: :green  },
    { order: 4, name: :blue   },
    { order: 5, name: :indigo },
    { order: 6, name: :violet },
  ]

  # render sprites diagonally to the screen
  # with a width and height of 50.
  args.outputs
      .sprites << args.state
                      .rainbow_colors
                      .map do |color| # <-- ~Array#map~ usage
                        [
                          color[:order] * 50,
                          color[:order] * 50,
                          50,
                          50,
                          "sprites/square-#{color[:name]}.png"
                        ]
                      end
end

each link

The function, given a block, invokes the block for each item in the Array. Array#each is synonymous to foreach constructs in other languages.

Example of using Array#each in conjunction with args.state and args.outputs.sprites to render sprites to the screen:

def tick args
  # define the colors of the rainbow in ~args.state~
  # as an ~Array~ of ~Hash~es with :order and :name.
  # :order will be used to determine render location
  #  and :name will be used to determine sprite path.
  args.state.rainbow_colors ||= [
    { order: 0, name: :red    },
    { order: 1, name: :orange },
    { order: 2, name: :yellow },
    { order: 3, name: :green  },
    { order: 4, name: :blue   },
    { order: 5, name: :indigo },
    { order: 6, name: :violet },
  ]

  # render sprites diagonally to the screen
  # with a width and height of 50.
  args.state
      .rainbow_colors
      .map do |color| # <-- ~Array#each~ usage
        args.outputs.sprites << [
          color[:order] * 50,
          color[:order] * 50,
          50,
          50,
          "sprites/square-#{color[:name]}.png"
        ]
      end
end

reject_nil link

Returns an Enumerable rejecting items that are nil, this is an alias for Array#compact:

repl do
  a = [1, nil, 4, false, :a]
  puts a.reject_nil
  # => [1, 4, false, :a]
  puts a.compact
  # => [1, 4, false, :a]
end

reject_false link

Returns an `Enumerable` rejecting items that are `nil` or `false`.

repl do
  a = [1, nil, 4, false, :a]
  puts a.reject_false
  # => [1, 4, :a]
end

product link

Returns all combinations of values between two arrays.

Here are some examples of using product. Paste the following code at the bottom of main.rb and save the file to see the results:

repl do
  a = [0, 1]
  puts a.product
  # => [[0, 0], [0, 1], [1, 0], [1, 1]]
end
repl do
  a = [ 0,  1]
  b = [:a, :b]
  puts a.product b
  # => [[0, :a], [0, :b], [1, :a], [1, :b]]
end

Numeric link

The Numeric class has been extend to provide methods that will help in common game development tasks.

frame_index link

This function is helpful for determining the index of frame-by-frame sprite animation. The numeric value self represents the moment the animation started.

frame_index takes three additional parameters:

frame_index will return nil if the time for the animation is out of bounds of the parameter specification.

Example using variables:

def tick args
  start_looping_at = 0
  number_of_sprites = 6
  number_of_frames_to_show_each_sprite = 4
  does_sprite_loop = true

  sprite_index =
    start_looping_at.frame_index number_of_sprites,
                                 number_of_frames_to_show_each_sprite,
                                 does_sprite_loop

  sprite_index ||= 0

  args.outputs.sprites << [
    640 - 50,
    360 - 50,
    100,
    100,
    "sprites/dragon-#{sprite_index}.png"
  ]
end

Example using named parameters. The named parameters version allows you to also specify a repeat_index which is useful if your animation has starting frames that shouldn't be considered when looped:

def tick args
  start_looping_at = 0

  sprite_index =
    start_looping_at.frame_index count: 6,
                                 hold_for: 4,
                                 repeat: true,
                                 repeat_index: 0,
                                 tick_count_override: args.state.tick_count

  sprite_index ||= 0

  args.outputs.sprites << [
    640 - 50,
    360 - 50,
    100,
    100,
    "sprites/dragon-#{sprite_index}.png"
  ]
end

The named parameter variant of frame_index is also available on Numeric:

def tick args
  sprite_index =
    Numeric.frame_index start_at: 0,
                        count: 6,
                        hold_for: 4,
                        repeat: true,
                        repeat_index: 0,
                        tick_count_override: args.state.tick_count

  sprite_index ||= 0

  args.outputs.sprites << [
    640 - 50,
    360 - 50,
    100,
    100,
    "sprites/dragon-#{sprite_index}.png"
  ]
end

elapsed_time link

For a given number, the elapsed frames since that number is returned. `Kernel.tick_count` is used to determine how many frames have elapsed. An optional numeric argument can be passed in which will be used instead of `Kernel.tick_count`.

Here is an example of how elapsed_time can be used.

def tick args
  args.state.last_click_at ||= 0

  # record when a mouse click occurs
  if args.inputs.mouse.click
    args.state.last_click_at = args.state.tick_count
  end

  # Use Numeric#elapsed_time to determine how long it's been
  if args.state.last_click_at.elapsed_time > 120
    args.outputs.labels << [10, 710, "It has been over 2 seconds since the mouse was clicked."]
  end
end

And here is an example where the override parameter is passed in:

def tick args
  args.state.last_click_at ||= 0

  # create a state variable that tracks time at half the speed of args.state.tick_count
  args.state.simulation_tick = args.state.tick_count.idiv 2

  # record when a mouse click occurs
  if args.inputs.mouse.click
    args.state.last_click_at = args.state.simulation_tick
  end

  # Use Numeric#elapsed_time to determine how long it's been
  if (args.state.last_click_at.elapsed_time args.state.simulation_tick) > 120
    args.outputs.labels << [10, 710, "It has been over 4 seconds since the mouse was clicked."]
  end
end

elapsed? link

Returns true if Numeric#elapsed_time is greater than the number. An optional parameter can be passed into elapsed? which is added to the number before evaluating whether elapsed? is true.

Example usage (no optional parameter):

def tick args
  args.state.box_queue ||= []

  if args.state.box_queue.empty?
    args.state.box_queue << { name: :red,
                              destroy_at: args.state.tick_count + 60 }
    args.state.box_queue << { name: :green,
                              destroy_at: args.state.tick_count + 60 }
    args.state.box_queue << { name: :blue,
                              destroy_at: args.state.tick_count + 120 }
  end

  boxes_to_destroy = args.state
                         .box_queue
                         .find_all { |b| b[:destroy_at].elapsed? }

  if !boxes_to_destroy.empty?
    puts "boxes to destroy count: #{boxes_to_destroy.length}"
  end

  boxes_to_destroy.each { |b| puts "box #{b} was elapsed? on #{args.state.tick_count}." }

  args.state.box_queue -= boxes_to_destroy
end

Example usage (with optional parameter):

def tick args
  args.state.box_queue ||= []

  if args.state.box_queue.empty?
    args.state.box_queue << { name: :red,
                              create_at: args.state.tick_count + 120,
                              lifespan: 60 }
    args.state.box_queue << { name: :green,
                              create_at: args.state.tick_count + 120,
                              lifespan: 60 }
    args.state.box_queue << { name: :blue,
                              create_at: args.state.tick_count + 120,
                              lifespan: 120 }
  end

  # lifespan is passed in as a parameter to ~elapsed?~
  boxes_to_destroy = args.state
                         .box_queue
                         .find_all { |b| b[:create_at].elapsed? b[:lifespan] }

  if !boxes_to_destroy.empty?
    puts "boxes to destroy count: #{boxes_to_destroy.length}"
  end

  boxes_to_destroy.each { |b| puts "box #{b} was elapsed? on #{args.state.tick_count}." }

  args.state.box_queue -= boxes_to_destroy
end

new? link

Returns true if Numeric#elapsed_time == 0. Essentially communicating that number is equal to the current frame.

Example usage:

def tick args
  args.state.box_queue ||= []

  if args.state.box_queue.empty?
    args.state.box_queue << { name: :red,
                              create_at: args.state.tick_count + 60 }
  end

  boxes_to_spawn_this_frame = args.state
                                  .box_queue
                                  .find_all { |b| b[:create_at].new? }

  boxes_to_spawn_this_frame.each { |b| puts "box #{b} was new? on #{args.state.tick_count}." }

  args.state.box_queue -= boxes_to_spawn_this_frame
end

Kernel link

Kernel in the DragonRuby Runtime has patches for how standard out is handled and also contains a unit of time in games called a tick.

tick_count link

Returns the current tick of the game. This value is reset if you call $gtk.reset.

global_tick_count link

Returns the current tick of the application from the point it was started. This value is never reset.