Cridia Contributions

Since I have taken much from the community ever since switching from UDK to UE4 a few months ago, I thought it was time to start sharing some of the stuff I have made. It will not nearly be as useful as some of the stuff others have created (like the excellent contributions from Rama), but I think some of you may find it useful.

First some disclaimers though:

  • I am in no way an experienced coder. If my code can be written in a way more optimal for performance, you are free to let me know!
  • The options I will add here are based on the source. While some functions can be implemented anywhere, some require you to add classes to the source. Again, if you guys have easier ways, you are welcome to let me know!
  • The stuff I am adding is mostly stuff that I made for my own project. I don’t think my skills are good enough to do requests, so for the moment I won’t do any :slight_smile:
  • I am not that experienced with writing tutorials/contributions and I can get kinda wordy sometimes so if you guys have any improvements for my posts, let me know!

When I add content, I will make sure that I post a link to the post right here, so you don’t need to search the thread to find anything. Hope you guys will like it!
CONTRIBUTION LIST

UMG Latent Action: Move Widget

UMG LATENT ACTION: MOVE WIDGET

For this blueprint function, you are going to need the source code.

First find the Engine\Source\Runtime\Engine\Private folder, and add the following .h file. I called it InterpolateWidgetToAction.h (since the move component action code shares the same naming convention):


// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

#pragma once

/** Action that interpolates a component over time to a desired position */
class FInterpolateWidgetToAction : public FPendingLatentAction
{
public:
	/** Time over which interpolation should happen */
	float TotalTime;
	/** Time so far elapsed for the interpolation */
	float TimeElapsed;
	/** If we are currently interpolating. If false, update will complete */
	bool bInterpolating;

	/** Function to execute on completion */
	FName ExecutionFunction;
	/** Link to fire on completion */
	int32 OutputLink;
	/** Object to call callback on upon completion */
	FWeakObjectPtr CallbackTarget;

	/** Component to interpolate */
	TWeakObjectPtr<UWidget> TargetWidget;

	/** If we should modify location */
	bool bInterpTranslation;
	/** Location to interpolate from */
	FVector2D InitialTranslation;
	/** Location to interpolate to */
	FVector2D TargetTranslation;

	/** If we should modify location */
	bool bInterpScale;
	/** Location to interpolate from */
	FVector2D InitialScale;
	/** Location to interpolate to */
	FVector2D TargetScale;

	/** If we should modify location */
	bool bInterpShear;
	/** Location to interpolate from */
	FVector2D InitialShear;
	/** Location to interpolate to */
	FVector2D TargetShear;

	/** If we should modify rotation */
	bool bInterpAngle;
	/** Rotation to interpolate from */
	float InitialAngle;
	/** Rotation to interpolate to */
	float TargetAngle;

	/** Should we ease in (ie start slowly) during interpolation */
	bool bEaseIn;
	/** Should we east out (ie end slowly) during interpolation */
	bool bEaseOut;

	FInterpolateWidgetToAction(float Duration, const FLatentActionInfo& LatentInfo, UWidget* Widget, bool bTranslate, bool bScale, bool bShear, bool bRotate, bool bInEaseOut, bool bInEaseIn)
		: TotalTime(Duration)
		, TimeElapsed(0.f)
		, bInterpolating(true)
		, ExecutionFunction(LatentInfo.ExecutionFunction)
		, OutputLink(LatentInfo.Linkage)
		, CallbackTarget(LatentInfo.CallbackTarget)
		, TargetWidget(Widget)
		, bInterpTranslation(bTranslate)
		, InitialTranslation(FVector2D::ZeroVector)
		, TargetTranslation(FVector2D::ZeroVector)
		, bInterpShear(bShear)
		, InitialShear(FVector2D::ZeroVector)
		, TargetShear(FVector2D::ZeroVector)
		, bInterpAngle(bRotate)
		, InitialAngle(0.f)
		, TargetAngle(0.f)
		, bInterpScale(bScale)
		, InitialScale(FVector2D::ZeroVector)
		, TargetScale(FVector2D::ZeroVector)
		, bEaseIn(bInEaseIn)
		, bEaseOut(bInEaseOut)
	{
	}

