• Runtimes
  • 合并皮肤之后,新的Spine形象的清晰度降低了

  • Modifié

参考Spine的官方代码,我在运行时加载两个SkeletonGraphics,然后将他们的皮肤进行组合,组合过程没有出错,新的组合的皮肤运行也正常,但是我发现组合之后的皮肤的清晰度比原来是降低了不少的,可能是什么原因呢?

        {
            // 确保两个CharacterFigureLoader实例都已加载
            if (playerFigure == null || clothesFigure == null)
            {
                log.Debug("playerFigure或clothesFigure未初始化,无法换装");
                yield break;
            }

            // 等待两个角色加载完成
            while (playerFigure.SkeletonGraphic == null || clothesFigure.SkeletonGraphic == null)
            {
                log.Debug("等待Spine骨骼加载完成...");
                yield return new WaitForSeconds(0.1f);
            }

            log.Debug($"开始换装: skinId={skinId}, skinType={skinType}");

            try
            {
                // 直接使用CharacterFigureLoader中的SkeletonGraphic引用
                Spine.SkeletonData clothesSkeletonData = clothesFigure.SkeletonGraphic.Skeleton.Data;
                Spine.SkeletonData playerSkeletonData = playerFigure.SkeletonGraphic.Skeleton.Data;

                // 从clothesFigure中获取头部皮肤
                Spine.Skin clothesHeadSkin = clothesSkeletonData.FindSkin("tou");
                if (clothesHeadSkin == null)
                {
                    log.Error("无法获取clothesFigure的头部皮肤");
                    yield break;
                }

                // 从playerFigure中获取身体皮肤
                Spine.Skin playerBodySkin = playerSkeletonData.FindSkin("shenti");
                if (playerBodySkin == null)
                {
                    log.Error("无法获取playerFigure的身体皮肤");
                    yield break;
                }

                // 从playerFigure中获取武器皮肤
                Spine.Skin playerWeaponSkin = playerSkeletonData.FindSkin("wuqi");
                if (playerWeaponSkin == null)
                {
                    log.Debug("未找到playerFigure的武器皮肤,尝试使用默认皮肤");
                    // 可以选择继续而不中断流程
                }

                // 创建源材质,用于重新组合皮肤
                Material sourceMaterial = playerFigure.SkeletonGraphic.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial;

                // 创建自定义组合皮肤
                Spine.Skin combinedSkin = new Spine.Skin("combined-skin");

                // 1. 先添加玩家的身体皮肤
                combinedSkin.AddSkin(playerBodySkin);

                // 2. 如果有武器皮肤,也添加进去
                if (playerWeaponSkin != null)
                {
                    combinedSkin.AddSkin(playerWeaponSkin);
                }

                // 3. 添加衣物角色的头部皮肤
                combinedSkin.AddSkin(clothesHeadSkin);

                log.Debug("已创建组合皮肤,准备重新打包");

                // 重新打包皮肤以创建新的纹理图集
                Texture2D runtimeAtlas;
                Material runtimeMaterial;

                Spine.Skin packedSkin = combinedSkin.GetRepackedSkin("combined-repacked",sourceMaterial,out runtimeMaterial,out runtimeAtlas);

                // 应用新的组合皮肤
                playerFigure.SkeletonGraphic.Skeleton.SetSkin(packedSkin);
                playerFigure.SkeletonGraphic.Skeleton.SetSlotsToSetupPose();

                // 更新骨骼图形
                playerFigure.SkeletonGraphic.Update(0);
                playerFigure.SkeletonGraphic.OverrideTexture = runtimeAtlas;
                playerFigure.SkeletonGraphic.AnimationState.Apply(playerFigure.SkeletonGraphic.Skeleton);

                // 清理缓存
                Spine.Unity.AttachmentTools.AtlasUtilities.ClearCache();

                log.Debug("头部换装完成");
            }
            catch (System.Exception e)
            {
                log.Error($"换装过程中发生错误: {e.Message}\n{e.StackTrace}");

                // 尝试恢复到初始状态
                try
                {
                    playerFigure.SkeletonGraphic.Skeleton.SetSkin("all");
                    playerFigure.SkeletonGraphic.Skeleton.SetSlotsToSetupPose();
                    playerFigure.SkeletonGraphic.Update(0);
                }
                catch (System.Exception restoreEx)
                {
                    log.Error($"恢复原始皮肤失败: {restoreEx.Message}");
                }
            }
        }
    Related Discussions
    ...

    合并皮肤后清晰度降低的原因可能与重新打包皮肤时的纹理设置有关。在 GetRepackedSkin 方法中,生成的新纹理可能没有使用与原始纹理相同的分辨率或过滤设置。确保在重新打包时,使用的 sourceMaterial 的纹理设置与原始材质一致,特别是纹理的分辨率和过滤模式。此外,检查 GetRepackedSkin 方法的实现,确保它没有在生成新纹理时降低分辨率。

      Spinebot 感谢您这么及时的回复,我将合并皮肤的代码,加了一个参数, Spine.Skin packedSkin = combinedSkin.GetRepackedSkin("combined-repacked",sourceMaterial,out runtimeMaterial,out runtimeAtlas,2048); 最大的图集改成了2048,默认应该是1024,清晰度就没问题了。可能是因为默认1024会限制打包图集,导致不清楚吧。

        mingjava 谢谢,这个查过了,我也是按照这样来做的,感谢你们,spine的官方论坛太贴心了

        Spinebot 目前我是将每个角色分为all/head/body/attach 这样的皮肤,all是所有皮肤组合的结果,在运行时我会将figure1的head/attach 与figure2的body进行合并,这样来实现head和body的组合。我的问题是,是否可以方便的将一个角色的head/body/attach分为三个部分单独导出,也就是可以单独导出指定的皮肤吗,如果可以的话,是是不是运行时去加载三个部分会更好。

          mingjava Unfortunately, there is no export setting to pack textures based on skins, but it is possible to pack texture atlas pages by folder. Therefore, if you have sorted images into folders by skin in advance, you can create texture packs for each skin. For more information, please refer to the following page in the Spine User Guide: Spine用户指南 纹理打包 #文件夹结构

            Misaki 您好,您的意思是我将head / attach /body 三个皮肤的图片都单独放在子文件夹吗?单独导出比如head子文件的图片到单独的图集,这样的话,是不是skin我也要把all/attach/body这三个都删除才行?这样会影响动作吗? 运行时我用包含了head图集的skin1 project去跟包含了attach/body的 skin2 project去合并吗?

              mingjava I'm not sure if I fully understand your question due to the influence of machine translation, but regarding your last question about combining skins at runtime, yes, that is the recommended method. If you haven't seen Spine Examples/Other Examples/Mix and Match yet, I recommend checking it out first. This scene is briefly introduced in the following video:
              【[spine-unity] 通过示例场景开始使用spine-unity运行时】 【精准空降到 18:22】 https://www.bilibili.com/video/BV1DV4y1A7uf/?share_source=copy_web&t=1102

                Misaki 您好,我看过了这个项目,但是我理解这个项目主要针对的是皮肤在一个spine项目里面定义的可以这样来做,如果我们把皮肤分拆到不同的项目里面,比如body和attach一个项目,head一个项目,在运行时拼接着两个皮肤就不行了吧,层级关系应该是会出问题吧。如果用替换附件的形式在运行时替换头这个皮肤的所有附件,我理解是可以的,但是因为一个头可能分为了很多的部分,可能有20个附件,又感觉这样替换比较繁琐。

                  mingjava I see, I think I understand what you are trying to do. To begin with, dividing the same character into multiple skeletons is not a recommended method. If the reason for dividing into multiple skeletons is because it is troublesome to set up the skins, it may be better to replace the texture atlas page at runtime. This is the method described in the following general runtime guide:
                  Spine运行时指南 运行时皮肤 #创建附件

                  In other words, create each head part in a uniform size and pack them using the texture packer.
                  If the number and size of the input images match the texture packer settings, the texture packer will always pack the textures in the same layout. Therefore, by replacing the texture atlas page images loaded in Unity with the page images packed using this method, you can complete the change in the appearance of the skeleton.

                  However, this does require the effort of preparing parts images in a uniform size. So, ultimately, it depends on where you want to put your effort. In some cases, it may be easier to have many skins for a single skeleton, as there are fewer things to keep in mind when preparing parts images. In addition, using skins allows you to use skin bones and skin constraints, so if the complexity varies greatly depending on the skin (e.g., some skins require only five parts for the head, while others require 30 parts), it is easier to manage by using skins.

                    Misaki 感谢您的热心解答,按照您的解答,我理解想在运行时构建一个角色的话,方案1就是替换贴图的形式,方案2就是同时加载两个完整的角色A和B,然后将B的head skin与A的attach,body skin组合到一起,但是A和B必须是完整的Spine项目。

                      mingjava I apologize, but I may have lost track of what you are trying to do. However, I thought the following might be the information you are looking for, so I will add it as a supplement.

                      It is possible to reference a single texture atlas with multiple skeletons. If that's what you're looking for, the following threads may be helpful:

                      Therefore, even if the skeleton is divided, each solution should work. Please let us know if it doesn't work.

                        Misaki 好的,我现在看看你提供的参考,我还想问个问题,就是只要在运行时打图集,那么一定需要所有参与打图集的图片必须是尺寸唯一才可以吧,否则打图集的布局方式不一致了,就也会导致程序运行问题。

                          mingjava If the part you want to replace is a mesh attachment, the replacement image must be the same size as the original image. However, if the attachment is not a mesh, you can replace it using the method shown in Spine Examples/Other Examples/Mix and Match Equip.

                            Misaki 这个例子我是看过的,是通过替换图集里面指定插槽的附件实现的局部换装。我现在的换装方案是,同时加载两个完整的角色A和B,A和B共用一个.skel.bytes文件,A和B的图集里面的相同图片的尺寸都是一样的,然后将B的head skin与A的attach,body skin组合到一起,这样来实现换装。这样应该是可行的方案吧?是否需要同一个图集里面的图片尺寸都不同,这样打图集的时候保持layout一致。如果运行时重新打图集的布局改变了,而atlas.txt不变,应该会出现贴图位置对不上的问题吧

                              mingjava

                              A和B共用一个.skel.bytes文件

                              This cannot be achieved unless the same skeleton is used. It is not possible to merge skeletons that have been exported separately, so if this is one of your requirements, you should create characters A and B using the same skeleton.

                              If you ultimately want to merge into a single .skel.bytes file, what is the actual reason for splitting the skeleton?

                                Misaki 目前我的A和B是使用的同一个骨骼,我是在运行时加载的三个导出的资源,类似与这样的代码,以便保证我获得的SkeletonGraphics都是使用的同样的骨骼,每个对象都包含三个皮肤attach,body,head。我现在的需求是,希望能够在运行时加载的A的皮肤attach和body,与B的皮肤head进行组合。这样是否可行?事实上,我已经实现了这样的功能,但是对于简单的比如待机的动作可以,但是复杂一些的攻击动作,涉及到层级变化的,动画效果就乱了。` // 创建资源
                                SpineAtlasAsset atlasAsset = SpineAtlasAsset.CreateRuntimeInstance(part.atlasText, part.textures, materialPropertySource, true);
                                SkeletonDataAsset skeletonDataAsset = SkeletonDataAsset.CreateRuntimeInstance(part.skeletonJson, atlasAsset, true);

                                            // 创建SkeletonGraphic
                                            SkeletonGraphic skeletonGraphic = SkeletonGraphic.NewSkeletonGraphicGameObject(skeletonDataAsset, this.gameObject.transform, skeletonGraphicMaterial);
                                            skeletonGraphic.gameObject.name = $"SkeletonGraphic_{part.partName}";
                                            skeletonGraphic.Initialize(false);`

                                  mingjava ` // 创建资源
                                  SpineAtlasAsset atlasAsset = SpineAtlasAsset.CreateRuntimeInstance(part.atlasText, part.textures, materialPropertySource, true);
                                  SkeletonDataAsset skeletonDataAsset = SkeletonDataAsset.CreateRuntimeInstance(part.skeletonJson, atlasAsset, true);

                                              // 创建SkeletonGraphic
                                              SkeletonGraphic skeletonGraphic = SkeletonGraphic.NewSkeletonGraphicGameObject(skeletonDataAsset, this.gameObject.transform, skeletonGraphicMaterial);
                                              skeletonGraphic.gameObject.name = $"SkeletonGraphic_{part.partName}";
                                              skeletonGraphic.Initialize(false);`