I search for solutions in this order: Past Code, Unreal Source, Wiki, BUF, groups.yahoo, google, screaming at monitor. – RegularX

Legacy:Mover (UT)

From Unreal Wiki, The Unreal Engine Documentation Site
Revision as of 20:28, 9 November 2016 by SeriousBarbie (Talk | contribs) (Hint to bDynamicLightMover and semi-solid brushes)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
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[edit]

Events Group[edit]

name Event
This event is fired when the Mover has finished opening (reached last position of KeyNum) and before the StayOpenTime begins to run. Other Mover types may implement other behaviour.
name Tag
(as usual)

Mover Group[edit]

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.
If the mover's surface stays completely dark, make sure that it does not share any surface with semi-solid brushes.
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 
The return group this mover belongs to. All movers in a return group form a linked list of "followers" with each mover's Leader pointing to the main mover that should be triggered, bumped, or otherwise activated. The leading mover will tell its Follower to open/close whenever it opens or closes itself. The follower in turn will do the same with its own follower, and so on. Make sure only one mover in a return group is flagged as the leader, otherwise you might experience weird stuff including "Infinite Script Recursion" crashes.
If none is given, Mover's BeginPlay() code sets it to the value of Tag. This may lead to undesired behaviour, so using an unique value for ReturnGroup seems to be the best solution if no other Movers depend on this Mover.
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[edit]

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[edit]

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[edit]

EMoverEncroachType[edit]

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[edit]

MV_MoveByTime 
Move linearly.
MV_GlideByTime 
Move with smooth acceleration.

EBumpType[edit]

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[edit]

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[edit]

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[edit]

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

Related Topics[edit]

Mover Tutorials[edit]