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:
- 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).
- 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:
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:
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:
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.
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).
- UDK中的Trigger
- UDk触发器试用(Trigger开门)
- UDK中的环境反射
- UDK
- jQuery 中的trigger
- jQuery中的trigger
- Unity中的trigger
- mysql中的trigger
- UDK:UDK 常用快捷键
- oracle中的procedure,function,trigger
- SQL Server中的触发器trigger
- Silverlight中的Action与Trigger
- sql下Trigger中的COLUMNS_updated
- Extjs中的Form之trigger
- jquery中的trigger()方法作用
- SAP HANA中的触发器(Trigger)
- jQuery中的.trigger()和.triggerHandler()
- Trigger
- Android仿百度贴吧看帖滑动返回效果
- uiscrollview scrollRectToVisible 没有效果
- 【其它】计算理论小知识点
- typedef struct和struct的区别
- nvarchar2转化为clob的方法或者“要修改数据类型,则要更改的列必须为空"
- UDK中的Trigger
- iOS使用位置和方向服务(来自苹果apple官方)
- Eclipse常用快捷键
- change of USD / ERU best quality film faced plywood--LongDa Wood
- anchorPoint
- Java基础复习第二天
- 关于++i与i++的总结
- SQL必知必会 笔记 第七章 创建计算字段
- (四)因果图法