VitalOverdose/DestructibleMover
Part of Vital'sPMT
Overview
A mover that can simulate being destroyed.
I say SIMULATE because to tell you the truth its just gets hidden as creating and destroying actors is not the most efficient way of making something vanish. Most seemingly destructible actors in unreal just simply get hidden , their collision turned off and an emitterfx spawned to simulate any debris. With this actor from being removed completely the actor then times itself so it knows when to switch back on again (simulating a respawn).
This is a very efficient system but its not without its problems, namely the lighting. As most shading on the bsp and static meshes in the game is calculated before the game is run and sort of 'baked on' the the textures used in level if a static/semi static object is removed then the shadows that were made by the object stay where they are. It is for this reason the movers in UT don't cast shadows.
The only way around it is to use dynamic lighting which sucks up a lot of the games available CPU power. Its not impossible to use dynamic lighting fx in your level otherwise they wouldn't be there ....use sparingly.
User Properties
(these properties are available to the mapper in unrealed)
DestroyAtKey - The key number at which the mover will vanish. Note: Remember the mover has to come back past the number again so for each complete move opening,opened,closing,closed the mover will end up vanishing twice if RebirthTime < ((movetime*Numkeys+stayopentime) - (DestroyAtKey x movetime)) RebirthTime - The length of time the mover stays hidden for. Note this is an INT/Integer variable (whole number) so you cant have any half seconds.
Internal Variables
(Properties not settable by the mapper in unrealed)
bnetgame - To help sort out what code to run in the timer; BaseFourCounter - in the original game code there are two timer() speeds the mover gets updated with, one of them is every second and the other is every 4 seconds. Basefourcounter get subtracted every timer call. When it reaches 0 It is reset to 4 and the original code form the timer function is allowed to do its thing. Timercounter - hold the amount of seconds until the mover reapears after vanishing
The Functions
Function BeginPlay()
(generic:called by the system just asthe actor enters gameplay)
Orig Script
In this function Ive had to include all of the code from the parent function as the bit i want to get to is buried right in the middle of the class. So what Ive done here is just stripped out the bit Ive changed so i can explain a bit about how Ive done it. This is not the full function, if you look at the completed script at the bottom of the page you will see the full code for this function.;-
// timer updates real position every second in network play if ( Level.NetMode != NM_Standalone ) { if ( Level.NetMode == NM_Client && bClientAuthoritative ) settimer(4.0, true); else settimer(1.0, true); if ( Role < ROLE_Authority ) return; } code continues...
-As you can see the timer can either be set to tick away at once a second or every 4 seconds. But we need the timer to tick once every second always, this is one way to do it;-
Function BeginPlay()
(generic:called by system as the actor enters gameplay).
Modified Script
simulated function BeginPlay() { // <-<-there's more code in the full function that should go in here. if ( Level.NetMode != NM_Standalone ) { bNetGame = true; // using a Boolean as a flag as it quick to check if ( Level.NetMode == NM_Client && bClientAuthoritative ) { BaseFourCounter == 4; // Base4counter is set to 4.This property acts as a timer and a flag 2 let timer() } // know to include the original code from the timer() function every 4 cycles. } settimer(1, true); // Timer is set to the standard timing rate of once per second. if ( (bNetGame ) Role < ROLE_Authority )) { Return; } // <-<-<- in the full function displayed in the completed script at the bottom of the page code continues...
Event KeyFrameReached()
(generic: called by this actor when it has reached a ketfame position)
This is another function from the parent class that contains some important code. So the new code has to contain no returns or things will start to malfunction. The code from the parent class is simply added with the .super function.
event KeyFrameREached() { if (( Keynum == DestroyAtKey ) && (bhidden==false)) { bHidden = True; Setcollision( false , false , false ); TimerCounter=RebirthTime; if (Base4counter = -1) // this will only be true once. After that Base4counter = 4; } super.keyframereached(); }
Function Timer()
(generic: Initialised by the programmer but called by the system)
This function is split up into 4 parts;-
- process timercounter
- check Bnetgame + if not exit at this point
- Process Bas4Counter
- code from parent class is executed here
function timer() { if ( TimerCounter > 0 ) { TimerCounter -= 1; if (TimerCounter == 0) { bHidden = False; SetCollision( true , true , true ); } } if ( !bNetGame ) Return; if ( BaseFourCounter > 0) { BaseFourCounter -= 1 if ( basefourcounter != 0 ) return; Basefourcounter=4; } super.Timer() }
DefaultProperties
I very rarley use default properties in uscript but this time i decided it would be better as leaving the DestroyAtKey at the default of 0 could cause problems if people dont realise that this dosent mean the hide/unde is disabled.
defaultproperties { DestroyAtKey=-1 RebirthTime=30 }
The Completed Script;
Here is the completed script for now;-
Note:I haven't finished fully testing this script yet..so there may be a few changes to it.
If anyone has any problems with it let me know.
//----------------------------------------------------------- // DestructibleMover by VitalOverdose // jan 2006, updated Feb 2006 //----------------------------------------------------------- class Destructible Extends Mover; Var() int DestroyAtKey; Var() float RebirthTime; var bool bBasefour; var int BaseFourCounter; var bool bnetgame; var int Timercounter; // When mover enters gameplay. simulated function BeginPlay() { local AntiPortalActor AntiPortalA; if (AntiPortalTag != '') { foreach AllActors( class'AntiPortalActor',AntiPortalA,AntiPortalTag ) { AntiPortals.Length = AntiPortals.Length + 1; AntiPortals[AntiPortals.Length - 1] = AntiPortalA; } } if ( Level.NetMode != NM_Standalone ) { bNetGame = true; // using a boolean as a flag as it quick to check if ( Level.NetMode == NM_Client && bClientAuthoritative ) { BaseFourCounter = 4; // Base4counter is set to 4.This property acts as a timer and a flag 2 let timer() } // know to include the original code from the timer() function every 4 cycles. } settimer(1, true); // Timer is set to the standard timing rate of once persecond. if ( (bNetGame ) &&( Role < ROLE_Authority )) Return; RealPosition = Location; RealRotation = Rotation; Super.BeginPlay(); // Init key info. KeyNum = Clamp( KeyNum, 0, ArrayCount(KeyPos)-1 ); PhysAlpha = 0.0; StartKeyNum = KeyNum; Move( BasePos + KeyPos[KeyNum] - Location ); // Set initial location. SetRotation( BaseRot + KeyRot[KeyNum] ); // Initial rotation. if ( ReturnGroup == '' ) ReturnGroup = tag; Leader = None; Follower = None; } event KeyFrameREached() { if ( Keynum == DestroyAtKey ) { bHidden = True; Setcollision( false , false , false ); TimerCounter=RebirthTime; } super.keyframereached(); } function timer() { if ( TimerCounter > 0 ) { TimerCounter -= 1; if (TimerCounter == 0) { bHidden = False; SetCollision( true , true , true ); } } if ( !bNetGame ) Return; if ( BaseFourCounter > 0) { BaseFourCounter -= 1; if ( basefourcounter != 0 ) return; } super.Timer(); } defaultproperties { DestroyAtKey=-1 RebirthTime=30 }