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.
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
.
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.
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
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.
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.
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
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
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
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
Given the crash course you’ve went through, let’s look at some scenarios that I’ve encountered within my own games.
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.
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
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
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.
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).
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
Here are the steps needed to release a DragonRuby game to PC, Mac, Linux, Raspberry Pi (ARM32/ARM64), and Web.
- Update the 6 values in `./mygame/metadata/game_metadata.txt` (name of game, game id, version, and icon)
- 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
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
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