| Home Page | Recent Changes | Preferences

Mover (UT)

UT :: Actor (UT) >> Brush >> Mover (Package: Engine)

(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:

  1. Opens as usual, with its speed determined by the MoveTime property.
  2. Pauses for a time equal to the OtherTime property.
  3. Closes as usual, with its speed determined by the MoveTime property.
  4. 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

It's possible to change the class of a mover after it's been created, but it requires Brush Hacking.

Related Topics

Mover Tutorials


Category Class (UT)

The Unreal Engine Documentation Site

Wiki Community

Topic Categories

Image Uploads

Random Page

Recent Changes

Offline Wiki

Unreal Engine

Console Commands

Terminology

FAQs

Help Desk

Mapping Topics

Mapping Lessons

UnrealEd Interface

UnrealScript Topics

UnrealScript Lessons

Making Mods

Class Tree

Modeling Topics

Chongqing Page

Log In