I'm a doctor, not a mechanic
What happens when an Actor is spawned
The information in this article are based on a snipped of native UT2004 code originally posted on Epic Game's ut2003mods mailing list. The code can be found as UnLevAct.cpp in the Engine\Src directory of the UT2004 UnrealScript source code download.
Contents
The actual UnLevAct.cpp snippet
The following is a copy of the code snippet this article is based on. This particular piece of code is called called from within the UnrealScript Spawn() function, to create the GameInfo and any ServerActors at map startup and also for any received replicated Actors. Even UnrealEd uses it to place new Actors in the map. Note that this code calls various UnrealScript functions, so for a complete picture you should have the UT2004 sources ready and browse to the relevant functions.
Hints for reading the code
A few things that will help understanding the code even if you're not into C++:
- The
->
operator does the same as the dot does in UnrealScript - it accesses variables or functions of an object. GetLevelInfo()
returns the current LevelInfo object, similar to the UnrealScript variableLevel
.- Variables in UnrealScript objects have the same name in C++. For example
Actor->PhysicsVolume
refers to UnrealScript variable of the same name. - The LevelInfo's
bBegunPlay
acts as general gate to UnrealScript execution. During map load and in UnrealEd it is 0 (False), so no UnrealScript code will be executed. At some point in map startup it is set to 1 (True), and from that point on until the map is unloaded UnrealScript functions can run. Any actors spawned before that (e.g. the GameInfo or any ServerActors) do not get any of the UnrealScript calls mentioned here and are instead initialized as part of map startup. - UnrealScript functions declared with the event keyword are called from C++ via
eventNameOfUScriptFunction(parameters)
, for exampleeventPreBeginPlay
calls the PreBeginPlay() function. - The native class name of Actor subclasses is prefixed with an A, the native name of non-Actor classes with a U and the native name of structs with an F, for example APhysicsVolume, UKarmaParams or FVector.
- Name literals do not exist. Instead, the values of all natively-used names are hard-coded as
NAME_name
, for example NAME_None for 'None'. - Class literals do exist, but look very different from UnrealScript. They are expressed as
nativeClassName::StaticClass()
. - Values for bool variables are 0 and 1, not False and True.
Class->GetDefaultActor()
corresponds to UnrealScript's.default.
syntax for accessing the default variable values of a class.DestroyActor()
is the function that is called by the UnrealScript Destroy() function. (see What happens when an Actor is destroyed)
General notes about this code snippet
- Only the parameters Class, Location, Rotation and Owner can be specified via the UnrealScript Spawn() function.
- The InName and Template parameters are always None for normal spawns. They are probably only used in special cases by UnrealEd.
- The
bRemoteOwned
parameter is only True when the Actor was received clientsidely via replication. - The Instigator parameter is set to the Instigator of the Actor the UnrealScript Spawn() function is called on.
- The UnrealScript Spawn() function parameter SpawnTag is applied by the native Spawn() implementation after ULevel::SpawnActor() returns. That means the value of the newly spawned Actor's Tag property does not have any meaningful value yet. (It is temporarily set to the class name.) Any changes to the Tag during the initialization of the spawned Actor are overridden with the SpawnTag value.
- The call to SetOwner() triggers the Owner's GainedChild() UnrealScript event before the new Actor is initialized. Among other things, this means you cannot use GotoState() on that Actor yet.
- Similarly, SetZone(), CheckEncroachment() and FindBase() may cause UnrealScript events to be called.
- The call to PostNetBeginPlay() only happens if the Actor wasn't received through replication. For replicated actors PostNetBeginPlay() will be called a bit later after the initial bunch of replicated variables have been received. If the Actor's bNetNotify property is True, PostNetReceive() might be called right before the PostNetBeginPlay() call.
Notes for Unreal Engine 1
- Unreal Engine 1 does not have volumes or Karma physics, so these parts of the code here do not apply.
- Before calling PreBeginPlay(), the UnrealScript event Spawned() is called.
- UT also calls the SpawnNotification() event of all SpawnNotify actors that want notification for this type of actor, somewhere between PostNetBeginPlay() and setting the Tag property. Unlike any other function called during execution of the Spawn() function, a SpawnNotify can actually change the actor eventually returned by the Spawn() call.
Notes for Unreal Engine 3
- The Actor's Level variable and the LevelInfo class are both called WorldInfo in Unreal Engine 3.
- The BeginPlay() and PostNetBeginPlay() functions have been removed, with PostBeginPlay() now taking PostNetBeginPlay()'s role. PostNetReceive() has been replaced by the more specific ReplicatedEvent() function that will be called for every received repnotify variable.
- Unreal Engine 3 does not use zones, so the related parts of the code here do not apply.
- The
Template
andbNoCollisionFail
parameters can be passed via the Spawn() function, but for replicated actors the template must be an actor the client knows about, ideally an Actor subobject, an Actor archetype or a bStatic or bNoDelete Actor in the current level.
The code
/*============================================================================= UnLevAct.cpp: Level actor functions Copyright 1997-2001 Epic Games, Inc. All Rights Reserved. =============================================================================*/ // // Create a new actor. Returns the new actor, or NULL if failure. // AActor* ULevel::SpawnActor ( UClass* Class, FName InName, FVector Location, FRotator Rotation, AActor* Template, UBOOL bNoCollisionFail, UBOOL bRemoteOwned, AActor* Owner, APawn* Instigator, UBOOL bNoFail ) { guard(ULevel::SpawnActor); if( GetFlags() & RF_Unreachable ) return NULL; // Make sure this class is spawnable. if( !Class ) { debugf( NAME_Warning, TEXT("SpawnActor failed because no class was specified") ); return NULL; } if( Class->ClassFlags & CLASS_Abstract ) { debugf( NAME_Warning, TEXT("SpawnActor failed because class %s is abstract"), Class->GetName() ); return NULL; } else if( !Class->IsChildOf(AActor::StaticClass()) ) { debugf( NAME_Warning, TEXT("SpawnActor failed because %s is not an actor class"), Class->GetName() ); return NULL; } else if( !GIsEditor && (Class->GetDefaultActor()->bStatic || Class->GetDefaultActor()->bNoDelete) ) { debugf( NAME_Warning, TEXT("SpawnActor failed because class %s has bStatic or bNoDelete"), Class->GetName() ); if ( !bNoFail ) return NULL; } // don't spawn bHighDetail actors if not wanted if( !GIsEditor && Class->GetDefaultActor()->bHighDetail && !bNoFail ) { if( GetLevelInfo()->DetailMode == DM_Low || GetLevelInfo()->bDropDetail || (GetLevelInfo()->NetMode == NM_DedicatedServer) ) { //debugf(TEXT("%s not spawned"),Class->GetName()); return NULL; } } #if 1 // sjs - level's outer is not transient so we must do this // doing this is a huge benefit for long running names, as the name table grows > 40 megs after long multiplayer games. if( !GTransientNaming && InName==NAME_None) InName = NAME_Transient; #endif // Use class's default actor as a template. if( !Template ) Template = Class->GetDefaultActor(); check(Template!=NULL); // Make sure actor will fit at desired location, and adjust location if necessary. if( (Template->bCollideWorld || (Template->bCollideWhenPlacing && (GetLevelInfo()->NetMode != NM_Client))) && !bNoCollisionFail ) if( !FindSpot( Template->GetCylinderExtent(), Location ) ) return NULL; // Add at end of list. INT iActor = Actors.Add(); AActor* Actor = Actors(iActor) = (AActor*)StaticConstructObject( Class, GetOuter(), InName, 0, Template ); Actor->SetFlags( RF_Transactional ); // Set base actor properties. Actor->Tag = Class->GetFName(); Actor->Region = FPointRegion( GetLevelInfo() ); Actor->Level = GetLevelInfo(); Actor->bTicked = !Ticked; Actor->XLevel = this; // Set network role. check(Actor->Role==ROLE_Authority); if( bRemoteOwned ) Exchange( Actor->Role, Actor->RemoteRole ); // Remove the actor's brush, if it has one, because moving brushes are not duplicatable. if( Actor->Brush ) Actor->Brush = NULL; // Set the actor's location and rotation. Actor->Location = Location; Actor->Rotation = Rotation; if( Actor->bCollideActors && Hash ) Hash->AddActor( Actor ); // init actor's physics volume Actor->PhysicsVolume = GetLevelInfo()->PhysicsVolume; // Set owner. Actor->SetOwner( Owner ); // Set instigator Actor->Instigator = Instigator; #ifdef WITH_KARMA // Initilise Karma physics for this actor (if there are any) KInitActorKarma(Actor); #endif // Send messages. Actor->InitExecution(); Actor->Spawned(); Actor->eventPreBeginPlay(); if( Actor->bDeleteMe && !bNoFail ) return NULL; Actor->eventBeginPlay(); if( Actor->bDeleteMe && !bNoFail ) return NULL; // Set the actor's zone. Actor->SetZone( iActor==0, 1 ); // Update the list of leaves this actor is in. Actor->ClearRenderData(); // Check for encroachment. if( !bNoCollisionFail ) { if( Actor->bCollideActors && Hash ) Hash->RemoveActor( Actor ); if( CheckEncroachment( Actor, Actor->Location, Actor->Rotation, 1 ) ) { DestroyActor( Actor ); return NULL; } if( Actor->bCollideActors && Hash ) Hash->AddActor( Actor ); } //if ( Actor->bCollideActors && !Actor->bBlockActors && !Actor->bUseCylinderCollision && (Actor->DrawType == DT_StaticMesh) ) // debugf(TEXT("%s shouldn't be using static mesh collision"),Actor->GetName()); // Send PostBeginPlay. Actor->eventPostBeginPlay(); if( Actor->bDeleteMe && !bNoFail ) return NULL; Actor->PostBeginPlay(); // Init scripting. Actor->eventSetInitialState(); // Find Base if( !GIsEditor && !Actor->Base && Actor->bCollideWorld && Actor->bShouldBaseAtStartup && ((Actor->Physics == PHYS_None) || (Actor->Physics == PHYS_Rotating)) ) Actor->FindBase(); // Success: Return the actor. if( InTick ) NewlySpawned = new(GEngineMem)FActorLink(Actor,NewlySpawned); // replicated actors will have postnetbeginplay() called in net code, after initial properties are received if ( !bRemoteOwned ) Actor->eventPostNetBeginPlay(); return Actor; unguardf(( TEXT("(%s)"), Class->GetName() )); }