假玩家 NPC · LSE¶
playerNpc* 造出来的是纯协议假玩家:客户端看到一个完整的玩家模型,有名字牌、会出现在 Tab 列表里,但服务端并没有这个玩家 —— 没有 AI、不占实体系统、不写存档。适合做商店老板、任务发布人、雕像、人形立牌这类「站在那里的角色」。
皮肤有四条来路:PNG 文件注册、从在线玩家采集、目录批量导入、base64 blob 恢复。皮肤必须先注册、再创建 NPC,否则创建会失败。库不落盘:重启后要自己重新注册皮肤、按自己的存档重建 NPC。
点击这个假玩家不进回调,要用 ghost* 轮询取(条目里 domain=npc)。
快速开始¶
const registerSkin = ll.import("HologramLib", "playerNpcRegisterSkin");
const create = ll.import("HologramLib", "playerNpcCreate");
const setNametag = ll.import("HologramLib", "playerNpcSetNametag");
// 1. 注册一张 PNG 皮肤(skinId 自己起,之后都用它引用)
registerSkin("E:/server/skins/girl.png", "girl", "", "slim");
// 2. 用这张皮肤在主世界造一个假玩家
const npc = create(100.5, 64, 100.5, 0, "§e小美", "girl");
// 3. 改名字牌(支持 § 颜色码)
setNametag(npc, "§e小美 §7<店员>");
npc 就是这个假玩家的 id,之后所有 playerNpc* 函数都用它。
皮肤注册表¶
把 PNG 贴图注册成皮肤¶
playerNpcRegisterSkin(pngPath, skinId, geometry, armSize)
- 参数:
pngPath:String
PNG 文件路径(建议写绝对路径)。图片必须是正方形,边长是 2 的幂且不小于 64(64 / 128 / 256 / 512 / 1024)skinId:String
给这张皮肤起的名字;空串 = 用文件名(不含扩展名)geometry:String
几何模型名;空串 = 用标准玩家模型geometry.humanoid.customarmSize:String
手臂粗细,"wide"(粗,默认)或"slim"(细)- 返回值:
true= 注册成功;文件打不开或尺寸不合规返回false。同一个skinId重复注册会覆盖旧皮肤 - 返回值类型:
Boolean -
示例:
注册图片平面(128 PNG,透明可选)¶
playerNpcRegisterImageSkin(pngPath, skinId, sizeBlocks, transparent)
- 参数:
pngPath:String
128×128 PNG 路径skinId:String
皮肤名;相同名字重复注册会覆盖sizeBlocks:Float
平面边长(格);1= 1×1 格(128 px/格),1.25= 1.25 格transparent:Booleantrue保留透明(alpha ≥ 128 不透明,其余挖空);false透明区域铺白- 返回值:
true= 注册成功 - 返回值类型:
Boolean - 说明:库内生成双面平面几何,几何 identifier 按尺寸唯一;拼接/摆放由调用方自己算
-
示例:
采集在线玩家的皮肤¶
playerNpcCaptureSkin(skinId, playerName)
- 参数:
skinId:String
注册名,不能为空playerName:String
在线玩家的名字(realName)- 返回值:采到的是当时那一份完整快照,这名玩家之后换肤不影响它;
skinId为空、玩家不在线返回false。同一个skinId重复采集会覆盖 - 返回值类型:
Boolean -
示例:
批量导入目录下的皮肤¶
playerNpcImportSkins(dirPath)
- 参数:
dirPath:String
目录路径。每个子文件夹的名字就是skinId,文件夹里要有一个.png贴图(必需)和一个.json几何模型(可选,缺省用标准玩家模型)- 返回值:成功导入的数量;
dirPath不是目录返回-1;子文件夹里找不到 PNG 会被跳过、不计入数量 - 返回值类型:
Integer -
示例:
查询皮肤是否已注册¶
playerNpcHasSkin(skinId)
- 参数:
skinId:String
皮肤名- 返回值:
true= 已注册 - 返回值类型:
Boolean -
示例:
列出已注册的全部皮肤名¶
playerNpcGetSkinIds()
- 返回值:所有
skinId;一套都没有时是空数组 - 返回值类型:
Array<String> -
示例:
注销一套已注册的皮肤¶
playerNpcUnregisterSkin(skinId)
- 参数:
skinId:String
要注销的皮肤名- 返回值:
true= 已注销;还有 NPC 在用这套皮肤时会被拒绝(返回false,先销毁那些 NPC 或给它们换肤),皮肤本来就不存在也返回false - 返回值类型:
Boolean -
示例:
把皮肤导出成 base64 字符串¶
playerNpcGetSkinBlobB64(skinId)
- 参数:
skinId:String
皮肤名- 返回值:这套皮肤完整快照的 base64 文本(可以直接写进文件或数据库);皮肤未注册返回空串
"" - 返回值类型:
String -
示例:
从 base64 字符串恢复皮肤¶
playerNpcRegisterSkinFromBlobB64(b64)
- 参数:
b64:StringplayerNpcGetSkinBlobB64导出的字符串- 返回值:
true= 注册成功(皮肤名沿用导出时的那一个);字符串解不开、为空或格式不合法返回false - 返回值类型:
Boolean -
示例:
把皮肤发给在线玩家换肤¶
playerNpcInjectSkin(viewerName, targetName, skinId)
- 参数:
viewerName:String
只发给谁看;空串 = 发给所有在线玩家,包含被换肤的本人(这是「让玩家自己看到自己被换肤」的正路)targetName:String
被换肤的在线玩家名字(realName)skinId:String
已注册的皮肤名- 返回值:
true= 已发出;目标不在线、skinId未注册、viewerName指定的观看者不在线都返回false。它不会自动重发:客户端偶尔丢一次的话,自己隔一会儿再调一次 - 返回值类型:
Boolean -
示例:
发给所有在线玩家换肤¶
playerNpcInjectSkinAll(targetName, skinId)
- 参数:
targetName:String
被换肤的在线玩家名字skinId:String
已注册的皮肤名- 返回值:等价于
viewerName传空串的playerNpcInjectSkin,失败原因相同 - 返回值类型:
Boolean -
示例:
创建与删除¶
在坐标上创建假玩家¶
playerNpcCreate(x, y, z, dim, name, skinId)
- 参数:
x:Float
世界坐标 Xy:Float
世界坐标 Yz:Float
世界坐标 Zdim:Integer
维度 id,0/1/2name:String
显示名,同时也是名字牌与 Tab 列表里的名字;空串 = 用默认名NPCskinId:String
已注册的皮肤名;空串 = 用默认的default(没注册过就会失败)- 返回值:假玩家 id;失败返回负数(
-3= 皮肤未注册) - 返回值类型:
Integer -
示例:
用随机 id 创建假玩家¶
playerNpcCreateRandom(x, y, z, dim, name, skinId)
- 参数:同
playerNpcCreate - 返回值:随机段(
0x10000000起)的假玩家 id,不会和自增 id 冲突;失败返回负数 - 返回值类型:
Integer -
示例:
指定 id 创建以恢复 NPC¶
playerNpcCreateWithId(x, y, z, dim, name, skinId, desiredId)
- 参数:
- 前 6 个参数:同
playerNpcCreate desiredId:Integer
想用的 id,必须大于0且当前没被占用- 返回值:成功就是
desiredId;desiredId <= 0或已被占用返回-2;皮肤未注册返回-3 - 返回值类型:
Integer -
示例:
删掉一个假玩家¶
playerNpcDestroy(id)
- 参数:
id:Integer
假玩家 id- 返回值:
true= 已删除(已经看到它的玩家会看到它消失);id 不存在返回false - 返回值类型:
Boolean -
示例:
删掉全部假玩家¶
playerNpcDestroyAll()
- 返回值:没有返回值
- 返回值类型:
Null -
示例:
属性¶
移动假玩家并可换维度¶
playerNpcSetPos(id, x, y, z, dim)
- 参数:
id:Integer
假玩家 idx:Float
新的世界坐标 Xy:Float
新的世界坐标 Yz:Float
新的世界坐标 Zdim:Integer
目标维度;填负数 = 只改坐标、保持原维度- 返回值:
true= 已改;id 不存在返回false。变更会合并到 tick 末尾一次性重发,不会反复闪 - 返回值类型:
Boolean -
示例:
设置假玩家的朝向¶
playerNpcSetRotation(id, yaw)
- 参数:
id:Integer
假玩家 idyaw:Float
朝向角,单位是度- 返回值:
true= 已改;id 不存在返回false - 返回值类型:
Boolean -
示例:
改假玩家头顶的名字牌¶
playerNpcSetNametag(id, text)
- 参数:
id:Integer
假玩家 idtext:String
名字牌文字,支持§颜色码;空串 = 清除名字牌。重复设同一个文本不会触发重发- 返回值:
true= 已改;id 不存在返回false - 返回值类型:
Boolean -
示例:
给假玩家换一套皮肤¶
playerNpcSetSkin(id, skinId)
- 参数:
id:Integer
假玩家 idskinId:String
已注册的皮肤名- 返回值:
true= 已换;皮肤未注册或 id 不存在返回false。换肤会让已经看到它的玩家看到它重建一次 - 返回值类型:
Boolean -
示例:
缩放模型并同步碰撞箱¶
playerNpcSetScale(id, scale)
- 参数:
id:Integer
假玩家 idscale:Float
缩放倍数,客户端硬限是0.0625 ~ 10- 返回值:
true= 已设;小于0.0625或大于10、id 不存在都返回false。变更会合并到 tick 末尾一次性重发 - 返回值类型:
Boolean -
示例:
设置假玩家的可见距离¶
playerNpcSetViewDistance(id, dist)
- 参数:
id:Integer
假玩家 iddist:Float
距离,单位方块;<= 0= 不限距离(默认是 96 格)- 返回值:
true= 已设;id 不存在返回false - 返回值类型:
Boolean -
示例:
打开或关闭这个假玩家¶
playerNpcSetEnabled(id, enabled)
- 参数:
id:Integer
假玩家 idenabled:Booleanfalse= 玩家看不到它(id 仍然有效,随时可以开回来),true= 恢复显示- 返回值:
true= 已设;id 不存在返回false - 返回值类型:
Boolean -
示例:
可见玩家白名单¶
只让名单里的玩家看到¶
playerNpcSetVisiblePlayers(id, players)
- 参数:
id:Integer
假玩家 idplayers:Array<String>
玩家名数组,按realName匹配;空数组 = 清除限制、全员可见- 返回值:
true= 已设;id 不存在返回false - 返回值类型:
Boolean -
示例:
白名单只填一个玩家¶
playerNpcSetVisiblePlayer(id, playerName)
- 参数:
id:Integer
假玩家 idplayerName:String
唯一可见的玩家名(realName)- 返回值:
true= 已设;id 不存在返回false - 返回值类型:
Boolean -
示例:
清除白名单恢复全员可见¶
playerNpcClearVisiblePlayers(id)
- 参数:
id:Integer
假玩家 id- 返回值:
true= 已清除(本来就没有限制也算成功);id 不存在返回false - 返回值类型:
Boolean -
示例:
查询¶
查询假玩家是否还存在¶
playerNpcExists(id)
- 参数:
id:Integer
假玩家 id- 返回值:
true= 存在 - 返回值类型:
Boolean -
示例:
查询 id 是否已被占用¶
playerNpcIsIdUsed(id)
- 参数:
id:Integer
要查的 id- 返回值:
true= 已被占用(此时playerNpcCreateWithId会返回-2) - 返回值类型:
Boolean -
示例:
列出所有假玩家的 id¶
playerNpcGetAllIds()
- 返回值:所有假玩家 id;一个都没有时是空数组
- 返回值类型:
Array<Integer> -
示例:
取运行态摘要用于排查¶
playerNpcGetDebugInfo(id)
- 参数:
id:Integer
假玩家 id- 返回值:摘要文本,包含 id、名字、皮肤、坐标、维度、朝向、视距、开关、已见玩家数与可见名单状态;id 不存在返回
not_found - 返回值类型:
String -
示例:
注意事项¶
- 皮肤先注册再建人:
skinId没注册过,创建会失败(-3)。运行中皮肤被注销或缺失,这个 NPC 会一直不生成,脚本侧不会有报错。 - 库不落盘:皮肤和 NPC 都只在内存里。服务端或脚本重启后要重新注册皮肤(或用
playerNpcGetSkinBlobB64/playerNpcRegisterSkinFromBlobB64把皮肤存盘,重启后恢复),NPC 要按自己的存档记录重建(playerNpcCreateWithId能还原原来的 id)。 - 假玩家会常驻 Tab 列表:库不下发 PlayerList 移除条目(客户端在皮肤条目还活着时移除会崩),所以只要玩家看到过这个 NPC,它的名字就会一直留在 Tab 列表里,直到 NPC 被销毁。
- 每个 tick 最多只生成 2 个 NPC(多个同时生成会让客户端一次收到大量数据而断开),所以可见性最多晚一个 tick;视距进出带 4 格滞回,避免在边界上反复重发皮肤。
- 属性变更会合并:同一 tick 里连改位置 / 朝向 / 缩放 / 皮肤,只会重建一次(不闪)。但每次重建都会把皮肤重发一遍,所以高频跟踪式移动不适合放在脚本 tick 里 —— LSE 侧没有轻量位置通道,这类需求在 C++ 侧用
setPositionLight。 - 点击要轮询:假玩家的交互不进回调,用
ghostPollInteractions()取,条目格式形如player=Steve action=2 domain=npc id=7(action=1右键交互,action=2攻击)。 - 部分能力没有脚本接口:
setRotationLight/setPositionLight/playAnimation/playAnimationTo/setEntitySpawnCallback目前只有 C++ 接口,脚本里ll.import会拿到null;可以先ll.hasExported("HologramLib", "...")判断再决定要不要用。 - 白名单按名字匹配:用的是玩家名(LSE 的
realName),不是 uuid;玩家改名后旧名单会失效。