Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions BUITween.uplugin
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
"FriendlyName": "BUITween",
"Description": "Tweening library for UMG",
"Category": "Other",
"CreatedBy": "benui",
"CreatedBy": "benui & The Hoodie Guy",
"CreatedByURL": "https://benui.ca/",
"DocsURL": "",
"DocsURL": "https://github.com/TheHoodieGuy02/UE4-UITween/",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": false,
Expand All @@ -21,4 +21,4 @@
"LoadingPhase": "Default"
}
]
}
}
110 changes: 96 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
# UI Tweening Libary for UE4/UMG
# UI Tweening Libary for Unreal Engine's UMG

Create simple tweens for UMG widgets from C++.
Create simple tweens for UMG widgets from C++ and BP.

```cpp
UBUITween::Create( SomeWidget, 0.2f )
.FromTranslation( FVector2D( -100, 0 ) )
.FromOpacity( 0.2f )
.ToTranslation( FVector2D( 20, 10 ) )
.ToOpacity( 1.0f )
.Begin();
```
https://user-images.githubusercontent.com/26211033/137335549-8e20eefa-bf7f-4415-af83-bf68cabe9b1c.mp4


## Compatible engine versions

The plugin was tested to work in 4.27. However, the functions are rather simple,
it is expected to work on at least far back as Unreal Engine 4.5.
It has yet been tested in Unreal Engine 5, but should also work right away without
much hassle.

## Why using code tweening and not the UMG timeline?

## Usage
While UMG's timeline animator is serviceable for prototyping UI animations, or used
as is for fixed animations, it lacks the flexibility to animate more modular,
procedurally built widgets (e.g. Inventory list, commands menu, etc.) dynamically.

For one thing, UMG Timeline can't expose animatable properties it controls to code.
This could be a problem if your animation needs to be adapting to prior interactions
(e.g. zooming in from / out to where a button is located on screen). It can also be
difficult to reuse in a different context, requiring to make a different sequence
for another possible context. Code driven tweening aims to solve that, by having
start point and end point properties exposed to code, and tweens to be reused as
functions or macro, allowing for more dynamic animations.

Of course, you can combine the best of both worlds, to achieve cool looking widget
animations.

## Usage in C++

The plugin module registers itself to tick automatically even during game-world
pause.
Expand Down Expand Up @@ -62,11 +79,75 @@ UBUITween::Create( MyWidget, 0.5f )

For the full API, check the source code.

## Usage in Blueprints

(exposed to BP by TheHoodieGuy02)

To use the widget tweening functions in BP, simply reparent your widget BP to
BUITweenWidget class. This User Widget class exposes the Create Widget Tween
to Blueprint, and also provides a struct for the parameters that Create Widget
Tween needs.

![](ReadmeFiles/CreateWidgetTween.png)

Please note that the animation will be executed in the tween instance's own C++
tick thread, therefore, do not execute this on Blueprint tick / every frame.

- Target Widget is the widget that you want to apply the tween on.
Expects a Widget Object.

- Tween Duration is the duration for the tween to last, in seconds.
Expects float value.

- Start Delay is the length of for the tween pausing at the start point, before
interpolating to the end point.
Expects float value.

- Start Appearance is the start point parameter of the transforms.
Expects WidgetAppearance struct.

- End Appearance is the end point parameter of the transforms.
Expects WidgetAppearance struct.

- Easing Type is the interpolation type for the tween.
Expects BUI Easing Type enum.

![](ReadmeFiles/WidgetAppearanceStruct.png)

The WidgetAppearance struct contains parameters that control how the widget appears in
the viewport. Some parameters only available to certain widget types, however, Create
Widget Tween function already do the casting and they'll be unused if the cast fails.

- Translation corresponds to the widget's render translation.
Widget type agnostic.

- Rotation corresponds to the widget's render rotation.
Widget type agnostic.

- Scale corresponds to the widget's render scale.
Widget type agnostic.

- Opacity corresponds to the widget's render opacity.
Widget type agnostic.

