UE4 Actor生命周期 SpawnActor DestroyActor剖析

2023-11-12

原创文章,转载请注明出处。

AActor很重要,只要是我们世界大纲列表内的物体,全部都是AActor。

目录

第一部分,从编辑器点击Play开始分析World里面全部的Actor的Spawn流程,分析到调用BeginPlay结束

1>下面从点击场景中的Play/PlayInEditor/Play In Standalone开始,代码执行的顺序,只是大致的。后续引擎版本变化可能会不同(猜测)。我从堆栈拷贝的代码,从下往上看。

2>上面生成了熟悉的GameInstance,GameMode怎么生成出来的?

3>上面生成了熟悉的GameInstance,那么GameMode怎么生成出来的?贴出堆栈的图

4>那么场景中的Actor列表怎么个调用BeginPlay的流程呢?

   1)会判断Actors列表是否已经被初始化过了。if( !AreActorsInitialized() ),看来这个方法会被调用多次

   2)没有被初始化的时候呢,有下面的代码,下面的则就是真正的初始化部分了。跟进去看一看

    3)对每个Actor上的全部组件进行初始化。上图,但是注意此时Actor的BeginPlay还没有执行。还没到Actor的BeginPlay,还在下头

     4)ActorBeginPlay调用的位置如下

第二部分,从代码层面调用SpawnActor,对其做分析

1>编码测试

2>源码分析

1)SpawnActor源码分析

2)Actor->PostSpawnInitialize源码分析

3)void AActor::PostActorConstruction()源码分析这个里面会初始化Actor下面的组件, 主要是下面的代码调用的BeginPlay

4)void AActor::DispatchBeginPlay(bool bFromLevelStreaming)源码分析

第三部分,从代码层面调用DestroyActor,对其做分析

1>编码测试

2>源码分析

1) void AActor::Destroyed()源码分析

2) RouteEndPlay(EEndPlayReason::Destroyed)源码分析



第一部分,从编辑器点击Play开始分析World里面全部的Actor的Spawn流程,分析到调用BeginPlay结束

在剖析Actor生命周期之前,我跟了一下Editor和Standlone的代码,找到了场景中actor列表的初始化的地方。

1>下面从点击场景中的Play/PlayInEditor/Play In Standalone开始,代码执行的顺序,只是大致的。后续引擎版本变化可能会不同(猜测)。我从堆栈拷贝的代码,从下往上看。

8 StartPlayInEditorGameInstance中调用了创建GameMode创建GameMode,这个是在GameInstance里面创建的
UGameInstance::CreateGameModeForURL(FURL InURL, UWorld * InWorld)
UWorld::SetGameMode(const FURL & InURL)

7 下面文章中还会接着介绍这个里面的代码
UGameInstance::StartPlayInEditorGameInstance(ULocalPlayer * LocalPlayer, const FGameInstancePIEParameters & Params)

6 这个方法就是在创建GameInstance,并且有趣的是上面几个方法也是在这里面调用的
//在这个里面创建了GameMode,就是上面的代码
UEditorEngine::CreateInnerProcessPIEGameInstance(FRequestPlaySessionParams & InParams, const FGameInstancePIEParameters & InPIEParameters, int InPIEInstanceIndex)

5>
UEditorEngine::OnLoginPIEComplete_Deferred(int LocalUserNum, bool bWasSuccessful, FString ErrorString, FPieLoginStruct DataStruct)

4>创建一个Editor新窗口的实例
UEditorEngine::CreateNewPlayInEditorInstance(FRequestPlaySessionParams & InRequestParams, const bool bInDedicatedInstance, const EPlayNetMode InNetMode)

3>拿PlayInEditor举例, 该方法中会判断一些条件,比如开着Matinee了,不让播放
UEditorEngine::StartPlayInEditorSession(FRequestPlaySessionParams & InRequestParams) 

2>从StartQueuedPlaySessionRequest过来的,并且从这个方法里面判断是哪一种, 是Standalone?还是Play/PlayInEditor?
UEditorEngine::StartQueuedPlaySessionRequestImpl()

1>点击编辑器中的Play按钮的时候调用的
UEditorEngine::StartQueuedPlaySessionRequest()

2>上面生成了熟悉的GameInstance,GameMode怎么生成出来的?

创建GameMode GetGameInstance()->CreateGameModeForURL
bool UWorld::SetGameMode(const FURL& InURL)
{
	if( IsServer() && !AuthorityGameMode )
	{
		AuthorityGameMode = GetGameInstance()->CreateGameModeForURL(InURL, this);
		if( AuthorityGameMode != NULL )
		{
			return true;
		}
		else
		{
			UE_LOG(LogWorld, Error, TEXT("Failed to spawn GameMode actor."));
			return false;
		}
	}

	return false;
}


创建GameMode的代码,可以发现其实这个平常我们在worldSetting里面对每一个umap设置的GameMode
就是在这被生成出来的。转定义就可以发现Settings->DefaultGameMode;是个TSubclassOf<>,
看到这是不是就知道咋回事了。
AGameModeBase* UGameInstance::CreateGameModeForURL(FURL InURL, UWorld* InWorld)
{
	省略了部分代码

	// Get the GameMode class. Start by using the default game type specified in the map's worldsettings.  It may be overridden by settings below.
	TSubclassOf<AGameModeBase> GameClass = Settings->DefaultGameMode;

	省略了部分代码

    最后其实看到这个GameMode呢也是被Spawn出来的一个Actor
	return World->SpawnActor<AGameModeBase>(GameClass, SpawnInfo);
}

3>上面生成了熟悉的GameInstance,那么GameMode怎么生成出来的?贴出堆栈的图

接着UGameInstance::StartPlayInEditorGameInstance讲

1>生成GameInstance之后,在该函数中生成了GameMode

2>接着生成完GameMode之后呢,调用了PlayWorld->CreateAISystem(); 创建AI系统

3>PlayWorld->InitializeActorsForPlay(URL); 主要讲一下这块,这个里头就是世界大纲里面所有的Actors的初始化

4>FNavigationSystem::AddNavigationSystemToWorld 添加AI导航网格

5>PlayWorld->BeginPlay();场景中初始化时候的Actor的BeginPlay在这

4>那么场景中的Actor列表怎么个调用BeginPlay的流程呢?

在场景开始时候,对所有的Actor进行初始化 UWorld::InitializeActorsForPlay

   1)会判断Actors列表是否已经被初始化过了。if( !AreActorsInitialized() ),看来这个方法会被调用多次

   2)没有被初始化的时候呢,有下面的代码,下面的则就是真正的初始化部分了。跟进去看一看

