Tag Arena – complete code¶
The finished script from the Build a Tag Arena tutorial, ready to compare against your own build.
-- ============================================================
-- Constants and local state
-- ============================================================
W, H = 320, 180
PLAYER_SIZE = 8
SPEED = 2
IT_SPEED = 2.4 -- "it" runs slightly faster
TAG_COOLDOWN = 60 -- frames before "it" can tag again (one second)
COL_BG = 0 -- black
COL_IT = 8 -- red
COLORS = { 12, 11, 10, 14 }-- blue, green, yellow, pink
state = "menu" -- "menu" | "waiting" | "playing" | "over"
is_host = false
next_color = 1 -- host only
tag_cooldown = 0 -- host only
taunt_timer = 0
function _init()
state = "menu"
is_host = false
next_color = 1
tag_cooldown = 0
taunt_timer = 0
print("Press H to host a game, J to join one")
end
function clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
function overlaps(a, b)
return a.x < b.x + PLAYER_SIZE and a.x + PLAYER_SIZE > b.x
and a.y < b.y + PLAYER_SIZE and a.y + PLAYER_SIZE > b.y
end
-- ============================================================
-- Session menu (same shape as the Pong tutorial)
-- ============================================================
function update_menu()
if key_pressed("h") then
state = "waiting"
is_host = true
net.host({ max_players = 4, title = "Tag arena" }, on_connected)
elseif key_pressed("j") then
state = "waiting"
is_host = false
net.join(on_connected)
end
end
function update_waiting()
-- Cancelling a dialog fires no callback, so the game stays here. Let the
-- player re-open host/join directly, or press M to return to the menu.
if key_pressed("m") then
state = "menu"
print("Press H to host a game, J to join one")
elseif key_pressed("h") then
is_host = true
net.host({ max_players = 4, title = "Tag arena" }, on_connected)
elseif key_pressed("j") then
is_host = false
net.join(on_connected)
end
end
-- ============================================================
-- Session setup: host provisions players and declares itself "it"
-- ============================================================
function provision_player(playerId)
local entry = {
x = math.random(8, W - 8 - PLAYER_SIZE),
y = math.random(8, H - 8 - PLAYER_SIZE),
col = COLORS[next_color],
}
next_color = next_color % #COLORS + 1
-- The first entry has to create the players branch (see the net.state
-- reference); later ones write through it
if net.state.players then
net.state.players[playerId] = entry
else
net.state.players = { [playerId] = entry }
end
end
function on_connected()
if is_host then
provision_player(net.id())
net.state.it = net.id()
net.on("peer.joined", function(playerId)
provision_player(playerId)
print("Player " .. playerId .. " joined the arena")
end)
net.on("peer.left", function(playerId)
net.state.players[playerId] = nil
if net.state.it == playerId then
net.state.it = net.id() -- "it" left: the host takes over
end
print("Player " .. playerId .. " left")
end)
end
-- Everyone announces tags by listening to the shared "it" key
net.on("it", function(path, value)
if value == net.id() then
print("You are it! Catch someone!")
else
print("Player " .. value .. " is it -- run!")
end
end)
net.on("event:taunt", function(from)
print("Player " .. from .. " taunts you!")
end)
net.on("ended", function()
state = "over"
print("The host closed the session. Press M for the menu.")
end)
state = "playing"
print("Connected as player " .. net.id() .. " (arrow keys; SPACE to taunt).")
end
-- ============================================================
-- Movement and taunting
-- ============================================================
function my_player()
local players = net.state.players
if not players then
return nil
end
return players[net.id()]
end
function update_movement()
local me = my_player()
if not me then
return -- not provisioned yet
end
local speed = SPEED
if net.state.it == net.id() then
speed = IT_SPEED
end
if key_pressed("ArrowLeft") then me.x = me.x - speed end
if key_pressed("ArrowRight") then me.x = me.x + speed end
if key_pressed("ArrowUp") then me.y = me.y - speed end
if key_pressed("ArrowDown") then me.y = me.y + speed end
me.x = clamp(me.x, 0, W - PLAYER_SIZE)
me.y = clamp(me.y, 0, H - PLAYER_SIZE)
end
function update_taunt()
taunt_timer = taunt_timer - 1
if key_pressed(" ") and taunt_timer <= 0 then
taunt_timer = 60 -- once per second at most
net.emit("taunt")
print("You taunt everyone!")
end
end
-- ============================================================
-- Tagging: the host is the referee
-- ============================================================
function update_tagging()
if tag_cooldown > 0 then
tag_cooldown = tag_cooldown - 1
return
end
local players = net.state.players
if not players then
return
end
local it = players[net.state.it]
if not it then
return
end
for id, p in pairs(players) do
if tonumber(id) ~= net.state.it and overlaps(it, p) then
net.state.it = tonumber(id) -- everyone's "it" listener fires
tag_cooldown = TAG_COOLDOWN
break
end
end
end
-- ============================================================
-- Game loop
-- ============================================================
function update_playing()
update_movement()
update_taunt()
if is_host then
update_tagging()
end
end
function _update()
if state == "menu" then
update_menu()
elseif state == "waiting" then
update_waiting()
elseif state == "playing" then
update_playing()
elseif state == "over" and key_pressed("m") then
net.leave()
_init()
end
end
-- ============================================================
-- Drawing
-- ============================================================
function draw_players()
for id, p in pairs(net.state.players or {}) do
fill_rect(p.col, p.x, p.y, PLAYER_SIZE, PLAYER_SIZE)
if tonumber(id) == net.state.it then
rect(COL_IT, p.x - 2, p.y - 2, PLAYER_SIZE + 4, PLAYER_SIZE + 4)
end
end
end
function _draw()
clear(COL_BG)
if state == "playing" or state == "over" then
draw_players()
-- Red arena border when you are the one chasing
if net.state.it == net.id() then
rect(COL_IT, 0, 0, W, H)
end
else
for i = 1, #COLORS do
fill_rect(COLORS[i], 60 * i + 20, (H - PLAYER_SIZE) / 2, PLAYER_SIZE, PLAYER_SIZE)
end
end
end