TArray<FString> UCurveBase::CreateCurveFromCSVString(const FString& InString) { // Array used to store problems about curve import TArray<FString> OutProblems; TArray<FRichCurveEditInfo> Curves = GetCurves(); const int32 NumCurves = Curves.Num(); const FCsvParser Parser(InString); const FCsvParser::FRows& Rows = Parser.GetRows(); if(Rows.Num() == 0) { OutProblems.Add(FString(TEXT("No data."))); return OutProblems; } // First clear out old data. ResetCurve(); // Each row represents a point for(int32 RowIdx=0; RowIdx<Rows.Num(); RowIdx++) { const TArray<const TCHAR*>& Cells = Rows[RowIdx]; const int32 NumCells = Cells.Num(); // Need at least two cell, Time and one Value if(NumCells < 2) { OutProblems.Add(FString::Printf(TEXT("Row '%d' has less than 2 cells."), RowIdx)); continue; } float Time = FCString::Atof(Cells[0]); for(int32 CellIdx=1; CellIdx<NumCells && CellIdx<(NumCurves+1); CellIdx++) { FRichCurve* Curve = Curves[CellIdx-1].CurveToEdit; if(Curve != NULL) { FKeyHandle KeyHandle = Curve->AddKey(Time, FCString::Atof(Cells[CellIdx])); Curve->SetKeyInterpMode(KeyHandle, RCIM_Linear); } } // If we get more cells than curves (+1 for time cell) if(NumCells > (NumCurves + 1)) { OutProblems.Add(FString::Printf(TEXT("Row '%d' has too many cells for the curve(s)."), RowIdx)); } // If we got too few cells else if(NumCells < (NumCurves + 1)) { OutProblems.Add(FString::Printf(TEXT("Row '%d' has too few cells for the curve(s)."), RowIdx)); } } Modify(true); return OutProblems; }
bool ImportCurvesEmbeddedInSoundWave() { // find/create our sound wave USoundWave* const SoundWave = ImportSoundWave(TargetPackageName, TargetAssetName, WaveFile); if (SoundWave) { // create our curve table internal to the sound wave static const FName InternalCurveTableName("InternalCurveTable"); SoundWave->Curves = NewObject<UCurveTable>(SoundWave, InternalCurveTableName); SoundWave->Curves->ClearFlags(RF_Public | RF_Standalone); SoundWave->Curves->SetFlags(SoundWave->Curves->GetFlags() | RF_Transactional); SoundWave->InternalCurves = SoundWave->Curves; // import curves from FBX float PreRollTime = 0.0f; if (FbxAnimUtils::ImportCurveTableFromNode(FbxFile, GetDefault<UFacialAnimationBulkImporterSettings>()->CurveNodeName, SoundWave->Curves, PreRollTime)) { // we will need to add a curve to tell us the time we want to start playing audio FRichCurve* AudioCurve = SoundWave->Curves->RowMap.Add(TEXT("Audio"), new FRichCurve()); AudioCurve->AddKey(PreRollTime, 1.0f); return true; } } return false; }
void FMatineeImportTools::SetOrAddKey( FRichCurve& Curve, float Time, float Value, float ArriveTangent, float LeaveTangent, EInterpCurveMode MatineeInterpMode ) { FKeyHandle KeyHandle = Curve.AddKey( Time, Value, false, Curve.FindKey( Time ) ); FRichCurveKey& Key = Curve.GetKey( KeyHandle ); Key.ArriveTangent = ArriveTangent; Key.LeaveTangent = LeaveTangent; Key.InterpMode = MatineeInterpolationToRichCurveInterpolation( MatineeInterpMode ); Key.TangentMode = MatineeInterpolationToRichCurveTangent( MatineeInterpMode ); }
float FCurveTableRowHandle::Eval(float XValue) const { SCOPE_CYCLE_COUNTER(STAT_CurveTableRowHandleEval); FRichCurve* Curve = GetCurve(); if(Curve != NULL) { return Curve->Eval(XValue); } return 0; }
bool FCurveTableRowHandle::Eval(float XValue, float* YValue) const { SCOPE_CYCLE_COUNTER(STAT_CurveTableRowHandleEval); FRichCurve* Curve = GetCurve(); if(Curve != NULL && YValue != NULL) { *YValue = Curve->Eval(XValue); return true; } return false; }
FGradientStopMark SColorGradientEditor::AddStop( const FVector2D& Position, const FGeometry& MyGeometry, bool bColorStop ) { FScopedTransaction AddStopTrans( LOCTEXT("AddGradientStop", "Add Gradient Stop") ); CurveOwner->ModifyOwner(); FTrackScaleInfo ScaleInfo(ViewMinInput.Get(), ViewMaxInput.Get(), 0.0f, 1.0f, MyGeometry.Size); FVector2D LocalPos = MyGeometry.AbsoluteToLocal( Position ); float NewStopTime = ScaleInfo.LocalXToInput( LocalPos.X ); TArray<FRichCurveEditInfo> Curves = CurveOwner->GetCurves(); FGradientStopMark NewStop; NewStop.Time = NewStopTime; if( bColorStop ) { FRichCurve* RedCurve = Curves[0].CurveToEdit; FRichCurve* GreenCurve = Curves[1].CurveToEdit; FRichCurve* BlueCurve = Curves[2].CurveToEdit; NewStop.RedKeyHandle = RedCurve->AddKey( NewStopTime, LastModifiedColor.R ); NewStop.GreenKeyHandle = GreenCurve->AddKey( NewStopTime, LastModifiedColor.G ); NewStop.BlueKeyHandle = BlueCurve->AddKey( NewStopTime, LastModifiedColor.B ); } else { FRichCurve* AlphaCurve = Curves[3].CurveToEdit; NewStop.AlphaKeyHandle = AlphaCurve->AddKey( NewStopTime, LastModifiedColor.A ); } return NewStop; }
void UMovieSceneSection::SetCurveDefault(FRichCurve& InCurve, float Value) { if (TryModify()) { InCurve.SetDefaultValue(Value); } }
void UMovieSceneSection::AddKeyToCurve(FRichCurve& InCurve, float Time, float Value, EMovieSceneKeyInterpolation Interpolation, const bool bUnwindRotation) { if (IsTimeWithinSection(Time)) { if (TryModify()) { FKeyHandle ExistingKeyHandle = InCurve.FindKey(Time); FKeyHandle NewKeyHandle = InCurve.UpdateOrAddKey(Time, Value, bUnwindRotation); if (!InCurve.IsKeyHandleValid(ExistingKeyHandle) && InCurve.IsKeyHandleValid(NewKeyHandle)) { MovieSceneHelpers::SetKeyInterpolation(InCurve, NewKeyHandle, Interpolation); } } } }
void UMovieSceneSection::AddKeyToCurve( FRichCurve& InCurve, float Time, float Value ) { if(IsTimeWithinSection(Time)) { Modify(); InCurve.UpdateOrAddKey(Time, Value); } }
void UMovieSceneParameterSection::AddScalarParameterKey( FName InParameterName, float InTime, float InValue ) { FRichCurve* ExistingCurve = nullptr; for ( FScalarParameterNameAndCurve& ScalarParameterNameAndCurve : ScalarParameterNamesAndCurves ) { if ( ScalarParameterNameAndCurve.ParameterName == InParameterName ) { ExistingCurve = &ScalarParameterNameAndCurve.ParameterCurve; break; } } if ( ExistingCurve == nullptr ) { int32 NewIndex = ScalarParameterNamesAndCurves.Add( FScalarParameterNameAndCurve( InParameterName ) ); ScalarParameterNamesAndCurves[NewIndex].Index = ScalarParameterNamesAndCurves.Num() + VectorParameterNamesAndCurves.Num() - 1; ExistingCurve = &ScalarParameterNamesAndCurves[NewIndex].ParameterCurve; } ExistingCurve->AddKey(InTime, InValue); }
void AProjectile::Explode() { BP_Explode(); const FVector Loc = GetActorLocation(); for (TActorIterator<AActor> aItr(GetWorld()); aItr; ++aItr) { const float distance = GetDistanceTo(*aItr); if (distance<AffectArea && aItr && aItr->GetRootComponent() && aItr->GetRootComponent()->Mobility == EComponentMobility::Movable) { FVector dir = aItr->GetActorLocation() - Loc; dir.Normalize(); FRichCurve* RadialDamageCurveData = RadialDamageCurve.GetRichCurve(); FRichCurve* RadialImpulseCurveData = RadialImpulseCurve.GetRichCurve(); ABaseCharacter* theChar = Cast<ABaseCharacter>(*aItr); //If Player apply damage if (theChar && RadialDamageCurveData) { //printr("Apply Damage"); UGameplayStatics::ApplyDamage(theChar, RadialDamageCurveData->Eval(distance), NULL, this, ExplosionDamageType); } if (RadialImpulseCurveData && aItr->GetRootComponent()->IsSimulatingPhysics() && Cast<UPrimitiveComponent>(aItr->GetRootComponent())) { Cast<UPrimitiveComponent>(aItr->GetRootComponent())->AddImpulse(dir*RadialImpulseCurveData->Eval(distance)); } } } Destroy(); }
AProjectile::AProjectile(const class FObjectInitializer& PCIP) : Super(PCIP) { //RootPoint = PCIP.CreateDefaultSubobject<USceneComponent>(this, TEXT("RootPoint")); // The Collision Component CollisionComp = PCIP.CreateDefaultSubobject<USphereComponent>(this, TEXT("SphereComp")); CollisionComp->MoveIgnoreActors.Add(this); CollisionComp->InitSphereRadius(5.0f); CollisionComp->BodyInstance.SetCollisionProfileName("Projectile"); CollisionComp->OnComponentHit.AddDynamic(this, &AProjectile::OnHit); RootComponent = CollisionComp; Mesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("ProjectileMesh")); Mesh->bReceivesDecals = false; Mesh->CastShadow = false; Mesh->AttachParent = CollisionComp; // Projectile Movement ProjectileMovement = PCIP.CreateDefaultSubobject<UProjectileMovementComponent>(this, TEXT("ProjectileComp")); ProjectileMovement->UpdatedComponent = CollisionComp; ProjectileMovement->InitialSpeed = 3000.f; ProjectileMovement->MaxSpeed = 3000.f; ProjectileMovement->bRotationFollowsVelocity = true; ProjectileMovement->bShouldBounce = true; bReplicates = true; FRichCurve* RadialDamageCurveData = RadialDamageCurve.GetRichCurve(); RadialDamageCurveData->AddKey(0, 90); RadialDamageCurveData->AddKey(380, 40); RadialDamageCurveData->AddKey(900, 0); FRichCurve* RadialImpulseCurveData = RadialImpulseCurve.GetRichCurve(); RadialImpulseCurveData->AddKey(0, 15000); RadialImpulseCurveData->AddKey(900, 9000); }
void FGradientStopMark::SetColor( const FLinearColor& InColor, FCurveOwnerInterface& CurveOwner ) { TArray<FRichCurveEditInfo> Curves = CurveOwner.GetCurves(); // Update the color component on each curve if( IsValidColorMark(Curves) ) { FRichCurve* RedCurve = Curves[0].CurveToEdit; FRichCurve* GreenCurve = Curves[1].CurveToEdit; FRichCurve* BlueCurve = Curves[2].CurveToEdit; RedCurve->SetKeyValue( RedKeyHandle, InColor.R ); GreenCurve->SetKeyValue( GreenKeyHandle, InColor.G ); BlueCurve->SetKeyValue( BlueKeyHandle, InColor.B ); } else if( IsValidAlphaMark(Curves) ) { FRichCurve* AlphaCurve = Curves[3].CurveToEdit; AlphaCurve->SetKeyValue( AlphaKeyHandle, InColor.A ); } }
void SColorGradientEditor::DeleteStop( const FGradientStopMark& InMark ) { FScopedTransaction DeleteStopTrans( LOCTEXT("DeleteGradientStop", "Delete Gradient Stop") ); CurveOwner->ModifyOwner(); TArray<FRichCurveEditInfo> Curves = CurveOwner->GetCurves(); FRichCurve* RedCurve = Curves[0].CurveToEdit; FRichCurve* GreenCurve = Curves[1].CurveToEdit; FRichCurve* BlueCurve = Curves[2].CurveToEdit; FRichCurve* AlphaCurve = Curves[3].CurveToEdit; if( InMark.IsValidAlphaMark( Curves ) ) { AlphaCurve->DeleteKey( InMark.AlphaKeyHandle ); } else if( InMark.IsValidColorMark( Curves ) ) { RedCurve->DeleteKey( InMark.RedKeyHandle ); GreenCurve->DeleteKey( InMark.GreenKeyHandle ); BlueCurve->DeleteKey( InMark.BlueKeyHandle ); } }
void FGradientStopMark::SetTime( float NewTime, FCurveOwnerInterface& CurveOwner ) { TArray<FRichCurveEditInfo> Curves = CurveOwner.GetCurves(); // Update the time on each curve if( IsValidColorMark(Curves) ) { FRichCurve* RedCurve = Curves[0].CurveToEdit; FRichCurve* GreenCurve = Curves[1].CurveToEdit; FRichCurve* BlueCurve = Curves[2].CurveToEdit; RedKeyHandle = RedCurve->SetKeyTime( RedKeyHandle, NewTime ); GreenKeyHandle = GreenCurve->SetKeyTime( GreenKeyHandle, NewTime ); BlueKeyHandle = BlueCurve->SetKeyTime( BlueKeyHandle, NewTime ); } else if( IsValidAlphaMark(Curves) ) { FRichCurve* AlphaCurve = Curves[3].CurveToEdit; AlphaKeyHandle = AlphaCurve->SetKeyTime( AlphaKeyHandle, NewTime ); } Time = NewTime; }
TArray<FString> UCurveTable::CreateTableFromCSVString(const FString& InString, ERichCurveInterpMode InterpMode) { // Array used to store problems about table creation TArray<FString> OutProblems; const FCsvParser Parser(InString); const FCsvParser::FRows& Rows = Parser.GetRows(); // Must have at least 2 rows (x values + y values for at least one row) if(Rows.Num() <= 1) { OutProblems.Add(FString(TEXT("Too few rows."))); return OutProblems; } // Empty existing data EmptyTable(); TArray<float> XValues; GetCurveValues(Rows[0], &XValues); // Iterate over rows for(int32 RowIdx = 1; RowIdx < Rows.Num(); RowIdx++) { const TArray<const TCHAR*>& Row = Rows[RowIdx]; // Need at least 1 cells (row name) if(Row.Num() < 1) { OutProblems.Add(FString::Printf(TEXT("Row '%d' has too few cells."), RowIdx)); continue; } // Get row name FName RowName = MakeValidName(Row[0]); // Check its not 'none' if(RowName == NAME_None) { OutProblems.Add(FString::Printf(TEXT("Row '%d' missing a name."), RowIdx)); continue; } // Check its not a duplicate if(RowMap.Find(RowName) != NULL) { OutProblems.Add(FString::Printf(TEXT("Duplicate row name '%s'."), *RowName.ToString())); continue; } TArray<float> YValues; GetCurveValues(Row, &YValues); if(XValues.Num() != YValues.Num()) { OutProblems.Add(FString::Printf(TEXT("Row '%s' does not have the right number of columns."), *RowName.ToString())); continue; } FRichCurve* NewCurve = new FRichCurve(); // Now iterate over cells (skipping first cell, that was row name) for(int32 ColumnIdx = 0; ColumnIdx < XValues.Num(); ColumnIdx++) { FKeyHandle KeyHandle = NewCurve->AddKey(XValues[ColumnIdx], YValues[ColumnIdx]); NewCurve->SetKeyInterpMode(KeyHandle, InterpMode); } RowMap.Add(RowName, NewCurve); } Modify(true); return OutProblems; }
TArray<FString> UCurveTable::CreateTableFromJSONString(const FString& InString, ERichCurveInterpMode InterpMode) { // Array used to store problems about table creation TArray<FString> OutProblems; if (InString.IsEmpty()) { OutProblems.Add(TEXT("Input data is empty.")); return OutProblems; } TArray< TSharedPtr<FJsonValue> > ParsedTableRows; { const TSharedRef< TJsonReader<TCHAR> > JsonReader = TJsonReaderFactory<TCHAR>::Create(InString); if (!FJsonSerializer::Deserialize(JsonReader, ParsedTableRows) || ParsedTableRows.Num() == 0) { OutProblems.Add(FString::Printf(TEXT("Failed to parse the JSON data. Error: %s"), *JsonReader->GetErrorMessage())); return OutProblems; } } // Empty existing data EmptyTable(); // Iterate over rows for (int32 RowIdx = 0; RowIdx < ParsedTableRows.Num(); ++RowIdx) { const TSharedPtr<FJsonValue>& ParsedTableRowValue = ParsedTableRows[RowIdx]; TSharedPtr<FJsonObject> ParsedTableRowObject = ParsedTableRowValue->AsObject(); if (!ParsedTableRowObject.IsValid()) { OutProblems.Add(FString::Printf(TEXT("Row '%d' is not a valid JSON object."), RowIdx)); continue; } // Get row name static const FString RowNameJsonKey = TEXT("Name"); const FName RowName = MakeValidName(ParsedTableRowObject->GetStringField(RowNameJsonKey)); // Check its not 'none' if (RowName == NAME_None) { OutProblems.Add(FString::Printf(TEXT("Row '%d' missing a name."), RowIdx)); continue; } // Check its not a duplicate if (RowMap.Find(RowName) != NULL) { OutProblems.Add(FString::Printf(TEXT("Duplicate row name '%s'."), *RowName.ToString())); continue; } // Add a key for each entry in this row FRichCurve* NewCurve = new FRichCurve(); for (const auto& ParsedTableRowEntry : ParsedTableRowObject->Values) { // Skip the name entry if (ParsedTableRowEntry.Key == RowNameJsonKey) { continue; } // Make sure we have a valid float key float EntryKey = 0.0f; if (!LexicalConversion::TryParseString(EntryKey, *ParsedTableRowEntry.Key)) { OutProblems.Add(FString::Printf(TEXT("Key '%s' on row '%s' is not a float and cannot be parsed."), *ParsedTableRowEntry.Key, *RowName.ToString())); continue; } // Make sure we have a valid float value double EntryValue = 0.0; if (!ParsedTableRowEntry.Value->TryGetNumber(EntryValue)) { OutProblems.Add(FString::Printf(TEXT("Entry '%s' on row '%s' is not a float and cannot be parsed."), *ParsedTableRowEntry.Key, *RowName.ToString())); continue; } FKeyHandle KeyHandle = NewCurve->AddKey(EntryKey, static_cast<float>(EntryValue)); NewCurve->SetKeyInterpMode(KeyHandle, InterpMode); } RowMap.Add(RowName, NewCurve); } Modify(true); return OutProblems; }
UWheeledVehicleMovementComponent4W::UWheeledVehicleMovementComponent4W(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { #if WITH_VEHICLE // grab default values from physx PxVehicleDifferential4WData DefDifferentialSetup; TEnumAsByte<EVehicleDifferential4W::Type> DiffType((uint8)DefDifferentialSetup.mType); DifferentialSetup.DifferentialType = DiffType; DifferentialSetup.FrontRearSplit = DefDifferentialSetup.mFrontRearSplit; DifferentialSetup.FrontLeftRightSplit = DefDifferentialSetup.mFrontLeftRightSplit; DifferentialSetup.RearLeftRightSplit = DefDifferentialSetup.mRearLeftRightSplit; DifferentialSetup.CentreBias = DefDifferentialSetup.mCentreBias; DifferentialSetup.FrontBias = DefDifferentialSetup.mFrontBias; DifferentialSetup.RearBias = DefDifferentialSetup.mRearBias; PxVehicleEngineData DefEngineData; EngineSetup.MOI = DefEngineData.mMOI; EngineSetup.MaxRPM = OmegaToRPM(DefEngineData.mMaxOmega); EngineSetup.DampingRateFullThrottle = DefEngineData.mDampingRateFullThrottle; EngineSetup.DampingRateZeroThrottleClutchEngaged = DefEngineData.mDampingRateZeroThrottleClutchEngaged; EngineSetup.DampingRateZeroThrottleClutchDisengaged = DefEngineData.mDampingRateZeroThrottleClutchDisengaged; // Convert from PhysX curve to ours FRichCurve* TorqueCurveData = EngineSetup.TorqueCurve.GetRichCurve(); for(PxU32 KeyIdx=0; KeyIdx<DefEngineData.mTorqueCurve.getNbDataPairs(); KeyIdx++) { float Input = DefEngineData.mTorqueCurve.getX(KeyIdx) * EngineSetup.MaxRPM; float Output = DefEngineData.mTorqueCurve.getY(KeyIdx) * DefEngineData.mPeakTorque; TorqueCurveData->AddKey(Input, Output); } PxVehicleClutchData DefClutchData; TransmissionSetup.ClutchStrength = DefClutchData.mStrength; PxVehicleAckermannGeometryData DefAckermannSetup; AckermannAccuracy = DefAckermannSetup.mAccuracy; PxVehicleGearsData DefGearSetup; TransmissionSetup.GearSwitchTime = DefGearSetup.mSwitchTime; TransmissionSetup.ReverseGearRatio = DefGearSetup.mRatios[PxVehicleGearsData::eREVERSE]; TransmissionSetup.FinalRatio = DefGearSetup.mFinalRatio; PxVehicleAutoBoxData DefAutoBoxSetup; TransmissionSetup.NeutralGearUpRatio = DefAutoBoxSetup.mUpRatios[PxVehicleGearsData::eNEUTRAL]; TransmissionSetup.GearAutoBoxLatency = DefAutoBoxSetup.getLatency(); TransmissionSetup.bUseGearAutoBox = true; for (uint32 i = PxVehicleGearsData::eFIRST; i < DefGearSetup.mNbRatios; i++) { FVehicleGearData GearData; GearData.DownRatio = DefAutoBoxSetup.mDownRatios[i]; GearData.UpRatio = DefAutoBoxSetup.mUpRatios[i]; GearData.Ratio = DefGearSetup.mRatios[i]; TransmissionSetup.ForwardGears.Add(GearData); } // Init steering speed curve FRichCurve* SteeringCurveData = SteeringCurve.GetRichCurve(); SteeringCurveData->AddKey(0.f, 1.f); SteeringCurveData->AddKey(20.f, 0.9f); SteeringCurveData->AddKey(60.f, 0.8f); SteeringCurveData->AddKey(120.f, 0.7f); // Initialize WheelSetups array with 4 wheels WheelSetups.SetNum(4); #endif // WITH_VEHICLE }
/** * Transforms CurveTable data into format more effecient to read at runtime. * UCurveTable requires string parsing to map to GroupName/AttributeSet/Attribute * Each curve in the table represents a *single attribute's values for all levels*. * At runtime, we want *all attribute values at given level*. */ void FAttributeSetInitter::PreloadAttributeSetData(UCurveTable* CurveData) { if(!ensure(CurveData)) { return; } /** * Get list of AttributeSet classes loaded */ TArray<TSubclassOf<UAttributeSet> > ClassList; for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt) { UClass* TestClass = *ClassIt; if (TestClass->IsChildOf(UAttributeSet::StaticClass())) { ClassList.Add(TestClass); #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // This can only work right now on POD attribute sets. If we ever support FStrings or TArrays in AttributeSets // we will need to update this code to not use memcpy etc. for (TFieldIterator<UProperty> PropIt(TestClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) { if (!PropIt->HasAllPropertyFlags(CPF_IsPlainOldData)) { ABILITY_LOG(Error, TEXT("FAttributeSetInitter::PreloadAttributeSetData Unable to Handle AttributeClass %s because it has a non POD property: %s"), *TestClass->GetName(), *PropIt->GetName()); return; } } #endif } } /** * Loop through CurveData table and build sets of Defaults that keyed off of Name + Level */ for (auto It = CurveData->RowMap.CreateConstIterator(); It; ++It) { FString RowName = It.Key().ToString(); FString ClassName; FString SetName; FString AttributeName; FString Temp; RowName.Split(TEXT("."), &ClassName, &Temp); Temp.Split(TEXT("."), &SetName, &AttributeName); if (!ensure(!ClassName.IsEmpty() && !SetName.IsEmpty() && !AttributeName.IsEmpty())) { ABILITY_LOG(Verbose, TEXT("FAttributeSetInitter::PreloadAttributeSetData Unable to parse row %s in %s"), *RowName, *CurveData->GetName()); continue; } // Find the AttributeSet TSubclassOf<UAttributeSet> Set = FindBestAttributeClass(ClassList, SetName); if (!Set) { // This is ok, we may have rows in here that don't correspond directly to attributes ABILITY_LOG(Verbose, TEXT("FAttributeSetInitter::PreloadAttributeSetData Unable to match AttributeSet from %s (row: %s)"), *SetName, *RowName); continue; } // Find the UProperty UNumericProperty* Property = FindField<UNumericProperty>(*Set, *AttributeName); if (!Property) { ABILITY_LOG(Verbose, TEXT("FAttributeSetInitter::PreloadAttributeSetData Unable to match Attribute from %s (row: %s)"), *AttributeName, *RowName); continue; } FRichCurve* Curve = It.Value(); FName ClassFName = FName(*ClassName); FAttributeSetDefaulsCollection& DefaultCollection = Defaults.FindOrAdd(ClassFName); int32 LastLevel = Curve->GetLastKey().Time; DefaultCollection.LevelData.SetNum(FMath::Max(LastLevel, DefaultCollection.LevelData.Num())); //At this point we know the Name of this "class"/"group", the AttributeSet, and the Property Name. Now loop through the values on the curve to get the attribute default value at each level. for (auto KeyIter = Curve->GetKeyIterator(); KeyIter; ++KeyIter) { const FRichCurveKey& CurveKey = *KeyIter; int32 Level = CurveKey.Time; float Value = CurveKey.Value; FAttributeSetDefaults& SetDefaults = DefaultCollection.LevelData[Level-1]; FAttributeDefaultValueList* DefaultDataList = SetDefaults.DataMap.Find(Set); if (DefaultDataList == nullptr) { ABILITY_LOG(Verbose, TEXT("Initializing new default set for %s[%d]. PropertySize: %d.. DefaultSize: %d"), *Set->GetName(), Level, Set->GetPropertiesSize(), UAttributeSet::StaticClass()->GetPropertiesSize()); DefaultDataList = &SetDefaults.DataMap.Add(Set); } // Import curve value into default data check(DefaultDataList); DefaultDataList->AddPair(Property, Value); } } }