- Color corresponds to the widget's content color.
Applicable to User Widget, Image, and Border widgets.

- Canvas Position corresponds to the widget's position relative to Canvas Panel parent.
Applicable to widgets in a Canvas Panel.

- Widget Visibility corresponds to the widget's visibility type.
Widget type agnostic.

- Max Desired Height corresponds to the widget's desired height limit.
Applicable to Size Box widgets.

- Widget Padding corresponds to the widget's padding in flowing containers.
Applicable to widgets in an Overlay, Vertical Box, or Vertical Box.

## Caveats
## Caveats and Issues

* I haven't performance-tested it beyond having 5-6 tweens running simultaneously.
* No Blueprint support.
For more updated list of issues, see the [Issues page](https://github.com/TheHoodieGuy02/UE4-UITween/issues).

## License

Expand All @@ -77,3 +158,4 @@ For the full API, check the source code.
If you find it useful, drop me a line [@_benui](https://twitter.com/_benui) on Twitter

[benui.ca](https://benui.ca)
**
Binary file added ReadmeFiles/CreateWidgetTween.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ReadmeFiles/EasingTypeEnum.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ReadmeFiles/UITweenExample.mp4
Binary file not shown.
Binary file added ReadmeFiles/WidgetAppearanceStruct.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 37 additions & 20 deletions Source/BUITween/Private/BUITweenInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,26 @@ void FBUITweenInstance::Begin()
}

// Set all the props to the existng state
TranslationProp.OnBegin( pWidget->RenderTransform.Translation );
ScaleProp.OnBegin( pWidget->RenderTransform.Scale );
RotationProp.OnBegin( pWidget->RenderTransform.Angle );
TranslationProp.OnBegin( pWidget->GetRenderTransform().Translation );
ScaleProp.OnBegin( pWidget->GetRenderTransform().Scale );
RotationProp.OnBegin( pWidget->GetRenderTransform().Angle );
OpacityProp.OnBegin( pWidget->GetRenderOpacity() );

{
UUserWidget* UW = Cast<UUserWidget>( pWidget );
if ( UW )
{
ColorProp.OnBegin( UW->ColorAndOpacity );
ColorProp.OnBegin( UW->GetColorAndOpacity() );
}
UImage* UI = Cast<UImage>( pWidget );
if ( UI )
{
ColorProp.OnBegin( UI->ColorAndOpacity );
ColorProp.OnBegin( UI->GetColorAndOpacity() );
}
UBorder* Border = Cast<UBorder>( pWidget );
if ( Border )
{
ColorProp.OnBegin( Border->ContentColorAndOpacity );
ColorProp.OnBegin( Border->GetContentColorAndOpacity() );
}
}

Expand All @@ -58,35 +58,46 @@ void FBUITweenInstance::Begin()
UOverlaySlot* OverlaySlot = Cast<UOverlaySlot>( pWidget->Slot );
UHorizontalBoxSlot* HorizontalBoxSlot = Cast<UHorizontalBoxSlot>( pWidget->Slot );
UVerticalBoxSlot* VerticalBoxSlot = Cast<UVerticalBoxSlot>( pWidget->Slot );
UCanvasPanelSlot* CanvasPanelSlot = Cast<UCanvasPanelSlot>(pWidget->Slot);
if ( OverlaySlot )
{
PaddingProp.OnBegin( FVector4(
OverlaySlot->Padding.Left,
OverlaySlot->Padding.Top,
OverlaySlot->Padding.Bottom,
OverlaySlot->Padding.Right ) );
OverlaySlot->GetPadding().Left,
OverlaySlot->GetPadding().Top,
OverlaySlot->GetPadding().Bottom,
OverlaySlot->GetPadding().Right ) );
}
else if ( HorizontalBoxSlot )
{
PaddingProp.OnBegin( FVector4(
HorizontalBoxSlot->Padding.Left,
HorizontalBoxSlot->Padding.Top,
HorizontalBoxSlot->Padding.Bottom,
HorizontalBoxSlot->Padding.Right ) );
HorizontalBoxSlot->GetPadding().Left,
HorizontalBoxSlot->GetPadding().Top,
HorizontalBoxSlot->GetPadding().Bottom,
HorizontalBoxSlot->GetPadding().Right ) );
}
else if ( VerticalBoxSlot )
{
PaddingProp.OnBegin( FVector4(
VerticalBoxSlot->Padding.Left,
VerticalBoxSlot->Padding.Top,
VerticalBoxSlot->Padding.Bottom,
VerticalBoxSlot->Padding.Right ) );
VerticalBoxSlot->GetPadding().Left,
VerticalBoxSlot->GetPadding().Top,
VerticalBoxSlot->GetPadding().Bottom,
VerticalBoxSlot->GetPadding().Right ) );
}
else if(CanvasPanelSlot)
{
FVector2D position = CanvasPanelSlot->GetPosition();
FVector2D size = CanvasPanelSlot->GetSize();
PaddingProp.OnBegin( FVector4(
position.X,
position.Y,
size.X,
size.Y ) );
}

