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
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
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.
!> NOTE: Rendering using an Array is "quick and dirty". It's generally recommended that you render using Hashes long term.
def tick args
args.outputs.sprites << [
640 - 50, # X
360 - 50, # Y
100, # W
100, # H
'sprites/square-blue.png' # PATH
]
end
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
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 class and manually define all sprite properties:
# Create type with ALL sprite properties AND primitive_marker
# you can manually define all sprite properties
class Sprite
attr_accessor :x, :y, :w, :h, :path, :angle, :a, :r, :g, :b,
:flip_horizontally, :flip_vertically,
:angle_anchor_x, :angle_anchor_y,
:blendmode_enum,
:anchor_x, :anchor_y
:tile_x, :tile_y, :tile_w, :tile_h,
:source_x, :source_y, :source_w, :source_h,
:source_x2, :source_y2, :source_x3, :source_y3, :x2, :y2, :x3, :y3,
def primitive_marker
:sprite
end
end
class BlueSquare < Sprite
def initialize(x: 0, y: 0, w: 0, h: 0k
@x = x
@y = y
@w = w
@h = 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
You can represent a sprite using the attr_sprite helper method:
class BlueSquare
# invoke the helper function at the class level for
# anything you want to represent as a sprite
attr_sprite
def initialize(x: 0, y: 0, w: 0, h: 0k
@x = x
@y = y
@w = w
@h = 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
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.
!> NOTE: Rendering using an Array is "quick and dirty". It's generally recommended that you render using Hashes long term.
Here is the minimum code:
def tick args
# X Y TEXT
args.outputs.labels << [640, 360, "I am a black label."]
end
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
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".
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
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
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
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
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
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:
- Windows: C:\Users[username]\AppData\Roaming\[devtitle]\[gametitle]
- MacOS: $HOME/Library/Application Support/[gametitle]
- Linux: $HOME/.local/share/[gametitle]
- HTML5: The data will be written to the browser's IndexedDB.
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/
!> IMPORTANT: File access functions are sandoxed and assume that the dragonruby binary lives alongside the game you are building. Do not expect file access 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.
- If you're using Arrays for your primitives (args.outputs.sprites << []), use Hash instead (args.outputs.sprites << { x: ... }).
- 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).
- Use .each instead of .map if you don't care about the return value.
- 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
- 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. - 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).
- Avoid deleting or adding to an array during iteration. Instead of:
args.state.fx_queue |fx|
fx.count_down ||= 255
fx.countdown -= 5
if fx.countdown < 0
args.state.fx_queue.delete fx
end
end
Do:
args.state.fx_queue |fx|
fx.count_down ||= 255
fx.countdown -= 5
end
args.state.fx_queue.reject! { |fx| fx.countdown < 0 }