• RuntimesUnity
  • Assigning Blend Mode Materials to SkeletonGraphic from script

Hi,

Is there any way to set the Blend Mode materials to runtime-instantiated SkeletonGraphic objects?

I am able to instantiate objects and assign them the default material, but when the model uses an animation which has screen or multiply blend modes, it doesn't load properly because the proper material hasn't been assigned.

Due to the sheer amount of assets in the project, importing them all into Unity is not viable, which is why we're loading them in runtime (without first importing them into Unity).

An issue I'm experiencing is that:

  • When I instantiate objects in runtime with assets imported into unity, in the inspector, the advanced tab will display the Blend Mode Materials (unassigned) which I can then assign in runtime and make the animations work as intended.
  • When I instantiate objects in runtime with assets which were not imported by unity (skeleton json, atlas definition and atlas images) then in the inspector, the advanced tab will not display Blend Mode Materials at all. So I can't set this values from the inspector when compared to imported assets.

Thanks in advance.

Related Discussions
...

To assign blend mode materials to SkeletonGraphic objects instantiated at runtime, you can follow these steps:

  1. Create Custom Blend Mode Materials: First, create custom materials for each blend mode (Additive, Multiply, Screen) using the appropriate Spine shaders. You can do this by duplicating the default SkeletonGraphic material and changing the shader to the corresponding blend mode shader.

  2. Assign Blend Mode Materials in Script: When you instantiate your SkeletonGraphic object at runtime, you need to manually assign the blend mode materials. Here is an example script to demonstrate how to do this:

using UnityEngine;
using Spine.Unity;

public class BlendModeMaterialAssigner : MonoBehaviour
{
    public Material additiveMaterial;
    public Material multiplyMaterial;
    public Material screenMaterial;

    public void AssignBlendModeMaterials(SkeletonGraphic skeletonGraphic)
    {
        var skeletonDataAsset = skeletonGraphic.SkeletonDataAsset;
        var blendModeMaterials = skeletonDataAsset.blendModeMaterials;

        // Assign custom materials
        blendModeMaterials.additiveMaterials[0].material = additiveMaterial;
        blendModeMaterials.multiplyMaterials[0].material = multiplyMaterial;
        blendModeMaterials.screenMaterials[0].material = screenMaterial;

        // Apply the materials to the skeleton
        blendModeMaterials.ApplyMaterials(skeletonGraphic.SkeletonData);
    }
}
  1. Instantiate and Assign Materials: When you instantiate your SkeletonGraphic object, call the AssignBlendModeMaterials method to assign the blend mode materials:
public class Example : MonoBehaviour
{
    public GameObject skeletonGraphicPrefab;
    public Material additiveMaterial;
    public Material multiplyMaterial;
    public Material screenMaterial;

    void Start()
    {
        GameObject instance = Instantiate(skeletonGraphicPrefab);
        SkeletonGraphic skeletonGraphic = instance.GetComponent<SkeletonGraphic>();

        BlendModeMaterialAssigner materialAssigner = instance.AddComponent<BlendModeMaterialAssigner>();
        materialAssigner.additiveMaterial = additiveMaterial;
        materialAssigner.multiplyMaterial = multiplyMaterial;
        materialAssigner.screenMaterial = screenMaterial;

        materialAssigner.AssignBlendModeMaterials(skeletonGraphic);
    }
}

This approach ensures that your SkeletonGraphic objects have the correct blend mode materials assigned at runtime, even if the assets were not imported into Unity beforehand.

The blendModeMaterials property of the skeletonDataAsset has no materials assigned, attempting to assign my materials as suggested throws an exception.

However, if an asset is imported into unity beforehand the blendModeMaterials has data (and materials) pre-assigned.

The code for loading the assets I'm using

    SpineAtlasAsset runtimeAtlas = SpineAtlasAsset.CreateRuntimeInstance(new TextAsset(File.ReadAllText(Path.Combine(directoryPath, fileName + ".atlas.txt"))), textures, skeletonGraphicDefaultMaterial, true);
    SpineDataAsset runtimeSkeletonDataAsset = SkeletonDataAsset.CreateRuntimeInstance(new TextAsset(File.ReadAllText(Path.Combine(directoryPath, fileName + ".json"))), runtimeAtlas, true);
    SpineGraphic uiObject = SkeletonGraphic.NewSkeletonGraphicGameObject(runtimeSkeletonDataAsset, worldTransform, skeletonGraphicDefaultMaterial);

