View Issue Details

IDProjectCategoryView StatusLast Update
0001830NoesisGUIC++ SDKpublic2020-11-11 19:37
Reportersteveh Assigned Tosfernandez  
PrioritynormalSeverityfeature 
Status resolvedResolutionno change required 
Product Version3.0.6 
Target Version3.0.8Fixed in Version3.0.8 
Summary0001830: Feature Request: Generic notification support within storyboards
Description

Hi guys, this is following on from this thread and our discussion:

https://www.noesisengine.com/forums/viewtopic.php?f=3&t=2066&p=11532#p11532

So the suggested workaround regarding a float instead of a boolean works for triggering the notification multiple times within a looping storyboard, but it doesn't work if we attempt to trigger a notify a single time multiple times, e.g:

<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(AudioTrigger.TriggerAudio)" Storyboard.TargetName="AudioTriggerElement" RepeatBehavior="Forever">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.5" Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>

It would be nice if there was a supported extension to add triggers within a storyboard. We would use this features in a couple of places:

1. Triggering Audio / Particle FX
2. Triggering a callback to do things like change data when we're half-way through an animation such as a level-up animation. Right now we do this with 2 textboxes:

<TextBlock x:Name="OldLevel" Text="{Binding Level, Converter={StaticResource SubtractionConverter}, ConverterParameter=1}" Visibility="Visible" />
<TextBlock x:Name="NewLevel" Text="{Binding Level}" Visibility="Collapsed" />

Then half-way through the storyboard, we change the visibility of the elements so the new value comes through:

<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="OldLevel">
<EasingDoubleKeyFrame KeyTime="0" Value="1.0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="OldLevel">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="NewLevel">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="NewLevel">
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>

It would be nice to be able to trigger a generic event at time 0.5 seconds in this example where I could update the binding for Level so we only had to have 1 textblock.

Hopefully this makes sense and you can see why we might want the ability to trigger generic notifications.

PlatformAny

Activities

sfernandez

sfernandez

2020-11-06 10:58

manager   ~0006714

Regarding the limitation you mentioned about triggering the notification multiple times within a looping storyboard, there is way to make it work with only a single key frame.
As I explained in the forums you can set the FillBehavior to "Stop" in the timeline so it resets the value to its default each time it repeats:

<Storyboard x:Key="anim" Duration="0:0:1" RepeatBehavior="3x">
<DoubleAnimationUsingKeyFrames FillBehavior="Stop" Storyboard.TargetName="rect" Storyboard.TargetProperty="(local:CustomTrigger.Trigger)">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.5" Value="0.5"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>

In that example, if the default value of CustomTrigger.Trigger property is -1, it will call the property changed callback for 0.5, -1, 0.5, -1, 0.5, -1.
If you only fire the trigger event for values >= 0 I think this approach fits with what you are asking and it is 100% compatible with Blend.

And you can use it for changing the text in the middle of an animation too. For example, you can do:

<Storyboard x:Key="anim" Duration="0:0:1">
<DoubleAnimationUsingKeyFrames FillBehavior="Stop" Storyboard.TargetName="text" Storyboard.TargetProperty="(local:CustomTrigger.Trigger)">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.5" Value="0.5"/>
</DoubleAnimationUsingKeyFrames>
... rest of animations
</Storyboard>
...
<TextBlock x:Name="text">
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding (local:CustomTrigger.Trigger), ElementName=text}" Comparison="GreaterThanOrEqual" Value="0">
<i:InvokeCommandAction Command="{Binding UpdateTextCommand}"/>
</ei:DataTrigger>
</i:Interaction.Triggers>

Does that make sense?

steveh

steveh

2020-11-06 17:16

reporter   ~0006715

Last edited: 2020-11-06 17:16

Hi Sergio,

Yes that makes sense. I've just given this a test and it works. The only downside is that there is no way to set the fill behaviour via the GUI in Blend, and our artists / designers tend to primarily use the GUI to edit the storyboards. I've spent a little while Googling around and the only way to change the fill behaviour from what I can see is by editing the XAML.

