PalForge
API リファレンス

Player

プレイヤーが今どこに立っているかを調べて、その近くにものを置く

このページでできるようになること

  • プレイヤーが今どこに立っているかを調べる
  • プレイヤーの足元や、少し離れた場所にパルを出す
  • プレイヤーを囲むように、複数のものを円状に並べる
  • プレイヤーと建物の距離を測る
  • プレイヤーがワールドに入るまで待ってから、上のことを実行する

プレイヤーの居場所を知る

Player には関数が 3 つあります。プレイヤーの位置が必要になったら、そのつど呼び出します。

local me = Player.character()                 -- APalPlayerCharacter, or nil
local at = Player.coordinate()                -- a Coord in centimetres, or nil
local up = Player.coordinateOffset(0, 0, 200) -- the same point, 2 m higher, or nil

3 つとも、プレイヤーがワールドにいないときは nil を返します。使う前に値を確認して ください。

関数戻り値nil になる条件
Player.character()ローカルの APalPlayerCharacter有効なプレイヤーポーンが存在しない
Player.coordinate()Coordxyz をセンチメートルで)ポーンがない、または座標の読み取りに失敗した
Player.coordinateOffset(dx, dy, dz)上の座標をオフセットした Coordcoordinate() と同じ条件

呼び出し方は 2 通りあります。どちらも同じテーブルです。

local api = require("palforge.api")

api.Player.coordinate()   -- namespaced access
Player.coordinate()       -- the installed global; the same table

位置は Coord、つまり数値 3 つの小さなテーブルとして返ります。 Coord の形を参照してください。

character

Player.character() は、ワールドにいるプレイヤー自身のキャラクターを返します。Unreal の 用語では「ポーン」、つまりプレイヤーが操作して歩き回っているアクターのことです。 FindFirstOf("PalPlayerCharacter") でポーンを探し、IsValid() を確認してから返します。 途中で何かが失敗すれば nil になります。検索は pcall の中で実行されるので、検索が例外を 投げてもエラーにはならず nil が返るだけです。

local me = Player.character()
if me then
    Effect.get("example:Regen"):apply(me)   -- the effect is defined elsewhere in the pack
end

取得は使う場所で行ってください。検索は呼び出しのたびに実行され、キャッシュは一切ありません。 そしてポーンはワールドをロードするたびに作り直されるため、起動時に変数へ入れておいた キャラクターは、もう存在しないものを指しています。

local event = require("palforge.core.event")

-- wrong: captured once, at mod load, when there is no world at all
local me = Player.character()
event.on("tick", function() Effect.get("example:Regen"):apply(me) end)

-- right: re-read per use, and guard
event.every(5000, function()
    local me = Player.character()
    if me then Effect.get("example:Regen"):apply(me) end
end)

返ってくるのは PalForge のオブジェクトではなく、ゲーム自身のアクターです。ゲームがそのアクター に載せているものには手が届きますが、それはこの API の外側なので、pcall の中に閉じ込めて ください(後述の「プレイヤーの正面にスポーンさせる」レシピがまさにそれです)。

coordinate

Player.coordinate() はポーンを探して、その位置を読み取ります。まず K2_GetActorLocation を試し、なければ GetActorLocation にフォールバックします。ポーンが ない場合も、読み取りが例外を投げた場合も nil になります。

返されるテーブルは、同じ 3 つの数値を 3 通りのキーで持ちます。そのため Lua 的なコードにも、 UE 的なコードにも、位置を順番に受け取るヘルパーにも、変換せずそのまま渡せます。

local c = Player.coordinate()
if c then
    print(c.x, c.y, c.z)          -- lowercase, the documented Coord shape
    print(c.X, c.Y, c.Z)          -- UE-style, same numbers
    print(c[1], c[2], c[3])       -- array form, same numbers
end

これはスナップショットであり、ライブビューではありません。呼び出しごとに新しいテーブルが 作られ、プレイヤーに追従することはありません。最新の値が必要なら再度呼び出してください。

