mcpField
L
lua-best-practices

lua-best-practices

github.com/overextended開発209 回インストール2 週間前に更新
リポジトリ

lua-best-practices について

FiveM リソース開発者向けの Lua 実践ガイドです。Overextended のリソースと ox_lib を基準に、コードやデータの読み込み、マニフェスト、設定、実行時 API、コード配置を説明します。

主な機能

  1. 01require と lib.load によるコード・データ読み込み
  2. 02リソースのマニフェストとエントリースクリプトの整理
  3. 03convars による設定管理
  4. 04ox_lib のキャッシュと実行時 API の利用
  5. 05CfxLua の構文と注釈の適用

活用シーン

  1. 01FiveM リソースの構成設計
  2. 02Lua モジュールとデータ読み込みの実装
  3. 03ox_lib 実行時機能の設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/overextended/skills --skill lua-best-practices

インストール後、Claude Code で/lua-best-practicesを入力して読み込まれたか確認します。

同梱ファイルagents/openai.yamlPATTERNS.md

詳細

Lua のベストプラクティス

Overextended が ox_inventory、ox_target、ox_fuel を作る方法に倣って、リソースの Lua を記述します。サイドごとにエントリースクリプトを 1 つだけ用意し、それ以外のファイルはすべて require で読み込むモジュールにします。データは lib.load で読み込み、設定は convar から読み取ります。また、独自のループを手書きするのではなく、ox_lib の cache と lib.* モジュールを使います。ox_lib 3.40.3 に基づいて確認しています。サーバーの ox_lib バージョンは、現在のディレクトリから上にたどって .ox/config.md の ox resources の行で確認してください。バージョンが古い場合は、以下の内容を該当バージョンのソースで確認してください。

@ox_lib/init.lua を含めると、グローバルの require が ox_lib 独自のローダーに置き換わります。このローダーは呼び出し元のファイルではなく、リソースのルートからパスを解決し、他のリソースのファイルも読み込めます。以下はすべて、このローダーを前提としています。

コードとデータの読み込み

