3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FiveM scriptの基礎と開発メモ

Last updated at Posted at 2024-10-10

FiveM Scriptの基礎と開発について

今までFiveMのScriptを開発してきたノウハウを備忘録として残しています。
一部はまだ動作未検証の部分があったり、環境依存のものもあります。筆者の環境は、QBCoreやox_lib、ox_target、ox_inventoryを使用している環境です。
ESXには対応していないのでご注意ください。

FiveM scriptの構成と役割

・fxmanifest.lua ・・・スクリプト構成を記載(記載のないものは読み込まれない)
・client.lua・・・クライアント側で動作するスクリプト(モーションなど)
・server.lua・・・サーバ側で動作するスクリプト(アイテム、お金の付与など)
・config.lua(shard.lua)・・・スクリプトを動作させるための設定ファイル(座標など)

ざっくりイメージ図

image.png

fxmanifest.luaについて

スクリプトの構成を記載しており、記載のないものは読み込まれない。

fxmanifest.lua
fx_version 'cerulean'
game 'gta5'

author 'KumaRider' --作成者名
description 'Gacha System with Sound for QBCore' --スクリプトの説明文
version '1.1.0' --スクリプトのバージョン

-- サーバースクリプト
server_scripts {
    'server/server.lua',
    'shared/shared.lua',  --server.luaでshard.luaを使用したいため記載
}

-- クライアントスクリプト
client_scripts {
    'client/client.lua',
}
--ここまでは基本的なスクリプトの構成

--ここから下はスクリプトによって必要なものを記載する。
-- QBCoreと依存関係があることを宣言
dependencies {
    'qb-core',
}

-- NUIファイル
ui_page 'html/index.html'

-- 必要なファイル
files {
    'html/index.html',
    'html/script.js',
    'html/sounds/*.ogg',
}

クライアントサイドからサーバサイドの呼び出し

client.lua
TriggerServerEvent('gacha:roll', ticketType)
server.lua
RegisterNetEvent('gacha:roll')
AddEventHandler('gacha:roll', function(ticketType)
    処理内容
end)

TriggerServerEventでサーバに対してイベントを送信(発火)する。
RegisterNetEventで発火したイベントをフックする。
AddEventHandlerで実際にどう処理をするのか記載する。

サーバサイドからクライアントサイドの呼び出し

server.lua
TriggerClientEvent('gacha:openGacha', source, ticketType)
client.lua
RegisterNetEvent('gacha:openGacha')
AddEventHandler('gacha:openGacha', function(ticketType)
    処理内容
end)

TriggerClientEcentでクライアントに対してイベントを送信(発火)する。
RegisterNetEventで発火したイベントをフックする。
AddEventHandlerで実際にどう処理をするのか記載する。

通知メッセージ(qbcore、ox_lib)

処理としては、サーバからqbcore,ox_libのイベントをクライアントに発火させる。

server.lua(qbcore)
TriggerClientEvent('QBCore:Notify', src, 'はずれ', 'success')

[src]がプレイヤー、[はずれ]がメッセージ、[success]が情報レベルでの表示

server.lua(ox_lib)
TriggerClientEvent('ox_lib:notify', src, 'You have received', 'success')

[src]がプレイヤー、[You hab received]がメッセージ、[success]が情報レベルでの表示

お金の取引(cash、bank)

処理は、サーバ側での処理で実施すること。(不正防止のため)

server.lua
--ユーザ指定
local Player = QBCore.Functions.GetPlayer(src)

--銀行口座での取引(受取)
Player.Functions.AddMoney('bank', 1000, 'Transaction_Log')
--銀行口座での取引(支払)
Player.Functions.RemoveMoney('bank', 1000, 'Transaction_Log')
server.lua
--ユーザ指定
local Player = QBCore.Functions.GetPlayer(src)

--現金での取引(受取)
Player.Functions.AddMoney('cash', 1000, 'Transaction_Log')
--現金での取引(支払)
Player.Functions.RemoveMoney('cash', 1000, 'Transaction_Log')

