PAYATHON 2026

Unity 2D 如何在运行时替换 Sprite 的辅助纹理?

数据小陈

可以实现,但要区分材质属性和 Sprite 辅助纹理

Material.SetTexture("_Shoes", texture2D) 修改的是材质的纹理属性,不会直接修改 Sprite 关联的 Secondary Textures 数据。EnableKeyword("_SHOES") 也不会自动启用同名纹理槽。

运行时换装时,可以把鞋子纹理改为由脚本控制的普通 Shader Graph 纹理属性,再通过 MaterialPropertyBlock 为当前角色赋值,避开与 Sprite 中同名辅助纹理的绑定冲突。

目前没有 Unity、渲染管线和 Shader Graph 的版本信息,也没有实际的节点连接图,暂时无法确定失败的唯一原因。

为什么能够识别属性,画面却没有变化?

Unity 的 SecondarySpriteTexture 将纹理与 Shader 属性名关联,供 Sprite 渲染时使用。因此,即使材质中存在 _Shoes,最终绘制时使用的也不一定是材质中保存的纹理。Unity:SecondarySpriteTexture

可以从三个位置排查:

  • Sprite 是否仍然通过 _Shoes 提供凉鞋纹理,导致材质赋值没有成为最终使用的值。
  • Shader Graph 是否采样了该属性,并将采样结果连接到最终颜色和透明度的合成路径。
  • 脚本使用的名称是否与属性的 Reference Name 完全一致,包括大小写。显示名称相同并不够。Unity:Shader Graph 属性参数

纹理属性和 Shader Keyword 是两套机制。只有 Shader 声明并使用了 _SHOES 关键字,它才可能影响渲染分支。普通纹理替换不需要根据属性名额外调用 EnableKeyword。Unity 的 SetTexture 文档中,启用关键字的示例针对特定 Shader 功能,不能直接推广到任意自定义纹理。Unity:Material.SetTexture

方案一:将鞋子纹理交给脚本控制

调整 Shader Graph

  1. 在 Blackboard 中添加一个 Texture2D 属性,将 Reference Name 设为 _RuntimeShoes,并勾选 Exposed,方便检查。
  2. 将该属性连接到鞋子对应的 Sample Texture 2D 节点,替换原先使用 _Shoes 的输入。
  3. 保留原来的鞋子颜色和透明度合成逻辑。
  4. 不要在 Sprite 的 Secondary Textures 中添加 _RuntimeShoes 同名条目。
  5. 保存 Shader Graph,确认 SpriteRenderer 使用的材质引用了它。

_RuntimeShoes 是这里新建的自定义属性名,需要同步修改 Shader Graph 和脚本,仅修改代码中的字符串不会生效。

为当前 SpriteRenderer 设置纹理

将下面的脚本挂到拥有 SpriteRenderer 的对象上,在 Inspector 中为 initialShoes 指定初始凉鞋纹理。换装时调用 SetShoes(bootsTexture)。

using UnityEngine;

[RequireComponent(typeof(SpriteRenderer))]
public class ShoesTextureSwitcher : MonoBehaviour
{
    [SerializeField] private Texture2D initialShoes;

    private static readonly int ShoesId =
        Shader.PropertyToID("_RuntimeShoes");

    private SpriteRenderer spriteRenderer;
    private MaterialPropertyBlock propertyBlock;

    private void Awake()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        propertyBlock = new MaterialPropertyBlock();
    }

    private void Start()
    {
        SetShoes(initialShoes);
    }

    public void SetShoes(Texture2D texture)
    {
        if (texture == null)
            return;

        Material material = spriteRenderer.sharedMaterial;
        if (material == null || !material.HasProperty(ShoesId))
        {
            Debug.LogError("当前材质缺少 _RuntimeShoes 属性。", this);
            return;
        }

        spriteRenderer.GetPropertyBlock(propertyBlock);
        propertyBlock.SetTexture(ShoesId, texture);
        spriteRenderer.SetPropertyBlock(propertyBlock);
    }
}

MaterialPropertyBlock.SetTexture 设置覆盖值,Renderer.SetPropertyBlock 将覆盖值应用到渲染器。先读取已有的 Property Block,可以保留其他代码已经设置的属性。如果其他脚本随后重新写入整个 Property Block,还需要协调它们的写入逻辑。Unity:SetTexture、Unity:SetPropertyBlock

这种方式控制的是实际显示效果,不会将 Sprite Editor 中原有的 _Shoes 条目改为靴子纹理。

方案二:保留 Secondary Textures 工作方式

如果鞋子必须继续作为 Sprite 自身的辅助纹理,可以预先准备关联不同鞋子纹理的 Sprite,再切换 SpriteRenderer.sprite。如果 Animator 也在驱动该属性,需要同步调整动画使用的 Sprite,否则下一帧可能切回原来的资源。

对于支持相应 API 的 Unity 版本,也可以使用带 secondaryTextures 参数的 Sprite.Create 创建新 Sprite,传入包含 _Shirt、_Pants、_Shoes 等条目的 SecondarySpriteTexture[],然后赋给 SpriteRenderer。这会创建一个新 Sprite,不会原地修改现有 Sprite;使用前应核对项目版本中的 API。Unity:Sprite.Create

采用运行时创建方式时,还需要处理原 Sprite 的矩形区域、pivot、pixelsPerUnit,以及网格、图集或其他附加数据,不能假设简单重建一次就能完整保留所有导入结果。

替换后仍然显示异常时检查什么?

先临时将鞋子采样结果直接接到最终输出,确认新纹理是否进入 Shader,再恢复衣服、裤子和鞋子的合成逻辑,以区分纹理赋值问题和合成问题。

还要检查 UV 和纹理布局。Secondary Textures 使用与主 Sprite 相同的 UV,靴子纹理需要与角色对应区域对齐。如果主 Sprite 使用整张角色图布局,而靴子纹理只裁出了鞋子,沿用同一套 UV 就可能采样到错误区域。使用 Sprite Atlas 时,也需要核对实际图集坐标。Unity:Sprite Editor 的 Secondary Textures

最后检查鞋子区域的 Alpha 和原 Sprite 的网格范围。即使纹理已切换,如果最终透明度仍排除了靴子区域,或靴子超出实际绘制的网格,画面也不会完整显示。

备注:内容仅供参考。