呼び出し用途動作
require 'client.utils'コードモジュール:関数や状態を定義してテーブルを return するファイルリソースとサイドごとにファイルを 1 回実行し、その後の呼び出しにはキャッシュされた値を返します。そのため、すべてのモジュールが同じインスタンスを共有します。循環 require があるとエラーになります。何も返さないファイルは true を返します。
lib.load 'data.stations'データファイル(テーブルを返す data/*.lua)と、副作用のために実行するファイル呼び出すたびにファイルを実行し、新しい値を返します。キャッシュはされません。第 2 引数として環境テーブルを省略可能で指定できます。
lib.loadJson 'data.prices'JSON データファイルdata/prices.json を読み込み、デコードしたテーブルを返します。
require '@ox_core.lib.init'他のリソースの Lua ライブラリファイルを自分のリソース内で実行するため、共有状態ではなく、自分用のコピーを取得します。他のリソースの現在の状態を読み取るには、そのリソースのエクスポートを使ってください。

3 つすべてに共通するパスのルール:

  • ドットはディレクトリの区切りで、拡張子は指定しません:require 'modules.inventory.server' は modules/inventory/server.lua を読み込みます。ファイル名にドットは使えません。
  • require 'client' は、まず client.lua、次に client/init.lua を試します。
  • @resource.path は別のリソースから読み込みます。@resource だけを指定すると、そのリソースの init.lua を読み込みます。
  • クライアントで読み込むファイルは、マニフェストの files に記載する必要があります。サーバー側のファイルはディスクから読み込まれるため、記載は不要です。

使い分け:

  • コードは require で読み込みます。 使用するファイルの先頭に local utils = require 'client.utils' を置き、すべてのモジュールの末尾でテーブルを return します。
  • データは lib.load で読み込みます。 データを使用する箇所で一度呼び出します(for id, data in pairs(lib.load('data.crafting') or {}) do)。キャッシュされないため、ループ内やイベントハンドラー内では決して呼び出さないでください。
  • 複数のモジュールで共有する読み取り専用の設定は、モジュールにできます。config.lua でテーブルを返し、各ファイルで local config = require 'config' とします。
  • ファイルはマニフェストスクリプトかモジュールのどちらか一方です。 ファイルを client_scripts に記載したうえで require もすると、2 回実行されます。
  • オプションの部分は必要になったときに読み込みます:if config.ox_target then return require 'client.target' end のようにするか、対応するフレームワークを検出した分岐内でのみフレームワークブリッジを読み込みます。
  • LoadResourceFile と load を組み合わせた独自の処理は書かないでください。ox_inventory 独自の data() ローダーは非推奨であり、lib.load が推奨されています。

マニフェスト

  • すべての ox リソースと同様に、fx_version 'cerulean'、lua54 'yes'、use_experimental_fxv2_oal 'yes' を指定します。
  • shared_script '@ox_lib/init.lua' は、リソース独自のスクリプトより前に記述し、ox_lib を dependencies に含めます。
  • 各サイドにつきエントリースクリプトは1つです(client/init.lua、server/init.lua、または lib.context に応じて require 'server' か require 'client' で終わる単一の init.lua)。エントリースクリプトから、それ以外のすべてを読み込みます。
  • files には、クライアントが読み込むすべてのモジュール、データファイル、ロケールを指定します('client/*.lua'、'data/*.lua'、'locales/*.json')。クライアントは files 内のすべてをダウンロードするため、サーバー専用のコードは含めないでください。
  • ox_libs { 'locale', 'table' } は、リソースの起動時にこれらの lib モジュールを読み込みます。ここに locale を指定するとロケール文字列も読み込まれます。これは lib.locale() を自分で呼び出すより ox_lib に適した方法です。それ以外の lib.* モジュールは初回使用時に読み込まれるため、決して require しないでください。

設定

  • サーバー所有者が調整する設定は、<resource>:<setting> という名前の convar です。使用するファイルの先頭で一度だけ読み込みます。例:GetConvarInt('ox_target:debug', 0)、GetConvarBool('inventory:target', false)、GetConvar('ox_target:defaultHotkey', 'LMENU')。構造化された設定は json.decode(GetConvar(...)) を通して取得します。所有者は server.cfg で設定するため、アップデートによって変更が上書きされることはありません。
  • 構造化データ(場所、アイテム、ショップ、レシピ)は data/*.lua ファイルにテーブルとして記述し、lib.load で読み込みます。
  • 小規模なリソースでは、テーブルを返す config.lua をモジュールとして読み込めます。リソースがすでに採用している方法に従ってください。

スコープ

  • すべての変数、関数、モジュールテーブルは local にします。モジュールは local Utils = {} とし、関数は function Utils.raycast(...) のように定義して、末尾で return Utils を記述します。
  • グローバル変数は、そのサイドのすべてのファイルから読み取る状態を保持するために意図的に使います。local の付け忘れであってはなりません。同じサイドにあるリソースのスクリプトとモジュールは、すべて同じグローバルスコープを共有します。
  • ホットループ内では、ネイティブをローカルのアップバリューとしてキャッシュします。例:local GetVehiclePedIsIn = GetVehiclePedIsIn

レイアウト

コードは上から下へ、短いステップに分けて記述し、ステップの間に空行を入れ、長い行はなるべく避けます。

  • 1行につき1つのステートメントを記述します。count = 0 return false は2行に分けます。
  • ステップの間には空行を入れます。ガード、検索、処理、応答の間などです。
  • ガードを1行にするのは、短く、本文が何も返さない return または return false だけの場合です。例:if not player or not player.shop then return false end。テーブルを返すガードや、それ以外の処理を行うガードは複数行にします。
  • 1行に収まらない条件は、複数のガードに分けます。1つの if を複数行に折り返すのは避けてください。
  • 呼び出しやテーブルコンストラクターが約120文字を超える場合は、引数またはフィールドを1行ずつ記述します。それより短いものは1行にまとめます。
  • インデントには .editorconfig またはファイルで使われている形式を使用します。

実行時の ox_lib

  • cache にはクライアント上のプレイヤーの状態が保持され、100 ms ごとに更新されます。cache.ped、cache.vehicle(徒歩の場合は false)、cache.seat、cache.weapon に加え、cache.playerId と cache.serverId が含まれます。両側には cache.resource と cache.game もあります。PlayerPedId() ではなく、cache.ped を参照してください。
  • lib.onCache('vehicle', function(value, oldValue) end) は、キャッシュされた値が変わると実行されます。変化をポーリングするループの代わりに使ってください。
  • スレッド: CreateThread と Wait を使います。繰り返し処理には ox_lib の SetInterval(fn, ms) と ClearInterval(id) を使います。「場所の近くで何かを行う」場合は、距離を確認するループではなく、lib.points.new({ coords = ..., distance = ..., onEnter = ..., onExit = ..., nearby = ... }) を使ってください。値を待つには lib.waitFor(fn, errMessage, timeout) を使います。timeout ms(デフォルトは 1000)を過ぎるとエラーになります。
  • コールバック: サーバーでは lib.callback.register(name, function(source, ...) end) を登録し、クライアントでは lib.callback.await(name, delay, ...) を呼び出します。delay はクライアント側のレート制限(ms)または false です。スロットリングされた呼び出しは何も返しません。クライアントはすべての引数を選べるため、サーバーのコールバックでは処理を実行する前に各引数(型、範囲、所有権、距離)を確認してください。
  • ロケール: locales/en.json はフォールバックであり、ox:locale convar で設定された言語の下にマージされます。locale('key', ...) は string.format で書式を整え、該当するキーがない場合はキー自体を返します。
  • ログ: lib.print.error、warn、info、verbose、debug を使います。管理者は ox:printlevel convar、または単一のリソースに対しては ox:printlevel:<resource> で出力を絞り込めます。
  • 依存関係: エントリースクリプトの先頭で if not lib.checkDependency('ox_lib', '3.30.0', true) then return end を使い、最低バージョンを確認してください。任意のリソースは GetResourceState('ox_target') == 'started' でガードしてください。

CfxLua

FiveM の Lua 5.4 ランタイムには、ox リソース全体で使われる構文が追加されています。次のように記述してください。

  • 定数名にはバッククォートのハッシュを使います。`WEAPON_UNARMED` はコンパイル時にハッシュ化されます。実行時に組み立てる名前には joaat(name) を使ってください。
  • 安全なナビゲーション:data.server and data.server.export の代わりに data.server?.export を使います。
  • 複合代入:count += 1。
  • 事前確保には table.create(arraySize, hashSize)、その場でクリアするには table.wipe(t) を使います。

アノテーション

モジュール関数には LuaLS コメント(---@param、---@return、---@class)でアノテーションを付け、値の型を推論できない場合は --[[@as OxClient]] で型を絞り込んでください。ox_lib には独自のアノテーションが含まれているため、エディターで lib.* の呼び出しの型がチェックされます。

例

完全なリソース構成と各セクションの例については、PATTERNS.md を参照してください。

コメント