• Godot Help
  • floating text affected by scale and rotation of a parent

I have a floating text node (parent: node2d, child: label) that basically pops up when bullet hits the target. The enemy in this case spawns these texts to indicate the damage done to it.

    public void OnHit()
    {
        var floatingText = floatingTextScene.Instantiate<FloatingText>();
        floatingText.Text = 50.ToString();
        AddChild(floatingText);
    }

The problem is that the enemy's rotation is affected by players position. As well as the enemy's scale Y is changed to flip the enemy depending on players position.. Together with the enemy scale and rotation - floating text is also affected.

Is it possible for the floating text to not have the scale and rotation affected by the parent nodes modifications, but keeping the relative position to enemy's pivot?

Basically I want the floating text to live it's own life but sill be pivoted to the center of the enemy.

I am using the _Ready function in combination with Tweens to animate the floating text

    public override void _Ready()
    {
        var label = GetNode<Label>("Text");
        label.Text = Text;
    
        label.Scale = new Vector2(0.3f, 0.3f); 
    
        var MoveSpeed = 100f;
        var finalPosition = Position + new Vector2((float)GD.RandRange(-1f, 1f), (float)GD.RandRange(-1f, 1f)) * MoveSpeed;

        var creationTween = CreateTween();
        creationTween.TweenProperty(label, "scale", new Vector2(1, 1), 0.2f).SetTrans(Tween.TransitionType.Bounce);
        creationTween.Parallel().TweenProperty(label, "position", finalPosition, 0.2f);
    
        creationTween.TweenProperty(label, "modulate", new Color(1, 1, 1, 0), 0.4f);
        creationTween.TweenCallback(new Callable(this, nameof(OnFadeOutComplete)));
    }
    
    private void OnFadeOutComplete()
    {
        QueueFree();
    }

One of the solutions I though about is just overriding the Scale to and Position on every frame in _Proccess function, but
a. it sounds like an overkill for such a simple task
b. this will most likely mess up with the tween'ing logic (since it also changes some of the values)

therefore I think the best solution would be to isolate the floating text from the parent's modifications. The question is how? I was thinking about spawning texts on the same level as parent node, but then I guess the text will still be affected by other logic above that which could lead to problems in the future. Should I just spawn labels right under GetTree() top-most node? So.. ideas/solutions are welcomed.

Parent it to the scene root node instead of enemy node.

    xyz this does not solve the problem. The default position for the node will be 0,0 and also the binding to player node will be lost.

    Another use-case than the floating text that I am asking about is the HP bar.

    consider a case when we need to display an HP bar for each enemy. I don't see this bar living anywhere else than the enemy itself

    now when the enemy moves around the map and flips - the HP bar faces the exact same issue as the floating text:

    I think in general it does not matter if we discuss this matter based on the original issue with the floating text, or based on this new use-case that I have just provided. The is issue that the underlying object moves together with the rest of the node, so I would like to know how to prevent it from happening. Setting the parent to something else than the binding object is logically wrong (hp bars of the enemies living in root of the scene?).

    I guess I can write the logic that will update the hp bar binding location to the target object and keep the nodes in sync that way, but logically it still is wrong to have HP bar someplace else than under the target node itself. Or not?

      The way I would do it is place a node empty above your enemies' head then have the child keep track of the position of that node and nothing else, should get rid of the rotation problem once and for all.

      So something like this pseudo code floatingText.global_transform.origin = enemyNodeText.global_transform.origin

      If you want to have the text follow the enemy rather than stay in one place after being spawned simply have the position data of the text follow the node in process until it fades. Let me know if origin works because I can't remember if that just keeps track of the position or if you need to do something specific to keep the position only, the documentation seems to imply this is fine though.

        dearme I think I might have figured it out. I need to abstract away the enemy node into something like:

        and when the transformations are applied - apply them specifically to area2d rather than the whole node. That way whenever I add to the root node of the enemy will not get affected by stuff that's happening inside of area itself..

        xyz was that what you meant as well?

        I will have to try this out and report if this solution works.

        Lethn right! I wanted to avoid adding nodes outside of "main" object (i.e. player, enemy) etc. but this might work.. Before that I would like to try another solution that I have posted about in previous answer to see if that works. This should simplify things as I will not have to update the location at all from under a child node..

          dearme If you want to keep the hierarchy you could set the node as top level, that should work fine.

            Lethn well it does not work exactly as I expected it to 🙂

            I need the object to be of type CharacterBody2D/KinematicBody2D in order to be able to walk and interact with physics objects/collisions.

            Therefore if I create a root node of type Node2d as a root then I can no longer tell it to MoveAndCollide. On the other side, if tell the child node (which used to be a root node ) to walk - then it simply goes independently from the root node like so:

            in order for this approach to work I needed to set the parent node position coordinates to child's CharacterBody2D after it walked with MoveAndCollide and updated the position. then I would need to update the child node's position again, to compensate for parent new global position (after I move it to child's position the child itself will be moved).

            This sounded like a hack and a lot of work, so instead, I decided to animate a sprite instead, for now. Now when I need to change the rotation or flip something I do it for a sprite, and when I need to walk I do it for the main node using the same old MoveAndCollide.

            Essentially I have the same structure as had, before I started making modifications and the only code update I needed was

                    var _spriteNode = GetNode<AnimatedSprite2D>("Sprite");
                    var angle = GlobalPosition.AngleToPoint(targetPosition);
                    _spriteNode.GlobalRotation = angle;
            
                    if (targetPosition.X < GlobalPosition.X)
                    {
                        _spriteNode.Scale = new Vector2(_spriteNode.Scale.X, -_startScaleY);
                    }
                    else if (targetPosition.X > GlobalPosition.X)
                    {
                        _spriteNode.Scale = new Vector2(_spriteNode.Scale.X, _startScaleY);
                    }

            You haven't made a separate child node beneath the sprite for the text to follow, that's why it's not working. The idea is you're setting the top level so that the text doesn't automatically copy the transform of the parent it belongs to, you then keep track of a child node's position presumably placed beneath the sprite because that's where you want it to stick and then the text should follow the sprite correctly regardless of the parent's rotation. If you want to rotate the text under specific conditions then you do that manually through code.

            I think the only catch with this method is if the parent rotates then the text would follow the child position upwards which I don't think is what you want, this is a bit of a tricky problem actually lol. Actually, what you could do is take advantage of local transform co-ordinates, then perhaps that way you could keep it completely static. Don't use global transform and have it stick to the parent's position at a fixed place without it doing any child transform following whatsoever.

            In fact using local co-ordinates may be the solution.

            dearme Setting the parent to something else than the binding object is logically wrong (hp bars of the enemies living in root of the scene?)

            No, that's exactly how it should be done. Not necessarily parented directly to root but to some high level dummy node that holds them all together. It's also perfectly ok if they live under the root and are grouped via Godot's named group mechanism for easy access. Each bar (or nametag etc...) should then hold a reference to its owner, and position itself with some offset from owner's global position, each frame in the _process() callback.

            It other words; make a scripted hp bar, with owner node and offset properties, and let the bar take care of itself. When owner dies let it just send a self destruct signal to the bar which can then delete itself. And that's all there is to it.