// Route various initialization functions and set volumes.
for( int32 LevelIndex=0; LevelIndex<Levels.Num(); LevelIndex++ )
{
	ULevel*	const Level = Levels[LevelIndex];
	Level->RouteActorInitialize();
}

    3)对每个Actor上的全部组件进行初始化。上图,但是注意此时Actor的BeginPlay还没有执行。还没到Actor的BeginPlay,还在下头

AActor::InitializeComponents()-> Actor中的初始化Actor上面的所有组件

GetComponents获取到所有的组件 初始化他们
void AActor::InitializeComponents()
{
	QUICK_SCOPE_CYCLE_COUNTER(STAT_Actor_InitializeComponents);

	TInlineComponentArray<UActorComponent*> Components;
	GetComponents(Components);

	for (UActorComponent* ActorComp : Components)
	{
		if (ActorComp->IsRegistered())
		{
			if (ActorComp->bAutoActivate && !ActorComp->IsActive())
			{
				ActorComp->Activate(true);
			}

			if (ActorComp->bWantsInitializeComponent && !ActorComp->HasBeenInitialized())
			{
				// Broadcast the activation event since Activate occurs too early to fire a callback in a game
				ActorComp->InitializeComponent();
			}
		}
	}
}

     4)ActorBeginPlay调用的位置如下

     真正场景中ActorsBeginPlay的流程如下面的堆栈图->Actor
     UWorld调用BeginPlay, 再到WorldSetting中,对所有的Actors进行BeginPlay的调用。有兴趣的BeginPlay里面的代码也建议看看。

第二部分,从代码层面调用SpawnActor,对其做分析

1>编码测试

我在代码里面写了一个SpawnActor,跟进去看看

我写的SpawnActor测试代码
void ABOCPLUSPLUSGameModeBase::BeginPlay()
{
	Super::BeginPlay();

	AActor* pActor = GWorld->SpawnActor(AActor::StaticClass());
	pActor->SetActorLocation(FVector::ZeroVector);
}

2>源码分析

下图是调用的堆栈

下面是SpawnActor里面的实现,贴出来吧,代码比较多。我写点注释记录一下

1)SpawnActor源码分析

