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 nil3 つとも、プレイヤーがワールドにいないときは nil を返します。使う前に値を確認して
ください。
| 関数 | 戻り値 | nil になる条件 |
|---|---|---|
Player.character() | ローカルの APalPlayerCharacter | 有効なプレイヤーポーンが存在しない |
Player.coordinate() | Coord(x、y、z をセンチメートルで) | ポーンがない、または座標の読み取りに失敗した |
Player.coordinateOffset(dx, dy, dz) | 上の座標をオフセットした Coord | coordinate() と同じ条件 |
呼び出し方は 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_CM は 100、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/event が
FindFirstOf("PalPlayerCharacter") を約 1 秒おきに確認し、有効な結果が 5 回連続したところ
でチャネルを emit します。したがって Player.character() は world.ready が発火する数秒
「前」からポーンを返しうるし、その後いつでもまた nil を返しはじめる可能性があります。
このチャネルは処理を開始するのに適した場所ですが、nil チェックを省く理由にはなりません。
レシピ
プレイヤーの足元にスポーンさせる
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 平面に潰して正規化し、座標に足してください。
---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
endGetActorForwardVector はポーンに対する生の Unreal 呼び出しで、この API の一部ではありま
せん。そのため pcall の中に置き、失敗時の方向をフォールバックとして用意しています。使う
のは X と Y の成分だけです。3D ベクトルをそのまま正規化するとカメラの上下でターゲットが
傾き、パルが空中や地面の下にスポーンしてしまいます。
プレイヤーの周囲に円状に配置する
coordinateOffset が最も効くのは、1 つの原点のまわりに複数の地点が必要なときです。
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 が返り、その連鎖の各段で実在のオブジェクトが
表示されました。
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 をセンチメートルで表す同じ形です。したがって
単純な距離計算に他のものは要りません。
---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 がどう使うのかがわかります。