Gah - a solution with more questions. – EntropicLqd
What happens when an Actor is destroyed
While they released a snippet of native code in UnLevAct.cpp that explains what happens when an Actor is spawned, Epic Games never released the corresponding code for destroying actors. However, though experiments and some experience with random bugs in one's own code, certain assumptions can be made about what exactly happens when an Actor is destroyed with the Destroy() function.
The events described here also happen clientsidely when a replicated actor that isn't bNetTemporary and wasn't "torn off" is no longer relevant to that client. If the actor becomes relevant again at some later point, it will be spawned again as if it is a new actor to that client.
Contents
Summary of events
Everything described in this section happens between the call to Destroy() and its return.
Be careful about calling Destroy() on an actor currently being destroyed. This subsequent Destroy() call will processed as usual, so you will have to manually implement something to break out of this recursion before it crashes the engine with an infinite script recursion error. Once an "inner" Destroy() call succeeds, any "outer" Destroy() will short-circuit and return True
without calling any additional events.
Validity checks
Before doing anything, the engine checks whether the actor can be destroyed at all. Actors with bStatic or bNoDelete set to True
cannot be destroyed. Neither can replicated actors with the bNetTemporary flag set to False
on a network client, unless they are no longer network relevant to that client. In that case destruction does not originate from the Destroy() function, but from replication code. The Destroy() function will return False
if it detects any of these conditions.
Also it seems PlayerControllers (or PlayerPawns in UE1) of clients cannot be destroyed directly on a network server, but the call to Destroy() causes them to be disconnected. That disconnect may happen a bit later, but after the connection is closed, the PlayerController (or PlayerPawn) is destroyed with the usual events getting called as described below. Destroy() returns False
here as well, even though the player actor starts disconnecting, which will eventually result in its destruction.
bPendingDelete
While the Destroy() function is executed, the Actor's bPendingDelete property is set to True
. Any event called directly or indirectly during destruction should check this property before doing anything that won't make sense on an Actor that is about to be deleted from the level.
EndState()
If the actor is in a state, the EndState() event is called. The actor doesn't actually leave the state, though. You could probably still switch states via GotoState(), but don't complain if you or your code gets confused.
Destroyed()
Next, the Destroyed() event is called. As mentioned above, the actor does not leave its current state, so you could probably override this event in states.
Detach(), BaseChanged()
If the actor is attached to another actor, that actor's Detach() event and the destroyed actor's BaseChange() event are called. Similarly, for any actor attached to the destroyed actor, the destroyed actor's Detach() event and the detached actor's BaseChange() event are called.
UnTouch()
If the actor is touching any other actors, those actors' UnTouch() event is called. It is not called on the destroyed actor itself, though.
Owner.LostChild()
Lastly, if the actor has an Owner, that owner actor's LostChild() event is called.
After Destroy() returned
When the Destroy() function (or the replication code that cleans up obsolete replicated actors) returns, the actor object is not actually gone, it just becomes unaccessible by other objects in UnrealScript.
If the Destroy() function was called from within a function of the destroyed actor, then that function continues to execute even after Destroy() returned successfully. If you execute code after a successful Destroy() call, things start getting awkward. You can still access the destroyed actor's variables and call its functions, but only directly, not through variables or function return values. You can even still assign the destroyed actor to variables, but accessing variable or calling functions through them fails with an Accessed None warning:
function DestructionTest() { local Actor Before, After; Before = Self; // logs "False True True" log(bDeleteMe @ Destroy() @ bDeleteMe); After = Self; // logs 3 Accessed None warnings, then logs "True False False False", // because accessing a variable through None returns its null value, // which is False for type bool log(bDeleteMe @ Self.bDeleteMe @ Before.bDeleteMe @ After.bDeleteMe); // logs 3x this actor's name, separated by single spaces // (even though the engine claimed it was accessing None in the previous log() call) log(Self @ Before @ After); }
Note that the Self keyword and variable assignments still work, just accessing class members through them no longer does. That bDeleteMe variable is set to True
by the Destroy() function after all the events described in the Summary of events section.