Aggressor

  • 21 giu 2021
  • Si è iscritto 24 mar 2015
  • Can you clarify this point?

    you can control the additive properties of the color when the _DarkColorAlphaAdditive "Additive DarkColor.A" property is enabled.

    For the first part are you saying that I set the color change to "DarkColorAlphaAdditive " instead of "Black" like so:

            
    mpb.SetColor("_DarkColorAlphaAdditive ", red);

    As for the "Additive DarkColor.A" enabling, can you clarify how I do that with a MaterialPropertyBlock or is there some other way to do this?

  • I am back with a followup question about the shader.

    Current I am setting a color change as so:

    mpb = new MaterialPropertyBlock();
    Color color = new Color(0, 255, 0, 0.5f);
    mpb.SetColor("_Black", color);
    _meshRenderer.SetPropertyBlock(mpb);
    

    However the alpha property is ignored. Even at 0% it shows the color change at 100%.

    Can you advise me how I can set the intensity of the color change? I would like to fade in and out the color changes as opposed to having them 100% on or off.

  • Yes I was hoping there was a simple solution without having to maintain a reference and use the +/- approach and the examples I saw without it were not ideal.

    Either way, I appreciate the help, thank you very much.

  • The issue is this:

    _skeletonAnimation.AnimationState.Event += (TrackEntry, Event) =>
            {
                if (animationEventCallback != null)
                {
                    animationEventCallback(Event.Data.Name, unitState);
                }
            };

    This is called during an init method. So this kept adding callbacks to the list so one invocation would call multiple callbacks.

    I was looking around for a clean way to clean this list (without maintaining a reference manually) and there doesn't seem to be a simple solution like Event.clearAll().

    Is there a way you can recommend to clear this Event list?

  • @Harald

    I created a minimal project and I am seeing the event fired only once in this case so the problem is definitely related to my code.

    Ill continue to debug on my end.

    Thanks.

  • I have a callback listening to events. I have verified that each event only appears once.

    However each event is fired twice (at the same time). I only call 1 animation and on one spine object and there is only one reference (so there arent two units firing events, or two listeners to one event).

    The code is relatively simple:

    // Event Listener
    _skeletonAnimation.AnimationState.Event += (TrackEntry, Event) =>
    {
        if (animationEventCallback != null)
        {
            animationEventCallback(Event.Data.Name, unitState); // Called twice for every event
        }
    };
    
    // The animation invoke
    animationEventCallback = (eventName, unit) => { Debug.Log(eventName) };
    _animationState.SetAnimation(0, "animationWithOneEventThatsCalledTwice", loop);
    
    

    Every single event is called twice at the same time even though it only appears once in the timeline in Spine2D.

    I did a time printout with each event and I get a log like this

    [Log] Time passed is 0.0204173 for grrblReaction
    [Log] Time passed is 0.0204173 for grrblReaction
    [Log] Time passed is 0.0204173 for monsterReaction
    [Log] Time passed is 0.0204173 for monsterReaction
    [Log] Time passed is 0.0755396 for moveGrrblForward
    [Log] Time passed is 0.0755396 for moveGrrblForward
    [Log] Time passed is 2.10146 for cameraShake
    [Log] Time passed is 2.10146 for cameraShake
    [Log] Time passed is 2.10146 for monsterKnockback
    [Log] Time passed is 2.10146 for monsterKnockback
    [Log] Time passed is 2.10146 for tweenMonsterRed
    [Log] Time passed is 2.10146 for tweenMonsterRed
    [Log] Time passed is 2.305758 for tweenMonsterWhite
    [Log] Time passed is 2.305758 for tweenMonsterWhite
    [Log] Time passed is 2.500231 for tweenMonsterNormal
    [Log] Time passed is 2.500231 for tweenMonsterNormal
    [Log] Time passed is 2.567319 for moveGrrblBack
    [Log] Time passed is 2.567319 for moveGrrblBack

    Can you think of a reason my events are firing twice with only one reference?

    Here is a screen shot of the spine file with each event being dispatched once:

  • I was checking the prefab not the runtime instance so I when I did at runtime I was able to see the materials after attaching.

    The other issue was the the SpriteAttacher script was not using the right shader, so I updated it to point to the Tint one

    public class SpriteAttacher : MonoBehaviour
    {
        public const string DefaultPMAShader = "Spine/Skeleton Tint";
        public const string DefaultStraightAlphaShader = "Spine/Skeleton Tint";
    
    

    Then I did the following to update the tinting and it worked.

    public void ChangeColor(Color color)
    {
        mpb = new MaterialPropertyBlock();
        mpb.SetColor("_Black", color); // Special Spine material refernce to do a Dark Tint 
        _meshRenderer = GetComponent<MeshRenderer>();
        _meshRenderer.SetPropertyBlock(mpb);
        for (int i = 1;  i < _meshRenderer.sharedMaterials.Length; i++)
        {
            _meshRenderer.SetPropertyBlock(mpb, i);
        }
    }
    

    Thank you for the extra help.

    I will now see if I can get the tweening to work...

  • The gear is attached at runtime and can change dynamically (it uses the legacy SpriteAttacher script) like so

    public void Attach(Sprite sprite, Slot slot)
            {
                var skeletonComponent = GetComponent<ISkeletonComponent>();
    
            if (sprite == null)
            {
                slot.Attachment = null;
                return;
            }
    
            if (attachmentShader == null)
            {
                attachmentShader = Shader.Find(DefaultStraightAlphaShader);
            }
    
            // This is required to prevent the arms getting distorted with the wrong rotation
            RegionAttachment attachment;
            if (slot.Attachment != null)
            {
                float rotation = ((RegionAttachment)slot.Attachment).Rotation;
                attachment = sprite.ToRegionAttachmentPMAClone(attachmentShader, rotation: rotation);
            } else
            {
                attachment = sprite.ToRegionAttachmentPMAClone(attachmentShader);
            }
            slot.Attachment = attachment;
        }
    

    I believe there is just the 1 material as well. However given sprites are attached at runtime Im not sure if that makes it more than one at runtime?

    If I comment out the slots that red overtakes and ruins the look (because its not using the SetPropertyBlock). For context, there are additional empty slots on the body where we attach the runtime sprites (like helmet and armor, so that list just makes sure when I do the coloring its to those special slots which can change at runtime).

    By attaching sprites to the slots at runtime, does this mean I have multiple textures? I would assume so since the coloring is separate.

    Given that I am passing in sprites that get attached, is there a way to get access to mesh renderer from the slot? If so I am hoping something like this would work?:

    slot.getMeshSomehow().SetPropertyBlock(mpb);

  • Thanks Harald,

    I took a look at those examples and I do like how the Dark Color tint works using the Skeleton Tint shader.

    When I use the MaterialPropertyBlock to set the color, I really like how that looks compared to the flat red I was applying. Its looks good for the base unit, but the gear (the stuff in darker red) is still off when I set the slot color.

    I went through each gear slot and manually set it to red, but the colors don't match.

    public void ChangeColor()
    {
        mpb = new MaterialPropertyBlock();
        mpb.SetColor("_Black", Color.red);
        var mesh = GetComponent<MeshRenderer>();
        mesh.SetPropertyBlock(mpb);
    
    var skeletonComponent = GetComponent<ISkeletonComponent>();
    foreach (Slot slot in skeletonComponent.Skeleton.Slots)
    {
        // _gearSlots is just a reference to each slot which can have a gear sprite attached, e.g. body armor
        if (_gearSlots.Contains(slot))
        {
            slot.SetColor(Color.red);
        }
    }
    }
    
    

    I assume its because I am not setting the MaterialPropertyBlock to get the same effect?

    I am not that familiar with shaders (and it's the first thing I plan to learn in depth after I get this freaking game finished).

    In order to fix this and have the attachments get the same coloring, do I need to get the mesh of the attachment (and how would I do that)? Or is there something else I need to do?

    Thank you.

    P.S. How do I change it back to no coloring after? I tried using Color.white but it changes it to white. [Update I was able to clear the mesh property block by setting it to null]

  • We are using the SpriteAttacher script to attach sprites and I I was curious if you know how we can solve an issue with dynamic coloring.

    When a unit gets hit, I want to tween its entire sprite to red then back to full color. I am currently using AllIn1Shader https://assetstore.unity.com/packages/vfx/shaders/all-in-1-sprite-shader-156513 and it works on the base unit sprite, but as you can see above it is not working for the attached sprites (the gear is not red its still its base color).

    Do you have a suggestion on how I can make sure all the attached sprites always update with the same color changes the root sprite has?

  • I needed the slot originally in order to know what to set to null but your suggestion is correct so I instead make a custom call just to clear all slots once!

    public void SetAllSlotsToNull()
    {
        var skeletonComponent = GetComponent<ISkeletonComponent>();
        foreach (Slot slot in skeletonComponent.Skeleton.Slots)
        {
            slot.Attachment = null;
        }
    }
    
    

    Phew.

    Further still as your suggestion, I should cache these slots and find them in the init! I am surprised it took me so long to do this optimization!

    Thank you, I know this was a long thread. I will be sure to send along some game keys when this is finished!

  • Ok I thought from the documentation you had to have the controller asset and would set events using the Unity animator animations. I will try as you suggest thank you.


    This worked:

    _skeletonAnimation.AnimationState.Event += (TrackEntry, Event) =>
    {
        if (Event.Data.Name == "requipAllGear")
        {
            UpdateGear();
        }
    
    if (Event.Data.Name == "removeAllGear")
    {
        RemoveAllGear();
    }
    };
    

    Thanks for the help


    The only problem now, is there is a split second freeze in the animation when the gear is removed. Im wondering if theres any performance optimizations you can think of?

    public void RemoveAllGear()
    {
        _spriteAttacher.Attach(null, T_EquipSlots.TRINKET);
        _spriteAttacher.Attach(null, T_EquipSlots.WEAPON);
        _spriteAttacher.Attach(null, T_EquipSlots.ARMOR);
        _spriteAttacher.Attach(null, T_EquipSlots.FRONT_LOWER_ARM);
        _spriteAttacher.Attach(null, T_EquipSlots.FRONT_UPPER_ARM);
        _spriteAttacher.Attach(null, T_EquipSlots.BACK_LOWER_ARM);
        _spriteAttacher.Attach(null, T_EquipSlots.BACK_UPPER_ARM);
        _spriteAttacher.Attach(null, T_EquipSlots.BACK_UPPER_ARM);
        _spriteAttacher.Attach(null, T_EquipSlots.HELMET);
        _spriteAttacher.Attach(null, T_EquipSlots.HELMET_BACK);
    }
    
  • As long as that doesnt cause a flicker we will try that

    Thanks.

    Will report back when implemented.


    @Harald, this documentation seems to require a SkeletonMecanim component to add events.

    Can manual events at time stamps be added using a SkeletonAnimation?

  • Ok we will setup an animation event can you point me to the docs which have a unity example for this.

    I do have a question tho:

    How do I get a callback when "SetupPose" is called within the skeleton? I need a hook so I can set the gear back.

    I would use this SetupPose callback to re-assign the gear, and then I would use this "Hide" animation event to set it all to null


    To clarify 1 thing though,

    I do not call setup pose. If you look at the sample project a sprite is attached to a slot. And when the death animation is called, its immediately set to null (not from client code).

    From a user perspective this seems like a bug. If I attach a sprite to a slot, and then hide it halfway through an animation, it should hide at that frame. I do not understand the point of allowing hiding of slots in the animator if when you use it in game, it instead hides the slot (sets it to null) at the start of the animation (without the client calling setup pose)

  • Ok sure here is my attach method:

    public void Attach(Sprite sprite, T_EquipSlots equipSlot)
            {
                var skeletonComponent = GetComponent<ISkeletonComponent>();
                Shader attachmentShader = Shader.Find(DefaultStraightAlphaShader);
                var slot = skeletonComponent.Skeleton.FindSlot(_SLOT_TYPE_TO_NAME[equipSlot]);
                if (sprite == null)
                {
                    slot.Attachment = null;
                    return;
                }
                X_Logger.Log("Attaching sprite " + sprite.name);
                // This is required to prevent the arms getting distorted with the wrong rotation
                RegionAttachment attachment;
                if (slot.Attachment != null)
                {
                    float rotation = ((RegionAttachment)slot.Attachment).Rotation;
                    attachment = sprite.ToRegionAttachmentPMAClone(attachmentShader, rotation: rotation);
                } else
                {
                    attachment = sprite.ToRegionAttachmentPMAClone(attachmentShader);
                }
                slot.Attachment = attachment;
            }

    I guess what confuses me is if the slot is null, how am I supposed to know if its null because the animation called setup pose and set it to null, or its null because the animation has hidden the slot?

    My confusion stems from I don't understand how I my code is able to know when a slot is hidden during the animation. Checking for null won't work because the original issue is the slots get set to null when the animation starts. So if there is not a "hidden" property, how am I supposed to know that I should stop attaching the gear to the slot during an animation that hides them half way through?

  • @Harald,

    Thanks I did try this, but now the gear stays on even after they should be hidden.

    Do I need to check if the slot is disabled (if so how?) and then not attach? In my head it seems like if the slot is hidden, the gear attachment should be ignored since the parent is not active. But I guess the way it works is that when a slot is hidden during an animation its just a reference that still needs to be read and only if its active attach the gear?

    Here was my initial attempt:

    In my UnitAnimator script I set the callback:

     public Action skeletonAnimationUpdateCompleteCallback;
     public void Init(S_Unit unitState)
    {
    
     _animator.UpdateComplete += delegate (ISkeletonAnimation animated)
     {
         skeletonAnimationUpdateCompleteCallback?.Invoke();
     };
    
     }
    

    Then in my main unit class I call update gear on this:

           // Update gear on skeleton for animations which might hide it during the animation
           unitAnimator.skeletonAnimationUpdateCompleteCallback = UpdateGear;
    
    public void UpdateGear()
       {
           if (_spriteAttacher == null)
           {
               X_Logger.LogError("Missing sprite attacher on " + gameObject);
           }
    
       // Check if has gear
       if (_unitState.trinketCardGO != null)
       {
           _spriteAttacher.Attach(_unitState.trinketState.gearSkinModel.spineSprite, T_EquipSlots.TRINKET);
       }
    
       if (_unitState.weaponCardGO != null)
       {
           _spriteAttacher.Attach(_unitState.weaponState.gearSkinModel.spineSprite, T_EquipSlots.WEAPON);
       }
    
       if (_unitState.armorCardGO != null)
       {
           M_GearSkin armor = _unitState.armorState.gearSkinModel;
           _spriteAttacher.Attach(armor.spineSprite, T_EquipSlots.ARMOR);
           _spriteAttacher.Attach(armor.frontLowerArmSprite, T_EquipSlots.FRONT_LOWER_ARM);
           _spriteAttacher.Attach(armor.frontUpperArmSprite, T_EquipSlots.FRONT_UPPER_ARM);
           _spriteAttacher.Attach(armor.backLowerArmSprite, T_EquipSlots.BACK_LOWER_ARM);
           _spriteAttacher.Attach(armor.backUpperArmSprite, T_EquipSlots.BACK_UPPER_ARM);
           _spriteAttacher.Attach(armor.frontArmorSprite, T_EquipSlots.BACK_UPPER_ARM);
       }
    
       if (_unitState.helmetCardGO != null)
       {
           if (_unitState.helmetState == null)
           {
               X_Logger.LogError("Null helmet state but non null helmet card go");
           }
    
           _spriteAttacher.Attach(_unitState.helmetState.gearSkinModel.spineSprite, T_EquipSlots.HELMET);
    
           if (_unitState.helmetState.gearSkinModel.helmetBackSprite)
           {
               _spriteAttacher.Attach(_unitState.helmetState.gearSkinModel.helmetBackSprite, T_EquipSlots.HELMET_BACK);
           }
           else
           {
               var helmetBackSlot = _skeletonAnimation.skeleton.FindSlot("helmetBack");
               helmetBackSlot.Attachment = null;
           }
    
           if (_unitState.helmetState.gearSkinModel.hideEars)
           {
               var frontEarSlot = _skeletonAnimation.skeleton.FindSlot("frontEar");
               frontEarSlot.Attachment = null;
    
               var backEarSlot = _skeletonAnimation.skeleton.FindSlot("backEar");
               backEarSlot.Attachment = null;
           }
       }
       }
    

    In my UpdateGear code, do I need to check if the slot is active and if so attach null?

    Also performing this is extremely expensive its dropping my fps from like 300 to 30 so I dont think this approach I took is correct.