2
\$\begingroup\$

I have a game I'm working on where the player can see the health bars of the player's avatar and other creatures in the game. I want this health bar to always follow the character/creature around. However, I can't figure out how to do this. I thought having the position change when the respective object moved would do the trick, but apparently, it doesn't. When watching the health bar's transform, however, it shows that it is moving with it's respective object but I don't see it on the game screen. Can anyone explain how I go about accomplishing this? Here's the Dropbox link to the project.

HealthBar Script

public class EnemyHealthBar:MonoBehaviour{
    public Texture2D healthBar;
    public Texture2D health;
    public Rect healthBarPosition;
    public Rect healthPosition;
    public GameObject monster;

    void Start(){
    }
    void Update(){
        transform.position = new Vector3(monster.transform.position.x, monster.transform.position.y, monster.transform.position.z);
        Debug.Log(transform.position);
    }
    void OnGUI(){
        GUI.DrawTexture(healthBarPosition, healthBar);
    }
}

Video of what's currently happening

Dropbox Link

\$\endgroup\$
  • \$\begingroup\$ Please edit your question to include a sample of your health bar positioning script, and a screengrab or video of the results you're seeing. A person shouldn't have to download or run all your source code just to see the one problem you're asking about. \$\endgroup\$ – DMGregory Oct 18 '16 at 22:16
  • \$\begingroup\$ @DMGregory Done, I had to put the video on YouTube, I have no idea how to add videos to questions on this site. \$\endgroup\$ – Robert Oct 18 '16 at 22:23
  • \$\begingroup\$ It looks like you're using the old GUI system instead of the new one introduced in 4.6. You also seem to be treating healthBarPosition as a constant - you never change it in your Update method, so it always has the same value, and thus the same absolute position in the viewport (unless there's another script modifying it?). Before we suggest alternatives, I'd like to check whether there's a particular reason you've gone this route. Would you be interested in solutions that use the new features, or would you prefer to stick to OnGUI? \$\endgroup\$ – DMGregory Oct 18 '16 at 22:28
  • \$\begingroup\$ I'm following a tutorial on YouTube that is teaching the old GUI system and this particular functionality is something I'm just going off of script with in regards to the video. \$\endgroup\$ – Robert Oct 18 '16 at 22:31
  • \$\begingroup\$ @DMGregory I would like to see solutions that use the new features if you don't mind. I'm going to have to switch to them regardless and will likely run into this again. Thanks in advance for helping me out. \$\endgroup\$ – Robert Oct 18 '16 at 22:35
6
\$\begingroup\$

Based on the discussion above, I'd recommend moving over to the new UI system for content like this. It will be a bit of an adjustment at first, but it gives you a lot of flexibility:

  • Using UI Image components, you can create scalable health bars, using the "sliced" type to get rounded corners that don't distort or get cut off as the bar changes size, or the various "filled" methods to reveal a portion of the bar (including circular bars if you wanted)

  • You can set up animations and effects on the bars in the editor, instead of needing to describe every behaviour in code within the OnGUI method. This also makes it more straightforward for a team to collaborate on UI, since it's not bottlenecked in one file

  • When you need different variations on the UI (eg. a boss character who needs a super-fancy-looking health bar) you can create them as new prefabs in the editor, instead of needing to maintain multiple copies of your UI-drawing script for each case

There's tons of tutorials out there for creating the health bar, so I won't go into too much detail about the asset setup here, focusing instead on the script to position it correctly.

So, I'll assume you've set up...

  • A UI Canvas that's containing your health UI, set to either Screen Space Camera or Screen Space Overlay modes (World space would be handled differently, if you want to go there)

  • A GameObject child of your canvas representing your health bar. It probably contains one or more child objects of its own for the various visuals (eg. bar frame, bar fill, character name text, status effect icon...)

Now, we can attach this script to the health bar GameObject:

public class UIAnchor : MonoBehaviour {

    // Assign this to the object you want the health bar to track:
    public Transform objectToFollow;

    // This lets us tweak the anchoring position in the object's local space
    // eg. if you want the bar to appear above the unit's head.
    public Vector3 localOffset;

    // This lets us tweak the anchoring position in our canvas space
    // eg. if we want the UI to sit off to the right on our screen.
    public Vector3 screenOffset;

    // Cached reference to the canvas containing this object.
    // We'll use this to position it correctly
    RectTransform _myCanvas;


    // Cache a reference to our parent canvas, so we don't repeatedly search for it.
    void Start () {
        _myCanvas = GetComponentInParent<Canvas>().GetComponent<RectTransform>();
    }

    // Use LateUpdate to apply the UI follow after all movement & animation
    // for the frame has been applied, so we don't lag behind the unit.
    void LateUpdate () {

        // Translate our anchored position into world space.
        Vector3 worldPoint = objectToFollow.TransformPoint(localOffset);

        // Translate the world position into viewport space.
        Vector3 viewportPoint = Camera.main.WorldToViewportPoint(worldPoint);

        // Canvas local coordinates are relative to its center, 
        // so we offset by half. We also discard the depth.
        viewportPoint -= 0.5f * Vector3.one; 
        viewportPoint.z = 0;

        // Scale our position by the canvas size, 
        // so we line up regardless of resolution & canvas scaling.
        Rect rect = _myCanvas.rect;
        viewportPoint.x *= rect.width;
        viewportPoint.y *= rect.height;

        // Add the canvas space offset and apply the new position.
        transform.localPosition = viewportPoint + screenOffset;
    }
}

This does have the effect of separating the health bar from the character object/script, which can be awkward when you're spawning characters at runtime, as you need to do a little work to spawn their health bar too and ensure it's wired-up correctly. Two approaches you can consider:

  • Add the health bar as a child of your character prefab, so they get spawned together. Modify the Start method above so instead of searching for a parent canvas, we find the main UI canvas in the scene and re-parent the health bar to it during initialization. Since both exist in the same prefab, you can have all their dependencies wired-up in advance (though without nested prefab support, this might mean you end up duplicating assets).

  • Have your character's health component expose a variable for a health bar prefab. During this health script's Start method, it spawns an instance of this prefab and parents it to the scene's UI canvas. It will also need to handle wiring-up the health bar to follow it, and its own health update to scale the right part of the health bar.

\$\endgroup\$
  • \$\begingroup\$ Perfect solution!!!! \$\endgroup\$ – Xtro Mar 13 '17 at 23:52
3
\$\begingroup\$

I would recommend that you make a new Canvas object as a child of the player object and in its Canvas component set the Render mode to World space. Then create your Healthbar as the child of this canvas.

\$\endgroup\$
0
\$\begingroup\$

I don't have enough reputation to comment, but expanding on DMGregory, you can use the following to keep it in a prefab and have it automatically find the transform and then reparent to the canvas:

    void Start () {
    objectToFollow = transform.parent;
    _myCanvas = GameObject.Find ("Canvas").GetComponent<RectTransform>();//GetComponentInParent<Canvas>().GetComponent<RectTransform>();
    transform.SetParent(_myCanvas, false);
}

The only problem is that since they are GUI objects, they show through everything, so you can spot an enemy a mile away by his health bar.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.