Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. Lua Dictionary
  3. Lua in Game Development (Roblox / LÖVE2D)

Lua in Game Development (Roblox / LÖVE2D)

Lua is widely adopted in game development. It is used in many environments including Roblox game logic, World of Warcraft add-ons, and LÖVE2D game engine scripts. Its lightweight, fast, and embeddable nature makes it an excellent fit for game engines.

Main Use Cases

EnvironmentDescription
LÖVE2DAn open-source framework for building 2D games entirely in Lua. Write the game loop in main.lua.
RobloxRoblox Studio uses Luau (a Lua derivative) as its internal scripting language. All game behavior, UI, and physics control are written in Lua.
World of WarcraftAdd-ons (extensions) can be written in Lua. Used for UI control, command additions, and chat automation.
DefoldGame logic in the 2D/3D engine Defold is written in Lua. Define functions in component lifecycle hooks.
Corona SDK / Solar2DA mobile-oriented 2D framework. Physics, animation, and networking can all be implemented in Lua alone.

LÖVE2D Basic Syntax

-- -----------------------------------------------
-- Basic structure of the LÖVE2D game loop
-- -----------------------------------------------

-- love.load(): called once when the game starts
function love.load()
    -- load resources and perform initialization here
end

-- love.update(dt): called every frame (dt is seconds elapsed since the previous frame)
function love.update(dt)
    -- update game logic here
end

-- love.draw(): called every frame for rendering
function love.draw()
    -- write drawing code here
end

-- love.keypressed(key): called the moment a key is pressed
function love.keypressed(key)
    if key == "escape" then
        love.event.quit()  -- quit the game on Escape key
    end
end

Sample Code

main.lua
-- main.lua — character selection demo using LÖVE2D
-- Manages Steins;Gate characters in a table and implements
-- a simple menu where you can select with arrow keys and confirm with Enter
--
-- Run: love . (main.lua must be in the current directory)

-- -----------------------------------------------
-- Define the character table
-- -----------------------------------------------
local characters = {
    { name = "Rintaro Okabe",   title = "Mad Scientist",             hp = 100, sp = 80  },
    { name = "Kurisu Makise",   title = "Genius Physicist",          hp = 90,  sp = 120 },
    { name = "Mayuri Shiina",   title = "Cosplayer",                 hp = 110, sp = 60  },
    { name = "Itaru Hashida",   title = "Hacker and Otaku",          hp = 95,  sp = 100 },
    { name = "Moeka Kiryu",     title = "Phone-Dependent Writer",    hp = 85,  sp = 90  },
}

-- -----------------------------------------------
-- Table holding the game state
-- -----------------------------------------------
local state = {
    selected_index = 1,       -- currently selected index
    confirmed      = false,   -- whether a selection has been confirmed
    confirmed_name = "",      -- name of the confirmed character
    blink_timer    = 0,       -- timer for blink animation
    blink_visible  = true,    -- whether the blinking text is visible
}

-- -----------------------------------------------
-- Color constants (LÖVE2D uses 0-1 range)
-- -----------------------------------------------
local COLOR = {
    bg        = { 0.08, 0.08, 0.15, 1 },   -- background color (dark navy)
    title     = { 0.90, 0.75, 0.20, 1 },   -- title color (gold)
    selected  = { 0.30, 0.80, 1.00, 1 },   -- selected item color (cyan)
    normal    = { 0.85, 0.85, 0.85, 1 },   -- normal text color
    sub       = { 0.60, 0.60, 0.70, 1 },   -- subtext color
    hp_bar    = { 0.20, 0.80, 0.40, 1 },   -- HP bar color (green)
    sp_bar    = { 0.20, 0.50, 1.00, 1 },   -- SP bar color (blue)
    bar_bg    = { 0.20, 0.20, 0.25, 1 },   -- bar background color
    confirmed = { 1.00, 0.40, 0.40, 1 },   -- highlight color after confirmation
}

-- -----------------------------------------------
-- Helper function: draw a filled rectangle
-- -----------------------------------------------
local function draw_rect(x, y, w, h, color)
    love.graphics.setColor(color)
    love.graphics.rectangle("fill", x, y, w, h)
end

-- -----------------------------------------------
-- Helper function: draw an HP/SP bar
-- -----------------------------------------------
local function draw_bar(x, y, w, h, value, max_value, color)
    -- draw the bar background
    draw_rect(x, y, w, h, COLOR.bar_bg)
    -- draw the fill proportional to the value
    local fill_w = math.floor(w * (value / max_value))
    draw_rect(x, y, fill_w, h, color)
end

