Uploaded by pbs.anton

DragonRuby Zine Issue 1 - v1.4

advertisement
DragonRuby
Zine
Issue 01
Hello DragonRuby
Table of Contents
Introduction to DragonRuby Game Toolkit ................. 02
Interview with Amir Rajan ............................... 05
The Fundamentals of Ruby ................................ 07
Essential Tools for a Game Programmer ................... 10
Screen Coordinates Explained ............................ 11
Finding Assets .......................................... 12
Code: Snake ............................................. 13
Game Ideas: Think Small! ................................ 15
Expanse Core Interview .................................. 17
Five Days with the Dragon ............................... 21
Avalon Interview ........................................ 29
Outputs Explained ....................................... 32
Drag & Drop ............................................. 34
Handling Input .......................................... 35
White Labyrinth Interview ............................... 37
The Adventures of Dagron ................................ 39
Featured Games .......................................... 40
Ceph’s Snake ............................................ 41
Tweetcart ............................................... 43
Resources ............................................... 44
DragonRuby Zine Issue 01 | Spring 2023
A community publication
Art and words by Brett Chalupa unless otherwise noted
Edited by Akzidenz, kfischer, and Levi
The code contained in this issue targets DragonRuby Game Toolkit v4.
1
Introduction to DragonRuby Game Toolkit
DragonRuby Game Toolkit (DRGTK) is a game engine powered by
the Ruby programming language. It’s unlike any other engine I’ve
used—the game reloads your code changes on the fly and you can
build your game for any modern device. The engine is fast and
small, and it’s disturbingly productive. But most of all, making
games with DRGTK is fun.
If we want to make fun games, shouldn’t the process be fun
too?
DRGTK makes some helpful, safe assumptions for games built
with it: they will run at 60 frames per second and they will be
1280x720 in resolution. At first it may seem offputting to lose a bit
of control, but, like many aspects of DRGTK, it lets you focus on
making your game fun instead of worrying about those sorts of
things.
DRGTK is data-driven and has an extremely small number of
concepts you need to know. Instead of memorizing dozens of types,
you only need to know a few basic, intuitive data structures and
where to put them, and you’re making a game in no time.
The core of every DRGTK is the tick method. It gets run 60
times every second. It’s also known as the game loop.
2
Similar to how screens on electronic devices refresh at a regular
rate (so fast that you don’t even perceive it), DragonRuby GTK
games refresh at a regular rate. It’s really helpful to start thinking in
terms of the game loop because it influences so much about game
programming, from animation speed to timers.
Let’s define a simple tick method:
def tick(args)
args.outputs.labels << { x: 120, y: 120,
text: "Hello Dragon!" }
end
That’s how simple it is to show some text on the screen with
DragonRuby Game Toolkit. 60 times every second that tick method
runs and outputs text to the screen.
Displaying an image is just as easy:
def tick(args)
args.outputs.sprites << { x: 120, y: 280, w: 100,
h: 80, path: 'sprites/misc/dragon-0.png' }
end
3
If we want to make our dragon fly around, all we need to do is
keep track of its position, check for arrow key presses, and move the
sprite around:
def tick(args)
args.state.dragon ||= { x: 120, y: 280, w: 100,
h: 80, path: 'sprites/misc/dragon-0.png' }
speed = 8
if args.inputs.up
args.state.dragon.y += speed
elsif args.inputs.down
args.state.dragon.y -= speed
end
if args.inputs.left
args.state.dragon.x -= speed
elsif args.inputs.right
args.state.dragon.x += speed
end
args.outputs.sprites << args.state.dragon
end
To get an image moving around the screen so quickly with so
few lines is really quite a feat! Try changing around speed and
seeing how that feels. Tuning values to feel just right is part of the
fun of making games.
That’s just a quick taste of a simple program that’s starting to
look like a game. Keep on exploring and learning DragonRuby Game
Toolkit to discover the joy that awaits when making games with it. It
truly doesn’t get that much more complicated.
Continue your flying lesson at book.dragonriders.community.
4
Interview with Amir Rajan
Amir is the creator of DragonRuby and a renowned game developer.
He’s known for his games A Dark Room, The Ensign, A Car That
Turns, and more. He was gracious enough to conduct an interview for
the first issue of the zine.
What were your motivations with starting DRGTK?
Between 2013 and 2018, I released three commercial games on
the Apple App Store, one on the Google Play Store, and was starting
on a port of one of the games to the Nintendo Switch. At that point I
noticed that my ability to release new games was being hindered by
upkeep of my existing games (because of mobile and console
release cycles). I needed to address the challenges of this upkeep
related to cross-platform distribution. DragonRuby Game Toolkit is
the solution to this problem. One codebase with the ability to
distribute to all platforms trivially.
What have some of the biggest surprises so far been with
launching and sharing the engine?
The biggest surprise by far has been how incredible the
DragonRuby community is. People who join our Discord server often
say that we have the most friendly and welcoming server they've ever
joined.
Do you have any long term goals or visions for the engine?
Take over the world, obviously.
On a more serious note, I want DragonRuby Game Toolkit to be
the engine that helps indie game developers painlessly release
cross-platform commercial games without having to make platformspecific tweaks to get the game playable. Indies don't have the
human or monetary capital to deal with compatability issues
(especially not in the eleventh hour/just before releasing to the
public). It's criminal that other engines impose these burdens on
game devs.
What are some common pitfalls you see indie developers
falling into, and how can they avoid them?
Gosh there are too many to list. Here are the most important
ones:
5
1. When starting out, prototype your core game loop that sets it
apart from other games. Be able to communicate this "hook" within
the first 20 seconds of your game.
2. Target hyperniche groups and avoid competing in generic,
saturated markets.
3. Build and ship a game within a 3-month time frame (stop
spending years building your game). Monetize through downloadable
content that expands upon the initial game. Market to your target
niche group throughout the development process.
4. Your game trailer should get to the hook within 5 seconds.
5. Your game should get first time players into the action within
20 seconds (see the first bullet point).
6. Target all platforms from the start. DragonRuby GTK lets you
easily ship your games for web, PC, and mobile from any operating
system.
Can you share what you’ve been working on lately? How’s
it going?
The top initiatives for 2023 are turnkey distribution to Steam
and Nintendo Switch Readiness Wizard (which will help you make
sure your game will be approved by Nintendo if you get access to
their platform. They have strict requirements for what your game
must support).
Find out more about Amir’s games at amirrajan.net and DragonRuby
at dragonruby.org.
Art by Akzidenz
6
The Fundamentals of Ruby
Writing code is the process of telling the computer what to do. You
issue commands to the computer, and it runs those commands.
Commands in Ruby are called “methods.” The rules and order of the
code you type is known as the syntax. If you get the syntax wrong, the
computer won’t listen to your command. If you want to tell the
computer to output some text in Ruby, you use the puts method:
puts "I'm so hungry!"
You’ll need to keep track of information in your programs, like
numbers and strings of characters that form words. Data gets stored
in variables, which you can then later access and manipulate based
on a variety of factors, like time passing or player input.
favorite_food = "apples"
puts "I'm so hungry for #{favorite_food}"
That code assigns the set of characters that form the word
“apples” to a variable called favorite_food. You can then use that
variable you created in your code and combine it with the statement
from before.
Let’s say there’s a hunger_level that tracks how hungry
someone is, and they only eat if their hunger_level is high enough.
We’d use conditional statements for that.
hunger_level = 5
if hunger_level > 12
puts "collapses"
elsif hunger_level > 8
puts "eats " + favorite_food
else
puts "stomach grumbles"
end
7
You can use a collection that’s known as an array to keep track
of multiple pieces of data. Creating an array entails putting data
separated by commas inbetween two square brackets:
levels = ["cave", "field"]
Array values are accessed via their index. This is a number that
refers to the value’s position in the array, counting from zero. So to
check what the second level is ("field"), you’d do:
levels[1]
You’ll often need to loop through the elements of an array,
which you can do with:
levels.each do |level|
puts level
end
Another common data structure is known as a hash. It contains
keys that map to values, which you can easily access. With an array,
you need to know an element’s position, which isn’t easy to
remember. With a hash, you just need to know the key. You access a
value with brackets like an array, but you use the key instead of the
index.
player = {
name: "Francis",
species: "Dragon",
hunger_level: 8
}
puts player[:name]
We’ve been modeling data in our code and calling the puts
method, but let’s write our own method. Methods are the foundation
of organizing code into reusable pieces. Methods in Ruby start with
8
the def keyword and finish with the end keyword.
Here’s a simple method:
def eat
player[:hunger_level] = 0
puts "no longer hungry"
end
Then you call the eat method just like we did with puts:
if player[:hunger_level] > 12
puts "collapses"
elsif player[:hunger_level] > 8
eat
else
puts "stomach grumbles"
end
Methods also accept arguments, known as parameters. For
puts, we’re passing in a string parameter to tell our program what to
output. Let’s have our eat method take a parameter for the amount
of energy the food gives our program when it eats.
def eat(energy)
player[:hunger_level] -= energy
if player[:hunger_level] <= 0
puts "no longer hungry"
end
end
If we had a food variable that’s a hash with an energy key, our
revised method would be called with: eat(food[:energy])
Those are some of the fundamental building blocks of Ruby.
Coding gets easier the more you work at it like any other skill. Follow
along with the examples in this zine, the DragonRuby GTK docs,
search online, and ask for help when you need it.
9
Essential Tools for a Game Programmer
Coding games requires some special programs and tools. Here are
the top five you’ll need to make awesome games.
1. Text Editor — You write code in a specialized editor different
from a word processor. Check out Visual Studio Code, Notepad++, or
Sublime Text.
2. Image Editor — No matter how simplistic the graphics in your
game are, you’ll need a program to adjust and modify them to be just
right. Check out Krita, Aseprite, or GIMP.
3. Sound Tool — Your game will likely have music and sound effects,
so a tool to edit sound files is needed. Check out Audacity,
GarageBand, jsfxr, LMMS, Ardour, Sunbox, Nanoloop, Hydrogen,
ChipTone, or 1BitDragon.
4. Pen and Paper — When working out problems, doodling, and
capturing notes, your trusty old pen and paper will treat you well!
5. Gamepad — A lot of players use controllers, so having one hooked
up to your computer while you’re working on your game can be really
helpful for testing.
10
Screen Coordinates Explained
When making 2D games, the images that get displayed (called
sprites) have an x and y position on the coordinate plane. This
represents where they’re drawn on the screen.
Much like charts, the x axis starts on the left and the y axis
starts on the bottom. A sprite at x: 0, y: 0 would be in the lower-left
of the screen. A sprite at x: 640, y: 360 would be near the center of
the screen.
A sprite’s x axis starts on the left and the y axis on the bottom,
just like the screen does. As a sprite moves around on the screen,
its x and y positions are changing.
11
Finding Assets
Do you find yourself wanting to make games but saying things like,
“I can’t make art!” or “What even is music?” Don’t fret! The internet
is full of helpful resources that will help make your games extra
special if you don’t want to make the sprites, music, and sound
effects in your game.
You’ll be able to find art and music in just about any style you
can imagine, from retro pixel art to vector graphics. While many
artists generously release their assets for free, you can pay for them
too. Consider donating or buying assets to support those artists!
Here are some great resources to help you out:
opengameart.org
itch.io/game-assets
kenney.nl
Browse around and see what you find. You might even get
inspired with an idea for a game!
12
Code: Snake
Code by Brett Chalupa & cromukent
My mother had a Nokia cellphone with Snake on it, and I’d play it during
car rides. I love the simplicity and fast-paced action of it. Here’s the code
for making your own version in DragonRuby GTK.
TILE_SIZE = 80
def tick(args)
args.state.game_over ||= false
args.state.parts ||= []
args.state.head ||= {
x: TILE_SIZE * 5, y: TILE_SIZE * 4, new_direction: :up,
w: TILE_SIZE, h: TILE_SIZE, r: 120, g: 220, b: 120
}
args.state.apple ||= spawn_apple(args)
head = args.state.head
args.outputs.labels << { x: 20, y: 700,
text: "Score: #{args.state.parts.length}",
alignment_enum: 0, size_enum: 8 }
unless args.state.game_over
if args.state.tick_count % 12 == 0
prev_pos = [head.x, head.y]
head.direction = head.new_direction
case head.direction
when :up
head.y += TILE_SIZE
when :down
head.y -= TILE_SIZE
when :left
head.x -= TILE_SIZE
when :right
head.x += TILE_SIZE
end
args.state.parts.each.with_index do |p, i|
next_prev_pos = [p.x, p.y]
p.x, p.y = prev_pos
prev_pos = next_prev_pos
end
if args.state.parts.any? { |p| head.intersect_rect?(p) }
args.state.game_over = true
end
end
if head.left >= args.grid.right
13
head.x = args.grid.left
elsif head.right <= args.grid.left
head.x = args.grid.right - head.w
elsif head.bottom >= args.grid.top
head.y = args.grid.bottom
elsif head.top <= args.grid.bottom
head.y = args.grid.top - head.h
end
if head.direction ==
head.new_direction
head.new_direction
else
head.new_direction
head.new_direction
end
:up || head.direction == :down
= :right if args.inputs.right
= :left if args.inputs.left
= :up if args.inputs.up
= :down if args.inputs.down
if head.intersect_rect?(args.state.apple)
args.state.parts << head.clone.merge({ r: 60, b: 34 })
args.state.apple = spawn_apple(args)
end
else
args.outputs.labels << [
{ x: args.grid.w / 2, y: 500,
text: "Game Over", alignment_enum: 1, size_enum: 12 },
{ x: args.grid.w / 2, y: 360, text: "Press SPACE to Restart",
alignment_enum: 1, size_enum: 6 }
]
if args.inputs.keyboard.key_down.space
$gtk.reset
end
end
bg = { x: 0, y: 0, w: args.grid.w, h: args.grid.h,
r: 166, g: 217, b: 244 }
args.outputs.solids << [bg, args.state.parts, args.state.apple,
args.state.head]
end
def spawn_apple(args)
{ x: rand(args.grid.w / TILE_SIZE) * TILE_SIZE,
y: rand(args.grid.h / TILE_SIZE) * TILE_SIZE,
w: TILE_SIZE, h: TILE_SIZE, r: 200, g: 40, b: 40 }
end
14
Game Ideas: Think Small!
When you’re getting started making games, it’s easy to bite off more
than you can chew. Maybe you want to make an online real-time
strategy game or a huge action adventure game. New game makers
often have been thinking about their dream game for years!
But you have to start small. If you want to run a marathon, you
wouldn’t go out and try to run 26.2 miles. You start with shorter
distances and build up your stamina, often times with a detailed
training plan. While there’s not a scientific training plan to go from
“I’ve never made a game before” to “I’ve made my dream game,”
there’s certainly a mindset that will help you get there.
You have to start small. Smaller than you’re probably thinking.
I’m talking like Pong, Snake, Breakout levels of simple. Arcade
games from decades ago are great candidates for your first games.
Try to adapt physical games like Tic-Tac-Toe, Klondike Solitaire, or
Checkers. Even the simplest games contain a wealth of game
programming concepts that will help you become a better
developer. Games with simple rules and not a lot of graphics will be
easier to make at the start.
As you get better at making games, try to take your dream
game or a game you love and distill it down to its simplest form. Ask
yourself: “What can I take away from this while still retaining what I
love?” If you’re a die-hard fan of Japanese role-playing games like
Dragon Quest, what if you removed the grinding, maps, and huge
story and instead made a thirty-minute long game with simple
combat and a boss fight at the end? Even that’s probably too large
of an idea for your first few games, but that mindset of thinking
small and whittling down will help you stay grounded.
The most important thing is that you finish your games! No
game is too small, too trivial. Make it, finish it, and share it in the
DragonRuby GTK Discord. We’d all love to see it. And then when
you’re ready to start your next game, make it a little bit bigger and
more complex. Before you know it’ll you’ll be ready for taking on that
dream game!
15
Screenshots from some of my earliest games. Simplistic but
foundational!
16
Expanse Core Interview with Owen Butler
I’ve been eagerly following along with Owen Butler’s impressive
progress of Expanse Core, his sci-fi Vampire Survivors’ inspired
game built with DRGTK. I asked him a few questions about how it’s
been going!
What’s the premise and scope of Expanse Core?
It’s heavily and shamelessly inspired by Vampire Survivors. I
think the first Git repo may have been called Space Survivors, but I
quickly changed it to something that I could build a story
around: Expanse Core. In the game, you are drifting through some
nameless space (the expanse) and get attacked by enemies. The
enemies drop upgrade cores, which level you up. Expanse Core!
Mechanically it shares many things in common with this
genre of game: run-based, waves of enemies, level-ups, and random
choice of weapons and power-ups as you level up. This leads to
unique builds using combinations of weapons and power-ups.
Scope-wise, I'd like to ensure that the game has all the
trappings normally seen in the genre, then add a little extra in the
form of an open world. In most games in this genre, you choose the
difficulty of your run in menus. I want to see if I can have zones of
the expanse (space) that are more difficult, and introduce neat ways
to travel quickly between them. This will make choosing the
difficulty of your run be part of the game's world rather than just a
menu option.
What’s your experience with game-making? Is Expanse
Core your first game of this size?
Expanse Core is my largest game yet. I've noodled around
with making small prototypes and experiments for many many years.
I used QBasic back in the day to make small experiments
and games. C with mode 13h graphics in DOS, C and C++ with SDL
to create small shooters influenced by 1942 and Raiden. For a while
I worked on a Java-based 2D game engine, which used OpenGL
through JOGL.
The repeated pattern I have fallen into in the past is enjoying
building the utilities and game engine so much that I never actually
finished a game. There's always a tweak or performance thing to
17
play with. I don't regret that, it's always fun!
I've not fallen into that trap with DRGTK.
What are some of your favorite aspects of using
DragonRuby GTK with this project?
DragonRuby GTK has an interface at a very pleasing level of
abstraction. It's low-level enough that the part of me that likes to
tinker is still satisfied; you still have to build some structure. At the
same time, it's high level enough that all the normal things I'd
normally tinker with (drawing to the screen, scaling, rotating, sound)
are already done. So, I'm naturally spending more time on actually
making a game. This is great!
Another aspect that I appreciate is how easy it makes it to
publish cross-platform builds to itch.io. It's a huge barrier to entry
that is melted away with the dragonruby-publish command-line tool.
Plus, the community is enthusiastic, helpful, and kind.
Did you use Ruby before DRGTK?
Not in earnest. I've worked as a software engineer using C,
C++, Java, and other languages in my career. I've come across
plenty of Ruby, but before DragonRuby GTK I'd never really written a
Ruby program that involved more than one class. I've used Ruby as
glue, or as an alternative when bash scripts get too big.
18
What tools do you use aside from DRGTK and what OS
do you work on?
I use a mix of Mac and Windows, depending on where I am.
GitHub for moving between them. Visual Studio Code for editing
(but with vim keybindings of course). Palantir for monitoring the
state of the game.
I've used a combo of tools to create sprites: ffmpeg to output
stock video (explosions) to frames, GIMP for editing images,
TexturePacker for creating sprite sheets, PixelFX Designer for
creating explosions, and more. I haven't even gotten to making
music for the game yet, but I plan to compose the music in
Cakewalk.
I found DRGTK through itch.io's Bundle for Racial Justice and
Equality. It introduced me to itch.io and the wealth of assets that are
available there and on other asset sites. There's a bonus thing I like
about DR: I like that it's close to the itch.io community. It's great
supporting other game developers by purchasing and using asset
packs.
What challenges have you faced with development?
For me, it's time. I have a full-time job that is not game
development and a young family. For the spare time that I do have, I
have other hobbies too! I guess that's another aspect that is great
about DragonRuby, you can get a great deal done in a small amount
19
of time.
What keeps you dedicated to continued development
of Expanse Core?
I've always experimented with games, so I guess it's a hobby. I
enjoy the journey and creative outlet. As long as that journey is still
enjoyable and I'm able to get creative with this game, I'll keep
plugging away at it.
Play in-progress builds and follow along with Expanse Core at
owenbutler.itch.io/expanse-core
Don’t Forget to Back Up
Your Game’s Source Code!
Art by Juniper
20
Five Days with the Dragon
Xed
Greetings, adventurer! I understand you are about to visit the dragon.
I have counseled with it for over 900 days. Allow me to advise you on
your journey.
Day 1
You enter a luminescent cave. The dragon's burrow resonates
with magical power. The dragon appears before you. You beckon to it,
"I seek the power of the Dragon Rider.”
Nodding, the dragon says, “Very well. You must prove yourself
worthy.”
“I am ready.”
“First: download my power.”
“Dragon, I have already done this. I have the latest version.”
The dragon smiles, “It’s okay if you have an older version.”
It continues, “Second: to harness my power you must choose
an artificer, to scribe the glyphs of my language.”
“Dragon, I know not where to begin.”
“You can choose free text editors like Notepad++, Nautilus, or
Visual Studio Code. There are also more powerful artificers such as
Emacs and Vim.”
“Dragon, there is no IDE?”
The dragon glares at you, balefully.
“Forgive me. I will find an artificer.”
“Lastly: answer these questions—the Dragon Riders will make
you answer them, so you’d better think about them now...
“What is your favorite game?
“What is your favorite hobby?
“Return to me when you have performed these tasks.”
Day 2
You return the next day. Emacs’ power was overwhelming.
Notepad++ will suffice, you tell yourself.
“Dragon, I am ready. My favorite game is Undertale, and I like
21
to read in my spare time.”
“A worthy response: this will please the Dragon Riders. They
will make an offering of sample app knowledge when you join the
Discord. We will begin the lesson.”
The dragon casts a spell. Glyphs appear in the air:
def tick(args)
args.outputs.sprites << [460, 470, 128, 101,
'dragonruby.png']
args.outputs.sprites << [610, 470, 128, 101,
'dragonruby.png', args.state.tick_count % 360]
args.outputs.sprites << [760, 470, 128, 101,
'dragonruby.png', 0, args.state.tick_count % 255]
end
3 images of rubies appear in front of you. 1 is still, 1 spins, 1
flashes.
“This is the first lesson: all of its secrets are reavealed in
samples/01_rendering_basics/04_sprites. To understand the
first lesson, you must:
•
•
•
Understand tick: it happens every frame.
Understand args: it is the root of all of my power.
Understand outputs: everything you share will pass through
them.
Understand Array: it is a gateway to larger constructs.
Understand modulo: this operator will help you cycle
numbers.”
“Dragon, I need math?”
“Yes. You’ll regret all of the slacking off you did in those
classes. Now go…”
Day 3
In the burrow, on the third day, the dragon says, “Are you
ready for the next lesson?”
•
•
“Yes. I am ready, I understand tick, args, outputs, Arrays,
22
and modulo.”
“Bah. You understand nothing of args! Its power is
LIMITLESS! Behold.”
The dragon casts a spell. Glyphs appear in the air:
def tick(args)
# create a player and set default values
# for the player's x, y, w (width), and h (height)
args.state.player.x ||= 100
args.state.player.y ||= 100
args.state.player.w ||= 50
args.state.player.h ||= 50
# render the player to the screen
args.outputs.sprites << {
x: args.state.player.x,
y: args.state.player.y,
w: args.state.player.w,
h: args.state.player.h,
path: 'sprites/square/green.png'
}
# move the player around using the keyboard
if args.inputs.up
args.state.player.y += 10
elsif args.inputs.down
args.state.player.y -= 10
end
if args.inputs.left
args.state.player.x -= 10
elsif args.inputs.right
args.state.player.x += 10
end
end
An adventurer, in the form of a green square, appears. To
your amazement, it responds to both the keyboard and the
controller: darting about the magical play-area as you command it.
“I had no idea,” you gasp.
23
“This is samples/02_input_basics/01_moving_a_sprite,” says
the dragon. “You have seen outputs.sprites and the Array. But now
you witness outputs.sprites with a Hash.”
To your delight, you don’t need to remember the order of the
arguments anymore—they are all labeled with Ruby symbols. What
sorcery is this?
“The Hash and Array are of Ruby. You must learn their power.”
You acknowledge, “Yes, dragon. I will.”
“I also offer you args.inputs and args.state. Use
args.inputs to learn of the player’s desire. Use args.state to
remember things.”
You ask, “Dragon, what is ||=?”
“||= is a conditional assignment operator. Think of it this way:
‘if args.state.player.x does not have a value, give it this value.’”
You inquire, “Dragon, what are += and -=?”
“Dragons do not waste time. We do not want to write
args.state.player.x = args.state.player.x + 10. We could,
but it takes up more space.”
Another question from you, “Where did player come from?
Does player always live in args.state?”
“No. When I asked args.state for a player, args.state created it
for me. This is OpenEntity. It is of GTK.”
“How can I know what is of Ruby and what is of GTK?”
The dragon gestures with tilde (~). A dark shroud covers the
play area. “This is the console.”
The -> symbol beckons input. The dragon obliges, entering
gtk.args.state.player.class.name.
-> gtk.args.state.player.class.name
=> GTK::OpenEntity
“We will not talk more of OpenEntity for today. For now, you
may think of it as a Hash.”
24
You must understand these things:
•
Understand args.state: we will store game state information
in it.
•
Understand args.inputs.up, down, left, and right: we will
use them to understand our player's intention.
•
Understand ||=: we will use it to initialize data.
Understand += and -=: we will use them to add and subtract
values from numbers.
• Understand that Hash and OpenEntity are your allies: we can
store complex information in them.
• Understand the console: we will use it to glean information
from Ruby and GTK.
Day 4
The dragon turns its head as you approach, "Excellent. You
have returned. After today's lesson, there is no turning back; you will
know enough to write a game."
Surprised, you ask, "Am I ready for such an undertaking?"
“Only you can answer this question. On to the lesson.”
The dragon casts a spell. Glyphs appear in the air:
•
def tick(args)
# create player+spikes and set default values
args.state.player ||= {
x: 100, y: 100, w: 50, h: 50,
path: 'sprites/square/green.png' }
args.state.spikes ||= {
x: 200, y: 200, w: 50, h: 50,
path: 'sprites/square/red.png' }
[args.state.spikes, args.state.player].each do |e|
args.outputs.sprites << { x: e.x,
y: e.y,
w: e.w,
h: e.h,
path: e.path }
end
25
# move the player around using the keyboard
if args.inputs.up
args.state.player.y += 10
elsif args.inputs.down
args.state.player.y -= 10
end
if args.inputs.left
args.state.player.x -= 10
elsif args.inputs.right
args.state.player.x += 10
end
# check to see if the player is touching the spikes
every 10 ticks,
# + play a sound if they are and the sound is not
already playing
if args.tick_count % 10 &&
args.state.player.intersect_rect?(args.state.spikes)
unless args.audio.key?(:ouch)
args.audio[:ouch] = { input: 'sounds/ouch.wav' }
end
end
end
The adventurer, in the form of a green square, appears again.
Spikes, in the form of a red square, also appear. When the player
moves into the spikes, you can hear their anguish.
The dragon intones, “I created this spell from the last one.
You will not find it in the grimoire of sample apps.”
“Dragon, are the sample apps not sacred?”
“THEY ARE NOT. You would be a fool not to bend them to
your own designs!” the dragon chortles.
It gazes at you. “Read the spell back to me, describe what is
happening.”
You focus, divining the meaning of the glyphs. “First, we place
Hashes inside args.state: one for the player and one for the spikes.”
26
“Correct.”
“Next, we create an Array with the spikes and the player. But
I do not know each.”
“What do you think each does?” asks the dragon.
“I guess that it will do something for each item in the Array,”
you surmise.
“Correct. Go on...”
“For each item in the array, now referred to as ‘e’, we create a
sprite from their Hash.”
“Very good.”
“Then we do what we did last time to move the player.
Dragon, I do not know args.tick_count, args.audio, or
intersect_rect?.”
“They are of GTK,” the dragon says. “args.tick_count is a
number that increases every tick. Every time tick happens,
tick_count goes up by 1. It starts at 0. tick happens 60 times
each second.
“intersect_rect? allows us to see if two rectangles
intersect. We can test anything that has x/y/w/h attributes. This
comes from GTK::Geometry, which is included in Hash and
OpenEntity.
“args.audio is a Hash. when we want to hear things, we
place Hashes with audio metadata in it. In this spell, we test the
args.audio Hash to see if :ouch is already in there, using
args.audio.key?(:ouch)”
The dragon concludes, “You must understand these things:
27
•
Understand Array’s each method: you will traverse collections
with it.
•
Understand GTK::Geometry.intersect_rect?: it is the
basis for collision testing.
•
Understand args.audio: the player will hear us with it.
•
Understand args.tick_count: we can perceive time with its
value.
Understand Hash.key?: we can test for keys in a Hash with it.
Day 5
You enter the luminescent cave for the fifth time. The
dragon’s burrow resonates with magical power.
“Dragon, I return. What will I learn today?”
“Today you test your newly acquired powers, for I bestow upon
you this quest: harness my power to create and ship a game.”
“SHIP a game?!”
“You will write a game, and you will publish it to itch.io—for
this is the way of the Dragon Rider,” the dragon commands.
“Return to me when you have shipped a game. Shed your
fears: the Dragon Riders will aid you. Share your triumphs and
tribulations with them. They will revel in your victories and aid you
when you are in need. Off with you. Make me proud.”
•
You are awaited in the Discord (discord.dragonruby.org). Join us,
Dragon Rider!
28
Avalon Interview with 68K
I look forward to playing James’ builds of Avalon, his turn-based
dungeon crawler, every time he posts an update. It’s inspiring to see
someone taking on such an ambitious project. I asked James a few
questions about how development has been going.
What are some of the inspirations for Avalon? What’s the
scope of the project?
Twenty-five years ago, I used to play all kinds of shareware
titles on Commodore Amiga. I remember one game was a dungeon
explorer with Avalon in the title. The game was unfinished, and it
didn’t have much to do, but it always stuck in my mind as the first
game I played that promised thrilling fantasy adventure with a
town<->dungeon gameplay loop. I thought it would be fun to try
working on the game that fired my imagination as a young teenager.
(Side note: Despite Amiga software being heavily archived and
preserved, I can no longer find that game!)
The other inspiration is the Might and Magic games—
specifically Might and Magic 6. That game is technically modest,
but, more than anything else I’ve played before or since, it delivered
what I expected a fantasy RPG to be: barely surviving in a dungeon,
then using the meagre gold you earned to buy basic equipment and
ale in the tavern, before taking stock of your situation to decide
where to go next.
The scope of Avalon is to be a dungeon-exploring RPG with
main quest progression that will take around 10-15 hours to
complete. It'll be quite easy once the game’s systems are
understood (but not before). That way the player will have fun
learning the game and enjoy the satisfaction of becoming powerful
via their own decisions.
What’s your experience with game making? Is Avalon
your first game of this size?
I previously released games on Xbox Live Indie Games and
have a few months professional game dev experience. If I complete
it, Avalon will exceed the size of anything I released for Xbox Live.
What challenges have you faced with development?
29
The two big challenges have been graphics and committing to a
web build of the game on itch.io.
For graphics, I have little art talent. I could get better, but that
time would directly compete with the time I spend writing code. I’m
getting over this by leaning on pixelated graphics with codegenerated elements. I replaced static art for dungeon walls with
raycast rendering and replaced AI-generated player portraits with
portraits that are randomly generated from simple facial elements.
I’m being stubborn and committing to the web build even if it
ends up limiting the scope of the game somewhat, because I think
having the game more readily playable is important. This has
required extra time in making sure code is more efficient and
involves some hacks such as muting the audio at times where the
game is busy and would struggle to not drop frames of audio
playback.
What are some of your favorite aspects of using
DragonRuby GTK with this project?
Definitely the community! It makes it quite a social hobby and
not just staring at a text editor coding. Because DragonRuby has a
fairly high-level API with docs and samples, it’s natural and easy that
any question I have that isn’t in the docs or samples is a welcome
question to the community.
It’s also a delight to be able to code in Ruby. Ruby is generally a
30
superb language, and it lends itself extremely well to game
programming. It would be an extremely unwise choice for building
games from scratch. DragonRuby GTK giving you an interface to do
anything you could imagine doing with SDL2 and cross-platform
builds quickly is amazing.
What tools are you using?
I am just using the main DragonRuby distribution, but for future
games I’d love to use Scale as a framework and/or Draco for entity
modeling.
I am using Visual Studio Code and Git Bash for Windows as a
terminal. I’m a big fan of the Linux subsystem for Windows and
would generally recommend that to Windows folks, but I’m using a
very small system drive and don't have the space to install the Linux
subsystem.
I use GitLab for project management—as well as the source
code repository. It gives me an issue tracker with a board to treat the
issues like sticky notes, a wiki for the game design, and a pipeline for
running unit tests. You can do all that with GitHub now, but that
wasn’t true back when I started using GitLab!
What keeps you dedicated to continued work on Avalon?
Everyone needs a hobby that lets them indulge in “me time”
and for me, making Avalon is it. I also know from experience that it’s
worth ploughing ahead on one project and avoiding the temptation
to switch to a new idea (I don’t have the time to do both!).
Also, the DragonRuby community’s “ship early, ship often” ethos
is a big boost—each time I push a new build to itch.io and post it in
the #show-and-tell Discord channel, I get feedback from folks who
are genuinely interested and want the project to succeed. People
have donated sprites, sent crash reports, and shared that they loved
taking a minute to try the game.
Follow along with Avalon builds on itch.io:
68st0x20.itch.io/avalon-dev
31
Outputs Explained
Whether you’re playing a sound effect, drawing a rectangle, showing
text, or displaying a sprite, your game has some way of giving
feedback to the player. DragonRuby GTK gives you args.outputs
to shovel data into that then gets delivered to the player. Here’s how
to handle outputting the most common information to your player in
your game:
# text
args.outputs.labels << {
x: 100, y: 100,
text: "Hello, world!",
}
# rectangle
args.outputs.solids << {
x: 100, y: 100,
w: 100, h: 100,
r: 240, g: 100, b: 100,
}
# sprite
args.outputs.labels << {
x: 100, y: 100,
w: 32, h: 32,
path: "sprites/ninja.png",
}
# sound effect
args.outputs.sounds << "sounds/jump.wav"
x and y represent the position on the screen. w and h are the
width and height. Many support r, g, b, and a values for red, green,
blue, and alpha colors (0 to 255). With just those four outputs,
combined with inputs, you can make all sorts of compelling games.
32
Art by Akzidenz
33
Drag & Drop
Need to add a bit of dragon drop (har har har) to your game? It’s as
simple as checking for mouse down and up and having your solid or
sprite follow the mouse cursor’s position.
def tick(args)
args.state.rect ||= {
x: 200, y: 200,
w: 120, h: 120,
r: 100, g: 100, b: 250,
dragging: false,
}
if args.inputs.mouse.down &&
args.inputs.mouse.inside_rect?(args.state.rect)
args.state.rect.dragging = true
args.state.rect.b = 150
elsif args.inputs.mouse.up
args.state.rect.dragging = false
args.state.rect.b = 250
end
if args.state.rect.dragging
args.state.rect.x =
args.inputs.mouse.x - args.state.rect.w / 2
args.state.rect.y =
args.inputs.mouse.y - args.state.rect.h / 2
end
args.outputs.solids << args.state.rect
end
Art by Akzidenz
34
Handling Input
A core part of what makes video games unique as a medium is that
they’re interactive. Whether it’s a mouse, keyboard, controller, or
touchscreen, players need some way to interact. Players prefer all
different kinds of control schemes, so it’s best to try to support as
many as reasonably possible for your game. DragonRuby GTK makes
it really easy to support a variety of input devices, all accessible via
arg.inputs.
A lot of games require directional input for things like moving
your character around the screen or changing the highlighted menu
option. There’s an easy way in DragonRuby GTK for checking the
keyboard arrow keys, WASD, controller d-pad, and controller analog
stick for directional movement all at the same time:
args.inputs.up
args.inputs.down
args.inputs.left
args.inputs.right
Those all return true when pressed or held, otherwise they
return false. Since you’re making games on your computer, it’s
often easiest to start with the keyboard. Let’s check for spacebar
being pressed with:
args.inputs.keyboard.key_down.space
Controllers have a similar interface for checking if a given
button is down:
args.inputs.controller_one.key_down.a
Combine these boolean checks in your game code to handle
input from multiple sources:
35
if args.inputs.up
# move player up
elsif args.inputs.down
# move player down
end
if args.inputs.left
# move player left
elsif args.inputs.right
# move player right
end
if args.inputs.keyboard.key_down.space ||
args.inputs.controller_one.key_down.a
# fire missile
end
There are also key_up and key_held properties too, so you
can really refine how your game controls.
Working with the mouse is just as simple. It has x, y, and click
properties, among others, that you can use.
args.inputs.mouse.x
args.inputs.mouse.y
args.inputs.mouse.click
No matter how you want your players to interact with your
game, DragonRuby GTK has your back when it comes to supporting
those options and making it easy.
36
White Labyrinth Interview with Podo
Brett Chalupa
Podo is a staple of the DragonRuby GTK community, regarded as
one of the experts on performance. Since early 2022, he’s been
working on an ambitious project tentatively titled: White Labyrinth.
Can you tell me a little bit about your project?
You play as the owner of a tavern/inn, but you run it inside the
dungeon where there are monsters and threats, including violent
customers. It's a basecamp survival game. I’m working with an artist
known as snowstorm on the game. We're planning to release it on
Steam.
What are some of the inspirations?
Tycoon games from the 1990s in which you have to keep
satisfying the customers to keep going. I reference Rimworld a lot
for the survival aspects.
Did you use Ruby before making White Labyrinth?
I made a Discord bot written in Ruby, and that was my only
prior programming experience.
What challenges have you faced with development?
Getting the graphics looking great has been the hardest part
of it.
What are some of your favorite aspects of using
DragonRuby GTK with this project?
DragonRuby GTK is the best game engine for my personal
taste. I really have difficult times learning any software that comes
with tons of user interface options (menus, buttons, windows, etc.)
where many elements often intersect in an implicit way. DragonRuby
is extremely explicit in that as a developer I can precisely control
and manage what order and amount each tick/frame of a given
moment will play out.
To me, it's a very bottom-up focused way of developing a large
project, allowing me to make sure I control every bit of it, ensuring
less bugs in the later stage of development.
What keeps you dedicated to the project?
37
I enjoy making a game that I'd play myself, so it has to have
good replayability.
Interested in following along? Message Podo#6822 on Discord to
get invited to the game’s server.
Screenshots from White Labyrinth
38
The Adventures of Dagron
The Game Developing Dragon
To be continued…
39
Featured Games
Curated by Akzidenz
Runelighter
by LittleB0xes
A strange and beautiful hybrid between
dungeon crawler and card memory
game. Pick up pairs of runes to gain a
magical ability and use it to fight your
foes. Watch out! Each time you play,
pairs of runes will have a different effect.
littleb0xes.itch.io/runelighter
Balloon Story
by Ceph
Control the movement of a balloon by
blowing air at it. It’s such a simple
premise that you instantly know what
you need to do. Actually doing it is
another matter, because the simple
gameplay quickly becomes
challenging… and then nearly
impossible.
ferociousfeind.itch.io/balloon-story
Fill the Glass
by gabemcarvalho
This is a perfect reminder that games
don’t need to have fancy or complicated
ideas to be fun. They just need to be
executed well. Pick up a glass, fill it with
water, put it on a table and repeat. It
takes just twenty seconds of your time
and it won’t waste a single one of them.
gabemcarvalho.itch.io/fill-the-glass
40
Code: Ceph’s Snake
Code by Ceph
How’s that old saying go? There’s more than one way to ride a
dragon! When coding games, there’s often more than one way to do
something. Sometimes there are trade-offs, other times it’s just
different. Code is an expression of how you think. Ceph shows
another way to go about coding Snake.
def tick(args)
snake = args.state.snake ||= [[3, 3]]
apple = args.state.apple ||= [6, 6]
args.state.snake_length ||= 1
args.state.movement ||= 1
args.state.newmove ||= 1
world_w = 16
world_h = 9
if args.state.gamestate == :end
$gtk.reset_next_tick if args.inputs.keyboard.space
args.outputs.labels << {
x: 640, y: 340,
alignment_enum: 1,
vertical_alignment_enum: 1,
size_enum: 2,
text: "Press SPACE to Restart"
}
end
case args.state.gamestate
when :end
args.outputs.labels << {
x: 640, y: 480,
alignment_enum: 1,
vertical_alignment_enum: 1,
size_enum: 8,
text: "Game Over"
}
else
if args.state.movement % 2 == 0
if args.inputs.left_right != 0
args.state.newmove = 2 - args.inputs.left_right
end
else
if args.inputs.up_down != 0
args.state.newmove = 1 - args.inputs.up_down
41
end
end
if args.tick_count % 12 == 0
args.state.movement = args.state.newmove
move = [[0, 1], [1, 0], [0, -1], [-1, 0]]
[args.state.movement]
new_pos = [
(snake.last[0]+move[0]) % world_w,
(snake.last[1]+move[1]) % world_h
]
args.state.gamestate = :end if snake.include?(new_pos)
snake << new_pos
if new_pos == apple
args.state.snake_length += 2
if args.state.snake_length > 141
args.state.gamestate = :end
end
while snake.include?(apple)
apple = [rand(world_w), rand(world_h)]
end
end
if snake.length > args.state.snake_length
snake.shift
end
args.state.snake = snake
args.state.apple = apple
end
end
# rendering
object_size = 80
to_render = []
snake.each do |obj|
to_render << {
x: obj[0] * object_size, y: obj[1] * object_size,
w: object_size, h: object_size,
r: 71, g: 208, b: 52
}
end
to_render << {
x: apple[0] * object_size, y: apple[1] * object_size,
w: object_size, h: object_size,
r: 206, g: 34, b: 48
}
args.outputs.background_color = [176, 222, 245]
args.outputs.sprites << to_render
args.outputs.labels << {
x: 20, y: 700, size_enum: 8,
text: "Score: #{args.state.snake.length.idiv(2)}"
}
end
42
Tweetcart
Code by hiro_r_b, words by Akzidenz & Levi
A Tweetcart is a tiny program like an animation, a visual effect, or
even a playable game. Tweetcart programmers use every trick in the
book to squash the code down until it fits into 280 characters—just
enough to fit in a Twitter post. Why would anyone do that? It takes a
certain kind of wild genius. The Tweetcart below is by hiro_r_b, the
undisputed champion of DragonRuby tweetcarting. Type the code
below in to an empty DragonRuby GTK app/main.rb file to see
what it makes.
TICK{bg!srand 5
m,n=(tc/270*c=360).vector
(q=[[0,0,99,0,0,16]]).each{|x,z,y,a,e,l|
l.times{v=-cos(a*c)*l/4
(0..(l-_1)/4).each{|j|sld!80+x*m+z*n-j,y,1,v,pal[21-j]}
x+=sin(c*a=a*0.8+(rand-0.5)/h=4+l/4)*l/3
y+=v
z+=sin(c*e=e*0.8+(rand-0.5)/h)*l/3
31*rand<l&&q<<[x,z,y,a,e,l-4]}}}
43
Resources
Continue your journey with DragonRuby Game Toolkit by checking
out the following community resources. Don’t worry, the next issue
of the zine will be out before you know it!
Read the official docs!
docs.dragonruby.org
Join us on Discord!
discord.dragonruby.org
Build a simple, complete game in Building Games
with DragonRuby, a free digital book.
book.dragonriders.community
Follow more advanced tutorials on the community website.
www.dragonriders.community/recipes
44
Ruby Cheatsheet
Basic Types
name = "Francis"
num = 583
float = 10.5
foods = ["apple", "pear"]
address = {
line1: "123 Sky Way",
line2: "Apt 2",
state: "New Dragon",
zip: "12345",
}
hungry = true
Comments
5
5
6
9
*
/
%
2
3
4
2
#
#
#
#
=>
=>
=>
=>
3 (subtract)
15 (multiply)
1.5 (divide)
1 (modulus)
Math.sin(r)
Math.cos(r)
Math.atan2(r2, r1)
Random Numbers
rand() # float in [0; 1)
rand(5) # int in [0; 5)
5 + rand(3) # int in [5; 8)
# just a note to self
Conditionals
Print to Console
&& # AND
|| # OR
puts "anything"
Logic
String Interpolation
puts "Hello, #{name}."
Loops
5.times do |i|
puts(i)
end
foods.each do |food|
puts(food)
end
address.each do |key, value|
puts("#{key}: #{value}")
end
Math
5 + 5 # => 10 (add)
45
if monday? || tuesday?
puts "not fun days"
elsif wednesday?
puts "fine day"
else
puts "great days"
end
Methods
def add(num1, num2)
num1 + num2
end
The return keyword is optional,
but can be used to return early:
def add(num1, num2)
return num1 if num2.nil?
DragonRuby GTK Cheatsheet
by Kota & Dee
Basic App
Rendered Bottom to Top
def tick(args)
args.outputs.solids << {
x: 300, y:200,
w: 30, h: 30
}
end
solids
static_solids
sprites
static_sprites
primitives
static_primitives
labels
static_labels
lines
static_lines
borders
static_borders
Primitives
Solids
args.outputs.solids << {
x: x_pos, y: y_pos,
w: width, h: height,
r: red, g: green,
b: blue, a: alpha
}
State
Store arbitrary data
args.state.speed = 8
Sprites
args.outputs.sprites << {
x: x, y: y, w: w, h: h,
r: r, g: g, b: b, a: a,
path: 'sprites/img.png',
angle: rotation_degrees,
}
Labels
args.outputs.label << {
x: x, y: y, text: "hi!",
r: r, g: g, b: b, a: a,
alignment_enum: 0,
size_enum: 0,
font: "font.ttf",
}
Set initial value in state
args.state.speed ||= 8
Get number of passed ticks
args.state.tick_count
Input
Directions
args.inputs.up (down, left,
right)
Keyboard
args.inputs.keyboard.key_down.z
Controller
args.inputs.controller_one.key_down.a
Mouse
args.inputs.mouse.click
args.inputs.mouse.x
args.inputs.mouse.y
46
DragonRiders.Community
2023
Download