Skip to content

Instantly share code, notes, and snippets.

@amirrajan
Last active May 23, 2025 06:51
Show Gist options
  • Save amirrajan/62c6b2f0f71678c4235c7acde7f34159 to your computer and use it in GitHub Desktop.
Save amirrajan/62c6b2f0f71678c4235c7acde7f34159 to your computer and use it in GitHub Desktop.
DragonRuby primer for Lua game devs (Love2D)

Lua is a great language. Whenever someone asks me how they can get started with game dev, I immediately point them to Lua and PICO8. The simplicity of Lua comes with a potential downside. As your skills as a coder grows, you’ll begin to hit the language’s limitations. Lua is something you’ll eventually outgrow.

When you feel like you’re hitting the limits of the Lua, consider Ruby as your next goal. The syntax will come naturally to you given that you’ve used Lua.

Functions

Here’s how functions are defined in Lua:

function foo(a, b)
  return a + b
end

And this is what a function looks like in Ruby.

def foo(a, b)
  return a + b
end

This syntax should look immediately familiar. The only differences is instead of using the function keyword, you use def.

Implicit Returns

Ruby provides a cleaner syntax for return values. By default, executed line of a function is automatically returned in Ruby (this is called an implicit return and is common in languages like Rust and Haskell ).

Because of implicit returns, you can exclude the return statment.

def foo(a, b)
  a + b
end

It’s totally fine to continue using return if you want to. Ruby doesn’t stop you from doing what’s already familiar. It just gives you small improvements that you can incorporate when you feel comfortable.

Named Parameters

If you’ve used Lua for any non-trivial amount of time, you’ll eventually create a function that takes in a lot of parameters. Keeping track of parameters that are passed in ordinally gets tough after a certain number, at which point you may have created a function with “named” parameters by passing in a table.

Here is how a function can be defined with named parameters in Lua (along with its execution).

-- definition
function foo(params)
  local a = params.a
  local b = params.b
  return a + b
end

-- usage
local result = foo({ a = 1, b = 2 })
print(result)  -- Output: 3

Ruby:

You can do something very simliar with Ruby.

The main difference is that a Table (know as a Hash) in Ruby looks a bit more like JSON. Instead of { a = 1, b = 2 }, you do { a: 1, b: 2 }.

# definition
def foo(params)
  a = params.a
  b = params.b
  a + b
end

# Usage
result = foo({ a: 1, b: 2 })
puts(result)  # Output: 3

Note: It’s worth mentioning that variables in Ruby are defined local to scope by default as opposed to globally (you don’t need the local keyword).

A facet of Ruby you’ll continue to see through out these comparisons is a how the language tries to minimize the noise of the code. With named parameters, we can remove the additional { } in Ruby.

# definition
def foo(params)
  a = params.a
  b = params.b
  a + b
end

# Usage
result = foo(a: 1, b: 2) # no need for curly braces
puts(result)  # Output: 3

Note: For the seasoned Ruby devs out there, Hash keys in DragonRuby quack like attr_accessors. For the Lua devs out there wondering what quacking is, it’s something that ducks and Ruby do (more info here).

Back to parameters. Quack.

We can take it a step further and expand the parameters out and remove the need from the prefix object.

# definition
def foo(a:, b:) # no need for wrapper object
  a + b
end

# Usage
result = foo(a: 1, b: 2)
puts(result)  # Output: 3

Here we see an added benefit with formalized named parameters. Both a and b are required parameters. You’ll get an exception about not passing in the correct number of parameters if you forget any required parameters in. This is something that isn’t available out of the box in Lua (you’d be forced to do that error handling within the method body).

Here is how you can provide a default value for a named parameter in Ruby.

def foo(a:, b: 10)
  a + b
end

# Usage
result = foo(a: 1) # b is defaulted to 10 if not provided, a is still a required parameter
puts(result)  # Output: 11

# Usage
result = foo(a: 1, b: 2)
puts(result)  # Output: 3

We’ve only covered functions so far, but I hope you’re already seeing how these subtle facets of the Ruby reduce noise in the code and provide additional benefits.

We can take the noice reduction a step further in fact. Parenthesis for function invocations in Ruby are optional as long as the execution is disambiguous.

def foo(a:, b: 10)
  a + b
