UDK中的Trigger

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 extends Trigger;
 
var () const string Prompt;
var bool 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
event Touch(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 ;
     }
}
 
event UnTouch(Actor Other)
{
     super .UnTouch(Other);
 
     if (Pawn(Other) != none )
     {
         PlayerController(Pawn(Other).Controller).myHUD.RemovePostRenderedActor( self );
         IsInInteractionRange = false ;
     }
}
 
simulated event 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
function bool 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
function GetTriggerUseList( float interactDistanceToCheck, float crosshairDist, float minDot, bool bUsuableOnly, out array <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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值