derkork
Topic Author
Posts: 1
Joined: 18 Feb 2018, 11:27

Unit health bars with NoesisGUI

18 Feb 2018, 13:06

I'm currently evaluating if we could use NoesisGUI for a RTS game. In this kind of games you have unit health bars which are rendered in world space and use billboarding to always face the camera. In a very simple case this could be just a plain quad texture however we plan on having some more complex health bars, which contain text and additional information, e.g. like these:

Image

We use the Unity game engine for building our game. From what we gather from the documentation NoesisGUI renders either on top of a camera (screen space overlay) or into a render texture that could be put on a quad in world space. So in theory I could create a render texture per unit, let Noesis render our complex unit health bar into that and then render this on the quad in world space which billboards to the camera. However, having potentially a few hundred units on screen, I'm concerned about the performance implications of having Noesis render to hundreds of little render textures. Also I would need to have a way of culling out units that are offscreen, so Noesis would not have to render the UI for them.

I couldn't find anything in the documentation on how to approach this, so I wonder if Noesis would be a good fit to draw lots of little world space UI elements like this and if so how this could be done in an efficient way.
 
User avatar
jsantos
Site Admin
Posts: 3205
Joined: 20 Jan 2012, 17:18
Contact:

Re: Unit health bars with NoesisGUI

21 Feb 2018, 12:31

Are those unit health bars really in world space? I mean, do they get smaller as the go farther?

I do not recommend the render texture approach because as you mention, you are going to need a lot of textures and the performance is probably going to suffer a bit (maybe not...). What I would do is just rendering the quads on top of the camera, just project the 3D point of the unit to the screen and just draw the 2D quad there. That way, you only have one XAML. If the quads needs to get smaller in 3D, then you can also apply a projection matrix to each of them.

This is definitely a good idea for one of our next public samples.
 
User avatar
sfernandez
Site Admin
Posts: 2336
Joined: 22 Dec 2011, 19:20

Re: Unit health bars with NoesisGUI

21 Feb 2018, 12:44

If you still want to pursue the option of rendering in 3D, you can have a single NoesisView rendering to a RenderTexture ALL the life bars and info, and use that as an atlas texture that you can set as source for the meshes you will render in 3D with the appropriate UV coordinates.

We did that when creating our plane cockpit video: viewtopic.php?f=12&t=380
 
HateDread
Posts: 17
Joined: 06 Feb 2020, 10:27

Re: Unit health bars with NoesisGUI

21 May 2021, 10:46

Are those unit health bars really in world space? I mean, do they get smaller as the go farther?

I do not recommend the render texture approach because as you mention, you are going to need a lot of textures and the performance is probably going to suffer a bit (maybe not...). What I would do is just rendering the quads on top of the camera, just project the 3D point of the unit to the screen and just draw the 2D quad there. That way, you only have one XAML. If the quads needs to get smaller in 3D, then you can also apply a projection matrix to each of them.

This is definitely a good idea for one of our next public samples.
Sorry to necro, but this is literally the only thread I could find mentioning this kind of game/UI layout.

Did anything like this ever make it into a public sample, @jsantos? I've looked through them all (I think) but could be wrong.

EDIT: If not, any guidance would be appreciated - still going through some WPF learnings. I can grok the idea of something like a main menu, with parts as children under other parts etc etc. But completely separate UI elements that can be positioned independently (e.g. RTS unit info/health/icon/whatever based on projection of world->screen positions) are a bit out of reach atm.

EDIT2: Realized I should clarify further - it's not even the rendering and being efficient right now, but more, as a xaml noob, how one approaches such a thing. In something like UE4's UMG, I would naively just make a separate widget, some "UnitOverlayWidget" or whatever, and just create/destroy each widget as their associated RTS unit is spawned/killed, and loop through them all and set their positions to be their respective units' position projected into screen space. Very different to having these arbitrarily-split-or-grouped xaml files.
Last edited by HateDread on 21 May 2021, 11:20, edited 1 time in total.
 
User avatar
jsantos
Site Admin
Posts: 3205
Joined: 20 Jan 2012, 17:18
Contact:

Re: Unit health bars with NoesisGUI

25 May 2021, 12:11

