言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

Lua辞典

  1. トップページ
  2. Lua辞典
  3. ゲーム開発での Lua(Roblox / LÖVE2D)

ゲーム開発での Lua(Roblox / LÖVE2D)

『Lua』はゲーム開発の分野で広く採用されているスクリプト言語です。Roblox のゲームロジック、World of Warcraft のアドオン、LÖVE2D のゲームエンジンスクリプトなど、さまざまな環境で活用されています。軽量・高速・組み込みやすいという特性が、ゲームエンジンとの相性を高めています。

主な活用場面

環境概要
LÖVE2DLua だけで 2D ゲームを作れるオープンソースフレームワークです。main.lua にゲームループを記述します。
RobloxRoblox Studio の内部スクリプト言語として Luau(Lua の派生)を採用しています。ゲームの挙動・UI・物理制御をすべて Lua で記述します。
World of Warcraftアドオン(拡張機能)を Lua で記述できます。UI 制御・コマンド追加・チャット自動化などに使われています。
Defold2D/3D ゲームエンジン Defold のゲームロジックは Lua で記述します。コンポーネントのライフサイクルフックに関数を定義します。
Corona SDK / Solar2Dモバイル向け 2D フレームワークです。物理エンジン・アニメーション・ネットワーク処理を Lua だけで実装できます。

LÖVE2D の基本構文

-- -----------------------------------------------
-- LÖVE2D のゲームループの基本構造
-- -----------------------------------------------

-- love.load() : ゲーム起動時に一度だけ呼ばれます
function love.load()
    -- リソースの読み込みや初期化をここで行います
end

-- love.update(dt) : 毎フレーム呼ばれます(dt は前フレームからの経過秒)
function love.update(dt)
    -- ゲームロジックの更新をここで行います
end

-- love.draw() : 毎フレーム描画処理が呼ばれます
function love.draw()
    -- 画面への描画処理をここで記述します
end

-- love.keypressed(key) : キーが押された瞬間に呼ばれます
function love.keypressed(key)
    if key == "escape" then
        love.event.quit()  -- Escape キーでゲームを終了します
    end
end

サンプルコード

main.lua
-- main.lua — LÖVE2D を使ったキャラクター選択デモです。
-- Steins;Gate のキャラクターをテーブルで管理し、
-- 上下キーで選択・決定できる簡易メニューを実装しています。
--
-- 実行: love .(カレントディレクトリに main.lua が必要です)

-- -----------------------------------------------
-- キャラクターテーブルを定義します
-- -----------------------------------------------
local characters = {
    { name = "Rintaro Okabe",   title = "狂気のマッドサイエンティスト",   hp = 100, sp = 80  },
    { name = "Kurisu Makise",   title = "天才物理学者",                   hp = 90,  sp = 120 },
    { name = "Mayuri Shiina",   title = "コスプレイヤー",                 hp = 110, sp = 60  },
    { name = "Itaru Hashida",   title = "ハッカー兼オタク",               hp = 95,  sp = 100 },
    { name = "Moeka Kiryu",     title = "フォーン依存のライター",          hp = 85,  sp = 90  },
}

-- -----------------------------------------------
-- ゲーム状態をまとめたテーブルです
-- -----------------------------------------------
local state = {
    selected_index = 1,       -- 現在選択中のインデックスです
    confirmed      = false,   -- 決定済みかどうかのフラグです
    confirmed_name = "",      -- 決定したキャラクター名です
    blink_timer    = 0,       -- 点滅アニメーション用タイマーです
    blink_visible  = true,    -- 点滅中のテキストを表示するかどうかです
}

-- -----------------------------------------------
-- 色の定数を定義します(LÖVE2D は 0〜1 の範囲です)
-- -----------------------------------------------
local COLOR = {
    bg        = { 0.08, 0.08, 0.15, 1 },   -- 背景色(濃紺)です
    title     = { 0.90, 0.75, 0.20, 1 },   -- タイトル色(ゴールド)です
    selected  = { 0.30, 0.80, 1.00, 1 },   -- 選択中の色(シアン)です
    normal    = { 0.85, 0.85, 0.85, 1 },   -- 通常テキストの色です
    sub       = { 0.60, 0.60, 0.70, 1 },   -- サブテキストの色です
    hp_bar    = { 0.20, 0.80, 0.40, 1 },   -- HP バーの色(緑)です
    sp_bar    = { 0.20, 0.50, 1.00, 1 },   -- SP バーの色(青)です
    bar_bg    = { 0.20, 0.20, 0.25, 1 },   -- バー背景色です
    confirmed = { 1.00, 0.40, 0.40, 1 },   -- 決定後のハイライト色です
}

-- -----------------------------------------------
-- ヘルパー関数: 塗りつぶし矩形を描画します
-- -----------------------------------------------
local function draw_rect(x, y, w, h, color)
    love.graphics.setColor(color)
    love.graphics.rectangle("fill", x, y, w, h)