Transaction_Logは任意の文字列で、なんの取引内容かを特定するためのテキストを入力する。

アイテムの取引

アイテムの取引処理もサーバ側で実施すること。(不正防止のため)
cashはアイテムとしてもあるため、以下でのコードでも付与可能。

server.lua
--ユーザ指定
local Player = QBCore.Functions.GetPlayer(src)

--アイテムの付与
Player.Functions.AddItem(item_name, 10)
--アイテム付与の通知
TriggerClientEvent('inventory:client:ItemBox', src, item_name, "add")

--アイテムの削除
Player.Functions.RemoveItem(item_name, 10)
--アイテム削除の通知(未検証)
TriggerClientEvent('inventory:client:ItemBox', src, item_name, "remove")

--cashの付与
Player.Functions.AddItem(cash, 10)
--cashの削除
Player.Functions.RemoveItem(cash, 10)

アイテム使用のフック

server.lua
QBCore.Functions.CreateUseableItem('example_item', function(source, item)
    処理内容
end)

※検証している中でox_inventoryからアイテム使用を以下で検知しようとしたが、うまく動作しなかったため、基本的には、QBCoreでフックするようにしている。
(2024.10.15追記)
アイテムの使用をフックするのではなく、アイテムを使用できるように登録するニュアンスのほうが正しい。

