创建Blueprint

核心方法

/** Create a new Blueprint and initialize it to a valid state. */
UBlueprint* FKismetEditorUtilities::CreateBlueprint(UClass* ParentClass, UObject* Outer, const FName NewBPName, EBlueprintType BlueprintType, TSubclassOf<UBlueprint> BlueprintClassType, TSubclassOf<UBlueprintGeneratedClass> BlueprintGeneratedClassType, FName CallingContext)
{
	FSecondsCounterScope Timer(BlueprintCompileAndLoadTimerData);
	check(FindObject<UBlueprint>(Outer, *NewBPName.ToString()) == NULL); 

	// Not all types are legal for all parent classes, if the parent class is const then the blueprint cannot be an ubergraph-bearing one
	if ((BlueprintType == BPTYPE_Normal) && (ParentClass->HasAnyClassFlags(CLASS_Const)))
	{
		BlueprintType = BPTYPE_Const;
	}
	
	// Create new UBlueprint object
	UBlueprint* NewBP = NewObject<UBlueprint>(Outer, *BlueprintClassType, NewBPName, RF_Public | RF_Standalone | RF_Transactional | RF_LoadCompleted);
	NewBP->Status = BS_BeingCreated;
	NewBP->BlueprintType = BlueprintType;
	NewBP->ParentClass = ParentClass;
	NewBP->BlueprintSystemVersion = UBlueprint::GetCurrentBlueprintSystemVersion();
	NewBP->bIsNewlyCreated = true;
	NewBP->bLegacyNeedToPurgeSkelRefs = false;
	NewBP->GenerateNewGuid();

	// Create SimpleConstructionScript and UserConstructionScript
	if (FBlueprintEditorUtils::SupportsConstructionScript(NewBP))
	{ 
		// >>> Temporary workaround, before a BlueprintGeneratedClass is the main asset.
		FName NewSkelClassName, NewGenClassName;
		NewBP->GetBlueprintClassNames(NewGenClassName, NewSkelClassName);
		UBlueprintGeneratedClass* NewClass = NewObject<UBlueprintGeneratedClass>(
			NewBP->GetOutermost(), *BlueprintGeneratedClassType, NewGenClassName, RF_Public | RF_Transactional);
		NewBP->GeneratedClass = NewClass;
		NewClass->ClassGeneratedBy = NewBP;
		NewClass->SetSuperStruct(ParentClass);
		// <<< Temporary workaround

		NewBP->SimpleConstructionScript = NewObject<USimpleConstructionScript>(NewClass);
		NewBP->SimpleConstructionScript->SetFlags(RF_Transactional);
		NewBP->LastEditedDocuments.Add(NewBP->SimpleConstructionScript);

		UEdGraph* UCSGraph = FBlueprintEditorUtils::CreateNewGraph(NewBP, UEdGraphSchema_K2::FN_UserConstructionScript, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
		FBlueprintEditorUtils::AddFunctionGraph(NewBP, UCSGraph, /*bIsUserCreated=*/ false, AActor::StaticClass());

		// If the blueprint is derived from another blueprint, add in a super-call automatically
		if( NewBP->ParentClass && NewBP->ParentClass->ClassGeneratedBy )
		{
			check( UCSGraph->Nodes.Num() > 0 );
			UK2Node_FunctionEntry* UCSEntry = CastChecked<UK2Node_FunctionEntry>(UCSGraph->Nodes[0]);
			FGraphNodeCreator<UK2Node_CallParentFunction> FunctionNodeCreator(*UCSGraph);
			UK2Node_CallParentFunction* ParentFunctionNode = FunctionNodeCreator.CreateNode();
			ParentFunctionNode->FunctionReference.SetExternalMember(UEdGraphSchema_K2::FN_UserConstructionScript, NewBP->ParentClass);
			ParentFunctionNode->NodePosX = 200;
			ParentFunctionNode->NodePosY = 0;
			ParentFunctionNode->AllocateDefaultPins();
			FunctionNodeCreator.Finalize();

			// Wire up the new node
			UEdGraphPin* ExecPin = UCSEntry->FindPin(UEdGraphSchema_K2::PN_Then);
			UEdGraphPin* SuperPin = ParentFunctionNode->FindPin(UEdGraphSchema_K2::PN_Execute);
			ExecPin->MakeLinkTo(SuperPin);
		}

		NewBP->LastEditedDocuments.Add(UCSGraph);
		UCSGraph->bAllowDeletion = false;
	}

	// Create default event graph(s)
	if (FBlueprintEditorUtils::DoesSupportEventGraphs(NewBP))
	{
		check(NewBP->UbergraphPages.Num() == 0);
		CreateDefaultEventGraphs(NewBP);
	}

	//@TODO: ANIMREFACTOR 1: This kind of code should be on a per-blueprint basis; not centralized here
	if (UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(NewBP))
	{
		UAnimBlueprint* RootAnimBP = UAnimBlueprint::FindRootAnimBlueprint(AnimBP);
		if (RootAnimBP == nullptr)
		{
			// Interfaces dont have default graphs, only 'function' anim graphs
			if(AnimBP->BlueprintType != BPTYPE_Interface)
			{
				// Only allow an anim graph if there isn't one in a parent blueprint
				UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(AnimBP, UEdGraphSchema_K2::GN_AnimGraph, UAnimationGraph::StaticClass(), UAnimationGraphSchema::StaticClass());
				FBlueprintEditorUtils::AddDomainSpecificGraph(NewBP, NewGraph);
				NewBP->LastEditedDocuments.Add(NewGraph);
				NewGraph->bAllowDeletion = false;
			}
		}
		else
		{
			// Make sure the anim blueprint targets the same skeleton as the parent
			AnimBP->TargetSkeleton = RootAnimBP->TargetSkeleton;
		}
	}

	// Create initial UClass
	IKismetCompilerInterface& Compiler = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>(KISMET_COMPILER_MODULENAME);

	// Skip validation of the class default object here, because (a) the new CDO may fail validation since this
	// is a new Blueprint that the user has not had a chance to modify any defaults for yet, and (b) in some cases,
	// default value propagation to the new Blueprint's CDO may be deferred until after compilation (e.g. reparenting).
	// Also skip the Blueprint search data update, as that will be handled by an OnAssetAdded() delegate in the FiB manager.
	const EBlueprintCompileOptions CompileOptions =
		EBlueprintCompileOptions::SkipGarbageCollection |
		EBlueprintCompileOptions::SkipDefaultObjectValidation |
		EBlueprintCompileOptions::SkipFiBSearchMetaUpdate;

	FBlueprintCompilationManager::CompileSynchronously(
		FBPCompileRequest(NewBP, CompileOptions, nullptr)
	);

	// Mark the BP as being regenerated, so it will not be confused as needing to be loaded and regenerated when a referenced BP loads.
	NewBP->bHasBeenRegenerated = true;

	UBlueprintEditorSettings* Settings = GetMutableDefault<UBlueprintEditorSettings>();
	if(Settings && Settings->bSpawnDefaultBlueprintNodes)
	{
		// Only add default events if there is an ubergraph and they are supported
		if(NewBP->UbergraphPages.Num() && FBlueprintEditorUtils::DoesSupportEventGraphs(NewBP))
		{
			// Based on the Blueprint type we are constructing, place some starting events.
			// Note, this cannot happen in the Factories for constructing these Blueprint types due to the fact that creating child BPs circumvent the factories
			UClass* WidgetClass = FindObject<UClass>(ANY_PACKAGE, TEXT("UserWidget"));
			UClass* GameplayAbilityClass = FindObject<UClass>(ANY_PACKAGE, TEXT("GameplayAbility"));

			TArray<FName> AutoSpawnedEventNames;
			int32 NodePositionY = 0;

			// Spawn any defined auto generated default events for the class.  Only do this for the most senior class specified, so
			// that subclasses may have an entirely different set of default nodes if they wish.
			UClass* DefaultNodesClass = NewBP->GeneratedClass;
			while ( DefaultNodesClass )
			{
				bool bFoundDefaultNodes = false;
				for ( TMultiMap<void*, FDefaultEventNodeData>::TIterator DataIt(AutoGeneratedDefaultEventsMap); DataIt; ++DataIt )
				{
					FDefaultEventNodeData Data = DataIt.Value();
					if ( DefaultNodesClass == Data.TargetClass )
					{
						bFoundDefaultNodes = true;
						FKismetEditorUtilities::AddDefaultEventNode(NewBP, NewBP->UbergraphPages[0], Data.EventName, Data.TargetClass, NodePositionY);
					}
				}

				if ( bFoundDefaultNodes )
				{
					break;
				}

				DefaultNodesClass = DefaultNodesClass->GetSuperClass();
			}
		}

		// Give anyone who wants to do more advanced BP modification post-creation a chance to do so.
		// Anim Blueprints, for example, adds a non-event node to the main ubergraph.
		for (TMultiMap<void*, FKismetEditorUtilities::FOnBlueprintCreatedData>::TIterator DataIt(OnBlueprintCreatedCallbacks); DataIt; ++DataIt)
		{
			FOnBlueprintCreatedData Data = DataIt.Value();
			if (NewBP->GeneratedClass->IsChildOf(Data.TargetClass))
			{
				FKismetEditorUtilities::FOnBlueprintCreated BlueprintCreatedDelegate = Data.OnBlueprintCreated;
				BlueprintCreatedDelegate.Execute(NewBP);
			}
		}
	}
	
	// Create the sparse class data and set the flag saying it is safe to serialize it
	UBlueprintGeneratedClass* BlueprintGeneratedClass = CastChecked<UBlueprintGeneratedClass>(NewBP->GeneratedClass);
	void* SparseDataPtr = BlueprintGeneratedClass->GetOrCreateSparseClassData();
	BlueprintGeneratedClass->bIsSparseClassDataSerializable = SparseDataPtr != nullptr;

	// Report blueprint creation to analytics
	if (FEngineAnalytics::IsAvailable())
	{
		TArray<FAnalyticsEventAttribute> Attribs;

		// translate the CallingContext into a string for analytics
		if (CallingContext != NAME_None)
		{
			Attribs.Add(FAnalyticsEventAttribute(FString("Context"), CallingContext.ToString()));
		}
		
		Attribs.Add(FAnalyticsEventAttribute(FString("ParentType"), ParentClass->ClassGeneratedBy == NULL ? FString("Native") : FString("Blueprint")));

		if(IsTrackedBlueprintParent(ParentClass))
		{
			Attribs.Add(FAnalyticsEventAttribute(FString("ParentClass"), ParentClass->GetName()));
		}

		const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>();
		Attribs.Add(FAnalyticsEventAttribute(FString("ProjectId"), ProjectSettings.ProjectID.ToString()));
		Attribs.Add(FAnalyticsEventAttribute(FString("BlueprintId"), NewBP->GetBlueprintGuid().ToString()));

		FEngineAnalytics::GetProvider().RecordEvent(FString("Editor.Usage.BlueprintCreated"), Attribs);
	}

	return NewBP;
}

创建Blueprint

UBlueprint* UAnimStrategy::CreateAnimBlueprint(UClass* ParentClass, const FString PackagePath,
                                                       const FName FileName, const FString SkeletonPath)
{
	const FString InnerPackagePath = PackagePath + FileName.ToString();
	UPackage* OuterForAsset = CreatePackage(nullptr, *InnerPackagePath);

	UClass* BlueprintClass = nullptr;
	UClass* BlueprintGeneratedClass = nullptr;

	//Get the blueprint class and generated blueprint class for a particular class type.
	KismetCompilerModule.GetBlueprintTypesForClass(AActor::StaticClass(), BlueprintClass,
	                                               BlueprintGeneratedClass);

	UAnimBlueprint* NewBlueprint = CastChecked<UAnimBlueprint>(FKismetEditorUtilities::CreateBlueprint(
		ParentClass, OuterForAsset, FileName, BPTYPE_Normal, UAnimBlueprint::StaticClass(),
		UBlueprintGeneratedClass::StaticClass(), FName("GeneratingAnimBlueprintTest")));

	return NewBlueprint;
}

创建AnimBlueprint

UAnimBlueprint* UAnimStrategy::CreateAnimBlueprint(UClass* ParentClass, const FString PackagePath,
                                                       const FName FileName, const FString SkeletonPath)
{
	const FString InnerPackagePath = PackagePath + FileName.ToString();
	UPackage* OuterForAsset = CreatePackage(nullptr, *InnerPackagePath);

	UObject* SkeletonObject = LoadObject<UObject>(nullptr, *SkeletonPath);

	//代码直接指定 UAnimBlueprint 和 UAnimBlueprintGeneratedClass
	UAnimBlueprint* NewBlueprint = CastChecked<UAnimBlueprint>(FKismetEditorUtilities::CreateBlueprint(
        ParentClass, OuterForAsset, FileName, BPTYPE_Normal, UAnimBlueprint::StaticClass(),
        UAnimBlueprintGeneratedClass::StaticClass(), FName("GeneratingAnimBlueprintTest")));
	NewBlueprint->TargetSkeleton = Cast<USkeleton>(SkeletonObject);

	return NewBlueprint;
}

将Blueprint保存到Content中

void UAnimStrategy::SaveAnimBlueprint(UAnimBlueprint* AnimBlueprint, UPackage* OuterForAsset)
{
	FAssetRegistryModule::AssetCreated(AnimBlueprint);
	// FAssetEditorManager::Get().OpenEditorForAsset(newBlueprint);
	OuterForAsset->SetDirtyFlag(true);
	TArray<UPackage*> PackagesToSave;
	PackagesToSave.Add(OuterForAsset);
	FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, false, false);
}

编译Blueprint

void UAnimStrategy::CompileBlueprint(UBlueprint* Blueprint)
{
	FCompilerResultsLog LogResults;
	LogResults.SetSourcePath(Blueprint->GetPathName());
	LogResults.BeginEvent(TEXT("Compile"));
	//指定编译选项
	const EBlueprintCompileOptions CompileOptions = EBlueprintCompileOptions::None;
	// if( bSaveIntermediateBuildProducts )
	// {
	// 	CompileOptions |= EBlueprintCompileOptions::SaveIntermediateProducts;
	// }
	FKismetEditorUtilities::CompileBlueprint(Blueprint, CompileOptions, &LogResults);
	LogResults.EndEvent();
}