USizeBox* SizeBox = Cast<USizeBox>( pWidget );
if ( SizeBox )
{
MaxDesiredHeightProp.OnBegin( SizeBox->MaxDesiredHeight );
MaxDesiredHeightProp.OnBegin( SizeBox->GetMaxDesiredHeight() );
}

// Apply the starting conditions, even if we delay
Expand Down Expand Up @@ -173,7 +184,7 @@ void FBUITweenInstance::Apply( float EasedAlpha )
}

bool bChangedRenderTransform = false;
FWidgetTransform CurrentTransform = Target->RenderTransform;
FWidgetTransform CurrentTransform = Target->GetRenderTransform();

if ( TranslationProp.IsSet() )
{
Expand Down Expand Up @@ -211,12 +222,18 @@ void FBUITweenInstance::Apply( float EasedAlpha )
UOverlaySlot* OverlaySlot = Cast<UOverlaySlot>( pWidget->Slot );
UHorizontalBoxSlot* HorizontalBoxSlot = Cast<UHorizontalBoxSlot>( pWidget->Slot );
UVerticalBoxSlot* VerticalBoxSlot = Cast<UVerticalBoxSlot>( pWidget->Slot );
UCanvasPanelSlot* CanvasPanelSlot = Cast<UCanvasPanelSlot>(pWidget->Slot);
if ( OverlaySlot )
OverlaySlot->SetPadding( PaddingProp.CurrentValue );
else if ( HorizontalBoxSlot )
HorizontalBoxSlot->SetPadding( PaddingProp.CurrentValue );
else if ( VerticalBoxSlot )
VerticalBoxSlot->SetPadding( PaddingProp.CurrentValue );
else if(CanvasPanelSlot)
{
CanvasPanelSlot->SetPosition( FVector2D(PaddingProp.CurrentValue.X,PaddingProp.CurrentValue.Y));
CanvasPanelSlot->SetSize(FVector2D(PaddingProp.CurrentValue.Z,PaddingProp.CurrentValue.W));
}
}
}
if ( MaxDesiredHeightProp.IsSet() )
Expand Down
115 changes: 115 additions & 0 deletions Source/BUITween/Private/BUITweenWidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2021 The Hoodie Guy


#include "BUITweenWidget.h"
#include "BUITween.h"
#include "BUIEasing.h"

#include "Components/Widget.h"
#include "Components/Image.h"
#include "Components/Border.h"
#include "Components/CanvasPanelSlot.h"
#include "Components/OverlaySlot.h"
#include "Components/VerticalBoxSlot.h"
#include "Components/HorizontalBoxSlot.h"
#include "Components/SizeBox.h"
#include "Blueprint/UserWidget.h"