	virtual void UpdateOperation(FLatentResponse& Response) override
	{
		// Update elapsed time
		TimeElapsed += Response.ElapsedTime();

		bool bComplete = (TimeElapsed >= TotalTime);

		// If we have a component to modify..
		if(TargetWidget.IsValid() && bInterpolating)
		{
			// Work out 'Blend Percentage'
			const float BlendExp = 2.f;
			float DurationPct = TimeElapsed/TotalTime;
			float BlendPct;
			if(bEaseIn)
			{
				if(bEaseOut)
				{
					// EASE IN/OUT
					BlendPct = FMath::InterpEaseInOut(0.f, 1.f, DurationPct, BlendExp);
				}
				else
				{
					// EASE IN
					BlendPct = FMath::Lerp(0.f, 1.f, FMath::Pow(DurationPct, BlendExp));
				}
			}
			else
			{
				if(bEaseOut)
				{
					// EASE OUT
					BlendPct = FMath::Lerp(0.f, 1.f, FMath::Pow(DurationPct, 1.f / BlendExp));
				}
				else
				{
					// LINEAR
					BlendPct = FMath::Lerp(0.f, 1.f, DurationPct);
				}
			}

			// Update translation
			if(bInterpTranslation)
			{
				FVector2D NewTranslation = bComplete ? TargetTranslation : FMath::Lerp(InitialTranslation, TargetTranslation, BlendPct);
				TargetWidget->SetRenderTranslation(NewTranslation);
			}

			// Update scale
			if (bInterpScale)
			{
				FVector2D NewScale = bComplete ? TargetScale : FMath::Lerp(InitialScale, TargetScale, BlendPct);
				TargetWidget->SetRenderScale(NewScale);
			}

			// Update Shear
			if (bInterpShear)
			{
				FVector2D NewShear = bComplete ? TargetShear : FMath::Lerp(InitialShear, TargetShear, BlendPct);
				TargetWidget->SetRenderShear(NewShear);
			}

			// Update Angle
			if(bInterpAngle)
			{
				float NewAngle = bComplete ? TargetAngle : FMath::Lerp(InitialAngle, TargetAngle, BlendPct);
				TargetWidget->SetRenderAngle(NewAngle);
			}
		}

		Response.FinishAndTriggerIf(bComplete || !bInterpolating, ExecutionFunction, OutputLink, CallbackTarget);
	}

#if WITH_EDITOR
	// Returns a human readable description of the latent operation's current state
	virtual FString GetDescription() const override
	{
		return FString::Printf( *NSLOCTEXT("FInterpolateWidgetToAction", "ActionTime", "Move (%.3f seconds left)").ToString(), TotalTime-TimeElapsed);
	}
#endif
};


This is a necessary class to get our latent function to work. This class takes care of updating the latent function.

Now, compile the source engine (don’t compile your project, or you will get an error message that a .dll file is busy) and once it’s done compiling, return to your own project. Here, we are going to make a BlueprintFunctionLibrary and add our own Latent function to it (or, if we already have one, just add to it).

If you don’t have your BlueprintFunctionLibrary yet, open up the editor and create yourself a new C++ class based on the “BlueprintFunctionLibrary” class. Once that is done, add the following code to it
**
MyBlueprintFunctionLibrary.h**
Add this before UCLASS()



UENUM()
namespace EMoveLatentAction
{
	enum Type
	{
		/** Move to target over specified time. */
		Move,
		/** If currently moving, stop. */
		Stop,
		/** If currently moving, return to where you started, over the time elapsed so far. */
		Return
	};
}

Add this within UCLASS()



public:
	UFUNCTION(BlueprintCallable, meta = (Latent, LatentInfo = "LatentInfo", WorldContext = "WorldContextObject", ExpandEnumAsExecs = "MoveAction", OverTime = "0.2"), Category = "Components")
		static void MoveWidgetTo(UWidget* Widget, FVector2D TargetTranslation, FVector2D TargetScale, FVector2D TargetShear, float TargetAngle, bool bEaseOut, bool bEaseIn, float OverTime, TEnumAsByte<EMoveLatentAction::Type> MoveAction, FLatentActionInfo LatentInfo);


Now, add the following to your CPP file

MyBlueprintFunctionLibrary.cpp
Add the following includes


#include "LatentActions.h"
#include "Private/InterpolateWidgetToAction.h"

Then add the function