-- -----------------------------------------------
-- love.load(): initialization
-- -----------------------------------------------
function love.load()
    love.window.setTitle("Character Selection — Steins;Gate")
    love.window.setMode(640, 480)
    love.graphics.setBackgroundColor(COLOR.bg)
end

-- -----------------------------------------------
-- love.update(dt): per-frame update
-- -----------------------------------------------
function love.update(dt)
    -- update the blink timer (toggles every 0.5 seconds)
    state.blink_timer = state.blink_timer + dt
    if state.blink_timer >= 0.5 then
        state.blink_timer   = 0
        state.blink_visible = not state.blink_visible
    end
end

-- -----------------------------------------------
-- love.draw(): rendering
-- -----------------------------------------------
function love.draw()
    local gfx = love.graphics

    -- draw title
    gfx.setColor(COLOR.title)
    gfx.print("=== Character Selection ===", 180, 20)

    -- draw character list
    for i, chara in ipairs(characters) do
        local y = 60 + (i - 1) * 60

        if i == state.selected_index then
            -- highlight the selected row
            draw_rect(40, y - 4, 560, 52, { 0.15, 0.25, 0.35, 1 })
            gfx.setColor(COLOR.selected)
            gfx.print("> " .. chara.name, 52, y)
        else
            gfx.setColor(COLOR.normal)
            gfx.print("  " .. chara.name, 52, y)
        end

        -- display the title
        gfx.setColor(COLOR.sub)
        gfx.print(chara.title, 72, y + 18)

        -- draw HP bar
        draw_bar(280, y + 6, 120, 10, chara.hp, 120, COLOR.hp_bar)
        gfx.setColor(COLOR.sub)
        gfx.print("HP", 258, y + 4)

        -- draw SP bar
        draw_bar(440, y + 6, 120, 10, chara.sp, 120, COLOR.sp_bar)
        gfx.setColor(COLOR.sub)
        gfx.print("SP", 418, y + 4)
    end

    -- draw instructions
    gfx.setColor(COLOR.sub)
    gfx.print("Up/Down: select   Enter: confirm   Escape: quit", 140, 430)

    -- if confirmed, display the message with blinking
    if state.confirmed and state.blink_visible then
        gfx.setColor(COLOR.confirmed)
        gfx.print(state.confirmed_name .. " selected!", 180, 390)
    end
end

-- -----------------------------------------------
-- love.keypressed(key): key input handling
-- -----------------------------------------------
function love.keypressed(key)
    if key == "up" then
        -- move cursor up (wrap to end when passing the first item)
        state.selected_index = state.selected_index - 1
        if state.selected_index < 1 then
            state.selected_index = #characters
        end
        state.confirmed = false

    elseif key == "down" then
        -- move cursor down (wrap to start when passing the last item)
        state.selected_index = state.selected_index + 1
        if state.selected_index > #characters then
            state.selected_index = 1
        end
        state.confirmed = false

    elseif key == "return" or key == "kpenter" then
        -- confirm the selection on Enter key
        state.confirmed      = true
        state.confirmed_name = characters[state.selected_index].name

    elseif key == "escape" then
        love.event.quit()  -- quit the game on Escape key
    end
end
(Run in LÖVE2D: love .)
-- A window opens and the character selection screen is displayed.

=== Character Selection ===

> Rintaro Okabe   Mad Scientist            HP [========  ]  SP [======    ]
  Kurisu Makise   Genius Physicist          HP [========  ]  SP [==========]
  Mayuri Shiina   Cosplayer                 HP [=========]   SP [=====     ]
  Itaru Hashida   Hacker and Otaku          HP [========  ]  SP [========  ]
  Moeka Kiryu     Phone-Dependent Writer    HP [=======   ]  SP [=======   ]

Up/Down: select   Enter: confirm   Escape: quit

-- After pressing Enter:
Rintaro Okabe selected! (blinking)

Roblox (Luau) Script Example

CharacterStats.lua
-- CharacterStats.lua — server script for Roblox Studio
-- Manages Steins;Gate character stats and sends data
-- to the client via a RemoteEvent
--
-- Place the script in ServerScriptService in Roblox and run it

local Players           = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Create a RemoteEvent (used to send data to the client)
local stats_event        = Instance.new("RemoteEvent")
stats_event.Name         = "StatsEvent"
stats_event.Parent       = ReplicatedStorage

-- -----------------------------------------------
-- Character stats table
-- -----------------------------------------------
local CHARACTER_STATS = {
    ["Rintaro Okabe"] = { hp = 100, sp = 80,  ability = "Future Gadget"        },
    ["Kurisu Makise"] = { hp = 90,  sp = 120, ability = "Time Leap Theory"      },
    ["Mayuri Shiina"] = { hp = 110, sp = 60,  ability = "Steins Gate's Choice"  },
    ["Itaru Hashida"] = { hp = 95,  sp = 100, ability = "Hacking"               },
    ["Moeka Kiryu"]   = { hp = 85,  sp = 90,  ability = "Blind Faith in FB"     },
}