server.lua(ox_inbentory)
--うまくフックできなかったコード
RegisterNetEvent('ox_inventory:useItem', function(data)

座標と向きと範囲

vector3やvector4で確認する。(vector4は向きも込)
config.luaで設定するときは範囲も含めて以下のように記述することが多い。

config.lua
coords = vector3(31.84, -170.77, 54.42), --3つ目は高さ
heading = 162.9, --向き
radius = 5.0 --範囲

範囲は、球体の範囲になるので、広げると上下にも広がるので注意が必要

アニメーション関連

client.luaに定義する。amb@prop_human_bum_bin@baseがアニメーションの種類。

client.lua
TaskPlayAnim(playerPed, "amb@prop_human_bum_bin@base", "base", 8.0, -8.0, 3000, 16, 0, false, false, false)

アニメーションの種類は以下のサイトに纏められており、猫のアニメーションなども記載されている。

オブジェクト(モデル)関連

オブジェクトは、クライアント側で生成する。
以下のコードは、自動販売機のモデルを生成するもので、config.luaで管理する構成でのコードである。

config.lua(shard.lua)
Config.VendingMachines = {
    {
        id = 'Vending_Machine_001',
        model = 'prop_vend_snak_01', -- 食品の自動販売機
        coords = vector3(-572.53, -1071.41, 21.33), -- 猫カフェ前座標
        heading = 0.0,
client.lua
Citizen.CreateThread(function()
    -- Configで定義された各自動販売機を配置
    for _, machineConfig in ipairs(Config.VendingMachines) do
        local vendingMachineModel = GetHashKey(machineConfig.model)
        local vendingMachineCoords = machineConfig.coords
        local vendingMachineHeading = machineConfig.heading

        -- モデルがロードされているか確認
        RequestModel(vendingMachineModel)
        while not HasModelLoaded(vendingMachineModel) do
            Citizen.Wait(500)
        end

        -- 自動販売機を生成し、配置
        local vendingMachineEntity = CreateObject(vendingMachineModel, vendingMachineCoords.x, vendingMachineCoords.y, vendingMachineCoords.z, true, false, true)
        SetEntityHeading(vendingMachineEntity, vendingMachineHeading) --向き
        FreezeEntityPosition(vendingMachineEntity, true) --動かないよう設定
        SetModelAsNoLongerNeeded(vendingMachineModel)
    end
end)

解説メモ
• CreateObject は、指定したモデルを指定した座標に生成するための関数。
• 第1引数:ハッシュ化された自動販売機のモデル。
• 第2〜4引数:生成する場所の座標(X, Y, Z)。
• 第5引数(true):物理的な衝突を有効にするかどうか。
• 第6引数(false):生成されたオブジェクトが動的に生成されるかどうか(falseで静的なオブジェクト)。
• 第7引数(true):ネットワーク化するかどうか(true で他のプレイヤーとも同期されます)。

以下の2つのサイトからObjectNameを検索して、組み込んでいます。

ox_targetとメニュー

心の目で見た際に出てくる項目を出すためのコード

client.lua
-- ox_target Interaction (座標ベースのターゲットゾーンを追加)
for _, machineConfig in ipairs(Config.VendingMachines) do
    exports['ox_target']:addSphereZone({
        coords = machineConfig.coords,
        radius = 2.0,
        options = {
            --商品購入の項目(1つ目)
            {
                name = 'vendingMachineBuy',
                label = '商品を購入する',
                icon = 'fa-solid fa-shopping-cart',
                onSelect = function(data)
                    showProductMenu(machineConfig.coords)
                end
            },
            --売上金回収の項目(2つ目)
            {
                name = 'vendingMachineCollect',
                label = '売上金を回収する',
                icon = 'fa-solid fa-money-bill-wave',
                onSelect = function(data)
                    collectEarnings(machineConfig.coords)
                end
            }
        }
    })
end

以下の画像は3つだけど、「商品を購入する」と「売上金を回収する」を表示するためのコード。
image.png

選択後の処理(onSelect)は、別の関数に飛ばして処理を記載している。
おそらく直接ここに記載しても動作すると思われる。

HTMLとか、JavaScriptとか

NUIを利用して連携する事が可能。

ゲーム内に以下のようにHTMLベースのページを表示させ、JavaScriptと連携させて商品の購入等を実装。

■VipShopのスクリプト(自作)
image.png

具体的なJavaScriptとの連携については、別の記事を作成予定。
(簡単な連携は以下の音声鳴動で記載)

音声鳴動

HTMLとJavaScriptを用いて音声を鳴動させる。
以下のサンプルは、server.luでclient.luaのイベントを発火させて、client.luaからHTML/Javascriptと連携して、音声を鳴動させている。

fxmanifest.lua
-- NUIファイル
ui_page 'html/index.html'

-- 必要なファイル
files {
    'html/index.html',
    'html/script.js',
    'html/sounds/*.ogg',
}
server.lua
TriggerClientEvent('gacha:playSound', src, 'get_s.ogg')
client.lua
RegisterNetEvent('gacha:playSound')
AddEventHandler('gacha:playSound', function(soundFile)
    SendNUIMessage({
        type = "playSound",
        sound = 'sounds/' .. soundFile,
        volume = 0.1 -- 必要に応じて調整
    })
end)
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Gacha Sound System</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <script src="script.js"></script>
  </body>
</html>
script.js
window.addEventListener("message", function (event) {
  const data = event.data;

  if (data.type === "playSound") {
    const notificationSound = new Audio(data.sound);
    notificationSound.volume = data.volume || 1.0;
    notificationSound.play();
  }
});

jobの取得とjobレベルの取得

client.lua
local playerJob = GetPlayerJob()
job_name = playerJob.name
job_lebel = playerJob.grade.level
job_onduty = playerJob.onduty --未検証(出勤状態かの取得)

jobのon dutyの人数取得(出勤状態の取得)

jobでのon dutyを把握するためには、サーバで確認する必要があるので、サーバ側でコードを作成する。

server.lua
local src = source
local players = QBCore.Functions.GetPlayers(src) --サーバ上のすべてのプレイヤー取得
local uwuCount = 0

for i = 1, #players do --#playersでサーバの人数分(要素数分)ループする。
    local player = QBCore.Functions.GetPlayer(players[i])
    if (player.PlayerData.job.name == "uwu" and player.PlayerData.job.onduty) then
        uwuCount = uwuCount + 1
    end
end

その他

一旦はここまでで順次追記していこうと思います。
あと別記事で作成したスクリプト記事も書こうと思います。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?