I may be able to set the fillbehavior when we use our custom export tool to convert the xaml source files into our image data, so I can probably programmatically set this property on all animations which affect the CustomTrigger.Trigger property.

Cheers,

-Steven

steveh

steveh

2020-11-06 22:29

reporter   ~0006716

Just to update you on this. So we've almost got it to a place where the audio designers are happy with this. So now I've got 2 DependencyProperties on our trigger:

TriggerProperty which is a bool
RepeatableTriggerProperty which is an int32

The reason we have kept both is that the audio designers like having a checkbox on the GUI instead of having to type in a number. I suggested that I could create an enum so they got a drop down selection, but they didn't like that idea. So we decided to create 2 properties and make the repeatable trigger in the "Advanced" section of the editor so it's hidden by default, but they still have the ability to fire the same trigger multiple times in the same storyboard.

With regards to the FillBehaviour, I added this functionality into our export pipeline and it works great. We have a tool where we feed in the .xaml files and it writes out a .xaml file in a slightly different format. This allows us to tag certain parts of the UI and they wont appear in the final build, e.g. we have meta data tags like the following:

<Grid x:Name="foo_console" Sumo.MetaData="Platform=xbox,ps4">
<!-- stuff -->
</Grid>
<Grid x:Name="foo_pc" Sumo.MetaData="Platform=pc">
<!-- stuff -->
</Grid>

The first grid will be completely removed from our PC package data, and the second grid will be completely removed from the PS4 and Xbox packages. We use this for a variety of things, but this is just 1 example. If we find a CustomTrigger.Trigger or CustomTrigger.TriggerRepeating as the target property of a storyboard, we automatically add a FillBehaviour Stop attribute in. This doesn't appear in Blend, but it does appear in our package data and it works as expect.

Right now we feel like we have enough workarounds to put this bug to bed. It'd be nice to have a fully supported feature which integrates nicely with Blend, but for now what we have is reasonable and it seems to work in all cases.

Cheers,

-Steven

sfernandez

sfernandez

2020-11-10 18:50

manager   ~0006718

Hi Steve, I added a suggestion to Visual Studio feedback to remark FillBehavior cannot be edited using the GUI of Blend:
https://developercommunity2.visualstudio.com/t/Blend-Add-Edit-Fill-Behavior-entry-t/1247283

Do you think we can close this ticket?

steveh

steveh

2020-11-10 18:59

reporter   ~0006719

Hi Sergio, yes, I think our pipeline is much better now and it seems to work great.

I'll await Microsoft's response but I'm not expecting anything any time soon, so for now our programmatic solution to automatically set the fill behaviour property works fine :)

Cheers!

steveh

steveh

2020-11-11 13:24

reporter   ~0006724

Hi Sergio,

Sorry, just reopening this ticket. We've found 1 case this doesn't work:

https://www.noesisengine.com/xamltoy/573cc06a07416b5d38f99392d6122cb8

Every time the value flips between -1 and 1 it'll fire an event internally, and this works great. But if the keyframe is at time 0, i.e:

  <StringAnimationUsingKeyFrames FillBehavior="Stop" Storyboard.TargetName="Value" Storyboard.TargetProperty="Text" RepeatBehavior="Forever">
    <DiscreteStringKeyFrame KeyTime="0" Value="1" /> <!-- initial keyframe at time 0 -->
    <DiscreteStringKeyFrame KeyTime="0:0:1" Value="1" /> <!-- key frame to loop the sound every 1 second -->
  </StringAnimationUsingKeyFrames>

Then this doesn't seem to have a frame to reset the value, the timeline instantly loops round without stopping and thus resetting the value back to -1. This means that we can't fire audio events on a looping animation from the very first frame.

Is there any way to get WPF to forcibly clear the value as soon as it loops so we always get the dependency changed events through?

Cheers,

-Steven

sfernandez

sfernandez

2020-11-11 17:44