void UBUITweenWidget::CreateWidgetTween(UWidget* TargetWidget, float TweenDuration, float StartDelay, FWidgetAppearance StartAppearance, FWidgetAppearance EndAppearance, EBUIEasingType EasingType)
{
UBUITween::Create(TargetWidget, TweenDuration, StartDelay)
.FromTranslation(StartAppearance.Translation)
.FromRotation(StartAppearance.Rotation)
.FromScale(StartAppearance.Scale)
.FromOpacity(StartAppearance.Opacity)
.FromColor(StartAppearance.Color)
.FromVisibility(StartAppearance.WidgetVisibility)
.FromCanvasPosition(StartAppearance.CanvasPosition)
.FromPadding(StartAppearance.WidgetPadding)
.ToTranslation(EndAppearance.Translation)
.ToRotation(EndAppearance.Rotation)
.ToScale(EndAppearance.Scale)
.ToOpacity(EndAppearance.Opacity)
.ToColor(EndAppearance.Color)
.ToVisibility(EndAppearance.WidgetVisibility)
.ToCanvasPosition(EndAppearance.CanvasPosition)
.ToPadding(EndAppearance.WidgetPadding)
.Easing(EasingType)
.Begin();
}

FWidgetAppearance UBUITweenWidget::GetInitialAppearanceStruct(UWidget* TargetWidget)
{
// The whole thing is almost the same with BUITweenInstance.cpp,
// except we're just getting the initial values.

FVector2D Translation = TargetWidget->RenderTransform.Translation;
float Rotation = TargetWidget->RenderTransform.Angle;
FVector2D Scale = TargetWidget->RenderTransform.Translation;
float Opacity = TargetWidget->GetRenderOpacity();

FLinearColor Color;
UUserWidget* UW = Cast<UUserWidget>(TargetWidget);
if (UW)
{
Color = UW->ColorAndOpacity;
}
UImage* UI = Cast<UImage>(TargetWidget);
if (UI)
{
Color = UI->ColorAndOpacity;
}
UBorder* Border = Cast<UBorder>(TargetWidget);
if (Border)
{
Color = Border->ContentColorAndOpacity;
}

FVector2D CanvasPosition;
UCanvasPanelSlot* CanvasSlot = Cast<UCanvasPanelSlot>(TargetWidget->Slot);
if (CanvasSlot)
{
CanvasPosition = CanvasSlot->GetPosition();
}

ESlateVisibility WidgetVisibility = TargetWidget->GetVisibility();

float MaxDesiredHeight;
USizeBox* SizeBox = Cast<USizeBox>(TargetWidget);
if (SizeBox)
{
MaxDesiredHeight = SizeBox->MaxDesiredHeight;
}

FMargin WidgetPadding;
UOverlaySlot* OverlaySlot = Cast<UOverlaySlot>(TargetWidget->Slot);
UHorizontalBoxSlot* HorizontalBoxSlot = Cast<UHorizontalBoxSlot>(TargetWidget->Slot);
UVerticalBoxSlot* VerticalBoxSlot = Cast<UVerticalBoxSlot>(TargetWidget->Slot);
if (OverlaySlot)
{
WidgetPadding = FVector4(
OverlaySlot->Padding.Left,
OverlaySlot->Padding.Top,
OverlaySlot->Padding.Bottom,
OverlaySlot->Padding.Right);
}
else if (HorizontalBoxSlot)
{
WidgetPadding = FVector4(
HorizontalBoxSlot->Padding.Left,
HorizontalBoxSlot->Padding.Top,
HorizontalBoxSlot->Padding.Bottom,
HorizontalBoxSlot->Padding.Right);
}
else if (VerticalBoxSlot)
{
WidgetPadding = FVector4(
VerticalBoxSlot->Padding.Left,
VerticalBoxSlot->Padding.Top,
VerticalBoxSlot->Padding.Bottom,
VerticalBoxSlot->Padding.Right);
}

return FWidgetAppearance(Translation, Rotation, Scale, Opacity, Color, CanvasPosition, Visibility, MaxDesiredHeight, Padding);
}

4 changes: 2 additions & 2 deletions Source/BUITween/Public/BUIEasing.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#include "CoreMinimal.h"
#include "CoreUObject.h"

UENUM()
enum class EBUIEasingType
UENUM(BlueprintType)
enum class EBUIEasingType : uint8
{
Linear,
Smoothstep,
Expand Down
Loading