GithubHelp home page GithubHelp logo

ue4-tinyclient's Introduction

UE4-微端实现基础(一)

场景内的资源

​ 实现微端其中的一个环节,就是需要把场景内的资源进行下载,加载,由于和直接生成的资源相比,场景内的资源可能是被编辑过的,它不同于直接通过CDO创建出来的对象,对象内部的PropertyDependence往往各不相同,所以第一步,要先把场景内的Actor信息,保存下来.

​ 在看UE内关于ULevel创建的那部分代码的时候,发现ULeve内部保存了所有的这个Level上的Actor的信息.通过Serialize逐一的创建出来,所以,可以仿照ULevel的做法,把所有要参与微端的Actor的信息保存到一个Asset内部,等游戏运行的时候,根据这个Asset内部存储的Actor信息,把他们创建出来,创建之前,先查询每个Actor所需的Dependence,把这些依赖下载完后以后并且Mount之后,我们就可以把这个Actor序列化出来,目前能想到的就是这么个流程

可以加群453886054交流

序列化所有需要参与微端的Actor

​ 这里的思路就是,创建一个UPackage,这样可以保存为了一个Asset,这个Asset内部保存了所有Actor的信息,这样在保存的时候,我们可以通过Serialize函数,将Actor的信息保存到uasset内,然后运行游戏的时候,就可以直接加载这个uasset对应的Pak文件,进而得到场景内所有Actor的信息.

  • 将场景内所有需要导出的Actor添加TagExport

    P1

  • ​在编辑器的Modes工具栏下选择ExportActoesMode,接着填写导出场景信息Asset所要保存的位置.点击导出button

    P2

开始保存导出Actors的信息

​ 直接来到DoExportActors函数,首先,获取到当前编辑器正在操作的Level,这里有个判断,是否存在导出的Asset,如果存在,就删除掉,这样是为了避免在后面复制对象的时候,名字出现重复

TArray<AActor*> CollertActors = (CurrentLevel->Actors);
FText ExportPackagePath = ExportPath->GetText();
//if Package exist , delete
UObject* p = (UObject*)FindPackage(nullptr, *ExportPackagePath.ToString());
if (p)
{
	TArray<UObject*> DeletTempActor;
	DeletTempActor.Add(p);
	FAssetDeleteModel deleTemp(DeletTempActor);
	deleTemp.DoDelete();
}

​ 然后,获取当上一步所填写的AssetPath,这里创建了一个UPackage,在UPackage下创建了一个UExportActors,这个UExportActors是用来系列化所有导出Actor的信息.这样在保存UPackage的时候,可以把所有的序列化信息存储到uasset中.

