Search code examples
godottweengdscript

Godot Fade In Between Black and White Using Tween Delay Doesn't Work


I am trying to switch in between 2 tween.interpolate_properties() depending on if the player is moving or not. However, the delay function does not work. Without delay on line 11, it works immediately (as it should). However, when i add a delay of 2 seconds, it looks like after two seconds it briefly flashes black (but not all the way) and then goes back to transparent. Attached is the code, please help!

extends Sprite
onready var TweenNode = get_node("../Tween")
onready var velocity

func _ready():
    modulate.a = 0

func _process(delta):
    velocity = get_node("../../").velocity
    if velocity == Vector2.ZERO:
        TweenNode.interpolate_property(self, "modulate", modulate, Color(0, 0, 0, 1), 5, Tween.TRANS_EXPO, Tween.EASE_OUT_IN, 2)
    else:
        TweenNode.interpolate_property(self, "modulate", modulate, Color(0, 0, 0, 0), .1, Tween.TRANS_EXPO, Tween.EASE_OUT_IN)
    TweenNode.start()

Solution

  • This is a case of "The computer is doing what you told it to".

    This line:

    TweenNode.interpolate_property(self, "modulate", modulate, Color(0, 0, 0, 1), 5, Tween.TRANS_EXPO, Tween.EASE_OUT_IN, 2)
    

    It says, after 2 seconds set modulate to the value modulate has right now. And then vary its value to Color(0, 0, 0, 1) in a span of 5 seconds, with such and such easing.

    And you are doing that every frame. So, after two seconds, every frame, it will set modulate to the value it had two seconds ago. The interpolation will try to change modulate, just for the value to get reset next frame.

    Hence:

    it looks like after two seconds it briefly flashes black (but not all the way) and then goes back to transparent

    If you leave it running, you will see that after other two seconds it does not go back to transparent. Instead it goes back to that slightly black you saw after two seconds. Because after four seconds, that is what modulate had two seconds ago.

    And, as you can imagine, that means it will start to get a little darker. And after a few minutes minutes it will be pretty much black.


    We could, of course, keep track of what interpolate_property call we have done. And to do that we would add a new property where we keep track of what value we told the Tween to interpolate to…


    Well, I'm going to take a side step and explain follow_property!

    With follow_property you can tell the Tween to interpolate a property to the value of another property.

    So, we add our new property, for example:

    var _modulate_target:Color
    

    We need to initialize it, by the way:

    func _ready():
        modulate.a = 0
        _modulate_target = modulate
    

    And then we can tell the Tween to make make one property follow the other property:

    _modulate_target = Color(0, 0, 0, 1)
    TweenNode.follow_property(
        self,
        "modulate",
        modulate,
        self,
        "_modulate_target",
        5,
        Tween.TRANS_EXPO,
        Tween.EASE_OUT_IN,
        2
    )
    TweenNode.start()
    

    And to avoid the reset behavior, we check the value of our new property, and if it is already the value we want, we do nothing:

    if _modulate_target != Color(0, 0, 0, 1):
        _modulate_target = Color(0, 0, 0, 1)
        TweenNode.remove(self, "modulate")
        TweenNode.follow_property(
            self,
            "modulate",
            modulate,
            self,
            "_modulate_target",
            5,
            Tween.TRANS_EXPO,
            Tween.EASE_OUT_IN,
            2
        )
        TweenNode.start()
    

    There I'm also removing any existing animation on modulate with:

    TweenNode.remove(self, "modulate")
    

    And you would follow that pattern for every change. I would argue for using setget, but given that you want different duration and delay that won't do. Instead we can simply wrap that into another func:

    func _tween_modulate(target:Color, duration:float, delay:float) -> void:
        if _modulate_target == target:
            return
    
        _modulate_target = target
        TweenNode.remove(self, "modulate")
        TweenNode.follow_property(
            self,
            "modulate",
            modulate,
            self,
            "_modulate_target",
            duration,
            Tween.TRANS_EXPO,
            Tween.EASE_OUT_IN,
            delay
        )
        TweenNode.start()
    

    And you use it like this:

        if velocity == Vector2.ZERO:
            _tween_modulate(Color(0, 0, 0, 1), 5, 2)
        else:
            _tween_modulate(Color(0, 0, 0, 0), .1, 0)
    

    Please notice that you still need _modulate_target to check if it is different than target. However, yeah, sure you can use interpolate_property:

    func _tween_modulate(target:Color, duration:float, delay:float) -> void:
        if _modulate_target == target:
            return
    
        _modulate_target = target
        TweenNode.remove(self, "modulate")
        TweenNode.interpolate_property(
            self,
            "modulate",
            modulate,
            target,
            duration,
            Tween.TRANS_EXPO,
            Tween.EASE_OUT_IN,
            delay
        )
        TweenNode.start()
    

    Using follow_property just made this refactor easier to explain.