Did anything like this ever make it into a public sample, @jsantos? I've looked through them all (I think) but could be wrong.
We started a example about that but it get lost because we didn't like it. Let me see if I can push this again.
In something like UE4's UMG, I would naively just make a separate widget, some "UnitOverlayWidget" or whatever, and just create/destroy each widget as their associated RTS unit is spawned/killed, and loop through them all and set their positions to be their respective units' position projected into screen space. Very different to having these arbitrarily-split-or-grouped xaml files.
Yes, this is exactly the same approach you would follow with Noesis. You can set the positions manually if you give names to each unit in XAML or you can use databinding for the XAML to a collection of positions.

I talked with @sfernandez and he is going to post a small snippet about this.
 
KeldorKatarn
Posts: 193
Joined: 30 May 2014, 10:26

Re: Unit health bars with NoesisGUI

25 May 2021, 12:16

As a suggestion for this: Once you guys fix #2004 I could see this being done by a custom control deriving from Canvas defining a custom Container class.
Like UnitOverlayHost and UnitOverlayItem. That last one could contain the logic to position the item correctly on the screen. The template itself could then just be anything,
and this could be used to basically host anything that has a 3D representation. Like quest markers or anything really.

Once those APIs are in place, I might actually try a sample for this in Caliburn.Noesis.
 
User avatar
sfernandez
Site Admin
Posts: 2336
Joined: 22 Dec 2011, 19:20

Re: Unit health bars with NoesisGUI

25 May 2021, 13:39