AActor* UWorld::SpawnActor( UClass* Class, FTransform const* UserTransformPtr, const FActorSpawnParameters& SpawnParameters )
{
	SCOPE_CYCLE_COUNTER(STAT_SpawnActorTime);
	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(ActorSpawning);

#if WITH_EDITORONLY_DATA
    判断当前的Level是否有值
	check( CurrentLevel ); 	
    //判断是不是在编辑器内,如果不是check会直接导致崩溃
	check(GIsEditor || (CurrentLevel == PersistentLevel));
#else
	ULevel* CurrentLevel = PersistentLevel;
#endif

	判断描述Actor信息类的Class是否是空的
	if( !Class )
	{
		UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because no class was specified") );
		return NULL;
	}

	SCOPE_TIME_GUARD_NAMED_MS(TEXT("SpawnActor Of Type"), Class->GetFName(), 2);

#if ENABLE_SPAWNACTORTIMER
	FScopedSpawnActorTimer SpawnTimer(Class->GetFName(), SpawnParameters.bDeferConstruction ? ESpawnActorTimingType::SpawnActorDeferred : ESpawnActorTimingType::SpawnActorNonDeferred);
#endif
    判断Actor是不是被废弃的,废弃的不允许生成
	if( Class->HasAnyClassFlags(CLASS_Deprecated) )
	{
		UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because class %s is deprecated"), *Class->GetName() );
		return NULL;
	}
    带有抽象反射信息的不允许生生
	if( Class->HasAnyClassFlags(CLASS_Abstract) )
	{
		UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because class %s is abstract"), *Class->GetName() );
		return NULL;
	}
    必须为AActor类型
	else if( !Class->IsChildOf(AActor::StaticClass()) )
	{
		UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because %s is not an actor class"), *Class->GetName() );
		return NULL;
	}
    判断类的模板信息是否正确
	else if (SpawnParameters.Template != NULL && SpawnParameters.Template->GetClass() != Class)
	{
		UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because template class (%s) does not match spawn class (%s)"), *SpawnParameters.Template->GetClass()->GetName(), *Class->GetName());
		if (!SpawnParameters.bNoFail)
		{
			return NULL;
		}
	}
判断类是否允许在构造函数中spawn,默认为false应该是。可以改成true。spawnInfo里头
	else if (bIsRunningConstructionScript && !SpawnParameters.bAllowDuringConstructionScript)
	{
		UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because we are running a ConstructionScript (%s)"), *Class->GetName() );
		return NULL;
	}
这个UWorld在被销毁过程中,同样也是不允许被Spawn的
	else if (bIsTearingDown)
	{
		UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because we are in the process of tearing down the world"));
		return NULL;
	}
判断坐标信息是否是有效的,无效不能spawn
	else if (UserTransformPtr && UserTransformPtr->ContainsNaN())
	{
		UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because the given transform (%s) is invalid"), *(UserTransformPtr->ToString()));
		return NULL;
	}

	ULevel* LevelToSpawnIn = SpawnParameters.OverrideLevel;
	if (LevelToSpawnIn == NULL)
	{
		// Spawn in the same level as the owner if we have one. @warning: this relies on the outer of an actor being the level.
		LevelToSpawnIn = (SpawnParameters.Owner != NULL) ? CastChecked<ULevel>(SpawnParameters.Owner->GetOuter()) : CurrentLevel;
	}

    Actor的名称,就是在世界中叫啥名
	FName NewActorName = SpawnParameters.Name;
	AActor* Template = SpawnParameters.Template;

模板是否有效
	if( !Template )
	{
		// Use class's default actor as a template.
		Template = Class->GetDefaultObject<AActor>();
	}
	check(Template);

名字是否为空
	if (NewActorName.IsNone())
	{
		// If we are using a template object and haven't specified a name, create a name relative to the template, otherwise let the default object naming behavior in Stat
		if (!Template->HasAnyFlags(RF_ClassDefaultObject))
		{
			NewActorName = MakeUniqueObjectName(LevelToSpawnIn, Template->GetClass(), *Template->GetFName().GetPlainNameString());
		}
	}
	else if (StaticFindObjectFast(nullptr, LevelToSpawnIn, NewActorName))
	{
		// If the supplied name is already in use, then either fail in the requested manner or determine a new name to use if the caller indicates that's ok

		if (SpawnParameters.NameMode == FActorSpawnParameters::ESpawnActorNameMode::Requested)
		{
			NewActorName = MakeUniqueObjectName(LevelToSpawnIn, Template->GetClass(), *NewActorName.GetPlainNameString());
		}
		else
		{
			if (SpawnParameters.NameMode == FActorSpawnParameters::ESpawnActorNameMode::Required_Fatal)
			{
				UE_LOG(LogSpawn, Fatal, TEXT("An actor of name '%s' already exists in level '%s'."), *NewActorName.ToString(), *LevelToSpawnIn->GetFullName());
			}
			else if (SpawnParameters.NameMode == FActorSpawnParameters::ESpawnActorNameMode::Required_ErrorAndReturnNull)
			{
				UE_LOG(LogSpawn, Error, TEXT("An actor of name '%s' already exists in level '%s'."), *NewActorName.ToString(), *LevelToSpawnIn->GetFullName());
			}
			return nullptr;
		}
	}

	// See if we can spawn on ded.server/client only etc (check NeedsLoadForClient & NeedsLoadForServer)
	if(!CanCreateInCurrentContext(Template))
	{
		UE_LOG(LogSpawn, Warning, TEXT("Unable to spawn class '%s' due to client/server context."), *Class->GetName() );
		return NULL;
	}

根据传入的Spawn参数,比如检测周围如果有碰撞信息了,碰撞重叠之后是否继续spawn?
	FTransform const UserTransform = UserTransformPtr ? *UserTransformPtr : FTransform::Identity;

	ESpawnActorCollisionHandlingMethod CollisionHandlingOverride = SpawnParameters.SpawnCollisionHandlingOverride;

	// "no fail" take preedence over collision handling settings that include fails
	if (SpawnParameters.bNoFail)
	{
		// maybe upgrade to disallow fail
		if (CollisionHandlingOverride == ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding)
		{
			CollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
		}
		else if (CollisionHandlingOverride == ESpawnActorCollisionHandlingMethod::DontSpawnIfColliding)
		{
			CollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		}
	}

	// use override if set, else fall back to actor's preference
	ESpawnActorCollisionHandlingMethod const CollisionHandlingMethod = (CollisionHandlingOverride == ESpawnActorCollisionHandlingMethod::Undefined) ? Template->SpawnCollisionHandlingMethod : CollisionHandlingOverride;

	// see if we can avoid spawning altogether by checking native components
	// note: we can't handle all cases here, since we don't know the full component hierarchy until after the actor is spawned
	if (CollisionHandlingMethod == ESpawnActorCollisionHandlingMethod::DontSpawnIfColliding)
	{
		USceneComponent* const TemplateRootComponent = Template->GetRootComponent();

		// Note that we respect any initial transformation the root component may have from the CDO, so the final transform
		// might necessarily be exactly the passed-in UserTransform.
		FTransform const FinalRootComponentTransform =
			TemplateRootComponent
			? FTransform(TemplateRootComponent->GetRelativeRotation(), TemplateRootComponent->GetRelativeLocation(), TemplateRootComponent->GetRelativeScale3D()) * UserTransform
			: UserTransform;

		FVector const FinalRootLocation = FinalRootComponentTransform.GetLocation();
		FRotator const FinalRootRotation = FinalRootComponentTransform.Rotator();

		if (EncroachingBlockingGeometry(Template, FinalRootLocation, FinalRootRotation))
		{
			// a native component is colliding, that's enough to reject spawning
			UE_LOG(LogSpawn, Log, TEXT("SpawnActor failed because of collision at the spawn location [%s] for [%s]"), *FinalRootLocation.ToString(), *Class->GetName());
			return nullptr;
		}
	}

	真正的Spawn在这呢,其实发现很有趣,调用的就是NewObject。但是前期要进行上面那么多的检测
所以我们在对A类生成的时候,一定要调用SpawnActor。不能直接调用NewObject!
	AActor* const Actor = NewObject<AActor>(LevelToSpawnIn, Class, NewActorName, SpawnParameters.ObjectFlags, Template);

判断是否spawn成功了,内存是否申请成功
	check(Actor);

#if ENABLE_SPAWNACTORTIMER
	SpawnTimer.SetActorName(Actor->GetFName());
#endif

清除actor的名字如果编辑器模式下
#if WITH_EDITOR
	Actor->ClearActorLabel(); // Clear label on newly spawned actors
#endif // WITH_EDITOR

	if ( GUndo )
	{
		ModifyLevel( LevelToSpawnIn );
	}

在这将生成的actor就加到我们上面Level里面的Actors列表对象里面了。头文件里面是两个TArray
一个列表,一个GC用的
	LevelToSpawnIn->Actors.Add( Actor );
	LevelToSpawnIn->ActorsForGC.Add(Actor);

#if PERF_SHOW_MULTI_PAWN_SPAWN_FRAMES
	if( Cast<APawn>(Actor) )
	{
		FString PawnName = FString::Printf(TEXT("%d: %s"), ThisFramePawnSpawns.Num(), *Actor->GetPathName());
		ThisFramePawnSpawns.Add(PawnName);
	}
#endif

	// 赋值spawn信息
	Actor->SpawnCollisionHandlingMethod = CollisionHandlingMethod;

#if WITH_EDITOR
	if (SpawnParameters.bHideFromSceneOutliner)
	{
		FSetActorHiddenInSceneOutliner SetActorHidden(Actor);
	}
	Actor->bIsEditorPreviewActor = SpawnParameters.bTemporaryEditorActor;
#endif //WITH_EDITOR

主要在这里面进行的Actor里面的初始化
	Actor->PostSpawnInitialize(UserTransform, SpawnParameters.Owner, SpawnParameters.Instigator, SpawnParameters.IsRemoteOwned(), SpawnParameters.bNoFail, SpawnParameters.bDeferConstruction);

	if (Actor->IsPendingKill() && !SpawnParameters.bNoFail)
	{
		UE_LOG(LogSpawn, Log, TEXT("SpawnActor failed because the spawned actor %s IsPendingKill"), *Actor->GetPathName());
		return NULL;
	}

	Actor->CheckDefaultSubobjects();

	// Broadcast notification of spawn
	OnActorSpawned.Broadcast(Actor);

#if WITH_EDITOR
	if (GIsEditor)
	{
		GEngine->BroadcastLevelActorAdded(Actor);
	}
#endif

	// Add this newly spawned actor to the network actor list. Do this after PostSpawnInitialize so that actor has "finished" spawning.
	AddNetworkActor( Actor );

	return Actor;
}