-- -----------------------------------------------
-- Event called when a player joins
-- -----------------------------------------------
Players.PlayerAdded:Connect(function(player)
    print(player.Name .. " joined the game.")

    -- Randomly assign a character
    local names = {}
    for name in pairs(CHARACTER_STATS) do
        table.insert(names, name)
    end
    local assigned_name  = names[math.random(1, #names)]
    local assigned_stats = CHARACTER_STATS[assigned_name]

    -- Send stats to the client
    stats_event:FireClient(player, {
        character = assigned_name,
        hp        = assigned_stats.hp,
        sp        = assigned_stats.sp,
        ability   = assigned_stats.ability,
    })

    print("Assignment complete: " .. player.Name .. " -> " .. assigned_name)
end)

-- -----------------------------------------------
-- Log stats for all characters
-- -----------------------------------------------
print("=== Character Stats List ===")
for name, stats in pairs(CHARACTER_STATS) do
    print(string.format(
        "  %-20s HP:%3d  SP:%3d  Ability: %s",
        name, stats.hp, stats.sp, stats.ability
    ))
end
print("Initialization complete")
(Displayed in the Roblox Studio output window)
=== Character Stats List ===
  Rintaro Okabe        HP:100  SP: 80  Ability: Future Gadget
  Kurisu Makise        HP: 90  SP:120  Ability: Time Leap Theory
  Mayuri Shiina        HP:110  SP: 60  Ability: Steins Gate's Choice
  Itaru Hashida        HP: 95  SP:100  Ability: Hacking
  Moeka Kiryu          HP: 85  SP: 90  Ability: Blind Faith in FB
Initialization complete

Common Mistakes

Creating global variables inside the game loop every frame in LÖVE2D causes frequent GC pauses

Creating tables or strings every frame inside the game loop (love.update / love.draw) increases garbage collector (GC) load and causes unstable frame rates. Values that need to persist across frames should be created only once in love.load() or an outer scope.

-- NG (a new table is created every frame)
function love.update(dt)
    local pos = { x = player.x + dt * 100, y = player.y }  -- becomes GC target every frame
    player.x = pos.x
end
-- OK (update fields directly to avoid creating a table)
function love.update(dt)
    player.x = player.x + dt * 100
end

Not using love.update(dt)'s dt for speed calculations makes behavior environment-dependent

dt (delta time) is the number of seconds elapsed since the previous frame. Multiplying by it ensures the same speed regardless of frame rate differences between environments. Using a fixed value causes speed to change when the frame rate changes.

-- NG (adding a fixed value makes speed frame-rate-dependent)
function love.update(dt)
    player.x = player.x + 5   -- speed doubles between 60fps and 30fps
end
-- OK (multiply by dt to define movement per second)
local SPEED = 300   -- 300 pixels per second
function love.update(dt)
    player.x = player.x + SPEED * dt   -- same speed at any frame rate
end

Trying to get LocalPlayer from a server script in Roblox

game.Players.LocalPlayer is a property only valid on the client side (LocalScript). Referencing it from a server script (Script) returns nil and causes unexpected errors.

-- NG (using LocalPlayer from a script placed in ServerScriptService)
local player = game.Players.LocalPlayer   -- nil is returned
print(player.Name)   -- attempt to index nil value
-- OK (in a server script, receive the player from the PlayerAdded event)
game.Players.PlayerAdded:Connect(function(player)
    print(player.Name .. " joined")
end)

-- Code using LocalPlayer should be written in a LocalScript (placed in StarterPlayerScripts, etc.)
local player = game.Players.LocalPlayer   -- valid in a LocalScript

Overview

The reasons Lua is widely used in game engines come down to three points: lightweight, easy to embed, and flexible data representation. A single table can serve as an array, dictionary, or object, making it well-suited for game entity management and configuration data. In LÖVE2D, you can quickly get a prototype running by simply defining three functions as the game loop: love.load, love.update, and love.draw. Roblox (Luau) adds its own extensions to Lua 5.x including type annotations and bitwise operators, making it suitable for large-scale game logic. World of Warcraft add-ons operate UI frames and event hooks through an API based on Lua 5.1. In all of these environments, table-centric data modeling and declarative event handling via callback registration are the fundamental patterns. For Lua embedded in C, see C API Integration as well.

If you find any errors or copyright issues, please .