The skeletonGraphicDefaultMaterial in this case refers to SkeletonGraphicDefault-Straight from the spine-unity materials package
I need to add the SkeletonGraphicMultiply-Straight and SkeletonGraphicScreen-Straight in order for my animations to work, but this doesn't seem to work with SkeletonGraphic objects instantiated in runtime (using the 3 files generated by the Spine editor). Is this a bug? or I'm doing something wrong?

  • Harald ответили на это сообщение.

    kb_z The blendModeMaterials property of the skeletonDataAsset has no materials assigned, attempting to assign my materials as suggested throws an exception.

    In general please always describe what the exception message reads (what type of exception is thrown), and in which line. "attempting to assign my materials as suggested throws an exception" is too imprecise. I assume that you receive a NullReferenceException and the problem lies with the following lines:

    blendModeMaterials.additiveMaterials[0].material = additiveMaterial;
    blendModeMaterials.multiplyMaterials[0].material = multiplyMaterial;
    blendModeMaterials.screenMaterials[0].material = screenMaterial;

    which should not be accessing the first element via [0].material, but should instead first add the element to the list. Otherwise the list would be empty.

    Sorry for that, I will try to word my reply better and add more details.

    This is the first project I've worked on that has Spine in it, we were tasked with migrating an old cocos2d-x project into Unity. The old cocos2d-x project used Spine 3.8, we have 4.2 (latest?) on Unity. All the assets were migrated to 4.2 using the migration scripts on github.

    As mentioned in the first post, because of the sheer amount of assets, we do not want to import everything into Unity.

    During the asset check, we found that some animations were not properly displaying after loading them using the following code:

        SpineAtlasAsset runtimeAtlas = SpineAtlasAsset.CreateRuntimeInstance(new TextAsset(File.ReadAllText(Path.Combine(directoryPath, fileName + ".atlas.txt"))), textures, skeletonGraphicDefaultMaterial, true);
        SpineDataAsset runtimeSkeletonDataAsset = SkeletonDataAsset.CreateRuntimeInstance(new TextAsset(File.ReadAllText(Path.Combine(directoryPath, fileName + ".json"))), runtimeAtlas, true);
        SpineGraphic uiObject = SkeletonGraphic.NewSkeletonGraphicGameObject(runtimeSkeletonDataAsset, worldTransform, skeletonGraphicDefaultMaterial);

    The files are stored in a separate directory and contain only the following files:

    • Skeleton JSON - filename.json
    • Texture File(s) - filename[#].png
    • Atlas file - filename.atlas.txt

    For most of the assets, the above code is enough to instantiate the spine objects for our artists to do the asset check. However it was found during the asset check that animations which included blend modes were not being properly loaded.

    In standard developer fashion, I imported one of the assets into Unity to try and get it to work. I modified the script to load the assets as follows:

       SpineDataAsset runtimeSkeletonDataAsset = Resources.Load<SkeletonDataAsset>(lookupPath);
       SpineGraphic uiObject = SkeletonGraphic.NewSkeletonGraphicGameObject(runtimeSkeletonDataAsset, worldTransform, skeletonGraphicDefaultMaterial);

    In addition to this, we enable Multiple Canvas Renderers for all objects by default and we start the pre-set animation loop so that we can verify each of them

    uiObject.allowMultipleCanvasRenderers = true;
    uiObject.AnimationState.SetAnimation(0, animationName, true);

    During testing we instantiated the same object using both methods, once for the external files, once for the same asset after being imported into Unity.

    Upon further inspection, I decided to check the SkeletonDataAsset generated by unity on the imported asset. I noticed that the Skeleton had blend mode materials assigned to it but they were not being applied.

    That's when I posted the original question above.

    After checking the response from the bot, I gave it a go. It... didn't work. At this point I'm still not sure if I'm doing the right thing.

            var blendModeMaterials = uiObject.skeletonDataAsset.blendModeMaterials;
            Debug.Log($"Blend Materials: Add[{blendModeMaterials.additiveMaterials.Count}] Mult[{blendModeMaterials.multiplyMaterials.Count}] Screen[{blendModeMaterials.screenMaterials.Count}]");
            blendModeMaterials.screenMaterials[0].material = skeletonGraphicDefaultMaterial_Screen;
            blendModeMaterials.multiplyMaterials[0].material = skeletonGraphicDefaultMaterial_Multiply;
            blendModeMaterials.ApplyMaterials(uiObject.SkeletonData);

    When running the above code on the assets imported by Unity, it throws an exception when replacing the multiply materials.

    Blend Materials: Add[5] Mult[0] Screen[4]
    UnityEngine.Debug:Log (object)
    SpineLoadingTest:LoadUIObject () (at Assets/Scripts/Sandbox/Spine/SpineLoadingTest.cs:238)
    SpineLoadingTest:LoadUIObjectOnClick () (at Assets/Scripts/Sandbox/Spine/SpineLoadingTest.cs:224)
    UnityEngine.EventSystems.EventSystem:Update () (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:530)
    
    ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
    Parameter name: index
    System.Collections.Generic.List`1[T].get_Item (System.Int32 index) (at <4b234520e36749be9cf6b053d911690f>:0)
    SpineLoadingTest.LoadUIObject () (at Assets/Scripts/Sandbox/Spine/SpineLoadingTest.cs:240)
    SpineLoadingTest.LoadUIObjectOnClick () (at Assets/Scripts/Sandbox/Spine/SpineLoadingTest.cs:224)
    UnityEngine.Events.InvokableCall.Invoke () (at <228a75ed9d5b4a658c89f48527e7e2e0>:0)
    UnityEngine.Events.UnityEvent.Invoke () (at <228a75ed9d5b4a658c89f48527e7e2e0>:0)
    UnityEngine.UI.Button.Press () (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70)
    UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114)
    UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57)
    UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
    UnityEngine.EventSystems.EventSystem:Update() (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:530)

    Running the same code against the assets in the external folder we also get an exception but this time is one line earlier, which points to the line where the screen materials are set.

    Blend Materials: Add[0] Mult[0] Screen[0]
    UnityEngine.Debug:Log (object)
    SpineLoadingTest:LoadUIObject () (at Assets/Scripts/Sandbox/Spine/SpineLoadingTest.cs:238)
    SpineLoadingTest:LoadUIObjectOnClick () (at Assets/Scripts/Sandbox/Spine/SpineLoadingTest.cs:224)
    UnityEngine.EventSystems.EventSystem:Update () (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:530)
    
    ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
    Parameter name: index
    System.Collections.Generic.List`1[T].get_Item (System.Int32 index) (at <4b234520e36749be9cf6b053d911690f>:0)
    SpineLoadingTest.LoadUIObject () (at Assets/Scripts/Sandbox/Spine/SpineLoadingTest.cs:239)
    SpineLoadingTest.LoadUIObjectOnClick () (at Assets/Scripts/Sandbox/Spine/SpineLoadingTest.cs:224)
    UnityEngine.Events.InvokableCall.Invoke () (at <228a75ed9d5b4a658c89f48527e7e2e0>:0)
    UnityEngine.Events.UnityEvent.Invoke () (at <228a75ed9d5b4a658c89f48527e7e2e0>:0)
    UnityEngine.UI.Button.Press () (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70)
    UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114)
    UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57)
    UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
    UnityEngine.EventSystems.EventSystem:Update() (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:530)

    In this case, the logs also show that the blend materials are not set at all for the latter.

    I then commented out the lines which set the materials and left only the ApplyMaterials line to see if it would have any effect, still nothing. I played around with the BlendModeMaterials class and found the RequiresBlendModeMaterials flag which if set to true, makes an extra option appear in the Advanced tab, in the inspector. I also noticed, however, that the Multiply Material and Screen Material have not been assigned (they're showing as None). So in runtime , I assigned the materials in the inspector with the ones provided here:

    • Packages/com.esotericsoftware.spine.spine-unity/Runtime/spine-unity/Materials/SkeletonGraphic-StaightAlphaTexture
      • SkeletonGraphicMultiply-Straight
      • SkeletonGraphicScreen-Straight

    And that made the animation display correctly. However I don't know how to achieve the same result from code, when using the assets loaded from the external folder.

    Any help would be greatly appreciated.

    • Harald ответили на это сообщение.

      Thanks for the detailed writeup!

      kb_z The old cocos2d-x project used Spine 3.8, we have 4.2 (latest?) on Unity.

      You are correct, version 4.2 is the latest version.

      kb_z This is the first project I've worked on that has Spine in it, we were tasked with migrating an old cocos2d-x project into Unity. The old cocos2d-x project used Spine 3.8, we have 4.2 (latest?) on Unity. All the assets were migrated to 4.2 using the migration scripts on github.

      As mentioned in the first post, because of the sheer amount of assets, we do not want to import everything into Unity.

      This sounds as if it would perhaps be a better solution to use editor scripting to automate your imports, instead of loading all assets via the API intended for runtime loading. Please note that the API for dynamically loading skeletons at runtime is rather intended for use cases where you have no alternative. It will be far less comfortable to achieve the same behaviour.

      Anyway, you are right that BlendModeMaterials have indeed been badly supported for runtime instantiation, thanks for reporting and sorry for the troubles! We have just pushed a commit which rearranged some code and provides an additional method SkeletonDataAsset.SetupRuntimeBlendModeMaterials() which performs the relevant setup at the SkeletonDataAsset.

      Runtime loading code would then look e.g. as follows with the additional line:

      SpineAtlasAsset runtimeAtlasAsset = SpineAtlasAsset.CreateRuntimeInstance(
          atlasText, textures, materialPropertySource, true, null, true);
      SkeletonDataAsset runtimeSkeletonDataAsset = SkeletonDataAsset.CreateRuntimeInstance(
          skeletonJson, runtimeAtlasAsset, true);
      runtimeSkeletonDataAsset.SetupRuntimeBlendModeMaterials(
          applyAdditiveMaterial, blendModeTemplateMaterials);

      We have also extended the example scene Spine Examples/Other Examples/Instantiate from Script accordingly to show instantiation with blend mode materials for both SkeletonAnimation and SkeletonGraphic.

      A new spine-unity 4.2 package is available for download here as usual:
      https://esotericsoftware.com/spine-unity-download
      Please let us know if this resolves the issues for your use case as well.

      Issue ticket URL for later reference:
      EsotericSoftware/spine-runtimes2542

      Hi, thanks for the update, I tweaked our implementation to match the sample provided and it worked like a charm.

      I'll get back to you if we encounter any other issues.

      Cheers.

      @kb_z Very glad to hear, thanks for letting us know.