UE4 shuffle array by RandomStream (C++)
|
I was doing a blueprint room script that would randomly spawn objects in the room. One thing led to another and I needed a way to shuffle an array of spawn points for room objects randomly (preferably in blueprints), but with a specified random seed, so that I could easily find a random order that I wanted to use and stick with it. Unreal Engine 4 has a built-in array shuffle function exposed to blueprints and randomness by seed with the FRandomStream struct, however there is a problem.
See it? I can’t use the built-in array shuffle algorithm with FRandomStream! This means that I could not get deterministic behavior based on my random seed. This needed to be solved. (If you want to skip directly to the C++ implementation details go to the Array Shuffle by Stream Functionality section). Note: UE4 4.9 brought some (fairly undocumented) changes to how the UArrayProperty is fetched and how the functions are declared. I’ve outlined some changes at the very end of this post that should make the rest of this 4.8 code compile for 4.9.
My first thought was to just implement right there in blueprints a shuffle function that took the random seed into account. Not the most generalized, but it would solve this particular predicament. So let’s try:
A bit messy as functional blueprints are, but it is basically just a blueprint implementation of the Fisher–Yates shuffle (same as the built-in uses internally) with the addition of using the random stream (also reserved for potential errors as it’s just for demonstrational purposes). This however will still not work. The reason? Blueprints set/get variables by reference, and (as far as I know) there is no way to pass by value. What this means is that the swap portion of the graph will not work. There may be ways around this but ultimately this is turning into a hassle… Oh well, queue my next solution: just do it in C++ instead. I admit, my familiarity with Unreal Engine 4’s C++ is quite sparse but as a somewhat veteran C++ user how hard can it be? Well the problem now is that C++ to blueprint has limited generic/template support. So just do it for my specific transform array? No, that’s not how I work. We need to make it generalized, for science! Existing Array Shuffle FunctionalityNow a few hours later digging through engine code and learning about the mysteries of a “custom thunk”, reflection and what not, I’ve finally made it and I’m sharing it here (mostly for educational purposes). First, let’s review how epic does its internal array shuffle (found in EngineSourceRuntimeEngineClassesKismetKismetArrayLibrary.h): // Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
...
/**
* Shuffle (randomize) the elements of an array
*
*@param TargetArray The array to shuffle
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Shuffle", CompactNodeTitle = "SHUFFLE", ArrayParm = "TargetArray|ArrayProperty"), Category="Utilities|Array")
static void Array_Shuffle(const TArray& TargetArray, const UArrayProperty* ArrayProperty);
...
static void GenericArray_Shuffle(void* TargetArray, const UArrayProperty* ArrayProp);
...
public:
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
...
DECLARE_FUNCTION(execArray_Shuffle)
{
Stack.StepCompiledIn(NULL);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
P_GET_OBJECT(UArrayProperty, ArrayProperty);
P_FINISH;
GenericArray_Shuffle(ArrayAddr, ArrayProperty);
}
(Parts that don’t really matter have been omitted) And here are the rest of the implementation details (found in EngineSourceRuntimeEnginePrivateKismetArrayLibrary.cpp): // Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
...
void UKismetArrayLibrary::GenericArray_Shuffle(void* TargetArray, const UArrayProperty* ArrayProp)
{
if (TargetArray)
{
FScriptArrayHelper ArrayHelper(ArrayProp, TargetArray);
int32 LastIndex = ArrayHelper.Num() - 1;
for (int32 i = 0; i < LastIndex; ++i)
{
int32 Index = FMath::RandRange(0, LastIndex);
if (i != Index)
{
ArrayHelper.SwapValues(i, Index);
}
}
}
}
...
void UKismetArrayLibrary::Array_Shuffle(const TArray& TargetArray, const UArrayProperty* ArrayProp)
{
// We should never hit these! They're stubs to avoid NoExport on the class. Call the Generic* equivalent instead
check(0);
}
Almost straight-forward right? Right. Array Shuffle by Stream FunctionalityAnyways, after some diddling and a few hours of crashes and debugging I finally came up with this: #pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "CppToBPFunctionLibrary.generated.h"
UCLASS()
class PROJECTSVS_API UCppToBPFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
private:
static void ShuffleArrayWithStream_impl(void* TargetArray, const UArrayProperty* ArrayProperty, const FRandomStream& Stream); // Real implementation
public:
UFUNCTION(BlueprintCallable, CustomThunk, meta = (DisplayName = "Shuffle by Stream", CompactNodeTitle = "SHUFFLESTREAM", ArrayParm = "TargetArray|ArrayProperty"), Category = "Utility")
static void ShuffleArrayWithStream(const TArray& TargetArray, const UArrayProperty* ArrayProperty, const FRandomStream& Stream); // Stub function
DECLARE_FUNCTION(execShuffleArrayWithStream)
{
Stack.StepCompiledIn(NULL);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
P_GET_OBJECT(UArrayProperty, ArrayProperty);
P_GET_STRUCT_REF(FRandomStream, Stream);
P_FINISH;
ShuffleArrayWithStream_impl(ArrayAddr, ArrayProperty, Stream);
}
};
#include "ProjectSVS.h"
#include "CppToBPFunctionLibrary.h"
void UCppToBPFunctionLibrary::ShuffleArrayWithStream(const TArray& TargetArray, const UArrayProperty* ArrayProperty, const FRandomStream& Stream)
{
UE_LOG(LogTemp, Error, TEXT("Stub shuffle func called - should not happen"));
check(0);
}
void UCppToBPFunctionLibrary::ShuffleArrayWithStream_impl(void* TargetArray, const UArrayProperty* ArrayProperty, const FRandomStream& Stream)
{
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
int32 LastIndex = ArrayHelper.Num() - 1;
for (int32 i = 0; i < LastIndex; ++i)
{
int32 Index = Stream.RandRange(0, LastIndex);
if (i != Index)
{
ArrayHelper.SwapValues(i, Index);
}
}
}
Implementation DetailsI’m no wizard but I’ll try to explain each segment as much as my knowledge permits. Disclaimer: I may have interpreted things wrong but at least it works ¯_(ツ)_/¯. The “Thunk” and the glueIn order to handle generic arrays in blueprint we need to supply our own “thunk”. A thunk is basically the glue between the C++ implementation and the blueprint interface. Thunks are usually auto-generated by the UHT (Unreal Header Tool) so that developers don’t need to worry about it, but for generic types we need to manually add this “glue”. The important bit here is in the blueprint function declaration’s UFUNCTION macro: UFUNCTION(BlueprintCallable, CustomThunk, meta = (DisplayName = "Shuffle by Stream", CompactNodeTitle = "SHUFFLESTREAM", ArrayParm = "TargetArray|ArrayProperty"), Category = "Utility") The CustomThunk meta-tag tells UHT that we will provide our own glue for this function. The ArrayParm is also very important (for nodes taking arrays) to let the system know what parameters are wildcard array properties. Can’t explain it much more than that I’m afraid (omitting them results in a node that can’t take a generic array, only an int32 array as specified). We declare and define this glue-function: DECLARE_FUNCTION(execShuffleArrayWithStream)
{
Stack.StepCompiledIn(NULL);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
P_GET_OBJECT(UArrayProperty, ArrayProperty);
P_GET_STRUCT_REF(FRandomStream, Stream);
P_FINISH;
ShuffleArrayWithStream_impl(ArrayAddr, ArrayProperty, Stream);
}
DECLARE_FUNCTION is a macro which alleviates the specific declaration needed for glue functions, and the parameter execShuffleArrayWithStream is the function name, which must be the same as the blueprint function using CustomThunk , with added exec in front of it. This is the way the engine expects it. From there on it gets a little wacky for the uninitiated but we’re fetching the actual variables needed for the real implementation provided by the private function static void ShuffleArrayWithStream_impl(...) . Within the exec glue function is probably the place to do type-specific operations if your implementation requires it. A few notes though:
The real implementationMoving on past this sticky-icky function we have the real implementation static void ShuffleArrayWithStream_impl(...) . This function uses array specific operations and doesn’t rely on the templated parameters, but for the sake of completeness I’ll explain it here as well. As evident by the other array functions defined in KismetArrayLibrary.cpp, FScriptArrayHelper is an useful class which basically takes the abstracted void* TargetArray and casts it to a generic array which can work on its elements in every way except fetching them. Basically anything that doesn’t require their type to be available (removing, swapping etc.). Also, comparing this to the built-in shuffle implementation we can see that I shamelessly took almost the same code and replaced FMath::RandRange(0, LastIndex); with Stream.RandRange(0, LastIndex); . All that work for 7 characters worth of changes (and the FRandomStream& parameter). VerdictIn this “tutorial” I’ve explained how to implement a blueprint function which takes a generic array input. This has been useful for me to learn and hopefully you as a reader learned something as well. This implementation is probably helpful if you ever want to implement a blueprint function which takes a generic parameter, however extra attention may be needed in the glue-function and implementation function to account for objects other than arrays. Don’t be afraid to ask questions if you have any thoughts about this, and happy developing! 4.9 ChangesUE4 4.9 made some changes to the system behind this functionality. Below are the changes made to the .h file and the .cpp file. UFUNCTION(BlueprintCallable, CustomThunk, meta = (DisplayName = "Shuffle by Stream", CompactNodeTitle = "SHUFFLESTREAM", ArrayParm = "TargetArray"), Category = "Utility")
static void ShuffleArrayWithStream(const TArray& TargetArray, const FRandomStream& Stream); // Stub function
DECLARE_FUNCTION(execShuffleArrayWithStream)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn(NULL);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
//P_GET_OBJECT(UArrayProperty, ArrayProperty); // Old 4.8 code
UArrayProperty* ArrayProperty = Cast(Stack.MostRecentProperty); // New 4.9 code
if (!ArrayProperty)
{
UE_LOG(LogTemp, Error, TEXT("Failed to get array context property"));
Stack.bArrayContextFailed = true;
return;
}
P_GET_STRUCT_REF(FRandomStream, Stream);
P_FINISH;
ShuffleArrayWithStream_impl(ArrayAddr, ArrayProperty, Stream);
}
The declaration for the stub function now omits the UArrayProperty parameter as well as in the macro’s ArrayParm parameter. As a result, the glue-function can no longer fetch the UArrayProperty with the P_GET_OBJECT macro and instead is fetched with with the Stack.MostRecentProperty property, casted appropriately. Some sanity and safety code is also added in. void UCppToBPFunctionLibrary::ShuffleArrayWithStream(const TArray& TargetArray, const FRandomStream& Stream)
{
UE_LOG(LogTemp, Error, TEXT("Stub shuffle func called - should not happen"));
check(0);
}
void UCppToBPFunctionLibrary::ShuffleArrayWithStream_impl(void* TargetArray, const UArrayProperty* ArrayProperty, const FRandomStream& Stream)
{
if (TargetArray)
{
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
int32 LastIndex = ArrayHelper.Num() - 1;
for (int32 i = 0; i < LastIndex; ++i)
{
int32 Index = Stream.RandRange(0, LastIndex);
if (i != Index)
{
ArrayHelper.SwapValues(i, Index);
}
}
}
}
With the exception of the stub function declaration omitting the UArrayProperty* the .cpp file is largely the same, with the added sanity check for TargetArray . |