end

-- -----------------------------------------------
-- ヘルパー関数: HP/SP バーを描画します
-- -----------------------------------------------
local function draw_bar(x, y, w, h, value, max_value, color)
    -- バーの背景を描画します
    draw_rect(x, y, w, h, COLOR.bar_bg)
    -- 値に応じた幅でバーを描画します
    local fill_w = math.floor(w * (value / max_value))
    draw_rect(x, y, fill_w, h, color)
end

-- -----------------------------------------------
-- love.load() : 初期化処理です
-- -----------------------------------------------
function love.load()
    love.window.setTitle("キャラクター選択 — Steins;Gate")
    love.window.setMode(640, 480)
    love.graphics.setBackgroundColor(COLOR.bg)
end

-- -----------------------------------------------
-- love.update(dt) : 毎フレームの更新処理です
-- -----------------------------------------------
function love.update(dt)
    -- 点滅タイマーを更新します(0.5 秒ごとに切り替えます)
    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() : 描画処理です
-- -----------------------------------------------
function love.draw()
    local gfx = love.graphics

    -- タイトルを描画します
    gfx.setColor(COLOR.title)
    gfx.print("=== キャラクター選択 ===", 180, 20)

    -- キャラクター一覧を描画します
    for i, chara in ipairs(characters) do
        local y = 60 + (i - 1) * 60

        if i == state.selected_index then
            -- 選択中の行をハイライト表示します
            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

        -- 称号を表示します
        gfx.setColor(COLOR.sub)
        gfx.print(chara.title, 72, y + 18)

        -- HP バーを描画します
        draw_bar(280, y + 6, 120, 10, chara.hp, 120, COLOR.hp_bar)
        gfx.setColor(COLOR.sub)
        gfx.print("HP", 258, y + 4)

        -- SP バーを描画します
        draw_bar(440, y + 6, 120, 10, chara.sp, 120, COLOR.sp_bar)
        gfx.setColor(COLOR.sub)
        gfx.print("SP", 418, y + 4)
    end

    -- 操作説明を描画します
    gfx.setColor(COLOR.sub)
    gfx.print("↑/↓: 選択   Enter: 決定   Escape: 終了", 140, 430)

    -- 決定済みの場合はメッセージを点滅表示します
    if state.confirmed and state.blink_visible then
        gfx.setColor(COLOR.confirmed)
        gfx.print(state.confirmed_name .. " を選択しました!", 180, 390)
    end
end

-- -----------------------------------------------
-- love.keypressed(key) : キー入力処理です
-- -----------------------------------------------
function love.keypressed(key)
    if key == "up" then
        -- 上キーでカーソルを上に移動します(先頭を超えると末尾に戻ります)
        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
        -- 下キーでカーソルを下に移動します(末尾を超えると先頭に戻ります)
        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
        -- Enter キーで選択を確定します
        state.confirmed      = true
        state.confirmed_name = characters[state.selected_index].name

    elseif key == "escape" then
        love.event.quit()  -- Escape キーでゲームを終了します
    end
end
(LÖVE2D で実行: love .)
-- ウィンドウが開き、キャラクター選択画面が表示されます。

=== キャラクター選択 ===

> Rintaro Okabe   狂気のマッドサイエンティスト   HP [========  ]  SP [======    ]
  Kurisu Makise   天才物理学者                   HP [========  ]  SP [==========]
  Mayuri Shiina   コスプレイヤー                 HP [=========]   SP [=====     ]
  Itaru Hashida   ハッカー兼オタク               HP [========  ]  SP [========  ]
  Moeka Kiryu     フォーン依存のライター          HP [=======   ]  SP [=======   ]

↑/↓: 選択   Enter: 決定   Escape: 終了

-- Enter キーを押すと:
Rintaro Okabe を選択しました!(点滅表示)

Roblox(Luau)でのスクリプト例

CharacterStats.lua
-- CharacterStats.lua — Roblox Studio 向けのサーバースクリプトです。
-- Steins;Gate キャラクターのステータスを管理し、
-- RemoteEvent 経由でクライアントにデータを送信します。
--
-- Roblox の場合はスクリプトを ServerScriptService に配置して実行します。

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

-- RemoteEvent を作成します(クライアントへのデータ送信に使います)
local stats_event = Instance.new("RemoteEvent")
stats_event.Name  = "StatsEvent"
stats_event.Parent = ReplicatedStorage

-- -----------------------------------------------
-- キャラクターステータスのテーブルです
-- -----------------------------------------------
local CHARACTER_STATS = {
    ["Rintaro Okabe"] = { hp = 100, sp = 80,  ability = "未来ガジェット" },
    ["Kurisu Makise"] = { hp = 90,  sp = 120, ability = "タイムリープ理論" },
    ["Mayuri Shiina"] = { hp = 110, sp = 60,  ability = "シュタインズゲートの選択" },
    ["Itaru Hashida"] = { hp = 95,  sp = 100, ability = "ハッキング" },
    ["Moeka Kiryu"]   = { hp = 85,  sp = 90,  ability = "FB への盲信" },
}