manager   ~0006725

Last edited: 2020-11-11 17:46

Hi, no problem at all.

Before going with the issue just want to remark you can specify the duration of the animation without adding another KeyFrame:

      <StringAnimationUsingKeyFrames FillBehavior="Stop" Storyboard.TargetName="Value" Storyboard.TargetProperty="Text" RepeatBehavior="Forever" Duration="0:0:1">
<DiscreteStringKeyFrame KeyTime="0" Value="1" /> <!-- initial keyframe at time 0 -->
</StringAnimationUsingKeyFrames>

Yes, it seems KeyFrames placed at time 0 are used when looping without passing through the default value, and I verified that WPF is also doing the same.
Advancing the time of the initial KeyFrame a bit (to 0:0:0.01 for example) allows the animation system to set the default value on each loop, but it seems a bit hacky.

      <StringAnimationUsingKeyFrames FillBehavior="Stop" Storyboard.TargetName="Value" Storyboard.TargetProperty="Text" RepeatBehavior="Forever" Duration="0:0:1">
<DiscreteStringKeyFrame KeyTime="0:0:0.01" Value="1" /> <!-- initial keyframe at time ~0 -->
</StringAnimationUsingKeyFrames>

I found another way of reseting the value for "looping" animations: https://stackoverflow.com/questions/2667836/how-to-trigger-a-event-handler-when-wpf-animation-repeated
The idea is to play the animation just one time instead of Forever, and then listen to the Completed event to fire the animation again:

      <Storyboard x:Key="anim">
<StringAnimationUsingKeyFrames FillBehavior="Stop" Storyboard.TargetName="Value" Storyboard.TargetProperty="Text" Duration="0:0:1">
<DiscreteStringKeyFrame KeyTime="0" Value="1" /> <!-- initial keyframe at time 0 -->
</StringAnimationUsingKeyFrames>
</Storyboard>
...
<i:Interaction.Triggers>
<ei:StoryboardCompletedTrigger Storyboard="{StaticResource anim}">
<ei:ControlStoryboardAction Storyboard="{StaticResource anim}">
</ei:StoryboardCompletedTrigger>
<i:Interaction.Triggers>

This way the property is always set to the default value after each iteration.

Any thoughts on this?

steveh

steveh

2020-11-11 18:30

reporter   ~0006726

Last edited: 2020-11-11 18:31

Hey, so the duration sounds ideal, unfortunately this trigger object is intended to be used by audio designers who work specifically with the WYSWIG Blend GUI, and I don't think the GUI exposes any of these properties via the interface. This might be one of those cases though where if they need support for it I can write the XAML for them.

With regards to the interaction trigger, that could work, but I feel it might complicate matters, right now animation is being stopped via some external C++ code (this is a countdown animation which loops every second and plays a 'blip' until we stop the animation). We could move all this logic to the XAML but that would require exposing extra properties and additional conditionals to the trigger to ensure that it doesn't continuously loop forever, it only loops whilst we're in the "game ending" phase:

<i:Interaction.Triggers>
<ei:StoryboardCompletedTrigger Storyboard="{StaticResource anim}">
<i:Interaction.Behaviors>
<ei:ConditionBehavior>
<ei:ConditionalExpression>
<!-- Extra Property to prevent it from looping when we set it -->
<ei:ComparisonCondition LeftOperand="{Binding InGameEndingPhase}" Operator="Equal" RightOperand="True" />
</ei:ConditionalExpression>
</ei:ConditionBehavior>
</i:Interaction.Behaviors>
<ei:ControlStoryboardAction Storyboard="{StaticResource anim}">
</ei:StoryboardCompletedTrigger>
<!-- And data trigger to stop the storyboard when we exit the phase -->
<ei:DataTrigger Binding="{Binding InGameEndingPhase}" Value="False">
<ei:ControlStoryboardAction Storyboard="{StaticResource anim}" ControlStoryboardOption="Stop">
</ei:DataTrigger>
</i:Interaction.Triggers>