2)Actor->PostSpawnInitialize源码分析

先贴出堆栈,比较明了

void AActor::PostSpawnInitialize(FTransform const& UserSpawnTransform, AActor* InOwner, APawn* InInstigator, bool bRemoteOwned, bool bNoFail, bool bDeferConstruction)
{
	
	UWorld* const World = GetWorld();
	bool const bActorsInitialized = World && World->AreActorsInitialized();

	CreationTime = (World ? World->GetTimeSeconds() : 0.f);

	此处判断必须是由服务器创建的
	check(GetLocalRole() == ROLE_Authority);
	ExchangeNetRoles(bRemoteOwned);

设置拥有者
	SetOwner(InOwner);

设置谁导致的这个Actor的生成。这块比较绕。这样想就明白了了
我觉得UE4设置InOwner,InInstigator就是在射击游戏的原型上衍生出来的。
比如人拿枪,人开枪,枪发射子弹。假如子弹就是这个被生成的断点Actor
那么InOwner是谁?是抢,子弹属于枪的
那么InInstigator是谁?是人,人开枪才有了子弹。
当然怎么设置取决于我们
	SetInstigator(InInstigator);

坐标的处理。。。。
	USceneComponent* const SceneRootComponent = FixupNativeActorComponents(this);
	if (SceneRootComponent != nullptr)
	{
		check(SceneRootComponent->GetOwner() == this);

		// Determine if the native root component's archetype originates from a converted (nativized) Blueprint class.
		UObject* RootComponentArchetype = SceneRootComponent->GetArchetype();
		UClass* ArchetypeOwnerClass = RootComponentArchetype->GetOuter()->GetClass();
		if (UBlueprintGeneratedClass* ArchetypeOwnerClassAsBPGC = Cast<UBlueprintGeneratedClass>(ArchetypeOwnerClass))
		{
			// In this case, the Actor CDO is a non-nativized Blueprint class (e.g. a child class) and the component's archetype
			// is an instanced default subobject within the non-nativized Blueprint's CDO. If the owner class also has a nativized
			// parent class somewhere in its inheritance hierarchy, we must redirect the query by walking up the archetype chain.
			if (ArchetypeOwnerClassAsBPGC->bHasNativizedParent)
			{
				do 
				{
					RootComponentArchetype = RootComponentArchetype->GetArchetype();
					ArchetypeOwnerClass = RootComponentArchetype->GetOuter()->GetClass();
				} while (Cast<UBlueprintGeneratedClass>(ArchetypeOwnerClass) != nullptr);
			}
		}

		if (Cast<UDynamicClass>(ArchetypeOwnerClass) != nullptr)
		{
			// For native root components either belonging to or inherited from a converted (nativized) Blueprint class, we currently do not use
			// the transformation that's set on the root component in the CDO. The reason is that in the non-nativized case, we ignore the default
			// transform when we instance a Blueprint-owned scene component that will also become the root (see USCS_Node::ExecuteNodeOnActor; in
			// the case of dynamically-spawned Blueprint instances, 'bIsDefaultTransform' will be false, and the scale from the SCS node's template
			// will not be applied in that code path in that case). Once a Blueprint class is nativized, we no longer run through that code path
			// when we spawn new instances of that class dynamically, but for consistency, we need to keep the same transform as in the non-
			// nativized case. We used to ignore any non-default transform value set on the root component at cook (nativization) time, but that 
			// doesn't work because existing placements of the Blueprint component in a scene may rely on the value that's stored in the CDO,
			// and as a result the instance-specific override value doesn't get serialized out to the instance as a result of delta serialization.
			SceneRootComponent->SetWorldTransform(UserSpawnTransform);
		}
		else
		{
			// In the "normal" case we do respect any non-default transform value that the root component may have received from the archetype
			// that's owned by the native CDO, so the final transform might not always necessarily equate to the passed-in UserSpawnTransform.
			const FTransform RootTransform(SceneRootComponent->GetRelativeRotation(), SceneRootComponent->GetRelativeLocation(), SceneRootComponent->GetRelativeScale3D());
			const FTransform FinalRootComponentTransform = RootTransform * UserSpawnTransform;
			SceneRootComponent->SetWorldTransform(FinalRootComponentTransform);
		}
	}

	// Call OnComponentCreated on all default (native) components
	DispatchOnComponentsCreated(this);

	// Register the actor's default (native) components, but only if we have a native scene root. If we don't, it implies that there could be only non-scene components
	// at the native class level. In that case, if this is a Blueprint instance, we need to defer native registration until after SCS execution can establish a scene root.
	// Note: This API will also call PostRegisterAllComponents() on the actor instance. If deferred, PostRegisterAllComponents() won't be called until the root is set by SCS.
	bHasDeferredComponentRegistration = (SceneRootComponent == nullptr && Cast<UBlueprintGeneratedClass>(GetClass()) != nullptr);

注册组件了。第一部分也出现过
	if (!bHasDeferredComponentRegistration)
	{
		RegisterAllComponents();
	}

#if WITH_EDITOR
	// When placing actors in the editor, init any random streams 
	if (!bActorsInitialized)
	{
		SeedAllRandomStreams();
	}
#endif

	看看有没有啥东西把我们给删了?
	if( IsPendingKill() && !bNoFail )
	{
		return;
	}

	通知一下,我这个Actor已经spawn出来了。
	PostActorCreated();

	执行本地和BP构造脚本。
	if (!bDeferConstruction)
	{
        在这呢在这呢
		FinishSpawning(UserSpawnTransform, true);
	}
	else if (SceneRootComponent != nullptr)
	{
		// we have a native root component and are deferring construction, store our original UserSpawnTransform
		// so we can do the proper thing if the user passes in a different transform during FinishSpawning
		GSpawnActorDeferredTransformCache.Emplace(this, UserSpawnTransform);
	}
}

3)void AActor::PostActorConstruction()源码分析
这个里面会初始化Actor下面的组件, 主要是下面的代码调用的BeginPlay