-- -----------------------------------------------
-- プレイヤーが参加したときに呼ばれるイベントです
-- -----------------------------------------------
Players.PlayerAdded:Connect(function(player)
    print(player.Name .. " がゲームに参加しました。")

    -- ランダムにキャラクターを割り当てます
    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]

    -- クライアントにステータスを送信します
    stats_event:FireClient(player, {
        character = assigned_name,
        hp        = assigned_stats.hp,
        sp        = assigned_stats.sp,
        ability   = assigned_stats.ability,
    })

    print("割り当て完了: " .. player.Name .. " → " .. assigned_name)
end)

-- -----------------------------------------------
-- ステータスを全キャラクター分ログに出力します
-- -----------------------------------------------
print("=== キャラクターステータス一覧 ===")
for name, stats in pairs(CHARACTER_STATS) do
    print(string.format(
        "  %-20s HP:%3d  SP:%3d  特技: %s",
        name, stats.hp, stats.sp, stats.ability
    ))
end
print("初期化完了")
(Roblox Studio の出力ウィンドウに表示されます)
=== キャラクターステータス一覧 ===
  Rintaro Okabe        HP:100  SP: 80  特技: 未来ガジェット
  Kurisu Makise        HP: 90  SP:120  特技: タイムリープ理論
  Mayuri Shiina        HP:110  SP: 60  特技: シュタインズゲートの選択
  Itaru Hashida        HP: 95  SP:100  特技: ハッキング
  Moeka Kiryu          HP: 85  SP: 90  特技: FB への盲信
初期化完了

よくあるミス

LÖVE2D でグローバル変数をゲームループ内で毎フレーム生成して GC が頻発する

ゲームループ(love.update / love.draw)の中でテーブルや文字列を毎フレーム生成すると、ガベージコレクタ(GC)の負荷が高まりフレームレートが不安定になります。フレームをまたいで使う値は love.load() や外部スコープで一度だけ生成します。

-- NG(毎フレーム新しいテーブルを生成している)
function love.update(dt)
    local pos = { x = player.x + dt * 100, y = player.y }  -- 毎フレームGCの対象になる
    player.x = pos.x
end
-- OK(フィールドを直接更新してテーブルの生成を避ける)
function love.update(dt)
    player.x = player.x + dt * 100
end

love.update(dt) の dt を使わず固定値で速度計算して環境依存になる

dt(デルタタイム)は前フレームからの経過秒数です。これを掛け合わせることで、フレームレートが異なる環境でも同じ速度で動くようになります。固定値を使うとフレームレートが変わったとき速度が変わります。

-- NG(固定値で加算するとフレームレートに依存する)
function love.update(dt)
    player.x = player.x + 5   -- 60fps と 30fps で速度が 2 倍変わる
end
-- OK(dt を掛けて 1 秒あたりの移動量を定義する)
local SPEED = 300   -- 1 秒あたり 300 ピクセル
function love.update(dt)
    player.x = player.x + SPEED * dt   -- どのフレームレートでも同じ速度
end

Roblox でサーバースクリプトから LocalPlayer を取得しようとする

game.Players.LocalPlayer はクライアント側(LocalScript)でのみ有効なプロパティです。サーバースクリプト(Script)から参照すると nil が返り、予期しないエラーが発生します。

-- NG(ServerScriptService に置かれたスクリプトから LocalPlayer を使う)
local player = game.Players.LocalPlayer   -- nil が返る
print(player.Name)   -- attempt to index nil value
-- OK(サーバースクリプトでは PlayerAdded イベントから player を受け取る)
game.Players.PlayerAdded:Connect(function(player)
    print(player.Name .. " が参加しました")
end)

-- LocalPlayer を使う処理は LocalScript に書く(StarterPlayerScripts 等に配置)
local player = game.Players.LocalPlayer   -- LocalScript なら有効

概要

Lua がゲームエンジンで多用される理由は、軽量性・組み込みやすさ・柔軟なデータ表現 の三点に集約されます。テーブルひとつで配列・辞書・オブジェクトのいずれにも使えるため、ゲームのエンティティ管理や設定データの記述に適しています。LÖVE2D では love.load / love.update / love.draw の三関数をゲームループとして定義するだけでプロトタイプを素早く動かせます。Roblox(Luau)では型アノテーションやビットwise 演算子など Lua 5.x への独自拡張が加えられており、大規模なゲームロジックにも対応しています。World of Warcraft のアドオンは Lua 5.1 ベースの API を通じて UI フレームやイベントフックを操作します。いずれの環境でも テーブルを中心としたデータモデリングコールバック登録による宣言的なイベント処理 が基本パターンとなっています。Lua の C 組み込みについては C API との連携 も合わせて確認してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。