This just seems quite a bit more complex than the C++ code which just does something like this:

void OnPhaseChanged(EPhase ePhase)
{
if (ePhase != GameEndPhase)
m_pAnimation->Stop();
}

And it also doesn't get around the issue of having a nice simple interface for audio designers to go in and just drag and drop these triggers in, this still requires code support to expose this data and setup all the bindings.

I'll keep these solutions in mind for the future. Right now we've gone with the solution of just triggering the audio at 0.01 seconds like you mentioned. This is a solution that audio designers can do without editing the XAML and it also doesn't require additional code to expose extra properties to data bind to.

sfernandez

sfernandez

2020-11-11 18:39

manager   ~0006727

I think you can still Stop the storyboard from code only because that will prevent the Completed event to be triggered so it won't loop again.
You won't need any extra property or write the extra condition in the StoryboardCompletedTrigger.
Anyway, you know better what option fits better for your designers and workflow.

steveh

steveh

2020-11-11 18:49

reporter   ~0006728

Ah, I wasn't aware of that, I thought Stopping via code would still trigger the completed trigger, I don't know why I thought that.

If that's the case then I think the looping in XAML could be a solution for us.

Thank you for your help and suggestion Sergio! I'm sorry thinks aren't as simple as they usually are as we have artists and designers using Blend and they're used to flashy editors like Unreal and the Adobe suite :)
Personally I like XAML and I'm happy to avoid Blend completely.

I think we can re-close this now, I think I have enough to go on and we have workarounds for the looping issue. Cheers!

sfernandez

sfernandez

2020-11-11 19:37

manager   ~0006729

It is good to have these conversations because one of our goals is to make life easier for designers and avoid as much as possible the intervention of programmers for designing the UI.
So knowing all these situations helps us improve our product.

Issue History

Date Modified Username Field Change
2020-11-05 18:28 steveh New Issue
2020-11-05 18:29 steveh Description Updated
2020-11-06 10:43 sfernandez Assigned To => sfernandez
2020-11-06 10:43 sfernandez Status new => assigned
2020-11-06 10:58 sfernandez Status assigned => feedback
2020-11-06 10:58 sfernandez Note Added: 0006714
2020-11-06 17:16 steveh Note Added: 0006715
2020-11-06 17:16 steveh Status feedback => assigned
2020-11-06 17:16 steveh Note Edited: 0006715
2020-11-06 22:29 steveh Note Added: 0006716
2020-11-10 18:50 sfernandez Status assigned => feedback
2020-11-10 18:50 sfernandez Note Added: 0006718
2020-11-10 18:59 steveh Note Added: 0006719
2020-11-10 18:59 steveh Status feedback => assigned
2020-11-10 19:39 sfernandez Status assigned => resolved
2020-11-10 19:39 sfernandez Resolution open => no change required
2020-11-10 19:39 sfernandez Fixed in Version => 3.0.8
2020-11-10 19:39 sfernandez Target Version => 3.0.8
2020-11-11 13:24 steveh Status resolved => feedback
2020-11-11 13:24 steveh Resolution no change required => reopened
2020-11-11 13:24 steveh Note Added: 0006724
2020-11-11 17:44 sfernandez Note Added: 0006725
2020-11-11 17:46 sfernandez Note Edited: 0006725
2020-11-11 18:30 steveh Note Added: 0006726
2020-11-11 18:30 steveh Status feedback => assigned
2020-11-11 18:31 steveh Note Edited: 0006726
2020-11-11 18:31 steveh Note Edited: 0006726
2020-11-11 18:39 sfernandez Status assigned => feedback
2020-11-11 18:39 sfernandez Note Added: 0006727
2020-11-11 18:49 steveh Note Added: 0006728
2020-11-11 18:49 steveh Status feedback => assigned
2020-11-11 19:37 sfernandez Status assigned => resolved
2020-11-11 19:37 sfernandez Resolution reopened => no change required
2020-11-11 19:37 sfernandez Note Added: 0006729