Unit health bars with NoesisGUI
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:
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.
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.
Re: Unit health bars with NoesisGUI
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.
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.
-
sfernandez
Site Admin
- Posts: 2983
- Joined:
Re: Unit health bars with NoesisGUI
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
We did that when creating our plane cockpit video: viewtopic.php?f=12&t=380
Re: Unit health bars with NoesisGUI
Sorry to necro, but this is literally the only thread I could find mentioning this kind of game/UI layout.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.
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.
Re: Unit health bars with NoesisGUI
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.Did anything like this ever make it into a public sample, @jsantos? I've looked through them all (I think) but could be wrong.
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.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.
I talked with @sfernandez and he is going to post a small snippet about this.
- KeldorKatarn
- Posts: 193
- Joined:
Re: Unit health bars with NoesisGUI
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.
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.
-
sfernandez
Site Admin
- Posts: 2983
- Joined:
Re: Unit health bars with NoesisGUI
The easiest way to achieve this is by using a MVVM approach and use an ItemsControl with data binding:
Then everytime you change the units data it will automatically notify the UI, and it will get updated.
Code: Select all
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; }
...
}
Code: Select all
<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>
Re: Unit health bars with NoesisGUI
Awesome, thanks for this - I have this mostly working. A few questionsThe easiest way to achieve this is by using a MVVM approach and use an ItemsControl with data binding:Code: Select allpublic 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; } ... }
Then everytime you change the units data it will automatically notify the UI, and it will get updated.Code: Select all<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>
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
Code: Select all
<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>
Code: Select all
<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!
-
sfernandez
Site Admin
- Posts: 2983
- Joined:
Re: Unit health bars with NoesisGUI
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:
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.
Code: Select all
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);
}
}
Code: Select all
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="CenteredCanvas.X" Value="{Binding ScreenPosX}"/>
<Setter Property="CenteredCanvas.Y" Value="{Binding ScreenPosY}"/>
</Style>
</ItemsControl.ItemContainerStyle>
Re: Unit health bars with NoesisGUI
Or you can enable the EnableExternalUpdate property and do manual updates whenever you consider it is appropriate.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.
Who is online
Users browsing this forum: Ahrefs [Bot], Bing [Bot], Google [Bot] and 54 guests