Player
查出玩家现在站在哪里,好把东西放到他附近
读完本页你可以做到
- 查出玩家现在站在哪里
- 在玩家脚下、或者稍微离开一点的地方放出一只帕鲁
- 在玩家周围摆一圈东西
- 测量玩家离某个建筑物有多远
- 等玩家真的进入世界之后,再去做上面这些事
玩家在哪里
Player 提供三个函数。需要知道玩家位置的时候,随时调用它们。
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玩家不在世界里的时候,三个函数都返回 nil。用之前先检查一下值。
| 函数 | 返回值 | 什么时候是 nil |
|---|---|---|
Player.character() | 本地的 APalPlayerCharacter | 没有有效的玩家 pawn |
Player.coordinate() | 一个 Coord —— x、y、z,单位是厘米 | 没有 pawn,或者位置读取失败 |
Player.coordinateOffset(dx, dy, dz) | 在上面那个坐标上加了偏移的 Coord | 和 coordinate() 的条件完全一样 |
调用方式有两种。两边是同一张表。
local api = require("palforge.api")
api.Player.coordinate() -- namespaced access
Player.coordinate() -- the installed global; the same table位置以 Coord 的形式返回,就是一张装着三个数字的小表 —— 见
Coord 的形状。
character
Player.character() 返回玩家在世界里的角色。用 Unreal 的说法叫 pawn,也就是玩家操控着
走来走去的那个 actor。它用 FindFirstOf("PalPlayerCharacter") 找到 pawn,对它调用
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在用到它的地方现取。每次调用都会重新查找,什么都不缓存;而且每次加载世界,pawn 都会被重新 创建 —— 所以启动时存进变量里的那个角色,指向的是一个已经不在了的东西。
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)返回给你的是游戏自己的 actor,不是 PalForge 的对象。游戏挂在它上面的东西你都够得着,但那些
调用在本 API 之外,所以请把它们放在 pcall 里(下面「在玩家正前方生成」那个例子做的正是
这件事)。
coordinate
Player.coordinate() 会找到 pawn 并读出它的位置。它优先用 K2_GetActorLocation,不行就
退回 GetActorLocation。没有 pawn,或者读取抛出异常,都返回 nil。
返回的表用三套键名装着同样的三个数字,所以它能直接丢进 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,一格一米)。z 是上方向。
偏移是沿世界坐标轴加上去的,不是沿玩家自己的朝向。它完全不知道玩家面朝哪边;不管玩家是看着
那个方向还是背对着,coordinateOffset(600, 0, 0) 都是从玩家出发沿 +X 方向 6 米的地方。想要
玩家正前方的点,看下面的例子。
Coord 的形状
Coord 是一张表,装着三个以厘米为单位的数字。形状就这些:
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
只要没有有效的玩家 pawn,这三个函数就都返回 nil:
- 模组加载时,以及标题画面 —— 还没进过任何世界
- 加载画面期间,pawn 正在被创建
- 离开世界之后、进入下一个世界之前
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 的,正是对这同一个 pawn 的轮询:core/event 大约每秒检查一次
FindFirstOf("PalPlayerCharacter"),连续五次拿到有效结果之后才发出这个频道。所以
Player.character() 有可能在 world.ready 触发的好几秒之前就已经能返回 pawn,之后也
可能在任何时刻重新开始返回 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 模组。core/spawn 会先找活着的 PalCheatManager,再找玩家
控制器上的那个,两者都没有时就自己造一个:对 PalPlayerController 自己的 CheatClass 调
StaticConstructObject(那个类为空时退回 /Script/Pal.PalCheatManager,再退回
/Script/Engine.CheatManager),造出来的对象挂在控制器上,所以这件事每个会话只发生一次,而不是
每次生成都来一遍。放进世界的生成真正需要的是一个玩家控制器:没有加载世界、或者还没连上的时候,
它会在够到游戏之前返回 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 是对 pawn 的原生 Unreal 调用,不属于本 API,所以它待在 pcall 里,
并配了一个合理的备用方向。只用到 X 和 Y 两个分量:如果把完整的 3D 向量拿去归一化,目标点会
随着镜头上下倾斜,帕鲁就会生成在半空中或者地面以下。
在玩家周围摆一圈
需要围着同一个原点取好几个点的时候,coordinateOffset 最好用。
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 每次都会重新读一遍 pawn:如果玩家在循环跑到
一半时离开了世界,剩下的几轮拿到的是 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的意思是未知,从来不是零——没有世界、没有玩家,或者这次读取答不上来。请像守 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()是玩家自己的 actor,想直接对玩家做点什么就用它。- 三个函数在没有世界的时候都返回
nil,用之前先检查。 - 玩家身上有什么也读得到:
Item.get(id):count(),nil表示未知。 - 每次都重新取一遍。它们都是快照,而且每次加载世界 pawn 都会被重建。
接下来读 Pal,看看 :spawn 拿你刚求出来的坐标做了什么。