end

# Usage
result = foo a: 1  # b is defaulted to 10 if not provided, a is still a required parameter
puts result   # Output: 11

# Usage
result = foo a: 1, b: 2
puts result   # Output: 3

These small tranformations are what’s called “progressive disclosure of complexity (and power)”. Again, none of these sytactical updates are required. You can write Ruby code just like you did with Lua. The key point being that Ruby gives you room to grow.

Game dev as a whole is a medium of expression, an artistic endevour. As you explore the language, you may find that it emobodies artistic expression. It’s why the language is often described as beautiful.

Object Oriented Arrays

The core collections that Ruby provides out of the box are extremely powerful. Let’s look at Arrays first.

This is how you’d define an Array in Lua and add 3 items to it.

letters = {}
table.insert(letters, "a")
table.insert(letters, "b")
table.insert(letters, "c")

And here is what it looks like in Ruby. You’ll notice that Array’s are more object oriented (the function exists on the instance of the array).

The push function on Array adds an element to the end (if you want to be a little bit clearer on this behavior, you can also use the function push_back (“push this element on the back of the Array”).

letters = []
letters.push("a")
letters.push("b")
letters.push("c")

# OR

letters = []
letters.push_back("a")
letters.push_back("b")
letters.push_back("c")

This is how you would remove the last element of an array in Lua:

letters = {"a", "b", "c"}
last_element = table.remove(letters)

Remove from the back of the array is done via pop (or pop_back: “pop this element from the back of the array”):

letters = ["a", "b", "c"]
last_element = letters.pop()

# OR

letters = ["a", "b", "c"]
last_element = letters.pop_back()

Remember when I mentioned that parenthesis are option in Ruby for functions? The code above can be written like this:

letters = ["a", "b", "c"]
last_element = letters.pop

# OR

letters = ["a", "b", "c"]
last_element = letters.pop_back

There are a ton of useful functions on the Array object in Ruby.

A Comparison of Common Array Operations

Let’s look at how you’d do the following in Lua for Arrays:

  • initialize an Array
  • pop a value from the back of an Array
  • pop a value from the front of an Array
  • push a value to the back of an Array
  • push a value to the front of an Array
  • insert a value at a specific index
  • remove a value at a specific index
  • get the length of an Array
  • get the value of the last element of an Array (without removing it)
  • get the value of the first element of an Array (without removing it)
  • print out the contents of an Array
-- array initialization
letters = {"a", "b", "c"}
-- pop a value from the back
last_element = table.remove(letters)
-- pop a value from the front
first_element = table.remove(letters, 1)
-- push a value to the back
table.insert(letters, "d")
-- push a value to the front
table.insert(letters, 1, "e")
-- insert a value at a specific index
table.insert(letters, 2, "f")
-- remove a value at a specific index
table.remove(letters, 2)
-- get the length of an array
length = #letters

letters = {"a", "b", "c"}
-- get the value of the last element of an array
last_element = letters[#letters]
-- get the second to last element of an array
second_to_last_element = letters[#letters - 1]
-- get the value of the first element of an array
first_element = letters[1]
-- print out the contents of an array
for i = 1, #letters do
  print(letters[i])
end

And here is how you would do the same in Ruby:

# array initialization
letters = ["a", "b", "c"]
# pop a value from the back
last_element = letters.pop # or letters.pop_back
# pop a value from the front
first_element = letters.shift # or letters.pop_front
# push a value to the back
letters.push("d") # or letters.push_back
# push a value to the front
letters.unshift("e") # or letters.push_front
# insert a value at a specific index
letters.insert(1, "f")
# remove a value at a specific index
letters.delete_at(1)
# get the length of an array
length = letters.length

letters = ["a", "b", "c"]
# get the value of the last element of an array
last_element = letters.last # or letters[letters.length - 1]
# get the second to last element of an array
second_to_last_element = letters[-2] # or letters[letters.length - 2]

# get the value of the first element of an array
first_element = letters.first # or letters[0]

# print out the contents of an array
puts letters # prints a b c with new lines
puts letters.inspect # prints ["a", "b", "c"]

Take a look at the API for the Array class in Ruby:

  • functions are part of the instance (object oriented)
  • arrays are zero indexed (just something to be aware of)
  • Pushing values: push, push_back, unshift, push_front
  • Popping values: pop, pop_back, shift, pop_front
  • Getting specific values: at, first, last, []
  • Printing the contents of an array doesn’t need enumeration: puts, inspect

Object Oriented Hashes

A similar object oriented quality exists for Hashes in Ruby. Let’s take a look at some functions on a Hash:

  • initialize a Hash
  • get a value from a Hash
  • set a value in a Hash
  • delete a value from a Hash
  • get the keys of a Hash
  • get the values of a Hash
  • get the length of a Hash
  • create a nested Hash
  • print out the contents of a nested Hash

Here’s what it looks like in Lua:

-- initialize a hash
person = { first_name = "John", last_name = "Doe" }
-- get a value from a hash
first_name = person.first_name
-- set a value in a hash
person.first_name = "Jane"
-- delete a value from a hash
person.first_name = nil
-- get the keys of a hash
keys = {}
for k, v in pairs(person) do
  table.insert(keys, k)
end
-- get the values of a hash
values = {}
for k, v in pairs(person) do
  table.insert(values, v)
end
-- get the length of a hash
length = 0
for k, v in pairs(person) do
  length = length + 1
end

-- create a nested hash
person = { first_name = "John", last_name = "Doe", address = { city = "New York", state = "NY" } }

-- print the values of an arbitrarily nested hash
function print_hash(hash)
  for k, v in pairs(hash) do
    if type(v) == "table" then
      print(k .. ":")
      print_hash(v)
    else
      print(k .. ": " .. v)
    end
  end
end

print_hash(person)

And here is what it looks like in Ruby:

# initialize a hash
person = { first_name: "John", last_name: "Doe" }
# get a value from a hash
first_name = person.first_name
# set a value in a hash
person.first_name = "Jane"
# delete a value from a hash
person.delete(:first_name)
# get the keys of a hash
keys = person.keys
# get the values of a hash
values = person.values
# get the length of a hash
length = person.length
# create a nested hash
person = { first_name: "John", last_name: "Doe", address: { city: "New York", state: "NY" } }
# print the values of an arbitrarily nested hash
puts person # prints the hash and takes care of the nested hash, and handles self referencing objects too

The benefites of Ruby’s Hash and Array constructs are beginning to crystalize:

  • Functions are part of the instance (object oriented) and provide things we commonly want (while Lua has to enumerate values to get the same things)
  • Automatic handling of nested objects and inspection

Loops

In Ruby, we didn’t have to perform any enumeration to get information of the object. So let’s show case enumeration in Ruby.

Here is how you would loop through the values of an Array in Lua (as seen before)::

letters = {"a", "b", "c"}
for i = 1, #letters do
  print(letters[i])
end

In Ruby, you can do something similar. Again this shows that the syntax of the language is a huge divergence from Lua.

letters = ["a", "b", "c"]
for i in 0..letters.length - 1 do
  puts letters[i]
end

The Ruby version is a bit more verbose, but hopefully it’s familiar to what you’ve seen in Lua. Ruby provides a cleaner syntax however. This is again a situation of “use it when it feels comforatble”. You don’t have to use this syntax, but it’s there if you want it.

letters = ["a", "b", "c"]
letters.each do |letter|
  puts letter
end

Using Ruby’s each function, we remove the need of tracking the index. We have access to the index if it’s needed via each_with_index:

letters = ["a", "b", "c"]
letters.each_with_index do |letter, index|
  puts index, letter
end

These functions take in what’s called a “block”. A block starts with do, followed by a pair of | (the parameters are contained between these), and ends with end.

You can also use the { } syntax for blocks. This is nice for one liners (the single line usage it’s not enforced, so you can make it multi-lined if you’d like):

letters = ["a", "b", "c"]
letters.each { |letter| puts letter }

To reiterate, if you want to use for loops, keep using them. The do/end and { } syntax is just there if you want to use it (it does yield much cleaner code in the long run).

Let’s do a quick filter of an Array. Let’s populate an Array with 10 random integers, and then filter out the even numbers. In Lua, you would do this:

random_ints = {}
for i = 1, 10 do
  table.insert(random_ints, math.random(1, 100))
end
even_ints = {}
for i = 1, #random_ints do
  if random_ints[i] % 2 == 0 then
    table.insert(even_ints, random_ints[i])
  end
end

We can do a similar thing in Ruby. Using conventional for loops:

random_ints = []
for i in 1..10 do
  random_ints.push(Numeric.rand(1..100))
end
even_ints = []
for i in random_ints do
  if i % 2 == 0
    even_ints.push(i)
  end
end

Using blocks is much cleaner however, we use Array’s new function to initialize the Array, and then use the find_all function to filter it. An integer’s property of being even or odd is so common that Ruby has built in functions for it: even? and odd?:

random_ints = Array.new(10) do
  Numeric.rand(1..100)
end
even_ints = random_ints.find_all do |number|
  number.even?
end
puts even_ints

Chaining functions like this is very common in Ruby:

even_ints = Array.new(10) { Numeric.rand(1..100) }
                 .find_all { |number| number.even? }
puts even_ints

We’ll look at more interesting examples of chaining later on. But I hope you’re beginning to see Ruby’s power and expressivity. At the same time, what you’re familiar with in Lua is still there for you until you feel comfortable with the new syntax.

Here’s the same concepts applied to a Hash. In Lua, you would do this to loop through the keys and values of a Hash:

person = { first_name = "John", last_name = "Doe" }
for k, v in pairs(person) do
  print(k .. ": " .. v)
end

In Ruby, you can do again use a for loop:

person = { first_name: "John", last_name: "Doe" }
for k, v in person do
  puts "#{k}: #{v}"
end

Or you can use the each function:

person = { first_name: "John", last_name: "Doe" }
person.each do |k, v|
  puts "#{k}: #{v}"
end

Index is also available if you need it:

person = { first_name: "John", last_name: "Doe" }
person.each_with_index do |(k, v), index|
  puts "#{k}: #{v} (#{index})"
end

This is what enumeration plus index tracking would look like in Lua:

person = { first_name = "John", last_name = "Doe" }
index = 1
for k, v in pairs(person) do
  print(k .. ": " .. v .. " (" .. index .. ")")
  index = index + 1
end

-- OR
person = { first_name = "John", last_name = "Doe" }
for i = 1, #person do
  k = person[i]
  v = person[k]
  print(k .. ": " .. v .. " (" .. i .. ")")
end

Things get a little worse in Lua’s case. Here is how you’d merge two Hashes (if there are conflicts, the last one wins):

person1 = { first_name = "John", last_name = "Doe" }
person2 = { age = 30, city = "New York" }
merged = {}
for k, v in pairs(person1) do
  merged[k] = v
end
for k, v in pairs(person2) do
  merged[k] = v
end
for k, v in pairs(merged) do
  print(k .. ": " .. v)
end

An this is what it looks like in Ruby:

person1 = { first_name: "John", last_name: "Doe" }
person2 = { age: 30, city: "New York" }
merged = person1.merge(person2)
puts merged

What about a 3 hash merge? In Lua, you would do this:

person1 = { first_name = "John", last_name = "Doe" }
person2 = { age = 30, city = "New York" }
person3 = { country = "USA", state = "NY" }
merged = {}
for k, v in pairs(person1) do
  merged[k] = v
end
for k, v in pairs(person2) do
  merged[k] = v
end
for k, v in pairs(person3) do
  merged[k] = v
end
for k, v in pairs(merged) do
  print(k .. ": " .. v)
end

And in Ruby:

person1 = { first_name: "John", last_name: "Doe" }
person2 = { age: 30, city: "New York" }
person3 = { country: "USA", state: "NY" }
merged = person1.merge(person2).merge(person3)
puts merged

Ruby provides you “splat” syntax for merging multiple hashes:

person1 = { first_name: "John", last_name: "Doe" }
person2 = { age: 30, city: "New York" }
person3 = { country: "USA", state: "NY" }
merged = { **person1, **person2, **person3 }
puts merged

OOP and Classes

Tables, Arrays, and Hashes give you a ton of mileage in both languages. But eventually you’ll want to associate data with methods (ie. classes).

Lua technically doesn’t have an OOP system. A clever use of tables and metatables can provide the initial facets of OOP.

Here is example of using metatables to create a class in Lua:

Person = {}
Person.__index = Person

function Person:new(first_name, last_name)
  local obj = { first_name = first_name, last_name = last_name }
  setmetatable(obj, Person)
  return obj
end

function Person:full_name()
  return self.first_name .. " " .. self.last_name
end

local john = Person:new("John", "Doe")
print(john:full_name())

Here are what classes look like in Ruby:

class Person
  # these are attributes of a class (read/write)
  attr :first_name, :last_name

  # this is your constructor
  def initialize(first_name, last_name)
    # instead of using ~self~ to assign values to the object, you
    # use ~@~ to assign values to the object (the @ symbol is means "private member variable")
    @first_name = first_name
    @last_name = last_name
  end

  def full_name
    "#{@first_name} #{@last_name}"
  end
end

john = Person.new("John", "Doe")
puts john.full_name

Classes are a natural construct in Ruby. The language was designed with OO in mind from the beginning. No metatables wrangling or other creative solutions are needed.

Here is an object composition that Lua wouldn’t be able to do. The Player class is of type Player, Position, and Name. It also gains the capabilities of both Position and Name.

module Position
  attr :x, :y, :w, :h
end

module Name
  attr :name
end

class Player
  include Position
  include Name
end

# usage
player = Player.new

player.x = 0
player.y = 0
player.w = 100
player.h = 100
player.name = "9S"

player.is_a?(Player) # true
player.is_a?(Position) # true
player.is_a?(Name) # true

Game Development

Given the crash course you’ve went through, let’s look at some scenarios that I’ve encountered within my own games.

Loot Selection

Given a definition of random loot that can be aquired, I want a function that takes in this random loot definition and returns a collection of items that were aquired.

The loot definition has an item name, a min and max range, and the chances that the item will be aquired.

Here’s what it looks like in Lua:

-- function to get loot
function get_loot(loot_definition)
  local result = {}

  -- for each item...
  for i, item in ipairs(loot_definition) do
    -- see if the item is aquired
    if item.probability > math.random() then
      -- if it is, add it to the result item, plus a random
      -- quantity between min and max
      table.insert(result,
                   {
                     name = item.name,
                     quantity = math.random(item.min_count, item.max_count)
                   })
    end
  end

  -- return the result
  return result
end

-- loot definition
local loot_definition = {
  { name = "Fur",  min_count = 1, max_count = 10, probability = 0.5 },
  { name = "Teeth", min_count = 5, max_count = 15, probability = 0.3 },
  { name = "Meat", min_count = 2, max_count = 8,  probability = 0.2 }
}

local loot = get_loot(loot_definition)
print("loot received")
for i, loot_item in ipairs(loot) do
  print("You got a " .. loot_item.name .. " of quantity " .. loot_item.quantity)
end

print("original loot and probabilities")
for i, loot_item in ipairs(loot) do
  print(loot_item.name .. " probability: " .. loot_item.probability .. ", min: " .. loot_item.min_count .. ", max: " .. loot_item.max_count)
end

This is how you’d implement the same thing in Ruby:

def get_loot(loot_definition)
  loot_definition.find_all { |item| item[:probability] > rand }
                 .map do |item|
                   {
                     name: item[:name],
                     quantity: Numeric.rand(item[:min]..item[:max])
                   }
                 end
end

loot_definition = [
  { name: "Fur",  min: 1, max: 10, probability: 0.5 },
  { name: "Teeth", min: 5, max: 15, probability: 0.3 },
  { name: "Meat", min: 2, max: 8,  probability: 0.2 }
]

loot = get_loot(loot_definition)
puts "loot recieved"
loot.each do |i|
  puts "You got a #{i[:name]} of quantity #{i[:quantity]}"
end

puts "original loot and probabilities"
loot.each do |loot|
  puts "#{item[:name]} probability: #{item[:probability]}, min: #{item[:min]}, max: #{item[:max]}"
end

Chaining functions in Ruby’s get_loot function communicates the intent: “find all items that have a probability greater than a random roll, and then map them to a new array of loot items”.

For a deeper dive of this real world example read through this writeup.

Geometry

Let’s look at computing pythagorean triples. A pythagorean triple is a right triangle where a^2 + b^2 = c^2 and a, b, c are all whole numbers (https://en.wikipedia.org/wiki/Pythagorean_triple).

I want to:

  • Find all pythagorean triples for combinations of a, b lengths between 1 and 100.
  • Only unique combinations (ie. (3, 4, 5) and (4, 3, 5) are the same)
  • After getting the unique triples, I want to sort them by area

Here’s what the computation looks like in Lua:

triples = {}
one_to_hundred = {}
for i = 1, 100 do
  table.insert(one_to_hundred, i)
end

for _,a in ipairs(one_to_hundred) do
  for _,b in ipairs(one_to_hundred) do
    -- Calculate the hypotenuse
    c = math.sqrt(a*a + b*b)
    -- If the hypotenuse is a whole number (pythagorean triple)
    if math.floor(c) == c then
      -- Check if a similar triangle already exists
      exists = false
      for _,triangle in ipairs(triples) do
        if (triangle.a == a and triangle.b == b and triangle.c == c) or (triangle.a == b and triangle.b == a and triangle.c == c) then
          exists = true
          break
        end
      end

      if not exists then
        -- Calculate the area
        area = (a*c) / 2
        -- Append the triangle to the list
        table.insert(triples, {a=a, b=b, c=c, area=area})
      end
    end
  end
end

-- Sort triangles by area
table.sort(triples, function(a, b)
  return a.area < b.area
end)

for _,triangle in ipairs(triples) do
  print(triangle.a, triangle.b, triangle.c, triangle.area)
end

And here is the same thing in Ruby:

# generate a list of numbers from 1 to 100
one_to_hundred = (1..100).to_a
# use Array's product function to get all combinations of a and b
all_combinations = one_to_hundred.product(one_to_hundred)

# triples are calculated using chaining functions
triples = all_combinations.map do |a, b|
            # given permutations of side a, and side b (product)
            # calculate the hypotenuse
            { a:, b:, c: Math.sqrt(a ** 2 + b ** 2) }
          end.find_all do |triangle|
            # where area is a whole number (pythagaroean triple)
            triangle[:c].floor == triangle[:c]
          end.uniq do |triangle|
            # unique based on sorted sides
            triangle.values.sort
          end.map do |triangle|
            # calculate the area
            triangle.merge(area: (triangle[:a] * triangle[:c]) / 2)
          end.sort_by do |triangle|
            # sort by area
            triangle[:area]
          end

puts triples

Love2D and DragonRuby

Let’s take a simple game and see what the implementation looks like in Love2D and DragonRuby.

The game does the following:

  • render a sprite with path “sprites/square/blue.png” at the center of the screen
  • assume a 16:9 aspect ratio with the sprite being 100x100
  • make it rotate to start off
  • if space is pressed, stop the rotation
  • if space is pressed again, start the rotation again

This is the Love2D version. Here’s how many “proud” lines of code it takes to implement the game (proud lines of code means that it’s code that I wouldn’t be embarassed to show off to a friend):

function love.conf(t)
    t.window.width = 1280
    t.window.height = 720
end

-- Game class
Game = {}
Game.__index = Game

function Game.new()
    local self = setmetatable({}, Game)
    self.square = Square.new()
    return self
end

function Game:update(dt)
    self.square:update(dt)
end

function Game:draw()
    self.square:draw()
end

-- Square class
Square = {}
Square.__index = Square

function Square.new()
    local self = setmetatable({}, Square)

    self.image = love.graphics.newImage("sprites/square/blue.png")
    self.x = love.graphics.getWidth() / 2
    self.y = love.graphics.getHeight() / 2
    self.rotation = 0
    self.rotate = true
    self.wasPressed = false

    self.scaleX, self.scaleY = 100/self.image:getWidth(), 100/self.image:getHeight()

    return self
end

function Square:update(dt)
    if love.keyboard.isDown('space') then
        if not self.wasPressed then
            self.rotate = not self.rotate
            self.wasPressed = true
        end
    else
        self.wasPressed = false
    end

    if self.rotate then
        self.rotation = self.rotation + dt
        if self.rotation >= 2 * math.pi then
            self.rotation = self.rotation - 2 * math.pi
        end
    end
end

function Square:draw()
    love.graphics.draw(self.image,
                       self.x, self.y,
                       self.rotation,
                       self.scaleX, self.scaleY,
                       self.image:getWidth() / 2, self.image:getHeight() / 2)
end

-- Main functions
function love.load()
    game = Game.new()
end

function love.update(dt)
    game:update(dt)
end

function love.draw()
    game:draw()
end

Ruby (DragonRuby):

class Square
  attr :x, :y, :w, :h, :angle, :rotating, :rotation_speed

  def initialize(x:, y:, w:, h:)
    @x = x
    @y = y
    @w = w
    @h = h
    @angle = 0
    @rotating = true
    @rotation_speed = 5
  end

  def tick(space_was_pressed)
    @rotating = !@rotating if space_was_pressed

    @angle += @rotation_speed if @rotating
  end

  def prefab
    {
      x: @x, y: @y, w: @w, h: @h,
      angle: @angle, anchor_x: 0.5, anchor_y: 0.5,
      path: "sprites/blue/square.png"
    }
  end
end

class Game
  attr_gtk

  def initialize
    @square = Square.new(x: 640, y: 360, w: 100, h: 100)
  end

  def tick
    @square.tick(inputs.keyboard.key_down.space)
    outputs.sprites << @square.prefab
  end
end

def tick(args)
  $game ||= Game.new
  $game.args = args
  $game.tick
end

For a more complex game see here’s one that’s written in Defold and DragonRuby.

Releasing the game

Building something is only half the battle. We also have to release it.

One of the biggest mistakes indie game devs make is not releasing cross platform from day one. You leave a large chunk of revenue on the table, lessen the discoverability of your game, and make it harder to get access to console dev kits (console makers want to see that you’re able to release on multiple platforms with varied resolutions, input methods, and platform architectures).

Love2D:

Here are the steps you’d need to take to release a Love2D game to PC, Mac, Linux, and Web.

For an Html Build, you’ll need to follow the instructions here: https://github.com/Davidobot/love.js

You have quite the road ahead of you. You’ll need to:

  • install love.js, node, python, and emscripten (for windows you’ll also need megasource and chocolatey)
  • then you have to follow the steps to create the package, wrapper html, added coop/coep headers for itch.io etc

For PC, Mac, and Linux, you’ll need to follow the instructions here: https://love2d.org/wiki/Game_Distribution. Buckle up, it’s an 11 page document.

For Arm64 Linux (Raspberry Pi and the rumored Steam Deck 2), you’ll need to follow the instructions here: https://github.com/Cebion/love2d_aarch64

Ruby (DragonRuby):

Here are the steps needed to release a DragonRuby game to PC, Mac, Linux, Raspberry Pi (ARM32/ARM64), and Web.

  1. Update the 6 values in `./mygame/metadata/game_metadata.txt` (name of game, game id, version, and icon)
  2. Run ./dragonruby-publish --package, all packages will be created and zipped under ./builds

Uh… that’s it. You can see it in action in fact: https://www.youtube.com/watch?v=DYBRzglsEzU

Todo

hammerspoon code:

g_lookup_dsl['mx'] = function()
  hs.alert("F1 down")
  hs.eventtap.event.newKeyEvent({}, 'F1', true):post()

  f1_up = hs.timer.delayed.new(0.10,
                               function()
                                 hs.eventtap.event.newKeyEvent({}, 'F1', false):post()
                               end)

  x_down = hs.timer.delayed.new(0.10,
                       function()
                         hs.alert("F1 down")
                         hs.eventtap.keyStroke({""}, "x")
                         f1_up.start()
                       end)
  x_down.start()
end

value types

t = {
    [{name = "object_a"}] = {
        [{name = "object_b"}] = "value_b"
    },
    [{name = "object_c"}] = {
        [{name = "object_d"}] = "value_d"
    }
}
print(t[{name = "object_a"}][{name = "object_b"}])  -- Will not ouput: value_b
print(t[{name = "object_c"}][{name = "object_d"}])  -- Will not output: value_d
hash = {
    {name: "object_a"} => {
        {name: "object_b"} => "value_b"
    },
    {name: "object_c"} => {
        {name: "object_d"} => "value_d"
    }
}

# Accessing nested values
puts hash[{name: "object_a"}][{name: "object_b"}]  # outputs: value_b
puts hash[{name: "object_c"}][{name: "object_d"}]  # outputs: value_d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment