UDK中的Trigger

来源:互联网 发布:大数据研究现状 编辑:程序博客网 时间:2024/05/21 10:06

It is very easy to set up interactive objects using trigger actors and kismet. However, what if we 

want to have the same kind of object in several places without repeating the Kismet sequences? 

We could use prefabs. But I’d like to have an on-screen prompt… This could be done with prefabs as 

well, with a little bit of imagination. Yet, it seems cleaner to me to do it in UnrealScript, and we’re 

here to learn it anyway.Using this as a thread to guide us through this article, we’ll see 

how a player can interact with (use) objects, which will lead us to the various trigger 

actors and how they can be used.DISCLAIMER: I’m assuming you know how to use 

triggers before reading this. If not, have a look here and there.

“Use” pipeline

Let’s start by looking at what happens when the player presses the “Use” button. Note that unless

 specified otherwise, all the functions are located in the PlayerController class.

The Use() exec function is picked up by the PlayerController, which (after some network-related checks) 

calls PerformedUseAction(). This function tries to leave the vehicle if we are in one, otherwise tries to enter

 the nearest vehicle. If those both fail, then it will look for triggers to activate, through the function called TriggerInteracted().

NOTE: This means that if for whatever reason, your game allows the player to interact with objects while in a 

vehicle (for instance if the vehicle is the main avatar), you’ll need to override PerformedUseAction(), or things will go wrong.

TriggerInteracted() (in its default implementation) gets all valid usable actors and sort them by order of “importance”. 

The importance is determined by two factors:

  1. How close the actor is  from the centre of the camera (that basically tells me if I’m looking straight at the trigger or not).
  2. How close the actor is from the player’s pawn.

NOTE: The first factor is camera-dependent (It’s calculated using the results for GetPlayerViewPoint()). This should be fine, 

but keep that in mind if you’re doing funky camera stuff.But what’s a valid usable actor by the way? 

That is determined by the GetTriggerUseList() function. For once, the function is quite well documented 

so I won’t spend any time explaining how it works, but I’ll draw your attention on a few things:

  • Like TriggerInteracted(), this function relies on GetPlayerViewPoint to compute validity.
  • While TriggerInteracted() works with any subclass of Actor, the default implementation of GetTriggerUseList() filters out anything that is not a subclass of Trigger.
  • Not only that, but Triggers that are not used by any Kismet sequence are also ignored.

The last point is quite annoying in my example as I want a code-driven usable object. This means this function will need to change.

Then, once the usable actors are sorted, the function goes through the list and calls Actor.UsedBy(Pawn) on each of them, 

until one says “OK, I’m responding to the use request!” (i.e. returns true). The others are skipped (we usually wouldn’t want to activate several things at the same time).

Creating a custom usable actor

The specifications of the actor described below are as follows:

  • It possess a static mesh with collisions.
  • While the player is inside a given radius of the actor, a on-screen prompt will be displayed.
  • If the player preses the “Use” key when prompted, a sound is played.

We’ll be extending the Trigger class, as it’s we’ll want many of its functionalities (e.g. scaling the triggering area by scaling the actor itself).

Adding a mesh

That’s the usual stuff of adding a StaticMeshComponent. A few more things, though: the Trigger class is hidden by default. 

so we need to set bHidden to false in the defaultproperties, or we won’t see our mesh. Also, we need to adjust the collisions to take the static mesh into account. The end result looks like below:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Class UsableActor extendsTrigger;
 
var() conststring Prompt;
varbool IsInInteractionRange;
 
DefaultProperties
{
    Begin Object Name=Sprite
        HiddenGame= true HiddenEditor= true
    End Object
 
    Begin Object Class=StaticMeshComponent Name=MyMesh
        StaticMesh=StaticMesh 'NodeBuddies.3D_Icons.NodeBuddy__BASE_SHORT'
    End Object
 
    CollisionComponent=MyMesh
 
    Components.Add(MyMesh)
    bBlockActors= true
    bHidden= false
}


A nicer touch would be exposing that StaticMeshComponent to the editor, so we can have different-looking interactable actors with the same code.

Displaying the prompt

To do this, we’ll be using the actor’s PostRenderFor function (more details here). To do the range check, 