coordinateOffset

Player.coordinateOffset(dx, dy, dz) は、プレイヤーの位置の各軸にオフセットを足したもの です。「ちょっとあっち」を手早く指定するための関数です。nil になる条件は coordinate() とまったく同じで、省略した引数は 0 として扱われます。

Player.coordinateOffset(300, 0, 0)      -- 3 m along +X
Player.coordinateOffset(0, 0, 200)      -- 2 m straight up
Player.coordinateOffset(nil, nil, 50)   -- only lift by 50 cm

単位はセンチメートルなので、100 が 1 メートルです。これは建築グリッドと同じ単位です (core.spatial.GRID_CM100、1 セルが 1 メートル)。z が上方向です。

オフセットはプレイヤー基準ではなく、ワールドの軸に対して適用されます。プレイヤーがどちらを 向いているかは考慮されないため、coordinateOffset(600, 0, 0) はプレイヤーが見ていようが 背を向けていようが +X 方向に 6 m の地点です。正面の地点が必要なら、後述のレシピを参照して ください。

Coord の形

Coord は、センチメートル単位の数値 3 つを持つテーブルです。形はこれだけです。

Prop

Type

同じ内容はゲーム内から出力することもできます。

local schema = require("palforge.core.schema")
print(schema.help("Coord"))
Coord {
  x             number     (required) world X in centimetres
  y             number     (required) world Y in centimetres
  z             number     (required) world Z in centimetres
}

これを主に受け取るのが Pal.Handle:spawn で、名前付きの形と配列の形の両方を受け取ります。

Pal.get("ChickenPal"):spawn(Player.coordinate())            -- named form, straight through
Pal.get("ChickenPal"):spawn{ at = { x = 12000, y = -3400, z = 500 } }
Pal.get("ChickenPal"):spawn{ at = { 12000, -3400, 500 } }   -- array form

:spawn は引数に x または [1] があるかどうかで座標とオプションテーブルを見分け、 各軸を a.x or a[1] として読み取ります。coordinate() は両方のキーを埋めて返すので、 このモジュールの戻り値はどちらの位置でも動きます。

nil が返るとき

3 つの関数は、有効なプレイヤーポーンが存在しない間はすべて nil を返します。

  • Mod のロード時、およびタイトル画面 — まだワールドに入っていない
  • ロード画面の最中 — ポーンが構築されている途中
  • ワールドを抜けたあと、次のワールドに入るまで

nil は異常ではなく正常な戻り値です。FindFirstOf の検索も座標の読み取りもそれぞれ pcall で保護されているため、ネイティブ呼び出しが例外を投げた場合も nil が返ります。 その代わり、確認する責任はすべて呼び出し側にあります。値を使う前にチェックしてください。

nil をそのまま渡すと、多くの場合エラーにならず静かに進みます。Effect.Handle:apply(target)target or GLOBAL をキーにして適用を管理するため、ワールドがない状態で apply(Player.character()) を呼ぶと、エフェクトはプレイヤーではなくグローバルのバケットに 適用され、成功したように見えます。先にガードしてください。

ワールドを待つなら world.ready チャネルを購読します。これは、プレイヤーがロード済みの ワールドにいることを PalForge が知らせる合図です。

local event = require("palforge.core.event")
local log   = require("palforge.utils.log").scope("example")

event.on("world.ready", function()
    local at = Player.coordinate()
    if not at then return end
    log.info(string.format("world ready at %.0f %.0f %.0f", at.x, at.y, at.z))
end)

world.ready を駆動しているのは、まさにこのポーンのポーリングです。core/eventFindFirstOf("PalPlayerCharacter") を約 1 秒おきに確認し、有効な結果が 5 回連続したところ でチャネルを emit します。したがって Player.character()world.ready が発火する数秒 「前」からポーンを返しうるし、その後いつでもまた nil を返しはじめる可能性があります。 このチャネルは処理を開始するのに適した場所ですが、nil チェックを省く理由にはなりません。

