Minecraft开发:如何将NPC(实体玩家)从Tab列表移除且保留皮肤
问题根源
客户端加载玩家皮肤的逻辑是:收到PacketPlayOutPlayerInfo.ADD_PLAYER包携带的GameProfile中的皮肤纹理数据后,需要异步拉取对应纹理资源,拉取完成后才会渲染到实体上。你当前固定10tick(0.5秒)后就发送REMOVE_PLAYER包,大部分场景下客户端还没完成皮肤拉取,自然无法正常显示皮肤。如果不发移除包,NPC就会一直留在Tab列表里。
可行解决方案
提供两种稳定实现方案,可根据你的开发环境选择:
方案1:优化延迟移除逻辑(无额外依赖,适配低版本服务端
先提前把NPC的皮肤纹理(texture和signature)预写入NPC的GameProfile中,减少客户端拉取耗时,同时延长移除包的发送延迟到40tick(2秒),覆盖绝大多数网络场景:
public static void addNPCPacket(EntityPlayer npc) { for(Player player : Bukkit.getOnlinePlayers()) { PlayerConnection connection = ((CraftPlayer)player).getHandle().playerConnection; connection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, npc)); connection.sendPacket(new PacketPlayOutNamedEntitySpawn(npc)); connection.sendPacket(new PacketPlayOutEntityHeadRotation(npc, (byte) (npc.yaw * 256 / 360))); // 延长到40tick保证皮肤加载完成 Bukkit.getScheduler().runTaskLater(Main.getPlugin(), () -> { connection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, npc)); }, 40); } }
方案2:拦截Tab列表数据包(更稳定,需ProtocolLib依赖)
通过ProtocolLib直接过滤Tab列表数据包中NPC的条目,无需延迟删除,完全不影响皮肤加载:
// 插件启动时注册监听 ProtocolLibrary.getProtocolManager().addPacketListener( new PacketAdapter(plugin, PacketType.Play.Server.PLAYER_INFO) { @Override public void onPacketSending(PacketEvent event) { PacketPlayOutPlayerInfo packet = (PacketPlayOutPlayerInfo) event.getPacket(); // 仅处理ADD_PLAYER动作的数据包 if (packet.a() == PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER) { List<PacketPlayOutPlayerInfo.PlayerInfoData> infoList = packet.b(); // 提前把所有NPC的UUID存入全局集合npcUUIDSet,匹配到就移除对应条目 infoList.removeIf(info -> npcUUIDSet.contains(info.getProfile().getId())); // 列表为空直接取消发送 if (infoList.isEmpty()) event.setCancelled(true); } } } );
使用该方案后不需要再不需要发送REMOVE_PLAYER包,NPC皮肤可正常加载,也不会出现在Tab列表中。
内容的提问来源于stack exchange,提问作者Undercraft_CR
相关产品推荐
相关产品推荐