if (bRunBeginPlay)
{
	SCOPE_CYCLE_COUNTER(STAT_ActorBeginPlay);
	DispatchBeginPlay();
}
void AActor::PostActorConstruction()
{
	UWorld* const World = GetWorld();
	bool const bActorsInitialized = World && World->AreActorsInitialized();

	if (bActorsInitialized)
	{
		PreInitializeComponents();
	}

	// If this is dynamically spawned replicated actor, defer calls to BeginPlay and UpdateOverlaps until replicated properties are deserialized
	const bool bDeferBeginPlayAndUpdateOverlaps = (bExchangedRoles && RemoteRole == ROLE_Authority) && !GIsReinstancing;

	if (bActorsInitialized)
	{
		这个比较熟悉吧,初始化这个actor下的所有components
		InitializeComponents();

		// actor should have all of its components created and registered now, do any collision checking and handling that we need to do
		if (World)
		{
			switch (SpawnCollisionHandlingMethod)
			{
			case ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn:
			{
				// Try to find a spawn position
				FVector AdjustedLocation = GetActorLocation();
				FRotator AdjustedRotation = GetActorRotation();
				if (World->FindTeleportSpot(this, AdjustedLocation, AdjustedRotation))
				{
					SetActorLocationAndRotation(AdjustedLocation, AdjustedRotation, false, nullptr, ETeleportType::TeleportPhysics);
				}
			}
			break;
			case ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding:
			{
				// Try to find a spawn position			
				FVector AdjustedLocation = GetActorLocation();
				FRotator AdjustedRotation = GetActorRotation();
				if (World->FindTeleportSpot(this, AdjustedLocation, AdjustedRotation))
				{
					SetActorLocationAndRotation(AdjustedLocation, AdjustedRotation, false, nullptr, ETeleportType::TeleportPhysics);
				}
				else
				{
					UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because of collision at the spawn location [%s] for [%s]"), *AdjustedLocation.ToString(), *GetClass()->GetName());
					Destroy();
				}
			}
			break;
			case ESpawnActorCollisionHandlingMethod::DontSpawnIfColliding:
				if (World->EncroachingBlockingGeometry(this, GetActorLocation(), GetActorRotation()))
				{
					UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because of collision at the spawn location [%s] for [%s]"), *GetActorLocation().ToString(), *GetClass()->GetName());
					Destroy();
				}
				break;
			case ESpawnActorCollisionHandlingMethod::Undefined:
			case ESpawnActorCollisionHandlingMethod::AlwaysSpawn:
			default:
				// note we use "always spawn" as default, so treat undefined as that
				// nothing to do here, just proceed as normal
				break;
			}
		}

		if (!IsPendingKill())
		{
			PostInitializeComponents();
			if (!IsPendingKill())
			{
				if (!bActorInitialized)
				{
					UE_LOG(LogActor, Fatal, TEXT("%s failed to route PostInitializeComponents.  Please call Super::PostInitializeComponents() in your <className>::PostInitializeComponents() function. "), *GetFullName());
				}

				bool bRunBeginPlay = !bDeferBeginPlayAndUpdateOverlaps && (BeginPlayCallDepth > 0 || World->HasBegunPlay());
				if (bRunBeginPlay)
				{
					if (AActor* ParentActor = GetParentActor())
					{
						// Child Actors cannot run begin play until their parent has run
						bRunBeginPlay = (ParentActor->HasActorBegunPlay() || ParentActor->IsActorBeginningPlay());
					}
				}

#if WITH_EDITOR
				if (bRunBeginPlay && bIsEditorPreviewActor)
				{
					bRunBeginPlay = false;
				}
#endif

重头戏在这呢,通知调用beginplay
				if (bRunBeginPlay)
				{
					SCOPE_CYCLE_COUNTER(STAT_ActorBeginPlay);
					DispatchBeginPlay();
				}
			}
		}
	}
	else
	{
		// Set IsPendingKill() to true so that when the initial undo record is made,
		// the actor will be treated as destroyed, in that undo an add will
		// actually work
		MarkPendingKill();
		Modify(false);
		ClearPendingKill();
	}
}

4)void AActor::DispatchBeginPlay(bool bFromLevelStreaming)源码分析

到这一步其实就到了Beginplay真正的调用位置了

void AActor::DispatchBeginPlay(bool bFromLevelStreaming)
{
	UWorld* World = (!HasActorBegunPlay() && !IsPendingKill() ? GetWorld() : nullptr);

	if (World)
	{
		ensureMsgf(ActorHasBegunPlay == EActorBeginPlayState::HasNotBegunPlay, TEXT("BeginPlay was called on actor %s which was in state %d"), *GetPathName(), (int32)ActorHasBegunPlay);
		const uint32 CurrentCallDepth = BeginPlayCallDepth++;

		bActorBeginningPlayFromLevelStreaming = bFromLevelStreaming;
		ActorHasBegunPlay = EActorBeginPlayState::BeginningPlay;

||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
在这里调用的BeginPlay在这里调用的BeginPlay在这里调用的BeginPlay在这里调用的BeginPlay
		BeginPlay();
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

		ensure(BeginPlayCallDepth - 1 == CurrentCallDepth);
		BeginPlayCallDepth = CurrentCallDepth;

		if (bActorWantsDestroyDuringBeginPlay)
		{
			// Pass true for bNetForce as either it doesn't matter or it was true the first time to even 
			// get to the point we set bActorWantsDestroyDuringBeginPlay to true
			World->DestroyActor(this, true); 
		}
		
		if (!IsPendingKill())
		{
			// Initialize overlap state
			UpdateInitialOverlaps(bFromLevelStreaming);
		}

		bActorBeginningPlayFromLevelStreaming = false;
	}
}

第三部分,从代码层面调用DestroyActor,对其做分析

1>编码测试

AActor* pActor = GWorld->SpawnActor(AActor::StaticClass());
	if (pActor)
	{
		pActor->SetActorLocation(FVector::ZeroVector);

代码中删除Actor的方式
		//GWorld->DestroyActor(pActor);
		pActor->Destroy();
	}
}

2>源码分析