レシピ

プレイヤーの足元にスポーンさせる

content/spawn_here.lua
local event = require("palforge.core.event")
local log   = require("palforge.utils.log").scope("example")

---Spawn `charId` where the player stands, lifted 50 cm so it settles onto the ground
---instead of floating. Returns false when there is no world.
local function spawnHere(charId, level)
    local at = Player.coordinateOffset(0, 0, 50)
    if not at then
        log.warn("no world yet - nothing spawned")
        return false
    end
    return Pal.get(charId):spawn{ at = at, level = level or 5 }   -- issued; arrives seconds later
end

event.on("world.ready", function()
    spawnHere("ChickenPal", 5)
end)

座標指定のスポーンは即座ではなく、スポーンのあとに移動が続く形です。ゲーム自身の呼び出しはプレイヤーの すぐそばにパルを出し、PalForge はそれが現れるのを待ってから指定座標へ移動させます。着地はぴったりです。 ここまで全体で 4〜8 秒かかるので、見た目には「自分の隣に現れてから移動していく」形になります。

:spawn はそのどれも起きないうちに返るので、戻り値は「呼び出しが発行された」だけを意味します。 Pal を参照してください。

CheatManagerEnabler mod は不要です。core/spawn はまず生きている PalCheatManager を探し、 次にプレイヤーコントローラー上のものを探し、どちらも無ければ自分で作ります。 PalPlayerController 自身の CheatClass に対する StaticConstructObject(それが null なら /Script/Pal.PalCheatManager、さらに /Script/Engine.CheatManager へフォールバック)で、作った オブジェクトはコントローラーに取り付けられるので、これはスポーンごとではなくセッションごとに 1 回だけ起きます。ワールドへのスポーンが本当に必要とするのはプレイヤーコントローラーです。ワールド 未読み込みや未接続の間は、ゲームに届く前に false を返し、作ることもできなかったと警告します。

プレイヤーの正面にスポーンさせる

「今どちらを向いているか」を返す専用のヘルパーはありません。キャラクターから前方ベクトルを 取り、XY 平面に潰して正規化し、座標に足してください。

content/spawn_ahead.lua
---The point `distanceCm` ahead of where the player is facing, or nil.
local function inFront(distanceCm)
    local me = Player.character()
    local at = Player.coordinate()
    if not (me and at) then return nil end

    local fwd
    pcall(function() fwd = me:GetActorForwardVector() end)
    local fx, fy = (fwd and fwd.X) or 1.0, (fwd and fwd.Y) or 0.0
    local mag = math.sqrt(fx * fx + fy * fy)
    if mag < 0.01 then fx, fy, mag = 1.0, 0.0, 1.0 end
    fx, fy = fx / mag, fy / mag

    return { x = at.x + fx * distanceCm, y = at.y + fy * distanceCm, z = at.z + 50 }
end

local at = inFront(600)   -- 6 m ahead
if at then
    Pal.get("ChickenPal"):spawn{ at = at, level = 10 }   -- lands exactly here, seconds later
end

GetActorForwardVector はポーンに対する生の Unreal 呼び出しで、この API の一部ではありま せん。そのため pcall の中に置き、失敗時の方向をフォールバックとして用意しています。使う のは X と Y の成分だけです。3D ベクトルをそのまま正規化するとカメラの上下でターゲットが 傾き、パルが空中や地面の下にスポーンしてしまいます。

プレイヤーの周囲に円状に配置する

coordinateOffset が最も効くのは、1 つの原点のまわりに複数の地点が必要なときです。

content/ring.lua
local ring = { { 400, 0 }, { -400, 0 }, { 0, 400 }, { 0, -400 } }

