Optional — builds on Activity 1.11. Nothing in Unit 2 requires it, but Unit 2 gets nicer if you have it.
Activity 1.11 used one metatable field, __index, to make method lookup fall back to a class table. There are about twenty more fields, and each one lets you say what a built-in operator should do when it meets your type.
Out of the box, it does not know.
local a = { x = 1, y = 2 }local b = { x = 10, y = 20 }
local ok = pcall(function() return a + b end)print("did it work?", ok)did it work? falseAdding two tables is an error, and reasonably so — Lua has no idea what you meant. Define __add and you have told it.
local Vec = {}Vec.__index = Vec
function Vec.new(x, y) return setmetatable({ x = x, y = y }, Vec)end
function Vec.__add(a, b) return Vec.new(a.x + b.x, a.y + b.y)end
function Vec.__tostring(v) return "(" .. v.x .. ", " .. v.y .. ")"end
local a = Vec.new(1, 2)local b = Vec.new(10, 20)
print(a + b)(11, 22)Two things happened there. a + b called your function, and print showed a readable vector instead of table: 0x14b2f30 — because print asks __tostring first.
__tostring is the one to add to everything. Debugging a game full of tables that all print as hex addresses is genuinely miserable, and this is a four-line fix per type.
| Field | Triggered by |
|---|---|
__add | a + b |
__sub | a - b |
__mul | a * b |
__div | a / b |
__unm | -a |
__eq | a == b |
__lt | a < b |
__concat | a .. b |
__tostring | print(a), tostring(a) |
__call | a() |
__index | a key that is not there |
Three rules that will save you an afternoon.
__eq is only consulted when both sides are tables. Comparing your vector to a number is simply false, and your function never runs.__ne or __gt. Lua builds ~= from __eq and > by swapping the operands into __lt.__len. Plain Lua honors it for tables and LÖVE does not — the same code gives a different answer in Unit 2. Write a :length() method instead.One caution on where this belongs. Operators are for types where the arithmetic is genuinely obvious — vectors, colors, money, times. Giving Enemy an __add because you could is how code becomes unreadable. If a reader has to look up what + means here, use a named method.
Start a fresh main.lua. You are building a 2D vector — the single most useful small type in game programming, and the reason this page exists.
x and a y.__add and __tostring.tostring call needed.-- main.lua - vectors with operators
local Vec = {}Vec.__index = Vec
function Vec.new(x, y) return setmetatable({ x = x, y = y }, Vec)end
function Vec.__add(a, b) return Vec.new(a.x + b.x, a.y + b.y)end
function Vec.__tostring(v) return "(" .. v.x .. ", " .. v.y .. ")"end
local a = Vec.new(1, 2)local b = Vec.new(10, 20)
print(a + b)(11, 22)__sub, __mul, and __unm.__mul takes a vector and a number, not two vectors. Multiplying two vectors has several possible meanings, so leave it out rather than pick one.-- main.lua - vectors with operators
local Vec = {}Vec.__index = Vec
function Vec.new(x, y) return setmetatable({ x = x, y = y }, Vec)end
function Vec.__add(a, b) return Vec.new(a.x + b.x, a.y + b.y)end
function Vec.__sub(a, b) return Vec.new(a.x - b.x, a.y - b.y)end
function Vec.__mul(v, s) return Vec.new(v.x * s, v.y * s)end
function Vec.__unm(v) return Vec.new(-v.x, -v.y)end
function Vec.__tostring(v) return "(" .. v.x .. ", " .. v.y .. ")"end
local a = Vec.new(1, 2)local b = Vec.new(10, 20)
print(a + b)print(b - a)print(a * 3)print(-a)(11, 22)(9, 18)(3, 6)(-1, -2)__eq and confirm that two separately-made vectors with the same numbers compare equal. Without it they never would, because two different tables are two different tables.pos = pos + vel * dt.pos.x = pos.x + vel.x * dt and the same again for y.-- main.lua - vectors with operators
local Vec = {}Vec.__index = Vec
function Vec.new(x, y) return setmetatable({ x = x, y = y }, Vec)end
function Vec.__add(a, b) return Vec.new(a.x + b.x, a.y + b.y)end
function Vec.__sub(a, b) return Vec.new(a.x - b.x, a.y - b.y)end
function Vec.__mul(v, s) return Vec.new(v.x * s, v.y * s)end
function Vec.__unm(v) return Vec.new(-v.x, -v.y)end
function Vec.__eq(a, b) return a.x == b.x and a.y == b.yend
function Vec.__tostring(v) return "(" .. v.x .. ", " .. v.y .. ")"end
local a = Vec.new(1, 2)local b = Vec.new(10, 20)
print(a + b)print(b - a)print(a * 3)print(-a)print(a == Vec.new(1, 2))
local pos = Vec.new(100, 200)local vel = Vec.new(5, -3)local dt = 2
pos = pos + vel * dtprint(pos)Vec:length() method using math.sqrt. A method, not __len — you were warned why.__lt comparing lengths, then table.sort a list of vectors with no comparison function at all.__mul by a number does something sensible at the edges. Clamping belongs in the metamethod.(11, 22)(9, 18)(3, 6)(-1, -2)true(110, 194)(110, 194) — two steps of velocity applied to a position, written the way you would write it on paper.