bool UWorld::DestroyActor( AActor* ThisActor, bool bNetForce, bool bShouldModifyLevel )
{
	SCOPE_CYCLE_COUNTER(STAT_DestroyActor);
	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(ActorDestroying);

判断Actor是否是有效的
	check(ThisActor);
	check(ThisActor->IsValidLowLevel());
	//UE_LOG(LogSpawn, Log,  "Destroy %s", *ThisActor->GetClass()->GetName() );

	SCOPE_CYCLE_UOBJECT(ThisActor, ThisActor);

	if (ThisActor->GetWorld() == NULL)
	{
		UE_LOG(LogSpawn, Warning, TEXT("Destroying %s, which doesn't have a valid world pointer"), *ThisActor->GetPathName());
	}

	如果已经在要删除的列表中,不往下处理
    if (ThisActor->IsPendingKillPending())
	{
		return true;
	}

	WorldSetting这个Actor是不允许被销毁的,请记住这一点
	// seamless travel and network games.
	if (GetWorldSettings() == ThisActor)
	{
		return false;
	}

	// In-game deletion rules.
	if( IsGameWorld() )
	{

		const bool bIsNetworkedActor = ThisActor->GetLocalRole() != ROLE_None;

		删除Actor是不允许从客户端进行删除的
		const bool bCanDestroyNetworkActor = ThisActor->GetLocalRole() == ROLE_Authority || bNetForce || ThisActor->bNetTemporary;
		if (bIsNetworkedActor && !bCanDestroyNetworkActor)
		{
			return false;
		}

		const bool bCanDestroyNonNetworkActor = !!CVarAllowDestroyNonNetworkActors.GetValueOnAnyThread();
		if (!bIsNetworkedActor && !bCanDestroyNonNetworkActor)
		{
			return false;
		}

		if (ThisActor->DestroyNetworkActorHandled())
		{
			// Network actor short circuited the destroy (network will cleanup properly)
			// Don't destroy PlayerControllers and BeaconClients
			return false;
		}

如果正在执行BeginPlay呢,那么在后续Begin完成之后再对其进行删除
		if (ThisActor->IsActorBeginningPlay())
		{
			FSetActorWantsDestroyDuringBeginPlay SetActorWantsDestroyDuringBeginPlay(ThisActor);
			return true;
		}
	}
	else
	{
		ThisActor->Modify();
	}

	// Prevent recursion
	FMarkActorIsBeingDestroyed MarkActorIsBeingDestroyed(ThisActor);

通知纹理流管理器这个Actor被删除了
	IStreamingManager::Get().NotifyActorDestroyed( ThisActor );

	这个里面会有很多的代码,在这里面进行删除
	ThisActor->Destroyed();

	将所有附加到这个Actor上面的Actor脱离
	TArray<AActor*> AttachedActors;
	ThisActor->GetAttachedActors(AttachedActors);

	if (AttachedActors.Num() > 0)
	{
		TInlineComponentArray<USceneComponent*> SceneComponents;
		ThisActor->GetComponents(SceneComponents);

		for (TArray< AActor* >::TConstIterator AttachedActorIt(AttachedActors); AttachedActorIt; ++AttachedActorIt)
		{
			AActor* ChildActor = *AttachedActorIt;
			if (ChildActor != NULL)
			{
				for (USceneComponent* SceneComponent : SceneComponents)
				{
					ChildActor->DetachAllSceneComponents(SceneComponent, FDetachmentTransformRules::KeepWorldTransform);
				}
#if WITH_EDITOR
				if( GIsEditor )
				{
					GEngine->BroadcastLevelActorDetached(ChildActor, ThisActor);
				}
#endif
			}
		}
	}

	判断我们这个actor是否是attach到其他的actor下面了
	USceneComponent* RootComp = ThisActor->GetRootComponent();
	if( RootComp != nullptr && RootComp->GetAttachParent() != nullptr)
	{
		AActor* OldParentActor = RootComp->GetAttachParent()->GetOwner();
		if (OldParentActor)
		{
			OldParentActor->Modify();
		}

从父节点上脱离
		ThisActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);

#if WITH_EDITOR
		if( GIsEditor )
		{
			GEngine->BroadcastLevelActorDetached(ThisActor, OldParentActor);
		}
#endif
	}

	ThisActor->ClearComponentOverlaps();

	通知设置owner为空
	if( ThisActor->GetOwner() )
	{
		ThisActor->SetOwner(NULL);
	}

	ULevel* const ActorLevel = ThisActor->GetLevel();
	if (ActorLevel)
	{
		ActorLevel->CreateReplicatedDestructionInfo(ThisActor);
	}

	// Notify net drivers that this guy has been destroyed.
	if (FWorldContext* Context = GEngine->GetWorldContextFromWorld(this))
	{
		for (FNamedNetDriver& Driver : Context->ActiveNetDrivers)
		{
			if (Driver.NetDriver != nullptr && Driver.NetDriver->ShouldReplicateActor(ThisActor))
			{
				Driver.NetDriver->NotifyActorDestroyed(ThisActor);
			}
		}
	}
	else if (WorldType != EWorldType::Inactive && !IsRunningCommandlet())
	{
		// Inactive worlds do not have a world context, otherwise only worlds in the middle of seamless travel should have no context,
		// and in that case, we shouldn't be destroying actors on them until they have become the current world (i.e. CopyWorldData has been called)
		UE_LOG(LogSpawn, Warning, TEXT("UWorld::DestroyActor: World has no context! World: %s, Actor: %s"), *GetName(), *ThisActor->GetPathName());
	}

	// Remove the actor from the actor list.
	RemoveActor( ThisActor, bShouldModifyLevel );

	// Invalidate the lighting cache in the Editor.  We need to check for GIsEditor as play has not begun in network game and objects get destroyed on switching levels
	if ( GIsEditor )
	{
		if (!IsGameWorld())
		{
			ThisActor->InvalidateLightingCache();
		}
		
#if WITH_EDITOR
		GEngine->BroadcastLevelActorDeleted(ThisActor);
#endif
	}
		
	清空这个actor上面的所有组件
	ThisActor->UnregisterAllComponents();

	将这个Actor和Actor上的所有组件标记为将要删除
	ThisActor->MarkPendingKill();
	ThisActor->MarkPackageDirty();
	ThisActor->MarkComponentsAsPendingKill();

	取消注册actor的tick函数
	const bool bRegisterTickFunctions = false;
	const bool bIncludeComponents = true;
	ThisActor->RegisterAllActorTickFunctions(bRegisterTickFunctions, bIncludeComponents);

	// Return success.
	return true;
}

1) void AActor::Destroyed()源码分析

void AActor::Destroyed()
{
	RouteEndPlay(EEndPlayReason::Destroyed);

告诉蓝图Destroyed了,这个就是蓝图中可覆盖的那个方法
	ReceiveDestroyed();
广播一下,其他地方可监听
	OnDestroyed.Broadcast(this);
}

2) RouteEndPlay(EEndPlayReason::Destroyed)源码分析

