どのゲームでも毎回同じように重み付けの実装をしているのでメモ
ロジック自体はどの言語でも一緒だけど、今回はUE4C++でテストします
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"
USTRUCT()
struct SANDBOX_API FSomething
{
GENERATED_USTRUCT_BODY()
UPROPERTY()
int32 Id;
UPROPERTY()
int32 Weight;
FSomething( int32 id = 0, int32 weight = 0 ) : Id( id ), Weight( weight ) {}
};
UCLASS()
class SANDBOX_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
template <class T>
static int32 Choice( const TArray<T>& source,
const std::function<uint32( const T* )> getWeightCallback )
{
check( source.Num() != 0 );
uint64 sum = 0;
TArray<uint32> weightArray;
for ( const auto& row : source )
{
const auto weight = getWeightCallback( &row );
sum += weight;
weightArray.Emplace( weight );
}
if ( sum == 0 )
{
return -1;
}
const auto r = FMath::RandRange( 1, sum );
uint64 tmp = 0;
for ( int32 i = 0; i < weightArray.Num(); ++i )
{
tmp += weightArray[ i ];
if ( r <= tmp )
{
return i;
}
}
checkNoEntry();
return -1;
}
UFUNCTION( BlueprintCallable )
static int32 ChoiceTest()
{
TArray<FSomething> someArray =
{
FSomething( 1, 100 ),
FSomething( 2, 200 ),
FSomething( 3, 150 ),
};
return Choice<FSomething>( someArray, [ & ] ( const auto row )
{
return row->Weight;
} );
}
};
実際にやってみると結構偏りがでます
LogBlueprintUserMessages: [Untitled_C_10] 0: 2229
LogBlueprintUserMessages: [Untitled_C_10] 1: 4460
LogBlueprintUserMessages: [Untitled_C_10] 2: 3311
const auto r = FMath::RandRange( 1, sum );
これはUE4関数で乱数を生成しているためで、もっと精度を高めたいのであれば他の乱数生成アルゴリズムを用いる必要があります
UE4の乱数生成BPの実装を見てみる(1)(Random integer in range)
C++11 の乱数ライブラリ