Mover (UT)
(see Mover for the UT2003 version of this class)
In UT Movers are brushes that move. They are not part of the BSP, but look as if they are to the player. Use them to create doors, elevators and other parts of dynamic level geometry. This page describes the class in detail.
Terminology: A mover is said to "open" when it moves from key 0 to the last defined key (NumKeys-1 for the technically-minded). Movement in the opposite direction is called "closing". Some subclasses of mover have the ability to open partially.
Properties
Mover Group
- bool bDamageTriggered
- The mover is triggered by taking damage. Use for doors that open when you shoot then, for example.
- bool bDynamicLightMover
- Setting this to True will cause the mover to change the way it is lit as it moves. This is very useful when a mover is going between areas with different brightnesses or colors of light. Note that BrushRayTraceKey is unused if the mover is dynamically lit.
The process isn't perfect by any means, and often has dirty shadows and flickering between different lightings on the brush. However, it does eliminate the annoying black patches on the undersides of lifts and the sides of doors. In addition this takes quite a bit more CPU power than normal. Use this only in areas where it is needed to make the mover look decent and processing power can be spared. - byte BrushRaytraceKey (const)
- This should be set to the number of a keyframe. When the map's lighting is rebuilt, the polys of the mover will always be lit as if it is in this position. (Naturally, this property is irrelevant if you have bDynamicLightMover = True.)
- bool bSlave
- This mover is a slave. It will follow another mover. See Compound Movers.
- bool bTriggerOnceOnly
- Go dormant after first trigger. Note that this doesn't work for all states (see Mover States below).
- name BumpEvent
- The name of an event to fire when any something bumps this mover. This allows you to have movers that can be either triggered or bumped: you have the BumpEvent trigger the mover's own Tag.
- EBumpType BumpType
- Determines what sort of things are counted as bumping this mover. Other things that bump it will be ignored.
- bool bUseTriggered
- This mover will be triggered by player grab.
- float DamageThreshold
- Minimum damage to trigger. Works with bDamageTriggered.
- float DelayTime
- Delay before starting to open.
- int EncroachDamage
- How much to damage encroached actors.
- byte KeyNum
- The number of the key that the mover is curently set to. Using the Brush Context Menu → Mover → Key command is the same as changing this value. See Keyframe for more on this.
- EMoverEncroachType MoverEncroachType
- Tells the mover what to do when it hits an actor while trying to move. For example, when a lift returns to the low position and hits a player's head. (see EMoverEncroachType enum below)
- EMoverGlideType MoverGlideType
- How the mover moves from one position to another. (see EMoverGlideType enum below)
- float MoveTime
- Time to spend moving between keyframes.
- byte NumKeys (const)
- This is the number of different positions (known as keys) that the mover has. The maximum possible value seems to be 64.
- float OtherTime
- TriggerPound stay-open time.
- name PlayerBumpEvent
- Optional event to cause when the player bumps the mover.
- name ReturnGroup
- if none, same as tag
- float StayOpenTime
- How long to remain open before closing.
- byte WorldRaytraceKey (const)
- Set this to the number of the key you want the mover to affect the world at. Basically, whatever you set this to is where the mover will block light. A neat trick you can do with this property is to set it to an unused key which you have positioned somewhere outside your map. That way the mover will not cast a shadow at all. This can alleviate the annoying problem of black patches underneath movers.
MoverSounds Group
The properties in this section set the sounds played by the mover as it travels.
- Sound ClosedSound
- When finish closing.
- Sound ClosingSound
- When start closing.
- Sound MoveAmbientSound
- Optional ambient sound when moving.
- Sound OpenedSound
- When finished opening.
- Sound OpeningSound
- When start opening.
The sounds in UT packages like DoorsMod are designed with this system in mind, and are usually found in sets of 3, eg "md2start", "md2loop", "md2end". Use start sounds with Opening, end sounds with Opened and loop with ambient.
Only MoveAmbientSound is affected by the value of Sounds → SoundVolume. The others play at full volume. (Some scripting would probably fix this for a custom class, since the Mover calls PlaySound, and this has an optional volume parameter.)
UnrealScript-Only Properties
- byte PrevKeyNum
- Previous keyframe.
- Actor (UT) SavedTrigger
- Who we were triggered by.
- int numTriggerEvents
- Number of times triggered (count down to untrigger)
- Mover Leader
- Mover Follower
- For having multiple movers return together. (see Compound Movers)
- vector KeyPos[8]
- rotator KeyRot[8]
- vector BasePos, OldPos, OldPrePivot, SavedPos
- rotator BaseRot, OldRot, SavedRot
- NavigationPoint (UT) myMarker
- Actor (UT) TriggerActor
- Actor (UT) TriggerActor2
- Pawn (UT) WaitingPawn
- bool bOpening, bDelaying, bClientPause
- bool bPlayerOnly
- Trigger RecommendedTrigger
- vector SimOldPos
- int SimOldRotPitch, SimOldRotYaw, SimOldRotRoll
- vector SimInterpolate
- vector RealPosition
- rotator RealRotation
- int ClientUpdate
Enums
EMoverEncroachType
- ME_StopWhenEncroach
- Stop when we hit an actor.
- ME_ReturnWhenEncroach
- Return to previous position when we hit an actor.
- ME_CrushWhenEncroach
- Crush the poor helpless actor.
- ME_IgnoreWhenEncroach
- Ignore encroached actors.
EMoverGlideType
- MV_MoveByTime
- Move linearly.
- MV_GlideByTime
- Move with smooth acceleration.
EBumpType
- BT_PlayerBump
- Can only be bumped by player.
- BT_PawnBump
- Can be bumped by any pawn.
- BT_AnyBump
- Cany be bumped by any solid actor.
States
The mover states, set in the InitialState property determine:
- how the mover is activated
- how it behaves once it has been activated
Note that other settings affect activation behavior too:
- the BumpType property affects who can activate it
- bDamageTriggered .....
- bUseTriggered ....
The "OpenTimed" states open the mover (from key 0 to the last key), wait then close and sleep again. Only the states with "Trigger" in the name respond to triggering.
- None
- The mover will not move through its keys, unless it is a slave.
- BumpButton
This is very similar to BumpOpenTimed, but designed to be used for a button which controls another mover which is set to TriggerOpenTimed (a door, for example).
Use the button mover's BumpEvent property to tie it to the door mover's Events → Tag. When the button is bumped, it moves and so does the door. The button is then frozen, and it will only return when the door has successfully closed. This is pretty much just a nice cosmetic touch: the button won't return until it can be used again.
In brief:
- The button mover's StayOpenTime is ignored.
- If the door is prevented from closing (by a player, say), the button will not return
- Once the door mover has finished closing, the button mover is closed.
- BumpOpenTimed
- The mover reacts to being touched. Set BumpType to determine what counts as touching it. Used for buttons (though see BumpButton for special cases). Can be used for doors, although TriggerControl is better.
- StandOpenTimed
- The mover reacts when the engine detects a player has stood on it. Used for lifts.
- TriggerControl
The mover opens while the trigger is active, and closes when unTriggered – ie if set up with a simple Trigger actor, as soon as the player steps out from the Trigger's radius the mover will start closing again.
Example: the door to the redeemer area in UT's DM-StalwartXL and CTF-Gauntlet. (note to anyone reading this: the Event page needs to explain that events can last for a duration, as in this case – there's an UnTrigger function too when a player steps out of a Trigger actor's radius.)
- TriggerOpenTimed
- When triggered, the mover opens fully, waits and closes again. For related topics on triggering see the links section below.
- TriggerPound
Similar to TriggerControl, but used for movers that open and close continuously. As soon as the corresponding trigger is activated, a mover using this state will begin the following cycle:
- Opens as usual, with its speed determined by the MoveTime property.
- Pauses for a time equal to the OtherTime property.
- Closes as usual, with its speed determined by the MoveTime property.
- Pauses for a time equal to the StayOpenTime property.
The mover loops through these actions until whatever activated the trigger leaves the trigger's collision area. When this happens, the mover immediately closes and remains at rest until triggered again.
- TriggerToggle
- The mover will open when triggered and stop at the open position (the last keyframe). The next time it is triggered, it will close, and so on. Note that bTriggerOnceOnly has no effect in this state.
Discussion
Xian: while trying to see how I can misuse brushes in a better and more efficient way since I don't like the way Epic coded UT as a whole, I found this code in Engine.Mover.FindTriggerActor():
ForEach AllActors(class 'Actor', A) if ( (A.Event == Tag) && (A.IsA('Trigger') || A.IsA('Mover')) ) { if ( A.IsA('Counter') || A.IsA('Pawn') ) { bPlayerOnly = true; return; //FIXME - handle counters } [...] }
How can a Trigger/Mover be a Pawn ? This seems like a nice logical error. Although the consequences may not be huge (since altogether the main triggering mechanism is done by movers/trigger classes), I just don't see how they could miss this... although it is trivial, check the next part:
bPlayerOnly = ( TriggerActor.IsA('Trigger') && (Trigger(TriggerActor).TriggerType == TT_PlayerProximity) ); if ( bPlayerOnly && ( TriggerActor2 != None) ) { bPlayerOnly = ( TriggerActor2.IsA('Trigger') && (Trigger(TriggerActor).TriggerType == TT_PlayerProximity) ); if ( !bPlayerOnly ) { A = TriggerActor; TriggerActor = TriggerActor2; TriggerActor2 = A; } }
My guess is the correct version is:
bPlayerOnly = ( TriggerActor.IsA('Trigger') && (Trigger(TriggerActor).TriggerType == TT_PlayerProximity) ); if ( bPlayerOnly && ( TriggerActor2 != None) ) { // Xian correction: TriggerActor to TriggerActor2 :) bPlayerOnly = ( TriggerActor2.IsA('Trigger') && (Trigger(TriggerActor2).TriggerType == TT_PlayerProximity) ); if ( !bPlayerOnly ) { A = TriggerActor; TriggerActor = TriggerActor2; TriggerActor2 = A; } }
Again the code as a whole works, but let's face it, the rule of ntars should NOT apply here and this SHOULD be fixed imo. Perhaps people who wish to take advantage of custom Mover classes which are dependant on Triggers and TriggerActor(2) pointers should rewrite this function to fix it accordingly. Any thoughts ?
Graphik: I understand the trivial bit, but unfortunately not the useful part. I mean, I'm assuming there is one.
Xian: In my opinon, the code needs a lot of optimization and checks (for example in most parts it does iterations to all Actors with their Event matching the current Tag, while proper way of doing it, is to check if the Actor has an Event set, but most importantly to not iterate at all if there is no Tag set; true it does it only in PBP, but still). This was just a code-related discussion, and I assumed coders that would create custom Movers for UT would find this interesting. If you want a useful side of it, let me put it this way: assuming I am right (and to me it looks like I am), I have yet to see a correction of logical errors which is not useful
Graphik: OK cool.
Xian: ok so an optimized version imo:
function FindTriggerActor() { local Actor A; TriggerActor = None; TriggerActor2 = None; // Xian: if there is no tag at all, why bother ? if (Tag == '') return; foreach AllActors(class 'Actor', A) if ((A.Event != '') && (A.Event == Tag) && (A.IsA('Trigger') || A.IsA('Mover')) ) { if ( A.IsA('Counter') ) { bPlayerOnly = true; return; //FIXME - handle counters } if (TriggerActor == None) TriggerActor = A; else if ( TriggerActor2 == None ) TriggerActor2 = A; } if ( TriggerActor == None ) { bPlayerOnly = (BumpType == BT_PlayerBump); return; } bPlayerOnly = ( TriggerActor.IsA('Trigger') && (Trigger(TriggerActor).TriggerType == TT_PlayerProximity) ); if ( bPlayerOnly && ( TriggerActor2 != None) ) { // Xian: fixed TriggerActor to TriggerActor2 bPlayerOnly = ( TriggerActor2.IsA('Trigger') && (Trigger(TriggerActor2).TriggerType == TT_PlayerProximity) ); if ( !bPlayerOnly ) { A = TriggerActor; TriggerActor = TriggerActor2; TriggerActor2 = A; } } }
function PostBeginPlay() { local mover M; //brushes can't be deleted, so if not relevant, make it invisible and non-colliding if ( !Level.Game.IsRelevant(self) ) { SetCollision(false, false, false); bCollideWorld = False; // Xian: :) SetLocation(Location + vect(0,0,20000)); // temp since still in bsp bHidden = true; } else { FindTriggerActor(); // Initialize all slaves. if( !bSlave && (Tag != '')) ) // Xian: tag check { foreach AllActors( class 'Mover', M, Tag ) { if( M.bSlave ) { M.GotoState(''); M.SetBase( Self ); } } } if ( Leader == None ) { Leader = self; if (ReturnGroup != '') // Xian: tag check again { foreach AllActors( class'Mover', M ) { if ( (M != self) && (M.ReturnGroup == ReturnGroup) ) { M.Leader = self; M.Follower = Follower; Follower = M; } } } } } }
function FinishNotify() { local Pawn P; if (WaitingPawn == None) return; // Xian: don't iterate unless needed // Note: this does kinda upsets the blanace of Bot decission in case they assigned the current // instance of the Mover as their special destination, but in most cases it's all about online play; // although this is not something I recommend for UT as a whole, these changes should help for // player-only mods that require online gameplay and is made for informative purposes if ( StandingCount > 0 ) for ( P=Level.PawnList; P!=None; P=P.nextPawn ) if ( P.Base == self) { // Xian: is it even relevant to execute code for players since they're aware of mover states ? if (P.IsA('Bot')) { P.StopWaiting(); if ( (P.SpecialGoal == self) || (P.SpecialGoal == myMarker) ) P.SpecialGoal = None; } if ( P == WaitingPawn ) WaitingPawn = None; } if ( WaitingPawn != None ) { if (WaitingPawn.IsA('Bot')) { WaitingPawn.StopWaiting(); if ( (WaitingPawn.SpecialGoal == self) || (WaitingPawn.SpecialGoal == myMarker) ) WaitingPawn.SpecialGoal = None; } WaitingPawn = None; } }
This is how it should all be in my opinion The code should execute faster. Not tested but in theory, it should be better.
Subclasses
- Types of Mover is a quick overview of the different classes of mover
- AssertMover?
- AttachMover
- ElevatorMover?
- GradualMover
- LoopMover?
- MixMover?
- RotatingMover
- FixedMover – custom class that fixes network bugs with movers than rotate in the process of opening or closing
It's possible to change the class of a mover after it's been created, but it requires Brush Hacking.
Related Topics
- Mover Topics is the hub for all related articles & tutorials on making specific movers
- Types of Trigger
- Event