FString AssetName = FPaths::GetBaseFilename(ExportPackagePath.ToString());
UPackage *Package = CreatePackage(nullptr, *ExportPackagePath.ToString());
if (Package)
{
	UExportActors* ExportAsset = NewObject<UExportActors>(Package, 	UExportActors::StaticClass(), *AssetName, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
	GetAllExportActor(CurrentLevel, ExportAsset, Package);
	Package->FullyLoad();
	//uasset path
    FString AssetDiskPath = FPackageName::LongPackageNameToFilename(ExportPackagePath.ToString(), *FPackageName::GetAssetPackageExtension());
	bool bSuccess = UPackage::SavePackage(
					Package,
					ExportAsset,
					EObjectFlags::RF_Public | EObjectFlags::RF_Standalone,
					*AssetDiskPath
				);

	UE_LOG(LogTemp, Log, TEXT("Saved Package: %s"), bSuccess ? TEXT("True") : TEXT("False"));
	AllExportActors.Empty();
	ExportActorsInfo.Empty();
	return;
}

对导出的Actor进行处理

​ 在上一步运行到GetAllExportActor的时候,开始对Actor进行一些处理,由于UE在保存资源的时候,会进行一些检测,所以下面进行了三步处理

  • 把所有的ActorOuter设值为UExportActors的对象

    //collert all export actors
    for (int i = 0; i < CurrentLevel->Actors.Num(); ++i)
    {
    	AActor* a = (CurrentLevel->Actors[i]);
    	if (a)
    	{
    		if (a->ActorHasTag(TEXT("Export")))
    		{
    			AActor* ExActor = (AActor*)(StaticDuplicateObject(a, ExportActor));
    			AllExportActors.Add(ExActor->GetName(), ExActor);
    			ExportActor->CollertActors.Add(ExActor);
    		}
    	}
    }
    

    然后ExportActor->CollertActors.Add(ExActor);会将所有Actor保存起来,等到后续UPackage保存的时候序列化使用.

  • 如果Actor内有引用到场景其他的Actor,这一步开始处理,将引用替换到上一步复制出来的Actor上,这里有点稍微有点复杂

    //if export actor has other ref(actor), replace the ref
    for (auto acotr : AllExportActors)
    {
    	FArchiveExport FAS;
    	FAS.SetIsSaving(true);
    	FAS.SetIsPersistent(true);
    	FAS.ArIsObjectReferenceCollector = true;
    	FAS.ArShouldSkipBulkData = true;
    	FAS.CurrentSerializeUObject = acotr.Value;
    	FAS.CurrentLevel = CurrentLevel;
    	FAS.AllExportActors = &AllExportActors;
    	acotr.Value->Serialize(FAS);
    }
    • FArchiveExport是一个仿照UE保存UPackage处理的类,IsSaving,IsPersistent,ISObjectReferenceCollector,ShouldSkipBulkData,这几个值,是参照UE处理保存过程的代码部分去设置的,目的是为了得到当前Actor所引用其他的Actor,在Serialize函数中,最重要的是:

      if (Obj->GetOuter() == (UObject*)CurrentLevel)
      {
      	UObjectProperty* Property = (UObjectProperty*)this->GetSerializedProperty();
      	uint8* DataPtr = Property->ContainerPtrToValuePtr<uint8>(CurrentSerializeUObject, 0);
      	FString Name = Obj->GetName();
      	UObject** DupValue = (UObject**)(AllExportActors->Find(Name));
      	if (DupValue)
      	{
      		UObjectProperty* l = (UObjectProperty*)((uint8*)CurrentSerializeUObject + Property->GetOffset_ForInternal());
      		UObjectProperty::SetPropertyValue(l, *DupValue);
      	}
      	else
      		//ref no tag(export)
      		check(false);
      }
      

      Obj是当前Actor所引用的Actor的对象,简单的说就是,A引用了B,那么此时在分析A的时候,Obj就是B,此处的判断Obj->GetOuter() == (UObject*)CurrentLevel,是为了过滤,只处理引用为场景中的Actor,接下来,把A从原来引用编辑器场景中的B替换为引用ExportActor收集起来的B,到此,完成的工作为,把所有导出的Actor放到了我们自己的资源内,并且资源内所有的Actor的引用也变成了资源内的Actor,和场景中的Actor没有了任何关系.

  • 上一步,我们把引用都替换了,但是针对单个Actor的资源依赖,还没有分析出来,因为场景中的ActorDependence不像BluePrint,Mesh,Material,等等,我们直接对这些资源的UPackage分析,可以得到所有的Dependence.场景中的Actor往往会改变模型,材质,音效等资源.造成和他们本身的CDO不同.所以,要想得到场景中ActorDependence,就会麻烦点,我也是在这里尝试改了好多代码,也没有成功,最后的解决办法,还是借助UEUPackage,和导出所有Actor一样,把单个Actor保存为一个资源,再让UE去分析这个UpackaheDependence,然后,把资源删除,这样针对导出的所有Actor都走遍这个流程.我们可以得到Actor所具体依赖了什么资源,这样,在我们进行微端下载的时候,就可以去下载对应的Pak

    //create temp asset for each
    for (auto acotr : AllExportActors)
    {		
    	FString TempPath("/Game/TempAsset");
    	FString TempAssetName = FPaths::GetBaseFilename(TempPath);
    	UPackage *TempPackage = CreatePackage(nullptr, *TempPath);
    	if (TempPackage)
    	{
    		UTempActor* TempExportAsset = NewObject<UTempActor>(TempPackage, UTempActor::StaticClass(), *TempAssetName, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
    		AActor* TempActor = (AActor*)(StaticDuplicateObject(acotr.Value, TempExportAsset));
    
    		FArchiveTempCopy FAS;
    		FAS.SetIsSaving(true);
    		FAS.SetIsPersistent(true);
    		FAS.ArIsObjectReferenceCollector = true;
    		FAS.ArShouldSkipBulkData = true;
    		FAS.CurrentSerializeUObject.Push(TempActor);
    		FAS.TempOuter = TempExportAsset;
    		FAS.ExportOuter = ExportActor;
    		TempActor->Serialize(FAS);
    
    		TempExportAsset->TempPtr = TempActor;
    		TempPackage->FullyLoad();
    		//uasset path
    		FString AssetDiskPath = FPackageName::LongPackageNameToFilename(TempPath, *FPackageName::GetAssetPackageExtension());
    		bool bSuccess = UPackage::SavePackage(
    					TempPackage,
    					TempExportAsset,
    					EObjectFlags::RF_Public | EObjectFlags::RF_Standalone,
    					*AssetDiskPath
    				);
    
    		//create temp asset
    		TArray<FString> UAssetFile;
    		UAssetFile.Add(AssetDiskPath);
    		static FName DirectoryWatcherName("AssetRegistry");
    		FAssetRegistryModule& DirectoryWatcherModule = FModuleManager::Get().LoadModuleChecked<FAssetRegistryModule>(DirectoryWatcherName);
    		DirectoryWatcherModule.Get().ScanModifiedAssetFiles(UAssetFile);
    		FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked<FAssetRegistryModule>("AssetRegistry");
    
    		//get all dependencies
    		TArray<FName> dependencies;
    		AssetRegistryModule.Get().GetDependencies(*TempPath, dependencies);
    		ExportActorsInfo.Add(TempActor->GetName(), dependencies);
    
    		//deltet temp asset
    		TArray<UObject*> DeletTempActor;
    		DeletTempActor.Add(TempExportAsset);
    		FAssetDeleteModel deleTemp(DeletTempActor);
    		deleTemp.DoDelete();
    	}
    }
    
    • 开始的前半部分,还是老样子,创建一个UPackage,在此下面实例化一个辅助对象TempExportAsset

    • 之前已经把所有的Actor复制了一份,并且放在了ExportAsset下边,此时我们再把这些Actor每个都单独的拿出来放到TempExportAsset下用于分析Dependence:

      FArchiveTempCopy FAS;
      FAS.SetIsSaving(true);
      FAS.SetIsPersistent(true);
      FAS.ArIsObjectReferenceCollector = true;
      FAS.ArShouldSkipBulkData = true;
      FAS.CurrentSerializeUObject.Push(TempActor);
      FAS.TempOuter = TempExportAsset;
      FAS.ExportOuter = ExportActor;
      TempActor->Serialize(FAS);
      

      CurrentSerializeUObject这里,我把它当成一个栈使用,因为在解析一个对象的引用的时候,需要把这个以用的Outer再修正为当前ActorOuter,简单说就是A引用了B,如果BOuterAOutre不一致,在保存的时候就会Crash

      TempOuter是当前AOuter.

      ExportActor是当前序列化的PropertyB是否是外部ExportActor下的,也是用来终结递归的,因为所有的引用都从ExportActor替换为TempOuter,就会跳出循环.

      具体分析函数:

      if (Obj->GetOuter() == ExportOuter)
      {
      	UObjectProperty* Property = (UObjectProperty*)this->GetSerializedProperty();
      	uint8* DataPtr = Property->ContainerPtrToValuePtr<uint8>(CurrentSerializeUObject[0], 0);
      	UObject* IsExist = FindObject<UObject>(TempOuter, *Obj->GetName());
      	if(IsExist)
      	{
      		UObjectProperty* l = (UObjectProperty*)((uint8*)CurrentSerializeUObject[0] + Property->GetOffset_ForInternal());
      		UObjectProperty::SetPropertyValue(l, IsExist);
      		return *this;
      	}
      	else
      	{
      		UObject* TempObject = (UObject*)(StaticDuplicateObject(Obj, TempOuter));
      		UObjectProperty* l = (UObjectProperty*)((uint8*)CurrentSerializeUObject[0] + Property->GetOffset_ForInternal());
      		UObjectProperty::SetPropertyValue(l, TempObject);
      
      		CurrentSerializeUObject.Insert(TempObject, 0);
      		TempObject->Serialize(*this);
      		CurrentSerializeUObject.Pop();						
      	}				
      }
      

      大致流程就是序列化当前AB,发现B是外部Outer下的,接着从TempOuter下拿到同样名字的对象,首先判断时候是已经替换过,如果有那么直接将当前A下的B替换,如果是还没有替换过的,那么就复制出一个放在TempOuter下,接着把当前A的引用替换为新的B,然后把新的B当成A,入栈,接着重复操作.

      这里把B当成A的理解是,在场景中我们可能会有A引用了B,而B又引用了C,所以这里,使用了这样的一个递归查询的操作,这样可以把A直接或间接的所用引用都替换掉,在保存的时候就不会出错了.

    • 接着后面的操作,就是保存这个临时的资源,然后调用GetDependencies来获取所有的资源依赖,存到了ExportActorsInfo下面,这个目前还没又导出的相关操作.这个可以根据自己的需求来具体的操作.

      接着就是删除这个资源,然后重复操作,对每个Actor解析一遍

保存导出Actor

经过上面的所有流程.现在已经完成的工作有:

  1. 所有带ExportActor已经保存.可以在编辑器中看到对应的资源

  2. 每个Actor对象具体的Dependence,都保存在ExportActorsInfo中,如果对微端功能有需求的,可以自己再添加一些功能,后续的一些功能,我也会继续添加.

    如果要测试的话,这里可以先把场景中的Actor删除,然后打包.因为目前,还没有针对每个资源文件单独打包,所以打开场景的时候,所有Actor是直接创建的.

Spawn导出的Actor

测试项目中创建的工作写在了MyProjectCharacter.cpp中的BeginPlay中,Spawn的流程为:

UWorld* world = GetWorld();
UPackage* ExportPackage = LoadPackage(NULL, TEXT("/Game/Dev/TestExprot"), LOAD_Async);
if (ExportPackage)
{
	UExportActors* ExportActor = NewObject<UExportActors>();
	ExportActor->AddToRoot();
	TArray<AActor*> AllActors = UExportActors::GlobalCollertActors;
	CurrentLevel = world->GetCurrentLevel();
	TMap<FString, AActor*> SpawnActors;
	for (int i = 0; i < AllActors.Num(); ++i)
	{
		AActor* a = (AActor*)(StaticDuplicateObject(AllActors[i], world->GetCurrentLevel()));
		SpawnActors.Add(a->GetName(), a);
	}
	TArray<UObject>  l;
	for (auto actor : SpawnActors)
	{
		FArchiveSpawn FAS;
		FAS.SetIsSaving(true);
		FAS.SetIsPersistent(true);
		FAS.ArIsObjectReferenceCollector = true;
		FAS.ArShouldSkipBulkData = true;
		FAS.CurrentSerializeUObject.Add(actor.Value);
		FAS.CurrentLevel = CurrentLevel;
		FAS.InLevelExportActors = &SpawnActors;
		actor.Value->Serialize(FAS);
		CurrentActor = actor.Value;
		world->SpawnActor<HookSpawnActor>(nullptr);
	}
}
  • 首先就是加载,我们用于保存ActorPackage,接着实例化UExportActors对象,去拿到里面所有的Actor

  • 此时,这些Actor还不是在当前的Level下,所以这里复制,当当前的Level

  • 光复制到Level下还不行,因为此时A放到了Level下,但是里面引用还是UExportActors对象下的B,所以此时还要去替换引用,使用FArchiveSpawn来完成此工作,将A的引用B,替换到Level下的B

  • 后面使用了一个修改的SpawnActor,内部做了修改,用于ActorSpawn.

  • 可以看到,场景中的Actor在场景中创建了出来

    3

​ 目前,只是把流程大致理了一下,资源下载的部分和其他一些细节,待后续补充,不足之处,望指正.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.