The easiest way to achieve this is by using a MVVM approach and use an ItemsControl with data binding:
public class Unit : NotifyPropertyChangedBase
{
  public float ScreenPosX { get => _x; set { _x = value; OnNotifyPropertyChanged("ScreenPosX"; } }
  public float ScreenPosY { get => _y; set { _y = value; OnNotifyPropertyChanged("ScreenPosY"; } }
  public float Life { get => _life; set { _life = value; OnNotifyPropertyChanged("Life"; } }
  ...  
  private float _x, _y, _life;
}

public class Army
{
  public ObservableCollection<Unit> Units { get; }
  ...
}
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid.Resources>
    <DataTemplate x:Key="UnitTemplate">
      <ProgressBar Value="{Binding Life}" Width="60" Height="10"/>
    </DataTemplate>
  </Grid.Resources>
  <ItemsControl ItemsSource="{Binding Units}" ItemTemplate="{StaticResource UnitTemplate}">
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <Canvas/>
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
      <Style TargetType="{x:Type ContentPresenter}">
        <Setter Property="Canvas.Left" Value="{Binding ScreenPosX}"/>
        <Setter Property="Canvas.Top" Value="{Binding ScreenPosY}"/>
      </Style>
    </ItemsControl.ItemContainerStyle>
  </ItemsControl>
</Grid>
Then everytime you change the units data it will automatically notify the UI, and it will get updated.
 
HateDread
Posts: 17
Joined: 06 Feb 2020, 10:27

Re: Unit health bars with NoesisGUI

12 Jul 2021, 18:00

The easiest way to achieve this is by using a MVVM approach and use an ItemsControl with data binding:
public class Unit : NotifyPropertyChangedBase
{
  public float ScreenPosX { get => _x; set { _x = value; OnNotifyPropertyChanged("ScreenPosX"; } }
  public float ScreenPosY { get => _y; set { _y = value; OnNotifyPropertyChanged("ScreenPosY"; } }
  public float Life { get => _life; set { _life = value; OnNotifyPropertyChanged("Life"; } }
  ...  
  private float _x, _y, _life;
}

public class Army
{
  public ObservableCollection<Unit> Units { get; }
  ...
}
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid.Resources>
    <DataTemplate x:Key="UnitTemplate">
      <ProgressBar Value="{Binding Life}" Width="60" Height="10"/>
    </DataTemplate>
  </Grid.Resources>
  <ItemsControl ItemsSource="{Binding Units}" ItemTemplate="{StaticResource UnitTemplate}">
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <Canvas/>
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
      <Style TargetType="{x:Type ContentPresenter}">
        <Setter Property="Canvas.Left" Value="{Binding ScreenPosX}"/>
        <Setter Property="Canvas.Top" Value="{Binding ScreenPosY}"/>
      </Style>
    </ItemsControl.ItemContainerStyle>
  </ItemsControl>
</Grid>
Then everytime you change the units data it will automatically notify the UI, and it will get updated.
Awesome, thanks for this - I have this mostly working. A few questions

1. How do I set the element centered on the ScreenPosX, ScreenPosY values, not top left? Preferably all in xaml.

2. I've noticed the elements lagging behind the screen-space position of the units they represent if I move the camera around quickly, whereas other on-screen solutions using that same screen-space position/data will render just fine (e.g. OpenGL-based text). Is there anything I should be watching out for with the approach you posted re. latency of NoesisGUI? I'd want the snappiest possible positioning of these unit icons.

A snippet from my main GameView.xaml
        <ItemsControl ItemsSource="{Binding PlayerFleet.Ships}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
					<Canvas/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <controls:SpaceObjectIcon></controls:SpaceObjectIcon>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemContainerStyle>
                <Style TargetType="{x:Type ContentPresenter}">
                    <Setter Property="Canvas.Left" Value="{Binding ScreenPosX}"/>
                    <Setter Property="Canvas.Top" Value="{Binding ScreenPosY}"/>
                </Style>
            </ItemsControl.ItemContainerStyle>
        </ItemsControl>
And the SpaceObjectIcon UC itself:
<UserControl x:Class="rtg.Controls.SpaceObjectIcon"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:rtg.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="100" d:DesignWidth="100" Width="100" Height="100" HorizontalAlignment="Stretch">
    <Grid Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Border VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Padding="5,5,5,5" BorderThickness="1,1,1,1" BorderBrush="White">
            <Grid Height="Auto" Margin="0,0,-2,-2" Width="Auto">
                <Image Source="/spaceship_icon_white.png" Stretch="Uniform" Height="90" Width="90"/>
                <Button Content="Button" Panel.ZIndex="-1" Background="{x:Null}" BorderBrush="{x:Null}"/>
            </Grid>
        </Border>
    </Grid>
</UserControl>


Thanks!
 
User avatar
sfernandez
Site Admin
Posts: 2336
Joined: 22 Dec 2011, 19:20

Re: Unit health bars with NoesisGUI

12 Jul 2021, 18:50

1. Canvas positions the top-left corner of child elements by default, so if you want to center them you can create alternative attached properties that calculate the center position and set that instead:
public static class CenteredCanvas
{
    public static float GetX(FrameworkElement element) { return (float)element.GetValue(XProperty); }
    public static void SetX(FrameworkElement element, float value) { element.SetValue(XProperty, value); }
    public static readonly DependencyProperty XProperty = DependencyProperty.RegisterAttached(
        "X", typeof(float), typeof(CenteredCanvas), new PropertyMetadata(0.0f, OnPosChanged));

    public static float GetY(FrameworkElement element) { return (float)element.GetValue(YProperty); }
    public static void SetY(FrameworkElement element, float value) { element.SetValue(YProperty, value); }
    public static readonly DependencyProperty YProperty = DependencyProperty.RegisterAttached(
        "Y", typeof(float), typeof(CenteredCanvas), new PropertyMetadata(0.0f, OnPosChanged));

    private static void OnPosChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement element = d as FrameworkElement;
        if (element != null)
        {
            element.SizeChanged -= OnElementSizeChanged;
            element.SizeChanged += OnElementSizeChanged;
            UpdatePos(element);
        }
    }

    private static void OnElementSizeChanged(object sender, SizeChangedEventArgs e)
    {
        UpdatePos((FrameworkElement)sender);
    }

    private static void UpdatePos(FrameworkElement element)
    {
        Canvas.SetLeft(element, GetX(element) - element.ActualWidth * 0.5);
        Canvas.SetTop(element, GetY(element) - element.ActualHeight * 0.5);
    }
}
<ItemsControl.ItemContainerStyle>
  <Style TargetType="{x:Type ContentPresenter}">
    <Setter Property="CenteredCanvas.X" Value="{Binding ScreenPosX}"/>
    <Setter Property="CenteredCanvas.Y" Value="{Binding ScreenPosY}"/>
  </Style>
</ItemsControl.ItemContainerStyle>
2. Is this Unity? We update our layout during MonoBehavior.LateUpdate() of the NoesisView component so you should update your viewmodels before that in order to reflect those changes in the same frame.
 
User avatar
jsantos
Site Admin
Posts: 3205
Joined: 20 Jan 2012, 17:18
Contact:

Re: Unit health bars with NoesisGUI

14 Jul 2021, 14:01

2. Is this Unity? We update our layout during MonoBehavior.LateUpdate() of the NoesisView component so you should update your viewmodels before that in order to reflect those changes in the same frame.
Or you can enable the EnableExternalUpdate property and do manual updates whenever you consider it is appropriate.

Who is online

Users browsing this forum: No registered users and 2 guests