void UBK_BlueprintFunctionLibrary::MoveWidgetTo(UWidget* Widget, FVector2D TargetTranslation, FVector2D TargetScale, FVector2D TargetShear, float TargetAngle, bool bEaseOut, bool bEaseIn, float OverTime, TEnumAsByte<EMoveLatentAction::Type> MoveAction, FLatentActionInfo LatentInfo)
{
	if (UWorld* World = ((Widget != NULL) ? Widget->GetWorld() : NULL))
	{
		FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
		FInterpolateWidgetToAction* Action = LatentActionManager.FindExistingAction<FInterpolateWidgetToAction>(LatentInfo.CallbackTarget, LatentInfo.UUID);

		const FVector2D WidgetTranslation = (Widget != NULL) ? Widget->RenderTransform.Translation : FVector2D::ZeroVector;
		const FVector2D WidgetScale = (Widget != NULL) ? Widget->RenderTransform.Scale : FVector2D::ZeroVector;
		const FVector2D WidgetShear = (Widget != NULL) ? Widget->RenderTransform.Shear : FVector2D::ZeroVector;
		const float	WidgetAngle = (Widget != NULL) ? Widget->RenderTransform.Angle : 0.f;

		const bool bTranslate = (Widget != NULL) ? Widget->RenderTransform.Translation != TargetTranslation : false;
		const bool bScale = (Widget != NULL) ? Widget->RenderTransform.Scale != TargetScale : false;
		const bool bShear = (Widget != NULL) ? Widget->RenderTransform.Shear != TargetShear : false;
		const bool bRotate = (Widget != NULL) ? Widget->RenderTransform.Angle != TargetAngle : false;

		// If not currently running
		if (Action == NULL)
		{
			if (MoveAction == EMoveLatentAction::Move)
			{
				// Only act on a 'move' input if not running
				Action = new FInterpolateWidgetToAction(OverTime, LatentInfo, Widget, bTranslate, bScale, bShear, bRotate, bEaseIn, bEaseOut);

				Action->TargetTranslation = TargetTranslation;
				Action->TargetScale = TargetScale;
				Action->TargetShear = TargetShear;
				Action->TargetAngle = TargetAngle;

				Action->InitialTranslation = WidgetTranslation;
				Action->InitialScale = WidgetScale;
				Action->InitialShear = WidgetShear;
				Action->InitialAngle = WidgetAngle;

				LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, Action);
			}
		}
		else
		{
			if (MoveAction == EMoveLatentAction::Move)
			{
				// A 'Move' action while moving restarts interpolation
				Action->TotalTime = OverTime;
				Action->TimeElapsed = 0.f;

				Action->TargetTranslation = TargetTranslation;
				Action->TargetScale = TargetScale;
				Action->TargetShear = TargetShear;
				Action->TargetAngle = TargetAngle;

				Action->InitialTranslation = WidgetTranslation;
				Action->InitialScale = WidgetScale;
				Action->InitialShear = WidgetShear;
				Action->InitialAngle = WidgetAngle;
			}
			else if (MoveAction == EMoveLatentAction::Stop)
			{
				// 'Stop' just stops the interpolation where it is
				Action->bInterpolating = false;
			}
			else if (MoveAction == EMoveLatentAction::Return)
			{
				// Return moves back to the beginning
				Action->TotalTime = Action->TimeElapsed;
				Action->TimeElapsed = 0.f;

				// Set our target to be our initial, and set the new initial to be the current position
				Action->TargetTranslation = Action->InitialTranslation;
				Action->TargetScale = Action->InitialScale;
				Action->TargetShear = Action->InitialShear;
				Action->TargetAngle = Action->InitialAngle;

				Action->InitialTranslation = WidgetTranslation;
				Action->InitialScale = WidgetScale;
				Action->InitialShear = WidgetShear;
				Action->InitialAngle = WidgetAngle;
			}
		}
	}
}

Now you should be good to go! Compile and open your project in the editor. Now, when you want to move something like an image in your interface, just use this latent function! Even though there’s technically a timeline available in UMG, I thought that sometimes it is easier to use this latent action. It can also be useful because you can use EaseIn and EaseOut (for instance, when you want interface elements to move in or out of the screen when opening/closing the interface). To give proper credit where due; this code is largely based on the MoveComponentTo code, with alterations to change the component to a widget and to change the location and rotation to the widget’s rendertransform.

5e9ec071a39971d5b799ebdfc7baf09e41ac863c.jpeg

If there are any problems, please let me know in this thread.