void AActor::RouteEndPlay(const EEndPlayReason::Type EndPlayReason)
{
	if (bActorInitialized)
	{
		if (ActorHasBegunPlay == EActorBeginPlayState::HasBegunPlay)
		{
调用EndPlay,并且也会通知Actor下面的所有组件也去EndPlay

			EndPlay(EndPlayReason);
		}

		// Behaviors specific to an actor being unloaded due to a streaming level removal
		if (EndPlayReason == EEndPlayReason::RemovedFromWorld)
		{
			ClearComponentOverlaps();

			bActorInitialized = false;
			if (UWorld* World = GetWorld())
			{
				World->RemoveNetworkActor(this);
			}
		}

		// Clear any ticking lifespan timers
		if (TimerHandle_LifeSpanExpired.IsValid())
		{
			SetLifeSpan(0.f);
		}
	}

	UninitializeComponents();
}

文章到此结束。谢谢,有问题请指正。

创作不易,大侠请留步… 动起可爱的双手,来个赞再走呗 <( ̄︶ ̄)>

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

UE4 Actor生命周期 SpawnActor DestroyActor剖析 的相关文章

  • 如何解决x盘莫名出现的msdia80.dll文件

    如何解决x盘莫名出现的msdia80 dll文件 参考文章 https zhuanlan zhihu com p 138954717
  • UE4学习日记——蓝图中的各种颜色代表什么

    节点类型颜色 白色 执行线 蓝色 调用的函数或事件 暗蓝色 结构体 草绿色 流程切换 绿色 纯函数 通常用于获取什么 青色 试着对象转换 紫色 函数定义 灰色 宏 红色 事件的定义 土黄色 时间轴 数据类型颜色 白色线条 执行线 程序的执行
  • UE4 C++ 类的4种引用类型,和异步加载资产

    UE4 C 类的4种引用类型 和异步加载资产 4种引用类型 对象引用 引用 World 的实例对象 就是直接定义 UPROPERTY EditAnywhere BlueprintReadOnly Category My 对象引用 UWorl
  • UE 材质学习

    值材质三原素 材质 材料 肌理 纹络 or 纹理 图案 Material Texture Pattern UE5中对应材质的 三原素 的内容 材质 Metallic 金属感 Roughness 粗糙度 Specular 高光 镜面 肌理 N
  • UE4 蓝图通信:接口调用

    UE4学习心得 蓝图间信息通信的几种方法 UE4的接口调用技术有点简单粗暴 而且主要体现在主蓝图对子蓝图的信息通信 在内容浏览器中添加一个蓝图接口 命名为TestInterface 双击打开接口 直接使用其创建时自带的一个接口函数 将其重命
  • 2022年11月7日--11月13日(ue4 tf1视频教程+cesium for ue源码CesiumUtility抄写,本周10小时,合计1737小时,剩余8263小时)

    目前 mysql 7 1 tf1 3 3 oss 12 1 蓝图反射 1 7 moba 1 5 webapp 2 4 mmoarpg 00A 04 socket 2 8 根据月计划 ue4 tf1视频教程 进度按照每天一小时时长视频 其余时
  • 【UE4】复杂背景人像抠图-飞浆AI-paddlepaddle深度训练模型

    前言 运用到Python3 7 UEC 蓝图 实现复杂背景人物使用PaddleHub深度训练模型进行抠像后在UE中使用 纯色背景人物仅材质就可实现 使用到的训练模型 deeplabv3p xception65 humanseg 1 准备工作
  • 11月8日 改良射线,蓝图 UE4斯坦福 学习笔记

    修改射线类型 更改了昨天的射线类型 void USInteractionComponent PrimaryInteract 射线 FHitResult FHit 碰撞体 FCollisionObjectQueryParams ObjectQ
  • UE4 实现用鼠标旋转场景中的物体

    本文实现的是用在UE4 中用鼠标旋转场景中的物体 点击到物体时开始物体旋转功能 移动鼠标物体跟着旋转相应的角度 松开鼠标左键物体停止旋转 下面介绍实现此功能的蓝图逻辑 由于比较简单就不一一介绍了 直接贴上蓝图代码 如有问题欢迎交流
  • UE_移动端测试使用

    教程流程 参照官方文档 android篇 https docs unrealengine com 5 1 zh CN android development requirements for unreal engine https docs
  • UE4_积分相同排名显示问题

    找了一下ue4 rank 函数相关 没找到合适的 自己简单写了个 解决积分相同时名次要一样 之后顺位排序 中国式排名 蓝图实现 c 原理一样 1 2 3 4 5
  • UE4/UE5 动画控制

    工程下载 https mbd pub o bread ZJ2cm5pu 蓝图控制sequence播放 倒播动画 设置开启鼠标指针 开启鼠标事件 在场景中进行过场动画制作 设置控制事件
  • UE4 UI界面

    在UE4中创建UI界面是创建一个widget 进去之后左上角是选择控件 找到直接拖上去 中间那个框代表的就是我们的屏幕 在button中打字也就是给button命名时需要在上面在拖一个text控件 更好的排版可以改变锚点 这四个就类似与边距
  • UE4的视频播放(Media Player)

    1 视频播放Begining 首先将需要播放的视频拖入 创建Media Player和Media Texture 创建Material 将材质改为User Interface 在UI界面 创建Image 将这个材质装入 在人物Pawn界面添
  • 4,引擎初始化--(4)加载地图--2,创建world(学习资料来源于UE4游戏框架)

    加载地图时 创建完默认GameMode 就要创建world了 首先读取到package 创建world 从这里可以看到 地图是可以在初始化建立的 GameInstance是在运行起来后建立 两者是独立的 设为当前World 并设定为全局GW
  • ue4_timeline时间轴

    1 给一个cube添加蓝图 需要修改的是z轴方向移动位置 将z轴传入时间轴 时间轴蓝图如下 z轴时间轴修改为 第一个节点 time 0 value 300 物体的z轴初始位置 第二个节点 time 1 value 600 z轴移动300个单
  • 4,引擎初始化--(5)初始化actor--6,生成PlayActor--(2)玩家登录--3,PostLogin()(学习资料来自于ue4游戏框架)

    一旦PlayerController生成出来 world关于玩家的网络的初始化工作全部完成 接下来 进行PostLogin 从而在玩家加入游戏时 能做一些设置工作 默认情况下 GameMode会为新生的PlayerController建立一
  • 蒙特卡洛积分、重要性采样、低差异序列

    渲染公式 渲染的目标在于计算周围环境的光线有多少从表面像素点反射到相机视口中 要计算总的反射光 每个入射方向的贡献 必须将他们在半球上相加 为入射光线 与法线 的夹角 为方便计算可以使用法线向量和入射向量 单位化 的乘积表示 对于基于图像的
  • 【UE5】监控摄像头效果(上)

    目录 效果 步骤 一 视角切换 二 摄像头画面后期处理 三 在场景中显示摄像头画面 效果 步骤 一 视角切换 1 新建一个Basic关卡 添加第三人称游戏资源到项目浏览器 2 新建一个Actor蓝图 这里命名为 BP SecurityCam
  • 【UE5】瞬移+马赛克过渡效果

    效果 步骤 1 新建一个工程 创建一个Basic关卡 2 添加第三人称游戏资源到内容浏览器 3 新建一个材质 这里命名为 M Pixel 打开 M Pixel 设置材质域为 后期处理 在材质图表中添加如下节点 此时效果如下 已经有马赛克的效