for _, d in ipairs(ring) do
    local at = Player.coordinateOffset(d[1], d[2], 50)
    if at then
        Pal.get("SheepBall"):spawn{ at = at, level = 3 }
    end
end

ガードは呼び出しごとに個別に書きます。coordinateOffset は毎回ポーンを読み直すため、 ループの途中でプレイヤーがワールドを抜けた場合、残りの反復は古い座標ではなく nil を 受け取ります。

プレイヤーが何を持っているか

ローカルプレイヤーのインベントリは読み取れます。しかもこの読み取りは、このビルドで唯一、端から端 まで計測されているアイテム経路です。Item.Handle:count()PalUtility の CDO からプレイヤー、 プレイヤーステート、InventoryData とたどり、ゲーム自身の CountItemNum に尋ねます。ロード済みの セーブで Wood に対して素の Lua の数値として 135 が返り、その連鎖の各段で実在のオブジェクトが 表示されました。

content/inventory_report.lua
local event = require("palforge.core.event")
local log   = require("palforge.utils.log").scope("example")

event.on("world.ready", function()
    for _, id in ipairs({ "Wood", "Stone", "Berries" }) do
        local n = Item.get(id):count()
        log.info(string.format("%-8s %s", id, n and tostring(n) or "unknown"))
    end
end)

nil不明という意味で、0 ではありません。ワールドが無い、プレイヤーが居ない、あるいは読み 取りが答えなかった、のいずれかです。Player.coordinate() とまったく同じようにガードしてください。

API を飛び越えて自分で CountItemNum を呼ぶこと自体は構いませんが、id は必ず包んでください。 inv:CountItemNum(FName("Wood")) は答えますが、FName が宣言されているところに素の Lua 文字列を 渡す inv:CountItemNum("Wood") は UE4SS の引数マーシャリングの内部でフォルトし、Palworld を落とし ます。pcall はそれを見られません。これは実際に、成功した読み取りの次の行で起きました。64 ビットの 兄弟 CountItemNum64 が PalForge のどこでも呼ばれていないのも同じ理由です。クラスの関数一覧には ありますが、そのパラメーターリストは読まれたことがなく、宣言を当て推量することの代償はすでに払って います。

ワールド上の何かとの距離を測る

ライブな建築は pos を持ち、これも x, y, z をセンチメートルで表す同じ形です。したがって 単純な距離計算に他のものは要りません。

content/nearest_palbox.lua
---The live PalBoxV2 nearest to the player, plus its distance in centimetres.
local function nearestPalBox()
    local me = Player.coordinate()
    if not me then return nil end

    local best, bestD2
    for _, inst in ipairs(Building.get("PalBoxV2"):instances()) do
        local p = inst.pos
        if p then
            local dx, dy, dz = p.x - me.x, p.y - me.y, p.z - me.z
            local d2 = dx * dx + dy * dy + dz * dz
            if not bestD2 or d2 < bestD2 then best, bestD2 = inst, d2 end
        end
    end
    if not best then return nil end
    return best, math.sqrt(bestD2)
end

local box, distance = nearestPalBox()
if box then
    print(string.format("nearest PalBox is %.1f m away", distance / 100))
end

まとめ

  • Player.coordinate() はプレイヤーの立っている位置です。単位はセンチメートルで、100 が 1 メートル、z が上方向です。
  • Player.coordinateOffset(dx, dy, dz) はその位置をワールド軸方向にずらした地点です。「自分のとなり」を短く書けます。
  • Player.character() はプレイヤー自身のアクターです。プレイヤーに直接何かを適用したいときに使います。
  • 3 つともワールドがないときは nil を返すので、使う前に必ず確認してください。
  • プレイヤーが持っているものも読み取れます。Item.get(id):count() で、nil は不明の意味です。
  • 値は毎回取り直してください。どれもスナップショットで、ポーンはワールドをロードするたびに作り直されます。

次は Pal を読むと、いま求めた座標を :spawn がどう使うのかがわかります。

On this page