we’ll simply use the trigger’s cylinder and react to the actor’s Touch and UnTouch events, like so:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
eventTouch(Actor Other, PrimitiveComponent OtherComp, Vector HitLocation, Vector HitNormal)
{
    super .Touch(Other, OtherComp, HitLocation, HitNormal);
 
    if (Pawn(Other) != none)
    {
        //Ideally, we should also check that the touching pawn is a player-controlled one.
        PlayerController(Pawn(Other).Controller).myHUD.AddPostRenderedActor( self );
        IsInInteractionRange = true;
    }
}
 
eventUnTouch(Actor Other)
{
    super .UnTouch(Other);
 
    if (Pawn(Other) != none)
    {
        PlayerController(Pawn(Other).Controller).myHUD.RemovePostRenderedActor( self );
        IsInInteractionRange = false;
    }
}
 
simulatedevent PostRenderFor(PlayerController PC, Canvas Canvas, Vector CameraPosition, Vector CameraDir)
{
    local Font previous_font;
    super .PostRenderFor(PC, Canvas, CameraPosition, CameraDir);
    previous_font = Canvas.Font;
    Canvas.Font = class'Engine' .Static.GetMediumFont();
    Canvas.SetPos( 400 , 300 );
    Canvas.SetDrawColor( 0 , 255 , 0 , 255 );
    Canvas.DrawText(Prompt); //Prompt is a string variable defined in our new actor's class.
    Canvas.Font = previous_font;
}


NOTE: To be honest, I was a bit surprised this worked, as the trigger’s cylinder is not the collision component. 

However, the UDN states that collision tests against unmoving actors are testing all the actor’s PrimitiveComponents. 

This also means that if your actor has several PrimitiveComponents that collide with actors without blocking them, the code above is very likely to give unexpected results.

Reacting to the “use” key

As seen in the first part of this article, our actor needs to override the UsedBy() function, where we’ll handle the actual interaction. Which in our case is quite simple:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
functionbool UsedBy(Pawn User)
{
    local bool used;
 
    used = super.UsedBy(User);
 
    if (IsInInteractionRange)
    {
        //If it matters, you might want to double check here that the user is a player-controlled pawn.
        PlaySound(SoundCue 'ImportTest.A_Use_Cue' ) //Put your own sound cue here. And ideally, don't directly reference assets in code.
        return true ;
    }
    return used;
}


We’ve also seen that we need to change the GetTriggerUseList function in our PlayerController, as our actor must be

 usable even if there is no Kismet tied to it. Instead of overriding the function as usual by calling it’s super first,

 I’ll copy/paste the parent function’s body and add my modifications, because I’d like to avoid parsing through all colliding actors twice.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
functionGetTriggerUseList( float interactDistanceToCheck, floatcrosshairDist, floatminDot, boolbUsuableOnly, outarray <Trigger>; out_useList)
{
    local int Idx;
    local vector cameraLoc;
    local rotator cameraRot;
    local Trigger checkTrigger;
    local SeqEvent_Used UseSeq;
 
    if (Pawn != None)
    {
        // grab camera location/rotation for checking crosshairDist
        GetPlayerViewPoint(cameraLoc, cameraRot);
        // search of nearby actors that have use events
        foreach Pawn.CollidingActors( class 'Trigger' ,checkTrigger,interactDistanceToCheck)
        {
            //8<------
            //Code from the parent function. I've snipped it, but you have to put it in
            //or you'll basically break Use events in Kismet.
            //8<------
 
            //If it's a usable actor and it hasn't already been added to the list, let's add it.
            if (UsableActor(checkTrigger) != None && (out_useList.Length == 0|| out_useList[out_useList.Length- 1 ] != checkTrigger))
            {
                out_useList[out_useList.Length] = checkTrigger;
            }
        }
    }
}


And there you go! Here’s a video of what it may look like:

A quick word on the other types of triggers

The Trigger class has a few children with slightly different behaviours:

  • Trigger_Dynamic: Same as a normal trigger except it can move at runtime.


  • Trigger_LOS: Holds and maintains a list of all player controllers having a line of sight to the trigger. 


  • By default, only the “LOS” Kismet event can be used with this trigger.


  • TriggerStreamingLevel: to control by script the loading of streaming levels.

There is also the TriggerVolume (and its Dynamic variant), which is not a subclass of Trigger. By default, 

you can’t “use” a trigger volume (it wouldn’t really make sense anyway).

0 0