随机推荐

  • 浅析如何设计一个亿级网关

    1 背景 1 1 什么是API网关 API网关可以看做系统与外界联通的入口 我们可以在网关进行处理一些非业务逻辑的逻辑 比如权限验证 监控 缓存 请求路由等等 1 2 为什么需要API网关 RPC协议转成HTTP 由于在内部开发中我们都是以
  • letax报错“ I can't write on file `***.pdf”

    问题 运行时代码报错 原因 是我打开了生成的所生成的pdf文档了 关闭文档和其所在的文件夹 重新运行 一切正常
  • 【软考-中级】系统集成项目管理工程师【总】

    网站 https bm ruankao org cn sign welcome 持续更新中 学习目标 完成2023上半年 软件中考任务 目标23年5月 考试前 完成 相关知识点学习 和练习 核心 十五至尊图 上面图是考试的核心 需要背下来
  • 眼保健操(UPWND公益版)保护眼睛

    眼保健操 UPWND公益版 新版六节眼保健操 请跟随音乐口令节奏做操 每节均有具体图文示范 网络时代 专家建议每使用电脑1小时 请休息5分钟 共分六节 第一节 按揉攒竹穴 第二节 按压睛明穴 第三节 按揉四白穴 第四节 按揉太阳穴 刮上眼眶
  • 浅谈RAID写惩罚(Write Penalty)与解决方案闪存荷尔蒙(FlashHormone)

    浅谈RAID写惩罚 Write Penalty 与解决方案闪存荷尔蒙 FlashHormone 介绍 通常在讨论不同RAID保护类型的性能的时候 结论都会是RAID 1提供比较好的读写性能 RAID 5读性能不错 但是写入性能就不如RAID
  • 一文了解 Python 中的生成器

    前言 生成器很容易实现 但却不容易理解 生成器也可用于创建迭代器 但生成器可以用于一次返回一个可迭代的集合中一个元素 现在来看一个例子 def yrange n i 0 while i lt n yield i i 1 每次执行 yield
  • 力扣刷题笔记 数组能形成多少数对

    代码如下 from collections import defaultdict class Solution def numberOfPairs self nums List int gt List int a defaultdict i
  • caffe-include caffe/caffe/hpp 出错

    文章目录 include include
  • C# 操作JSON的几种方式

    关于Json数据在开发中的重要性 自然不言而喻 本篇通过两种在c 中常用的方式来实现对Json数据的序列化和反序列化 为了实现大多数的需求 我们采用稍微复杂一点的数据模型 首先我们有以下json数据 name 张三 age 20 idCar
  • Unity Shader入门精要第四章:学习Shader 所需的数学基础--坐标空间

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一 4 6 1 为什么要使用这么多不同的坐标空间 二 4 6 3 顶点的坐标空间变换过程 4 6 4 模型空间 4 6 6 观察空间 4 6 7 裁剪空间 总结 法线变换 参考
  • python中index方法_Python中的index()方法使用教程

    Python中的index 方法使用教程 index 方法确定字符串str 如果起始索引beg和结束索引end在末尾给出了找到字符串或字符串的一个子串 这个方法与find 方法一样 只是如果没有找到子符趾会抛出一个异常 语法 以下是inde
  • 阿里SR Gateway:IDLE_TIMEOUT:Websocket session is idle for too long time

    阿里SR在10秒内无数据流输入 SR会自动断开 sdk本身在请求建立链接后 长时间没有发送任何数据 超过10s后 服务端会返回40000004错误信息 Gateway IDLE TIMEOUT Websocket session is id
  • C++ list 类学习笔记

    双向循环链表list list是双向循环链表 每一个元素都知道前面一个元素和后面一个元素 在STL中 list和vector一样 是两个常被使用的容器 和vector不一样的是 list不支持对元素的任意存取 list中提供的成员函数与ve
  • windows cmd命令整理

    文章目录 windows 路由表 命令行设置环境变量 windows cmd 查看文件内容 类似linux cat type file name txt cmd 切换用户 类似 linux su user name runas user u
  • vue 微信分享

    场景 一个h5界面要求点击右上角三个点后点击微信好友分享带标题和图片给好友 vue项目中 分享携带头部标题 简介和缩略图等信息 1 下载微信的 weixin js sdk npm install weixin js sdk 2 在需要分享的
  • 顺序表的逆置算法

    顺序表的逆置算法 1 逆置原理 2 算法实现 3 经典例题1 4 经典例题2 1 逆置原理 顺序表的逆置即将线性表 a1 a2 a3 an 转化为 an an 1 an 2 a2 a1 此操作在程序设计中经常使用 2 算法实现 includ
  • 第十四届蓝桥杯(第三期)模拟赛试题与题解 C++

    第十四届蓝桥杯 第三期 模拟赛试题与题解 C 试题 A 问题描述 请找到一个大于 2022 的最小数 这个数转换成十六进制之后 所有的数位 不含前导 0 都为字母 A 到 F 请将这个数的十进制形式作为答案提交 答案提交 这是一道结果填空的
  • 使用腾讯云clb实现https转发

    腾讯云clb实现https进行转发 七层反代 四层负载均衡和七层负载均衡有什么区别 四层均衡能力 是基于 IP 端口的负载均衡 七层是基于应用层信息 如 HTTP 头部 URL 等 的负载均衡 四到七层负载均衡 就是在对后台的服务器进行负载
  • SpringBoot读取配置文件的两种方式以及自定义配置文件的读取

    1 读取默认配置文件中的数据 application properties 直接使用 Value注解获取数据 2 使用Environment获取数据 防止乱码统一编码格式 注入Environment 使用getProperty获取数据 3
  • UE4 Actor生命周期 SpawnActor DestroyActor剖析

    原创文章 转载请注明出处 AActor很重要 只要是我们世界大纲列表内的物体 全部都是AActor 目录 第一部分 从编辑器点击Play开始分析World里面全部的Actor的Spawn流程 分析到调用BeginPlay结束 1 gt 下面