UDN
Search public documentation:

MasteringUnrealScriptDelegatesCH
English Translation
日本語訳
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

第十二章- DELEGATES(代理)

Delegates(代理)是到实例中到函数的引用。Delegates(代理)由两个编程概念组成:函数和变量。您已经知道了变量如何存放一个特定类型的值以及它在运行时是如何改变的。从某种程度上说,代理和变量类似,因为它们也可以存储值并且可以在运行时改变。但是在代理中,那个值是指在一个类中声明的另一个函数。代理的行为也和函数类似,因为可以执行它们。在适当的情况下变量和函数的结合使代理变成了如此强大的工具。

12.1 –概述

当运行时要求代码执行具有动态性和灵活性时,通常会使用代理。传统的方法不灵活,并且通常有一定的限制。请考虑以下情况:

  var int GlobalVar;
  
  function Foo(float value)
  {
     GlobalVar = value;
     Bar();
  }
  
  function Bar()
  {
     switch (GlobalVar)
     {
        case 0:
           DoThis();
           break;
        case 1:
           DoThat();
           break;
        default:
           DoDefault();
           break;
     }
  }
  

这段代码是在运行时动态地改变代码执行的一种方法。但是它并不是一种灵活的方法。因为当需要添加很多条件时,则需要更多的精力来维护这个不断增加的Bar()函数。请考虑以下情况:

  delegate Bar();
  
  function Foo(float value)
  {
     switch (value)
     {
        case 0:
           Bar = DoThis();
           break;
        case 1:
           Bar = DoThat();
           break;
        default:
           Bar = DoDefault();
           break;
     }
  
     Bar();
  }
  

这段代码比先前好了一些,因为它已经解决了两个问题。首先,它删除了全局变量和当运行Bar()时对该变量的判断。但是,因为仍然存在switch语句,所以它仍然具有和先前一样的维护问题。这次不是Bar()函数变得难于维护,而是Foo()函数变得难于维护。请考虑一下情况:

  delegate Bar();
  
  function Foo(delegate<Bar> BarDelegate)
  {
     Bar = BarDelegate;
     Bar();
  }
  

这段代码比之前的更加好,意味它已经删除了switch语句。无论以后添加多少个不同的条件,都不需要维护Foo() 或Bar()函数。

12.2 - 声明代理(Delegate)

代理(Delegate)的声明方法和函数声明一样,但是它没有使用关键字function,而是使用关键字delegate。

  delegate Foo();
  

这个类现在有一个名称为Foo()的代理。

代理参数

和函数一样,代理可以具有参数。当把函数和代理结合使用时,函数也必须包含和代理一样的参数,请考虑以下情况:

  delegate Foo(const float Bar, const float Doh);
  
  function FooBoom(const float Bar, const float Doh);
  
  function FooFail(const float Bar);
  

把FooBoom()分配给Foo()是有效的,但是把FooFail()分配给Foo()是无效的。但其中对于这个规则的一个例外是可选参数。请考虑一下情况:

  delegate Foo(const float Bar, const float Doh, optional float Moe);
  
  function FooBoom(const float Bar, const float Doh);
  

A把FooBoom()分配给Foo()仍然是有效的,但您不能在FooBoom()中使用Moe。代理也可以具有返回参数。

默认行为

当还没有为代理分配函数时,定义代理的主体来设置默认行为。请考虑以下情况:

  delegate Foo()
  {
     `Log(“Default behavior.”);
  }
  
  function Bar()
  {
     `Log(“Non default behavior.”);
  }
  
  function Bing()
  {
     Foo = Bar;
     Foo();
     Foo = none;
     Foo();
  }
  

这样将会向脚本日志中输出以下信息:

  ScriptLog: Non default behavior.
  ScriptLog: Default behavior.
  

12.3 - 代理变量

可以像使用变量那样来使用代理。尽管它们不能像浮点值或整型值那样进行算术计算,但是可以对它们进行赋值及将它们进行比较。语法和在UnrealScript中为其它变量赋值一样。

  delegate Foo();
  
  function Bar();
  
  function PostBeginPlay()
  {
     Foo = Bar;
  }
  

有时候通过比较代理来查看它们当前正在引用的函数是什么。请考虑以下情况:

  delegate Foo();
  
  function Bar();
  
  function Rod();
  
  function PostBeginPlay()
  {
     Foo = Bar;
  
     if (Foo == Bar)
        `Log(“Foo is assigned to Bar()”);
  
     Foo = Rod;
  
     if (Foo != Bar)
        `Log(“Foo is not assigned to Bar()”);
  }
  

通过使用那样的比较函数,可以帮助我们排除其它的全局变量,从而跟踪查出代理指向的是哪个函数。

12.4 - 传递Delegate(代理)给函数

代理和变量一样,我们也可以在函数参数中使用它们。当您想在函数和实例之间传递代理时,这是有用的。比如:

  delegate Foo();
  
  function Bar();
  
  function PassDelegate()
  {
     ReceiveDelegate(Bar);
  }
  
  function ReceiveDelegate(delegate<Foo> FooDelegate)
  {
     Foo = FooDelegate;
  }
  

当代理本身相对于其它类来说是保护成员或私有成员时,这种分配代理的方法是很重要的。因为代理是私有的或保护的,所以其他类不能正常地访问代理。比如:

  class Pizza extends Object;
  
  private delegate Eat();
  
  function EatMe()
  {
     Eat();
  }
  
  function HowToEat(delegate<Eat> EatDelegate)
  {
     Eat = EatDelegate;
  }
  
  class Mushroom extends Object;
  
  function SpitOut()
  {
     `Log(“I spit out the mushrooms, as they are disgusting.”);
  }
  
  function EatPizza(Pizza pizza)
  {
     if (pizza != none)
     {
        pizza.HowToEat(SpitOut);
        pizza.EatMe();
     }
  }
  

12.5 - 代理和内存

当代理引用了世界中的actor实例中存在的函数时,删除那个actor实例是安全的。但是,如果代理引用了存在于另一个对象实例中的函数,则必须把代理设为none。因为UnrealScript不能根据请求来销毁对象实例,所以所有的循环引用都必须被删除。否则将不能对该对象实例进行垃圾回收,那么当关卡改变或当游戏退出时将可能会发生内存泄露。

12.6 - UISCENE 和 UIOBJECT DELEGATES(代理)

UIScenes and the UIObjects used within them make use of delegates to provide easy methods of customizing the functionality of those elements. Because delegates are most commonly used in this context by modders, the delegates found within these classes are listed and explained below. UIScenes 和UIObjects使用代理来提供自定义这些元素的功能的简单方法。因为在这种情境下代理经常由modders(游戏修改人员)使用,以下列出并解释了在这些类中找到的代理。

UISCENE 代理

  • OnSceneActivated(UIScene ActivatedScene, bool bInitialActivation) - 当页面变为激活页面时调用这个代理。ActivatedScene是变为激活状态的UIScene,如果这是该页面第一次变为激活状态,那么设置bInitialActivation为真。

  • OnSceneDeactivated(UIScene DeactivatedScene) - 当页面变为非激活状态时调用这个代理。DeactivatedScene是变为非激活状态的UIScene。

  • OnTopSceneChanged(UIScene NewTopScene) -当在最顶部的页面上使用这个UIScene时调用这个函数,并且另一个UIScene将变为最顶部的页面。NewTopScene是将要变为新的最顶部页面的UIScene。UIScenes可以堆叠在彼此上面,这个层次堆叠属性允许您把各种不同的页面组合到一起。比如,可以使得背景UIScene很少改变,而把交互的UIScene放在顶部层次上。

  • bool ShouldModulateBackgroundAlpha(out float AlphaModulationPercent) -当渲染父项页面时,该代理为UIScene提供了一种改变它使用的透明度量的方法。当渲染该页面下面的页面时,使用AlphaModulationPercent的值来调整alpha(透明度)。如果返回值为真,那么当渲染该页面下面的页面时则应该应用alpha调整。

UIOBJECT 的代理

  • OnCreate(UIObject CreatedWidget, UIScreenObject CreatorContainer) -当创建UIObject时调用这个代理。CreatedWidget是被创建的UIObject,UIScreenObject是那个刚创建的控件的容器。

  • OnValueChanged(UIObject Sender, int PlayerIndex) -当这个UIObject的值改变时调用这个函数。这个代理仅和包含数据值的UIObject相关。Sender是调用这个代理的UIObject,PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

  • bool OnRefreshSubscriberValue(UIObject Sender, int BindingIndex) - 当UIObject接收到RefreshSubscriberValue函数调用时调用这个代理。Sender是调用这个代理的UIObject;对于这些具有很多数据仓库绑定的UIObjects来说,BindingIndex指出刷新哪个数据仓库绑定。实现这个代理的类可以使用它。如果UIObject将手动地刷新它的值则返回真。

  • OnPressed(UIScreenObject EventObject, int PlayerIndex) -当按下UIObject时调用这个代理。这个代理不能在所有类型的UIObject上实现。EventObject是调用这个代理的UIScreenObject;PlayerIndex PlayerIndex是Engine.GamePlayers的索引值,Engine.GamePlayers指向触发这个事件的玩家。

  • OnPressRepeat(UIScreenObject EventObject, int PlayerIndex) -当按下控件并且用户按下向下箭头键时调用这个代理。不是所有类型的控件都可以实现这个代理。EventObject是调用这个代理的UIScreenObject。PlayerIndex是Engine.GamePlayers的索引值,Engine.GamePlayers指向触发这个事件的玩家。

  • OnPressRelease(UIScreenObject EventObject, int PlayerIndex) -当不再押下这个控件时调用这个代理。不是所有类型的控件都能实现这个代理。EventObject是调用这个代理的UIScreenObject。PlayerIndex是Engine.GamePlayers的索引值,Engine.GamePlayers指向触发这个事件的玩家。

  • bool OnClicked(UIScreenObject EventObject, int PlayerIndex) - 当不再押下这个控件时调用它。不是所有类型的控件都能实现这个代理。和OnPressRelease的不同之处是仅在接收按下匹配键的UIObject调用这个函数。但OnPressRelease可以在任何曾经处于光标聚焦状态但是释放了按键的UIObject上进行调用,可以是没有接收按键押下的UIObject。EventObject是指调用这个代理的UIObject。PlayerIndex是Engine.GamePlayers的索引值,Engine.GamePlayers指向触发这个事件的玩家。

  • OnDoubleClick(UIScreenObject EventObject, int PlayerIndex) -当控件接受到一个双击事件时调用这个代理。不是所有类型的控件都能实现这个函数。EventObject是调用这个代理的UIScreenObject。

  • bool OnQueryToolTip(UIObject Sender, out UIToolTip CustomToolTip) - 这为子类或容器提供了一种重载显示的标准工具提示的方法。Sender是现实工具提示的UIObject。CustomToolTip是将要现实的工具提示信息。如果函数返回真,则显示工具提示;返回假,则阻止显示工具提示。

  • bool OnOpenContextMenu(UIObject Sender, int PlayerIndex, out UIContextMenu CustomContextMenu) -这给脚本提供了一种显示自定义关联菜单的方法,当用户右击时会弹出该菜单。Sender是现实关联菜单的UIObject。PlayerIndex是Engine.GamePlayers中的索引值,该Engine.GamePlayers指向触发那个事件的玩家。CustomContextMenu是将要显示的自定义关联菜单。如果返回真则显示自定义关联菜单,如果返回假则不显示关联菜单。

  • OnCloseContextMenu(UIContextMenu ContextMenu, int PlayerIndex) -当系统想关闭当前激活的关联菜单时调用这个函数。ContextMenu是即将被关闭的关联菜单。PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

  • OnContextMenuItemSelected(UIContextMenu ContextMenu, int PlayerIndex, int ItemIndex) -当用户从关联菜单中选择一项时调用这个函数。ContextMenu是指调用这个代理的关联菜单。PlayerIndex是Engine.GamePlayers的索引值,Engine.GamePlayers指向触发这个事件的玩家。ItemIndex是指关联菜单的MenuItems(菜单项)数组的索引值。

  • OnUIAnimEnd(UIObject AnimTarget, int AnimIndex, UIAnimationSeq AnimSeq) - 当已经完成一个UI动画时调用这个函数。AnimTarget是调用这个代理的UIObject,AnimIndex是动画的索引值,UIAnimationSeq是动画序列。

12.7 – 虚幻引擎3和虚幻竞技场3中的其它代理

您最好了解一下虚幻引擎3和虚幻竞技场3中的一些重要的代理,因为它们提供了做很多事情的有用方法。这个部分中将会提供一些虚幻引擎3和虚幻竞技场3中存在的代理。

AUDIOCOMPONENT

  • OnAudioFinished(AudioComponent AC) - 当AudioComponent已经完成播放它的当前SoundCue(或者是完成播放或者调用Stop()函数)时调用这个代理。AC指向激活这个代理的AudioComponent。

GAMEINFO

  • bool CanUnpause() - 当您需要实现游戏是否可以取消暂停状态的更加精确的条件时这个函数是有用的。默认情况下,它仅是一个判断标志。

GAMEVIEWPORTCLIENT(游戏视图客户端)

  • bool HandleInputKey(int ControllerId, name Key, EInputEvent EventType, float AmountDepressed, optional bool bGamepad) - 这为子类提供了处理从视口中接收到的按键输入事件的机会。当按键事件传入到interactions数组进行处理之前调用它。ControllerId指向触发这个事件的控制器,EventType定义了发生的事件类型,AmountDepressed用于模拟类型的控制器,如果事件来自游戏控制器则bGamepad为真。

  • bool HandleInputAxis(int ControllerId, name Key, float Delta, float DeltaTime, bool bGamepad) -这为子类提供了处理从视口中接收到的坐标轴输入事件的机会。在坐标轴事件被传入到interactions数组进行处理之前调用它。ControllerId指向触发这个事件的控制器,Key是涉及到的按键,Delta是运动delta(间隔),DeltaTime是指自从上一次更新坐标轴后过去的时间(以秒为单位),如果这个输入事件来自游戏控制器则bGamepad为真。

  • bool HandleInputChar(int ControllerId, string Unicode) - 这为子类提供了处理从视口中接收的字符输入事件的机会。在字符事件被传入到interactions数组进行处理之前调用它。ControllerId指向触发这个事件的控制器,Unicode是输入的字符。

INTERACTION(交互)

  • bool OnReceivedNativeInputKey(int ControllerId, name Key, EInputEvent EventType, optional float AmountDepressed = 1.f, optional bool bGamepad) -和GameViewportClient.HandleInputKey类似,但是当从GameViewportClient(游戏视图客户端)的native代码中调用事件时才使用它。

  • bool OnReceivedNativeInputAxis(int ControllerId, name Key, float Delta, float DeltaTime, optional bool bGamepad) - 和GameViewportClient.HandleInputAxis类似,但是当从GameViewportClient(游戏视图客户端)的native代码中调用事件时才使用它。

  • bool OnReceivedNativeInputChar(int ControllerId, string Unicode) -和GameViewportClient.HandleInputChar 类似,但是当从GameViewportClient(游戏视图客户端)的native代码中调用事件时才使用它。

  • OnInitialize() -当native初始化完成后,将会在使用native代码实现的Init()函数中调用该代理。

ONLINEACCOUNTINTERFACE(在线账户接口)

  • OnCreateOnlineAccountCompleted(EOnlineAccountCreateStatus ErrorStatus) - 当完成账户创建程序时调用这个代理。ErrorStatus将会表明账户创建是否成功。

ONLINECONTENTINTERFACE(在线内容接口)

  • OnContentChange() - 当用户的任何内容发生改变时调用这个代理。

  • OnReadContentComplete(bool bWasSuccessful) - 当完成内容读取请求时调用这个代理。如果读取成功则设置bWasSuccessful为真。

  • OnQueryAvailableDownloadsComplete(bool bWasSuccessful) -当内容下载查询完成时调用这个代理。如果查询成功则设置bWasSuccessful为真。

ONLINEGAMEINTERFACE(在线游戏接口)

  • OnCreateOnlineGameComplete(bool bWasSuccessful) - 当完成了在线游戏创建程序时调用它。如果游戏创建成功则设置bWasSuccessful为真。

  • OnDestroyOnlineGameComplete(bool bWasSuccessful) -当在先游戏销毁程序成时调用它。如果游戏销毁成功则设置bWasSuccessful为真。

  • OnFindOnlineGamesComplete(bool bWasSuccessful) -当在线游戏查找程序完成时调用它。如果游戏查找成功则设置bWasSuccessful为真。

  • OnCancelFindOnlineGamesComplete(bool bWasSuccessful) -当取消在线游戏查找程序时调用它。如果成功地取消了在线游戏查找程序则设置bWasSuccessful为真。

  • OnJoinOnlineGameComplete(bool bWasSuccessful) -当加入在线游戏程序完成时调用它。如果加入游戏成功则设置bWasSuccessful为真。

  • OnRegisterPlayerComplete(bool bWasSuccessful) -当玩家注册程序完成时调用这个代理。如果注册成功则设置bWasSuccessful为真。

  • OnUnregisterPlayerComplete(bool bWasSuccessful) -当玩家取消注册的程序完成时则调用它。如果取消注册成功则设置bWasSuccessful为真。

  • OnStartOnlineGameComplete(bool bWasSuccessful) - 当游戏状态已经变为started(启动)状态时调用它。如果异步程序成功则设置bWasSuccessful为真。

  • OnEndOnlineGameComplete(bool bWasSuccessful) -当游戏状态变为ended(结束状态)时调用它。如果异步程序成功,则设置bWasSuccessful为真。

  • OnArbitrationRegistrationComplete(bool bWasSuccessful) - 当游戏已经完成仲裁注册时调用这个代理。如果异步程序成功,则设置bWasSuccessful为真。

  • OnGameInviteAccepted(OnlineGameSettings GameInviteSettings) -当用户接受一个游戏邀请时调用这个代理。在接受邀请之前,它可以代码使清除任何已存在的状态。

ONLINENEWSINTERFACE (在线消息接口)

  • OnReadGameNewsCompleted(bool bWasSuccessful) -当消息读取程序完成时调用这个代理。如果程序执行成功,则设置bWasSuccessful为真。

  • OnReadContentAnnouncementsCompleted(bool bWasSuccessful) -当内容发布程序完成时调用这个代理。如果程序执行成功则设置bWasSuccessful为真。

ONLINEPLAYERINTERFACE(在线玩家接口)

  • OnLoginChange() -当登录改变时调用它。

  • OnLoginCancelled() -当取消登录请求时调用它。

  • OnMutingChange() -当静音列表改变时调用它。

  • OnFriendsChange() -当朋友列表改变时调用它。

  • OnLoginFailed(byte LocalUserNum, EOnlineServerConnectionStatus ErrorCode) -当由于任何原因导致登录失败时调用它。LocalUserNum指向控制器的id。ErrorCode代表着发生的错误。

  • OnLogoutCompleted(bool bWasSuccessful) -当注销完成时调用它。如果正确地完成了异步调用那么将设置bWasSuccessful为真。

  • OnReadProfileSettingsComplete(bool bWasSuccessful) -当上一个读取人物简介设置请求完成时,调用它。如果成功地完成了异步调用则设置bWasSuccessful为真。

  • OnWriteProfileSettingsComplete(bool bWasSuccessful) - 当书写人物简介设置请求完成时,调用它。如果成功地完成了异步调用则设置bWasSuccessful为真。

  • OnReadFriendsComplete(bool bWasSuccessful) -当完成好友读取请求时调用它。如果完成了读取请求则设置bWasSuccessful为真。

  • OnKeyboardInputComplete(bool bWasSuccessful) -当完成按键输入请求时调用它。如果正确地完成了异步调用则设置bWasSuccessful为真。

  • OnAddFriendByNameComplete(bool bWasSuccessful) -当已经完成通过名字添加朋友时调用它。如果正确地完成了异步调用则设置bWasSuccessful为真。

  • OnFriendInviteReceived(byte LocalUserNum, UniqueNetId RequestingPlayer, string RequestingNick, string Message) - 当本地玩家的好友邀请到达时调用它。LocalUserNum指向本地玩家,RequestingPlayer是向本地玩家发送邀请的玩家的唯一标识,RequestingNick是发送请求的玩家的昵称,Message是一个附加消息。

  • OnReceivedGameInvite(byte LocalUserNum, string InviterName) -当本地用户接收到游戏邀请时调用它。LocalUserNum指向本地用户,InviterName是邀请人的名称。

  • OnJoinFriendGameComplete(bool bWasSuccessful) -当本地用户完成加入朋友的游戏时调用它。如果找到了并加入了游戏会话则设置bWasSuccessful真。

  • OnFriendMessageReceived(byte LocalUserNum, UniqueNetId SendingPlayer, string SendingNick, string Message) - 当本地用户接收到一条好友信息时调用它。LocalUserNum指向本地用户,RequestingPlayer是发送邀请给本地用户的玩家的唯一标识,RequestingNick是发送请求的玩家的昵称,Message是一条附加消息。

ONLINEPLAYERINTERFACEEX(在线玩家接口扩展)

  • OnDeviceSelectionComplete(bool bWasSuccessful) -当设备选择请求完成时调用它。如果成功地完成了设备选择,则设置bWasSuccessful为真。

  • OnUnlockAchievementComplete(bool bWasSuccessful) - This is called when the achievement unlocking request has completed. bWasSuccessful is set true if the unlock achievement has completed successful. 当解除成绩锁定请求时调用它。如果成功地完成了解除成绩锁定则设置bWasSuccessful为真。

  • OnProfileDataChanged() - 当玩家个人信息数据的外部修改完成时调用它。

ONLINESTATSINTERFACE(在线统计数据接口)

  • OnReadOnlineStatsComplete(bool bWasSuccessful) -当完成读取在线状态时调用它。如果正确地完成了异步调用则设置bWasSuccessful为真。

  • OnFlushOnlineStatsComplete(bool bWasSuccessful) - 当完成刷新在线状态时调用它。如果正确地完成了异步调用则设置bWasSuccessful为真。

  • OnRegisterHostStatGuidComplete(bool bWasSuccessful) -当主机统计数据guid注册完成时调用它。如果正确地完成了异步调用则设置bWasSuccessful为真。

ONLINESTATSREAD(在线统计数据读取)

  • OnStatsReadComplete() -当完成读取统计数据时调用它。

ONLINESTATSWRITE

  • OnStatsWriteComplete() - 当完成书写统计数据时调用它。

ONLINESYSTEMINTERFACE(在线系统接口)

  • OnLinkStatusChange(bool bIsConnected) -当网络连接状态改变时调用它。如果找到了某种连接则设置bIsConnected为真。

  • OnExternalUIChange(bool bIsOpening) - T当外部UI显示改变状态时调用它。如果UI处于打开状态则设置bIsOpening为真。

  • OnControllerChange(int ControllerId, bool bIsConnected) -当控制器连接的状态改变时调用它。ControllerId 指向连接状态已经改变了的控制器,如果控制器是连接着的,那么设置bIsConnected 为真。

  • OnConnectionStatusChange(EOnlineServerConnectionStatus ConnectionStatus) -当在线服务器连接状态改变时调用它。ConnectionStatus包含了关于新连接的状态的信息。

  • OnStorageDeviceChange() -当检测到存储设备改变时调用它。

ONLINEVOICEINTERFACE(在线声音接口)

  • OnPlayerTalking(UniqueNetId Player) -当玩家正在进行本地或远程交谈时调用它。将会在每帧为每个激活的谈话者调用一次该代理。Player指向正在谈话的玩家。

  • OnRecognitionComplete() - 当完成对给定玩家的语音识别时调用它。然后您可以调用GetRecognitionResults()来获得已经识别的话语。

PARTICLESYSTEMCOMPONENT(粒子系统组件)

  • OnSystemFinished(ParticleSystemComponent Psystem) -当粒子系统完成’播放’粒子特效时调用它。Psystem指向粒子系统本身,所以如果您在另一个实例中重载这个代理,那么您可以访问调用这个代理的ParticleSystemComponent(粒子系统组件)。

PLAYERCONTROLLER(玩家控制器)

  • bool CanUnpause() -当您需要不同的逻辑来判定玩家控制器什么时候可以取消暂停这个游戏时,可以重载这个代理。

UICOMBOBOX(UI组合框)

  • UIEditBox CreateCustomComboEditbox(UIComboBox EditboxOwner) -当您需要不同的逻辑来创建编辑框时您可以重载它。EditboxOwner是调用那个代理的UIComboBox。返回创建的编辑框。

  • UIToggleButton CreateCustomComboButton(UIComboBox ButtonOwner) - 当您需要不同的逻辑来创建开关组合按钮时您可以重载它。ButtonOwner是调用那个代理的UIComboBox。返回创建的开关按钮。

  • UIList CreateCustomComboList(UIComboBox ListOwner) - 当您需要使用不同的逻辑来创建列表时重载它。ListOwner是调用代理的那个UIComboBox。返回创建的列表。

UICOMP_DRAWCOMPONENTS(UICOMP_描画组件)

  • OnFadeComplete(UIComp_DrawComponents Sender) - 当完成淡入淡出时调用它。Sender是调用这个代理的UIComp_DrawComponent。

UIDATAPROVIDER(UI数据提供者)

  • OnDataProviderPropertyChange(UIDataProvider SourceProvider, optional name PropTag) -当属性改变时调用它。因为您可以使用一些其它的调用函数作为替换,所以设计它的目的是在数据提供者和他们拥有的数据仓库之间使用。SourceProvider是调用代理的UIDataProvider,PropTag是发生改变的属性的名称。

UIDATASTORE(UI数据仓库)

  • OnDataStoreValueUpdated(UIDataStore SourceDataStore, bool bValuesInvalidated, name PropertyTag, UIDataProvider SourceProvider, int ArrayIndex) - 当这个数据仓库暴露的值已经被更新时调用它。它为数据仓库提供了一种通知用户从数据仓库更新他们的值的方法。SourceDataStore是调用代理的数据仓库;如果所有的值都是无效的,一次需要进行完全刷新时则设置bValuesInvalidated为真;PropertyTag是被更新的数据域的标记;SourceProvider是包含改变数据的数据仓库;如果数据域是一组数据,那么ArrayIndex指向改变的数组元素,否则ArrayIndex将是INDEX_NONE (-1)。

UIDATASTORE_GAMESTATE(UI数据仓库_游戏状态)

  • OnRefreshDataFieldValue() -当数据域刷新时调用它。

UIEDITBOX(UI编辑框)

  • bool OnSubmitText(UIEditBox Sender, int PlayerIndex) - 在编辑框处于聚焦的情况下,当用户按下回车键或激活任何绑定到UIKey_SubmitText上的其它动作时调用它。Sender是调用代理的编辑框,PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。如果您想在完成时清除编辑框则返回真。

UIEVENT(UI事件)

  • AllowEventActivation(int ControllerIndex, UIScreenObject InEventOwner, Object InEventActivator, bool bActivateImmediately, out const array IndicesToActivate) – UILIST(UI列表)

  • OnSubmitSelection(UIList Sender, optional int PlayerIndex = GetBestPlayerIndex()) -在列表处于聚焦的情况下,当用户按下回车键或调用绑定到UIKey_SubmitText上的任何其它动作时调用它。Sender是调用这个代理的列表;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向产生这个事件的玩家。

  • OnListElementsSorted(UIList Sender) -当列表的元素已经分类后调用它。Sender是调用这个代理的列表。

UIOPTIONLISTBASE(UI选项列表基类)

  • UIOptionListButton CreateCustomDecrementButton(UIOptionListBase ButtonOwner) -当您想创建您自己的自减按钮时您可以重载它。ButtonOwner是调用这个代理的选项列表基类。返回您创建的UIOptionListButton。

  • UIOptionListButton CreateCustomIncrementButton(UIOptionListBase ButtonOwner) -当您想创建您自己的自加按钮时可以重载它。ButtonOwner是调用这个代理的选项列表基类。返回您创建的UIOptionListButton。

UISCREENOBJECT(UI屏幕对象)

  • NotifyActiveSkinChanged() -当激活的皮肤改变时调用它。它将会应用这个控件的风格并把这个通知传递到它的所有子项。仅当把这个代理真正地赋值给一个成员函数时才会调用它。

  • bool OnRawInputKey(const out InputEventParameters EventParms) - 它使UnrealScript可以使用实际的输入按键名称来响应输入。当接收到这个控件响应的输入按键事件并控件处于正确的状态来处理这个事件时调用这个代理。控件接收到的输入的按键和状态是通过UI编辑器的按键绑定对话框(F8)管理的。这个代理在Kismet之间进行调用。EventParams包含了关于输入按键的信息。如果返回值为真,则意味着这个输入按键已经被处理并且停止进一步处理。

  • bool OnRawInputAxis(const out InputEventParameters EventParms) - 和OnRawInputKey一样。

  • OnProcessInputKey(const out SubscribedInputEventParameters EventParms) -使UnrealScript可以使用UI输入别名来响应输入。当接收到这个控件响应的输入按键事件并且控件处于正确的状态来处理事件时调用这个代理。控件接收到的输入的按键和状态是通过UI编辑器的按键绑定对话框(F8)管理的。这个代理在Kismet之后native代码处理输入之前进行调用。EventParams包含了关于事件的信息。如果返回真,意味着处理了这个按键并且停止进一步的处理。

  • OnProcessInputAxis(const out SubscribedInputEventParameters EventParms) -和OnProcessInputKey类似。

  • NotifyPositionChanged(UIScreenObject Sender) - 当UIScreenObject的位置改变时调用。Sender是改变了位置的UIScreenObject。

  • NotifyResolutionChanged(const out Vector2D OldViewportsize, const out Vector2D NewViewportSize) -当渲染这个UIScreenObject的视口的分辨率已经改变时调用它。OldViewportSize是先前的分辨率,NewViewportSize是新的分辨率。

  • NotifyActiveStateChanged(UIScreenObject Sender, int PlayerIndex, UIState NewlyActiveState, optional UIState PreviouslyActiveState) -所有的激活逻辑都已经发生后,当UIScreenObject的UIState改变时,调用它。Sender是改变状态的UIScreenObject;PlayerIndex是Engine.GamePlayers中的索引值,该Engine.GamePlayers指向激活这个状态的玩家;NewlyActiveState是指现在即获得状态;PreviouslyActiveState是指UIScreenObject先前所在的状态。

  • NotifyVisibilityChanged(UIScreenObject SourceWidget, bool bIsVisible) -当UIScreenObject改变了可见性时调用它。SourceWidget是改变了可见性的控件;如果UIScreenObject是可见的则设置bIsVisible为真。

  • OnPreRenderCallBack() -在渲染之前调用这个代理。

UISCROLLBAR(UI滚动条)

  • OnScrollActivity(UIScrollbar Sender, float PositionChange, optional bool bPositionMaxed = false) - T 当检测到任何滚动行为时调用这个代理。Sender是发送事件的UIScrollBar;PositionChange是滚动按钮改变到的调整值;如果标记已经到了最大位置则设置bPositionMaxed为真。暂时返回值没有用处。

  • OnClickedScrollZone(UIScrollbar Sender, float PositionPerc, int PlayerIndex) -当用户点击滚动区域的任何地方时调用它。Sender是发送事件的UIScrollBar;PositionPerc是一个0.f 到1.f之间的值,代表了渐增按钮和渐减按钮之间的点击位置。0.f在渐减按钮附近,1.f是渐增按钮。PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

UISCROLLBARMARKERBUTTON(UI滚动条标记按钮)

  • OnButtonDragged(UIScrollbarMarkerButton Sender, int PlayerIndex) -当用户按下按钮并使用鼠标拖拽它时调用该代理。Sender是调用代理的UIScrollbarMarkerButton;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

UITABBUTTON(UITab按钮)

  • IsActivationAllowed(UITabButton Sender, int PlayerIndex) -这使得其它的UI控件可以重载这个按钮的行为。Sender是激活的UITabButton;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

UITABCONTROL(UI TAB 控制)

  • OnPageActivated(UITabControl Sender, UITabPage NewlyActivePage, int PlayerIndex) - 当激活了新的页面时调用它。Sender是调用这个代理的UITabControl;NewlyActivePage是新激活的UITabPage;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

  • OnPageInserted(UITabControl Sender, UITabPage NewPage, int PlayerIndex) -当插入一个新页面时调用它。Sender是调用这个代理的UITabControl;NewPage是新插入的UITabPage;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

  • OnPageRemoved(UITabControl Sender, UITabPage OldPage, int PlayerIndex) -当删除一个新页面时调用它。Sender是调用这个代理的UITabControl;OldPage是要删除的UITabPage;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

UITOOLTIP(UI工具提示)

  • ActivateToolTip(UIToolTip Sender) -当要激活工具提示时调用它。Sender是调用这个代理的UIToolTip。

  • DeactivateToolTip() -当要禁用工具提示时调用它。

  • bool CanShowToolTip(UIToolTip Sender) - 当工具提示需要知道是否显示时调用该代理。它使得其它的控件也可以阻止显示工具提示。Sender是正在讨论的UIToolTip。如果您想显示工具提示,则返回真。

ONLINEGAMEINTERFACEIMPL

  • OnFindOnlineGamesComplete(bool bWasSuccessful) -和Engine.OnlineGameInterface.OnFindOnlineGamesComplete()一样。

  • OnCreateOnlineGameComplete(bool bWasSuccessful) - 和Engine.OnlineGameInterface.OnCreateOnlineGameComplete()一样。

  • OnDestroyOnlineGameComplete(bool bWasSuccessful) -和Engine.OnlineGameInterface.OnDestroyOnlineGameComplete()一样。

  • OnCancelFindOnlineGamesComplete(bool bWasSuccessful) -和Engine.OnlineGameInterface.OnCancelFindOnlineGamesComplete()一样。

  • OnJoinOnlineGameComplete(bool bWasSuccessful) -和Engine.OnlineGameInterface.OnJoinOnlineGameComplete()一样。

  • OnRegisterPlayerComplete(bool bWasSuccessful) -和Engine.OnlineGameInterface.OnRegisterPlayerComplete()一样。

  • OnUnregisterPlayerComplete(bool bWasSuccessful) -和Engine.OnlineGameInterface.OnUnregisterPlayerComplete()一样。

  • OnStartOnlineGameComplete(bool bWasSuccessful) -和Engine.OnlineGameInterface.OnStartOnlineGameComplete()一样。

  • OnEndOnlineGameComplete(bool bWasSuccessful) -和Engine.OnlineGameInterface.OnEndOnlineGameComplete()一样。

  • OnArbitrationRegistrationComplete(bool bWasSuccessful) -和Engine.OnlineGameInterface.OnArbitrationRegistrationComplete()一样。

  • OnGameInviteAccepted(OnlineGameSettings GameInviteSettings) -和Engine.OnlineGameInterface.OnGameInviteAccepted()一样。

ONLINEGAMEINTERFACEGAMESPY

  • OnGameInviteAccepted(OnlineGameSettings GameInviteSettings) -这和Engine.OnlineGameInterface.OnGameInviteAccepted()一样。

  • OnRegisterPlayerComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnRegisterPlayerComplete()一样。

  • OnUnregisterPlayerComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnUnregisterPlayerComplete()一样。

ONLINESUBSYSTEMGAMESPY

  • OnLoginChange() -这和Engine.OnlinePlayerInterface.OnLoginChange()一样。

  • OnLoginCancelled() -这和Engine.OnlinePlayerInterface.OnLoginCancelled()一样。

  • OnMutingChange() -这和Engine.OnlinePlayerInterface.OnMutingChange()一样。

  • OnFriendsChange() -这和Engine.OnlinePlayerInterface.OnFriendsChange()一样。

  • OnLoginFailed(byte LocalUserNum,EOnlineServerConnectionStatus ErrorCode) -这和Engine.OnlinePlayerInterface.OnLoginFailed()一样。

  • OnLogoutCompleted(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnLogoutCompleted()一样。

  • OnReadProfileSettingsComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnReadProfileSettingsComplete()一样。

  • OnWriteProfileSettingsComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnWriteProfileSettingsComplete()一样。

  • OnReadFriendsComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnReadFriendsComplete()一样。

  • OnPlayerTalking(UniqueNetId Player) -这和Engine.OnlineVoiceInterface.OnPlayerTalking()一样。

  • OnRecognitionComplete() -这和Engine.OnlineVoiceInterface.OnRecognitionComplete()一样。

  • OnReadOnlineStatsComplete(bool bWasSuccessful) -这和Engine.OnlineStatsInterface.OnReadOnlineStatsComplete()一样。

  • OnFlushOnlineStatsComplete(bool bWasSuccessful) -这和Engine.OnlineStatsInterface.OnFlushOnlineStatsComplete()一样。

  • OnLinkStatusChange(bool bIsConnected) -这和Engine.OnlineSystemInterface.OnLinkStatusChange()一样。

  • OnExternalUIChange(bool bIsOpening -这和Engine.OnlineSystemInterface.OnExternalUIChange()一样。

  • OnControllerChange(int ControllerId, bool bIsConnected) -这和Engine.OnlineSystemInterface.OnControllerChange()一样。

  • OnConnectionStatusChange(EOnlineServerConnectionStatus ConnectionStatus) -这和Engine.OnlineSystemInterface.OnConnectionStatusChange()一样。

  • OnStorageDeviceChange() -这和Engine.OnlineSystemInterface.OnStorageDeviceChange()一样。

  • OnCreateOnlineAccountCompleted(EOnlineAccountCreateStatus ErrorStatus) -这和Engine.OnlineAccountInterface.OnCreateOnlineAccountCompleted()一样。

  • OnKeyboardInputComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnKeyboardInputComplete()一样。

  • OnAddFriendByNameComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnAddFriendByNameComplete()一样。

  • OnFriendInviteReceived(byte LocalUserNum, UniqueNetId RequestingPlayer, string RequestingNick, string Message) -这和Engine.OnlinePlayerInterface.OnFriendInviteReceived()一样。

  • OnReceivedGameInvite(byte LocalUserNum, string InviterName) -这和Engine.OnlinePlayerInterface.OnReceivedGameInvite()一样。

  • OnJoinFriendGameComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnJoinFriendGameComplete()一样。

  • OnFriendMessageReceived(byte LocalUserNum, UniqueNetId SendingPlayer, string SendingNick, string Message) -这和Engine.OnlinePlayerInterface.OnJoinFriendGameComplete()一样。

  • OnRegisterHostStatGuidComplete(bool bWasSuccessful) -这和Engine.OnlineStatsInterface.OnRegisterHostStatGuidComplete()一样。

  • OnReadGameNewsCompleted(bool bWasSuccessful) -这和Engine.OnlineNewsInterface.OnReadGameNewsCompleted()一样。

  • OnReadContentAnnouncementsCompleted(bool bWasSuccessful) -这和Engine.OnlineNewsInterface.OnReadContentAnnouncementsCompleted()一样。

ONLINESUBSYSTEMLIVE

  • OnLoginChange() -这和Engine.OnlinePlayerInterface.OnLoginChange()一样。

  • OnLoginCancelled() -这和Engine.OnlinePlayerInterface.OnLoginCancelled()一样。

  • OnMutingChange() -这和Engine.OnlinePlayerInterface.OnMutingChange()一样。

  • OnFriendsChange() -这和Engine.OnlinePlayerInterface.OnFriendsChange()一样。

  • OnLoginFailed(byte LocalUserNum,EOnlineServerConnectionStatus ErrorCode) -这和Engine.OnlinePlayerInterface.OnLoginFailed()一样。

  • OnLogoutCompleted(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnLogoutCompleted()一样。

  • OnKeyboardInputComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnKeyboardInputComplete()一样。

  • OnLinkStatusChange(bool bIsConnected) -这和Engine.OnlineSystemInterface.OnLinkStatusChange()一样。

  • OnExternalUIChange(bool bIsOpening) -这和Engine.OnlineSystemInterface.OnExternalUIChange()一样。

  • OnControllerChange(int ControllerId, bool bIsConnected) -这和Engine.OnlineSystemInterface.OnControllerChange()一样。

  • OnConnectionStatusChange(EOnlineServerConnectionStatus ConnectionStatus) -这和Engine.OnlineSystemInterface.OnConnectionStatusChange()一样。

  • OnStorageDeviceChange() -这和Engine.OnlineSystemInterface.OnStorageDeviceChange()一样。

  • OnFindOnlineGamesComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnFindOnlineGamesComplete()一样。

  • OnCreateOnlineGameComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnCreateOnlineGameComplete()一样。

  • OnDestroyOnlineGameComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnDestroyOnlineGameComplete()一样。

  • OnCancelFindOnlineGamesComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnCancelFindOnlineGamesComplete()一样。

  • OnJoinOnlineGameComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnJoinOnlineGameComplete()一样。

  • OnRegisterPlayerComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnRegisterPlayerComplete()一样。

  • OnUnregisterPlayerComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnUnregisterPlayerComplete()一样。

  • OnReadProfileSettingsComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnReadProfileSettingsComplete()一样。

  • OnWriteProfileSettingsComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnWriteProfileSettingsComplete()一样。

  • OnDeviceSelectionComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterfaceEx.OnDeviceSelectionComplete()一样。

  • OnUnlockAchievementComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterfaceEx.OnUnlockAchievementComplete()一样。

  • OnProfileDataChanged() -这和Engine.OnlinePlayerInterfaceEx.OnProfileDataChanged()一样。

  • OnStartOnlineGameComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnStartOnlineGameComplete()一样。

  • OnEndOnlineGameComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnEndOnlineGameComplete()一样。

  • OnArbitrationRegistrationComplete(bool bWasSuccessful) -这和Engine.OnlineGameInterface.OnArbitrationRegistrationComplete()一样。

  • OnReadFriendsComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnReadFriendsComplete()一样。

  • OnGameInviteAccepted(OnlineGameSettings InviteSettings) -这和Engine.OnlineGameInterface.OnGameInviteAccepted()一样。

  • OnContentChange() -这和Engine.OnlineContentInterface.OnContentChange()一样。

  • OnReadContentComplete(bool bWasSuccessful) -这和Engine.OnlineContentInterface.OnReadContentComplete()一样。

  • OnQueryAvailableDownloadsComplete(bool bWasSuccessful) -这和Engine.OnlineContentInterface.OnQueryAvailableDownloadsComplete()一样。

  • OnPlayerTalking(UniqueNetId Player) -这和Engine.OnlineVoiceInterface.OnPlayerTalking()一样。

  • OnRecognitionComplete() -这和Engine.OnlineVoiceInterface.OnRecognitionComplete()一样。

  • OnReadOnlineStatsComplete(bool bWasSuccessful) -这和Engine.OnlineStatsInterface.OnReadOnlineStatsComplete()一样。

  • OnFlushOnlineStatsComplete(bool bWasSuccessful) -这和Engine.OnlineStatsInterface.OnFlushOnlineStatsComplete()一样。

  • OnAddFriendByNameComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnAddFriendByNameComplete()一样。

  • OnFriendInviteReceived(byte LocalUserNum, UniqueNetId RequestingPlayer, string RequestingNick, string Message) -这和Engine.OnlinePlayerInterface.OnFriendInviteReceived()一样。

  • OnReceivedGameInvite(byte LocalUserNum,string InviterName) -这和Engine.OnlinePlayerInterface.OnReceivedGameInvite()一样。

  • OnJoinFriendGameComplete(bool bWasSuccessful) -这和Engine.OnlinePlayerInterface.OnJoinFriendGameComplete()一样。

  • OnFriendMessageReceived(byte LocalUserNum, UniqueNetId SendingPlayer, string SendingNick, string Message) -这和Engine.OnlinePlayerInterface.OnFriendMessageReceived()一样。

  • OnRegisterHostStatGuidComplete(bool bWasSuccessful) -这和Engine.OnlineStatsInterface.OnRegisterHostStatGuidComplete()一样。

UTBOT

  • bool CustomActionFunc(UTBot B) -当机器人处于CustomAction状态时调用这个代理。B是调用这个代理的机器人。

UTDATASTORE_ONLINESTATS

  • OnStatsReadComplete(bool bWasSuccessful) -当完成读取统计数据时调用它。如果成功地完成了异步调用则设置bWasSuccessful为真。

UTDEPLOYEDACTOR

  • OnDeployableUsedUp(actor ChildDeployable) -当要销毁已部署的actor时调用这个代理。ChildDeployable是销毁其本身的那个actor。

UTDRAWMAPPANEL

  • OnActorSelected(Actor Selected, UTPlayerController SelectedBy) -当双击节点时调用这个代理。Selected是选中的actor,SelectedBy是执行选择的UTPlayerController。

UTEXPLOSIONLIGHT

  • OnLightFinished(UTExplosionLight Light) - 当光源结束并不再发光时调用它。Light是调用这个代理的actor。

UTKACTOR

  • OnBreakApart() - 当物理actor分裂时调用它。

  • bool OnEncroach(actor Other) - 当物理actor正在受到侵蚀时调用它。Other是正侵蚀这个物理actor的actor。

UTMISSIONGRI

  • OnBinkMovieFinished() -当电影播放完毕时调用它。

UTSCOREBOARDPANEL

  • OnSelectionChange(UTScoreboardPanel TargetScoreboard, UTPlayerReplicationInfo PRI) - 当选项改变时调用这个代理。TargetScoreboard是调用这个代理的记分板;PRI是选中的玩家复制信息。

UTSIMPLEIMAGELIST

  • bool OnDrawItem(UTSimpleImageList SimpleList, int ItemIndex, float Xpos, out float Ypos) -当要描画某项时调用这个代理。SimpleList是调用代理的列表;ItemIndex是List数组的索引值;Xpos是用于描画项目的X坐标;Ypos是用于描画项目的Y坐标。返回假则使用默认的方法渲染项目。

  • OnItemChosen(UTSimpleImageList SourceList, int SelectedIndex, int PlayerIndex) -当选中了列表中的一项时调用此代理。SourceList是调用这个代理的列表;SelectedIndex是新选中的索引值;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

  • OnSelectionChange(UTSimpleImageList SourceList, int NewSelectedIndex) -当选项索引改变时调用这个代理。SourceList是调用这个代理的列表。NewSelectedIndex是新的选项索引。

UTSIMPLELIST

  • bool OnDrawItem(UTSimpleList SimpleList, int ItemIndex, float XPos, out float Ypos) -当将描画某项时调用此代理。SimpleList是调用这个代理的列表;ItemIndex是列表数组的索引值;Xpos是描画项目的X坐标;Ypos是用于描画项目的Y坐标。如果返回值为假则使用默认的方法渲染项目。

  • bool OnDrawSelectionBar(UTSimpleList SimpleList, float Ypos) -当要描画选择条时调用这个代理。SimpleList是调用这个代理的列表;Ypos是用于描画那项的Y坐标。如果返回假则使用默认的方法来描画选择条。

  • bool OnPostDrawSelectionBar(UTSimpleList SimpleList, float YPos, float Width, float Height) - 当描画完选择条后调用这个代理。SimpleList是调用这个代理的列表;Ypos是选择条描画所在的Y坐标;Width是选择条的描画宽度;Height是选择条的描画高度。返回值没有用途。

  • OnItemChosen(UTSimpleList SourceList, int SelectedIndex, int PlayerIndex) -当选中了列表中的一项时调用该代理。SourceList是调用这个代理的列表;SelectedIndex是新的选项索引;PlayerIndex是Engine.GamePlayers中的索引值,它指向触发这个事件的玩家。

  • OnSelectionChange(UTSimpleList SourceList, int NewSelectedIndex) -当选项索引改变时调用这个代理。SourceList是调用这个代理的列表;NewSelectedIndex是新的选项索引。

UTSKELCONTROL_CANTILEVERBEAM

  • vector EntireBeamVelocity() - 这个代理返回了整个光束的穿行速度。

UTSKELCONTROL_TURRETCONSTRAINED

  • OnTurretStatusChange(bool bIsMoving) -当炮塔状态改变时调用它。如果炮塔正在运动则设置bIsMoving为真。

UTSLOWVOLUME

  • OnDeployableUsedUp(actor ChildDeployable) -这和UTGame.UTDeployedActor.OnDeployableUsedUp()一样。

UTTABPAGE.UC

  • OnTick(float DeltaTime) - 将在每次tick(更新)中调用它。DeltaTime是每个tick事件之间的时间,以秒为单位。

UTUIFRONTEND_BINDKEYS360

  • MarkDirty() - 调用这个代理来标记个人简介信息已经修改。

UTUIFRONTEND_BINDKEYSPC

  • MarkDirty() -调用这个代理来标记个人简介信息已经修改。

UTUIFRONTEND_BINDKEYSPS3

  • MarkDirty() -调用这个代理来标记个人简介信息已经修改。

UTUIFRONTEND_BOTSELECTION

  • OnAcceptedBots() -当用户接受他们当前的机器人选项设置时调用它。

UTUIFRONTEND_SETTINGSPANELS

  • OnMarkProfileDirty(optional bool bDirty = true) -当个人简介信息被其它东西修改了而不是用户修改了选项的值时调用这个代理。设置bDirty为真来把个人简介信息标记为已修改状态。

  • OnNotifyOptionChanged(UIScreenObject InObject, name OptionName, int PlayerIndex) -当用户改变了选项列表中的其中一个选项时调用它。InObject是调用这个代理的UIScreenObject;OptionName改变的选项的名称;PlayerIndexn 是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发那个事件- UTUIFRONTEND_WEAPONPREFERENCE的玩家。

  • MarkDirty() - 调用这个代理来把个人简介信息标记为已修改状态。

UTUIMENULIST

  • OnSubmitSelection(UIObject Sender, optional int PlayerIndex = GetBestPlayerIndex()) -当焦点在这个列表上的情况下,当用户按下回车键或绑定到UIKey_SubmitListSelection上的任何其它动作按钮时调用这个代理。Sender是调用这个代理的UIObject;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

UTUIOPTIONLIST

  • OnOptionFocused(UIScreenObject InObject, UIDataProvider OptionProvider) -当一个选项获得聚焦时调用这个代理。InObject是调用这个代理的UIScreenObject;OptionProvider是选项的数据提供者。

  • OnOptionChanged(UIScreenObject InObject, name OptionName, int PlayerIndex) -当选项已经改变时调用这个代理。InObject是调用这个代理的UIScreenObject;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

  • OnAcceptOptions(UIScreenObject InObject, int PlayerIndex) -当按下选项列表中的接受按钮时调用它。InObject是调用这个代理的UIScreenObject;PlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

UTUIPANEL_MAPCYCLE

  • OnMapSelected() -当用户在这个页面上选择一个地图时调用它。

UTUIPANEL_SINGLEMAP

  • OnMapSelected() -当用户在这个页面上选择一个地图时调用它。

UTUIPRESSBUTTON

  • OnBeginPress(UIScreenObject InObject, int InPlayerIndex) -当用户刚刚按下按钮时调用它。InObject是调用这个代理的UIScreenObject;InPlayerIndex是Engine.GamePlayers的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

  • OnEndPress(UIScreenObject InObject, int InPlayerIndex) -当用户刚刚释放按钮上的鼠标左键时调用它。InObject是调用这个代理的UIScreenObject;InPlayerIndex是Engine.GamePlayers中的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

UTUISCENE

  • OnShowAnimationEnded() -当页面的显示动画已经终止时调用它。

  • OnHideAnimationEnded() -当页面的隐藏动画已经终止时调用它。

  • OnSceneOpened(UIScene OpenedScene, bool bInitialActivation) -当隐藏最顶部的页面后,页面已经打开时便调用它。OpenedScene是调用这个代理的页面;如果这是第一次激活打开的页面,则设置bInitialActivation为真。

UTUISCENE_MESSAGEBOX

  • OnSelection(UTUIScene_MessageBox MessageBox, int SelectedOption, int PlayerIndex) -当用户已经从他们可以获得的选项中选择一个选项时调用它。MessageBox是调用这个函数的UTUIScene_MessageBox;SelectionOption是选择的选项;PlayerIndex是Engine.GamePlayers中的索引值,该Engine.GamePlayers指向触发这个事件的玩家。

  • OnClosed() -当已经完全关闭了信息框后调用这个代理。

  • bool OnMBInputKey(const out InputEventParameters EventParms) -当信息框接收到任何输入时调用该代理。EventParams包含了关于输入事件的信息。如果已经处理了输入并且不再需要进一步的处理则返回真。

UTUISCENE_SAVEPROFILE

  • OnSaveFinished() -当已经保存个人简介信息时调用它。

UTUITABPAGE_CHARACTERPART

  • transient OnPartSelected(ECharPart PartType, string InPartID) -当用户在这个页面中选中一部分时调用它。PartType包含了关于选中部分的信息;PartID是PartType的ID。

  • transient OnPreviewPartChanged(ECharPart PartType, string InPartID) -当用户改变了在该页面上选中的部分时调用它。PartType包含了关于选中部分的信息;PartID是PartType的ID。

UTUITABPAGE_FINDQUICKMATCH

  • OnSearchComplete(bool bWasSuccessful) -当已经完成搜索时调用它。如果成功地完成了异步调用则设置bWasSuccessful为真。

UTUITABPAGE_GAMEMODESELECTION

  • OnGameModeSelected(string InGameMode, string InDefaultMap, string GameSettingsClass, bool bSelectionSubmitted) - 当从这个页面选择游戏模式时调用它。InGameMode是选中的游戏模式;InDefaultMap使选中游戏模式的默认地图;GameSettingsClass是游戏设置的类的名称;如果提交了选项那么bSelectionSubmitted为真。

UTUITABPAGE_MAPSELECTION

  • OnMapSelected() - 当用户在这个页面上选择了一个地图时调用它。

UTUITABPAGE_MUTATORS

  • OnAcceptMutators(string InEnabledMutators) - 当用户接受了mutators(设置方法)的当前设置时调用它。InEnabledMutators是已经接受的mutators(设置方法)列表。

UTUITABPAGE_OPTIONS

  • OnAcceptOptions(UIScreenObject InObject, int PlayerIndex) -当已经接受了当前选项时则调用它。InObject是调用这个代理的UIScreenObject;PlayerIndex是Engine.GamePlayers的索引,该Engine.GamePlayers指向触发这个事件的玩家。

  • OnOptionChanged(UIScreenObject InObject, name OptionName, int PlayerIndex) -当页面上的其中一个选项改变时调用它。InObject是调用这个代理的UIScreenObject;OptionName是选项的名称;PlayerIndex是Engine.GamePlayers的索引,该Engine.GamePlayers指向触发这个事件的玩家。

  • OnOptionFocused(UIScreenObject InObject, UIDataProvider OptionProvider) -当其中一个选项获得聚焦时调用它。InObject是调用这个代理的UIScreenObject;OptionProvider是获得聚焦的选项的数据提供者。

UTUITABPAGE_SERVERBROWSER

  • transient OnBack() -当用户想返回时调用该代理。

  • transient OnSwitchedGameType() -当用户使用组合框改变游戏类型时调用该代理。

  • transient OnPrepareToSubmitQuery(UTUITabPage_ServerBrowser Sender) -当用户要提交一个服务器查找时调用该代理。Sender是调用这个代理的UTUITabPage_ServerBrowser。

UTUITABPAGE_SERVERFILTER

  • transient OnSwitchedGameType() -当用户改变游戏类型时调用该代理。

指南 12.1 – 随机事件MUTATOR(设置器), 第一部分:简介&创建最初的类

通过这一系列的指南,您将会创建一个可以为竞赛中的玩家产生各种随机事件的mutator(设置器)。

1. 打开您最喜欢的文本编辑器,并创建一个新文件,命名为 UTMutator_RandomEvents.uc。

2. 从声明脚本类开始。因为我们正在制作一个mutator(设置器),所以我们将继承Engine中的Mutator类。您的第一行代码应该如下所示:

  class UTMutator_RandomEvent extends UTMutator;
  

3. 在继续向下进行之前,我们需要想一些可以使虚幻竞技场3变得更加有趣的事件。为了节约您的时间,这里是我已经想到的一些事件。

  • 所有的玩家接收一个救赎者(非常强大的武器)。
  • 检查所有玩家并查看它们的当前生命值是否高于50,如果是,那么则给他们一些装甲。
  • 强制在地图上重新生成所有武器项。

4. 我们创建一个defaultproperties块。因为在我们的mutator中没有任何全局变量,所以它看上去有点空。

  defaultproperties
  {
     Name=”Default__UTMutator_RandomEvent”
  }
  

您现在可以编译这个类了。

指南 12.2 – 随机事件MUTATOR(设置器), 第二部分:计时逻辑

从现在开始,我们需要书写一些计时代码。Mutator本身负责每个60秒触发一个随机事件。

1. 首先,我们将重载PostBeginPlay()函数。记住当关卡初始化完毕并准备运行时调用PostBeginPlay()函数,它的执行正好在游戏已经加载之后游戏开始运行之前。

  function PostBeginPlay()
  {
     super.PostBeginPlay();
  }
  

2. 我们将会使用一个计时器来触发随机事件。

  function PostBeginPlay()
  {
     super.PostBeginPlay();
     SetTimer(60.f, true);
  }
  

3. 然后创建一个称为Timer的新函数,并按照以下方式书写出来:

  function Timer()
  {
  }
  

当mutator调用PostBeginPlay()时,它将会创建并分配一个每个60秒触发一次的新计时器。提供参数true,以便计时器可以不断地循环,直到我们告诉它停止为止。默认情况下,SetTimer()将会在调用它本身的实例中调用一个名称为Timer()的函数。

指南 12.3 - 随机事件MUTATOR(设置器), 第三部分:使用DELEGATES(代理)

1. 现在我们创建我们的代理函数并修改我们的Timer函数。

  delegate RandomEvent();
  
  function Timer()
  {
     RandomEvent();
  }
  

当调用Timer()函数时,我们将会调用代理RandomEvent()。

2. 从现在开始,我们将创建三个函数来处理上面描述的每个随机事件的逻辑。我们也会处理改变运行不同事件的逻辑。

  function GiveBonusArmor()
  {
  }
  
  function GiveRedeemerToAll()
  {
  }
  
  function ForceRespawn()
  {
  }
  

3. 我们将会修改我们的Timer()函数来处理事件的随机选择。

  function Timer()
  {
     switch (Rand(3))
     {
        case 0:
           RandomEvent = GiveBonusArmor;
           break;
  
        case 1:
           RandomEvent = GiveRedeemerToAll;
           break;
  
        case 2:
           RandomEvent = ForceRespawn;
           break;
  
        default:
           RandomEvent = GiveBonusArmor;
           break;
     }
  
     RandomEvent();
  }
  

正如您看到的,当每次调用Timer()函数时,我们将会在switch语句中运行一个随机函数调用。然后调用了RandomEvent(),它将会调用我们分配给代理的函数。

指南 12.4 - 随机事件MUTATOR(设置器), 第四部分: GIVEBONUSARMOR()

1. 在WorldInfo实例中,有一个迭代器,它允许我们在世界中存在的所有Pawns间进行迭代。我们将是使用这个迭代器找出地图中所有玩家pawns。现在我们修改GiveBonusArmor()函数,如下所示:

  function GiveBonusArmor()
  {
     local UTPawn P;
  
     foreach WorldInfo.AllPawns(class'UTPawn', P)
     {
     }
  }
  

因此,当运行GiveBonusArmor()函数时,它将在关卡中的UTPawn类及UTPawn子类的所有pawns间进行迭代,并把结果输出到局部变量P中。

2. 所以现在,我们需要过滤我们从迭代器中获得的pawns来和我们需要的条件相匹配。这个条件是受到奖励装甲的Pawn的生命值必须大于50。因此,我们将添加条件语句if,如下所示:

  function GiveBonusArmor()
  {
     local UTPawn P;
  
     foreach WorldInfo.AllPawns(class'UTPawn', P)
     {
        if (P != none && P.Health >= 50)
        {
        }
     }
  }
  

尽管通常迭代器返回的P应该永远不是none,但是每次检查它是否是none仍然是一个良好的习惯。这是非常有用的,因为这个检测并不会消耗很多处理量而且会防止访问对象是none的错误发生。一旦我们已经进行了none判断后,我们判断了pawn的生命值。

3. 在虚幻竞技场3中,玩家可以具有的装甲有三种类型。当玩家具有更多的生命值时我们可以奖励玩家更多的装甲。

  function GiveBonusArmor()
  {
     local UTPawn P;
  
     foreach WorldInfo.AllPawns(class'UTPawn', P)
     {
        if (P != none && P.Health >= 50)
        {
           P.ThighpadArmor = Max(class'UTArmorPickup_Thighpads'.default.ShieldAmount, P.ThighpadArmor);
  
           if (P.Health >= 80)
              P.VestArmor = Max(class'UTArmorPickup_Vest'.default.ShieldAmount, P.VestArmor);
  
           if (P.Health >= 90)
              P.HelmetArmor = Max(class'UTArmorPickup_Helmet'.default.ShieldAmount, P.HelmetArmor);
        }
     }
  }
  

因此,如果玩家满足生命值大于50的第一个要求,那么将会奖励玩家一些护膝。如果它们的生命值超过80,那么我们将会奖励它们一些盔甲背心。最后,如果它们的生命值大于90,那么我们奖励它们一个装甲头盔。

4. 现在我们添加一些声音,以便接收到奖励的玩家可以听到一些东西。我们按照如下的方式修改函数:

  function GiveBonusArmor()
  {
     local UTPawn P;
     local SoundCue S;
  
     foreach WorldInfo.AllPawns(class'UTPawn', P)
     {
        if (P != none && P.Health >= 50)
        {
           P.ThighpadArmor = Max(class'UTArmorPickup_Thighpads'.default.ShieldAmount, P.ThighpadArmor);
           S = class'UTArmorPickup_Thighpads'.default.PickupSound;
  
           if (P.Health >= 80)
           {
              P.VestArmor = Max(class'UTArmorPickup_Vest'.default.ShieldAmount, P.VestArmor);
              S = class'UTArmorPickup_Vest'.default.PickupSound;
           }
  
           if (P.Health >= 90)
           {
              P.HelmetArmor = Max(class'UTArmorPickup_Helmet'.default.ShieldAmount, P.HelmetArmor);
              S = class'UTArmorPickup_Helmet'.default.PickupSound;
           }
  
           if (S != none)
              P.PlaySound(S);
        }
     }
  }
  

我们添加了一个局部SoundCue变量,以便当我们给出武器时可以设置它。当玩家满足各种要求时,SoundCue将会被设置为我们最终要播放的那个声音。最后,我们判断是否给我们的变量S分配了声效(注意默认的变量不是必须具有none以外的值),如果已经为S分配了声效,那么我们让得到奖励的pawn播放声音。

指南 12.5 - 随机事件MUTATOR(设置器), 第五部分: GIVEREDEEMERTOALL

1. 和GiveBonusArmor()函数类似,这个函数也是对世界中的所有pawns的函数迭代开始。所以我们以如下代码开始:

  function GiveRedeemerToAll()
  {
     locale UTPawn P;
  
     foreach WorldInfo.AllPawns(class'UTPawn', P)
     {
     }
  }
  

2. 因为所有的玩家都会接收一个redeemer(救赎者武器),所以我们根本不需要任何条件。我们只要简单地给每个玩家一个redeemer(救赎者武器)即可。为了完成这个过程,我们需要生成redeemer(救赎者)武器项并把它给予每个pawn。所以,我们需要生成redeemer(救赎者武器),并把到它的引用保存到一个变量中,以便我们可以使用它。最终,我们按照如下方式修改代码:

  function GiveRedeemerToAll()
  {
     local UTPawn P;
     local UTWeap_Redeemer_Content R;
  
     foreach WorldInfo.AllPawns(class'UTPawn', P)
     {
        R = Spawn(class'UTWeap_Redeemer_Content');
     }
  }
  

那段代码没有完成很多功能,它仅是在世界中产生redeemers(救赎者武器),或许某个幸运的人会偶然发现所有的redeemers(但实际上因为玩家获取的是武器拾取物而不是武器本身,所以这种可能是不会出现的!)。

3. 现在我们已经生成了一个redeemers (救赎者武器),我们需要把它给予一个玩家。首先,我们要判断是否有一个我们可以给与它redeemer(救赎者武器)的玩家。因为我们要检查pawn的存在性,所以我们也检查它是否是有效的pawn。最后,我们会产生具有适当参数的(救赎者武器)。

  function GiveRedeemerToAll()
  {
     local UTPawn P;
     local UTWeap_Redeemer_Content R;
  
     foreach WorldInfo.AllPawns(class'UTPawn', P)
     {
        if (P != none && P.bCanPickupInventory && P.Health > 0 && P.Controller != none)
           R = Spawn(class'UTWeap_Redeemer_Content', P,, P.Location, P.Rotation);
     }
  }
  

条件看上去非常复杂,所以我们再简单地看一下它们:

  • P是否为none?
  • P 是否可以拾取武器项?
  • P是否还有生命值?
  • P是否具有有效的控制器?

4. 最后我们可以把redeemers (救赎者武器)分配给玩家了。通过以下代码实现:

  function GiveRedeemerToAll()
  {
     local UTPawn P;
     local UTWeap_Redeemer_Content R;
  
     foreach WorldInfo.AllPawns(class'UTPawn', P)
     {
        if (P != none && P.bCanPickupInventory && P.Health > 0 && P.Controller != none)
        {
           R = Spawn(class'UTWeap_Redeemer_Content', P,, P.Location, P.Rotation);
  
           if (R != none)
           {
              if (WorldInfo.Game.PickupQuery(P, class'UTWeap_Redeemer_Content', R))
                 R.GiveTo(P);
              else
                 R.Destroy();
           }
        }
     }
  }
  

我们再次判断了是否真正地产生了R。有时候,由于各种原因会导致生成失败,所以R可能为none。为了避免任何访问none对向的错误,我们检测R的有效性。我们最后进行的判断是确认pawn是否可以拾取redeemers (救赎者武器)。如果pawn可以,那么就把redeemers (救赎者武器)给予那个pawn,否则我们将销毁产生的redeemer。

指南 12.6 - 随机事件MUTATOR(设置器),第六部分: FORCERESPAWN

1. 我们首先要做的是创建一个动态数组并使用拾取物工厂引用来填充它。所以我们先向我们的mutator(设置器)类添加一个全局的动态数组。

  class UTMutator_RandomEvent extends UTMutator;
  
  private var array<UTPickupFactory> PickupFactories;
  

我们设置这个变量为全局私有变量的原因是我们不希望其它类可以修改这个数组。

2. 现在我们已经有了一个可以存储拾取物工厂引用的全局变量,那么我们可以修改PostBeginPlay()函数,以便可以填充这个动态数组。

  function PostBeginPlay()
  {
     local UTPickupFactory pickup_factory;
  
     super.PostBeginPlay();
     SetTimer(60.f, true);
  
     foreach AllActors(class'UTPickupFactory', pickup_factory)
     {
     }
  }
  

现在将迭代整个关卡中属于UTPickupFactory及其子类的所有actors。

3. 在这个迭代中,我们将在验证它之后存储每个结果。

  function PostBeginPlay()
  {
     local UTPickupFactory pickup_factory;
  
     super.PostBeginPlay();
     SetTimer(60.f, true);
  
     foreach AllActors(class'UTPickupFactory', pickup_factory)
     {
        if (pickup_factory != none)
           PickupFactories.AddItem(pickup_factory);
     }
  }
  

现在我们已经建立了一个拾取物工厂的动态数组供我们使用。我们这样做的原因是AllActors迭代器是非常慢的。我们每次都进行迭代使得强制生成拾取物变得没有意义。

4. 现在我们书写ForceRespawn函数。

  function ForceRespawn()
  {
     local int i;
  
     for (i = 0; i < PickupFactories.length; ++i)
     {
     }
  }
  

5. 最后我们告诉所有的拾取物工厂进行重置。

  function ForceRespawn()
  {
     locale int i;
  
     for (i = 0; i < PickupFactories.length; ++i)
        if (PickupFactories[i] != none)
           PickupFactories[i].Reset();
  }
  

指南 12.7 - 随机事件MUTATOR(设置器),测试

1. 编译代码,并启动虚幻竞技场3。

2. 登录或者选择离线打游戏。

3. 然后选择Instant Action(即时战斗)游戏。

4. 选择Deathmatch游戏类型,并选择地图。

5. 跳转到Settings面板并按下Mutators按钮。


图片 12.1 –Mutators 按钮把您带到了Mutator(设置器)选择及配置屏幕。

6. 把UTMutator_RandomEvent设置器添加到Enabled Mutators(启用的Mutators)列表中。


图片 12.2 –添加了那个mutator(设置器)。

7. 稍等一会后,您便会看到这三个随机事件的其中一个发生。在这个例子中为玩家提供了防护物奖励。


图片 12.3 –为玩家提供了防护物奖励。

在本指南中,我们看到了如何在一个单独的实例中创建及使用代理。在这种情况下,通过扩展Mutator类使得随着时间推移发生更多的事件变得更加容易。因为我们不需要过多地担心对计时逻辑的维护,所以这个一种非常灵活的处理方法。

指南 12.8 –武器设置器, 第一部分: 简介& 建立最初的类

在本指南中,我们将会制作一个玩家可以改变其开火类型的武器。每种开火类型模仿一种现有武器,它们是火箭发射器(rocket launcher)、防空炮(flak cannon)、脉冲步枪(shock rifle)和bio rifle(生化步枪)。火箭发射器类型将会导致立即碰撞爆炸。防空炮将会导致立即碰撞爆炸并且会产生一些高射炮火。脉冲步枪导致立即电击组合物。生化步枪将会导致立即碰撞粘性物爆炸,它可以落下一些粘性的飞溅物。

1. 首先,在..\MasteringUnrealScript\Classes文件夹中创建新的UnrealScript文件,命名为UTWeap_MultiEnforcer.uc, MultiEnforcer_Base.uc, MultiEnforcer_Bio.uc, MultiEnforcer_Flak.uc, MultiEnforcer_Rocket.uc 和 MultiEnforcer_Shock.uc。

2. 为UTWeap_MultiEnforcer声明类及默认属性。我们将会继承UTWeap_Enforcer类,因为我们想在创建一种新武器的过程中做尽可能少的工作。

  class UTWeap_MultiEnforcer extends UTWeap_Enforcer
  
  defaultproperties
  {
     Name=”Default__ UTWeap_MultiEnforcer”
  }
  

3. 声明MultiEnforcer_Base的类及默认属性。

  class MultiEnforcer_Base extends Object;
  
  defaultproperties
  {
     Name=”Default__MultiEnforcer_Base"
  }
  

4. 声明MultiEnforcer_Bio的类及默认属性。我们继承了MultiEnforcer_Base类,因为基本的功能是在MultiEnforcer_Base类中进行处理的。

  class MultiEnforcer_Bio extends MultiEnforcer_Base;
  
  defaultproperties
  {
     Name="Default__MultiEnforcer_Bio"
  }
  

5. 声明MultiEnforcer_Flak类及默认属性。

  class MultiEnforcer_Flak extends MultiEnforcer_Base;
  
  defaultproperties
  {
     Name="Default__MultiEnforcer_Flak"
  }
  

6. 声明MultiEnforcer_Rocket的类及默认属性。

  class MultiEnforcer_Rocket extends MultiEnforcer_Base;
  
  defaultproperties
  {
     Name="Default__MultiEnforcer_Rocket"
  }
  

7. 声明MultiEnforcer_Shock的类及默认属性。

  class MultiEnforcer_Shock extends MultiEnforcer_Base;
  
  defaultproperties
  {
     Name="Default__MultiEnforcer_Shock"
  }
  

8. 保存所有的新脚本。

指南 12.9 –武器设置器,第二部分: 设置UTWEAP_MULTIENFORCER

这个类代表武器本身,它处理了和武器相关的几乎所有东西。这包括类似于显示网格物体使得看上去玩家正在持有武器的视觉效果、武器创建的声效及武器开火管理等。因为我们继承了UTWeap_Enforcer类,所以它已经为我们完成了大部分工作。这使得我们有更多的事件把关注改变武器的行为使其达到我们期望的效果。

1. 这个武器将依赖于4个开火类:MultiEnforcer_Bio、MultiEnforcer_Flak、MultiEnforcer_Rocket和MultiEnforcer_Shock。因为它们都继承于MultiEnforcer_Base,所以我们仅设置到MultiEnforcer_Base的依赖。

  class UTWeap_MultiEnforcer extends UTWeap_Enforcer
     dependson(MultiEnforcer_Base);
  

2. 我们需要几个全局变量来存储关于我们的开火类型的数据。

  var private array<MultiEnforcer_Base> FireTypes;
  var private int CurrentIndex;
  var const array< class<MultiEnforcer_Base> > FireTypeClasses;
  

FireTypes是一个私有数组,它用于放置MultiEnforcer_Base的对象实例。FireTypes也可以存放MultiEnforcer_Base类的任何子类。FireTypes是私有的,因为我想阻止其它类访问它 。CurrentIndex是一个整型值,它存储FireTypes中的当前索引值。我们使用CurrentIndex来设置想使用的开火类型。CurrentIndex是私有的,因为我们想阻止其它类访问它。FireTypeClasses是一个数组,它存放了武器可以使用的开火类型。FireTypeClasses是一个常量,因为在运行时不需要修改这个数组。注意,在“> >”之间有空格。这些空格是必须存在的,否则UnrealScript编译器将会产生错误。

3. 让我们想默认属性中添加一些其它定义。

  defaultproperties
  {
     CurrentIndex=0
     FireTypeClasses(0)=class'MultiEnforcer_Rocket'
     FireTypeClasses(1)=class'MultiEnforcer_Shock'
     FireTypeClasses(2)=class'MultiEnforcer_Flak'
     FireTypeClasses(3)=class'MultiEnforcer_Bio'
     InventoryGroup=3
     ItemName="MultiEnforcer"
     PickupMessage="MultiEnforcer"
     FiringStatesArray(1)="WeaponSwitching"
     Name="Default__UTWeap_MultiEnforcer"
     ObjectArchetype=UTWeap_Enforcer'UTGame.Default__UTWeap_Enforcer'
  }
  

CurrentIndex初始值为0。FireTypeClasses具有这里定义的所有数组项,因为它在运行时是常量。InventoryGroup是定义在父类中的变量,它决定了这个武器所在的组。PickupMessage是当拾取武器时给出的信息,在这个实例中我们总是称它为MultiEnforcer。FiringStatesArray是当触发一个开火模式时武器将要初始化的状态名称列表。我们将制作一个新的二级开火状态WeaponSwitching,这也是我们为什么设置它的原因。Name是这个对象的名称,我们可以在其它类中引用它。ObjectArchetype使这个对象的父类,从这个类中我们可以衍生出我们需要的大多数其它属性。

4. 现在,我们开始书写PostBeginPlay()函数,它将会处理武器的初始设置。

  function PostBeginPlay()
  {
     super.PostBeginPlay();
  }
  

5. 为了使武器正常工作,我们需要在PostBeginPlay()中创建我们的开火类型对象的实例。

  function PostBeginPlay()
  {
     local int i;
  
     super.PostBeginPlay();
  
     if (FireTypeClasses.length > 0)
     {
        for (i = 0; i < FireTypeClasses.length; ++i)
           if (FireTypeClasses[i] != none)
              FireTypes.AddItem(new FireTypeClasses[i]);
     }
  }
  

我们首先判断FireTypeClasses数组中是否有元素。如果没有任何元素,那么就没有必要继续执行了。从那之后,我们迭代整个数组,创建新的开火类型的对象实例,并把它们添加到我们的FireTypes数组中。我们使用关键字New,因为这些类最终都继承object而不是Actor(对于这个类我们使用Spawn())。

6. 因为我们最终将会使用代理来绑定我们的开火类型,所以我们需要以不泄露内存的方式清除它们。请参照12.5章节的关于代理和内存的讨论部分。所以,我们将会重载Destroyed()函数。当使用已经Destroy()销毁对象或者实例应经被虚幻引擎销毁时将会自动地调用Destroyed()函数。

  simulated function Destroyed()
  {
     super.Destroyed();
  }
  

7. 因为我们不能销毁对象实例,所以我们所需要做的就是删除到这些实例的任何引用,以便虚幻引擎可以对它们进行垃圾回收并从内存中删除它们。

  simulated function Destroyed()
  {
     local int i;
  
     super.Destroyed();
  
     if (FireTypes.length > 0)
     {
        for (i = 0; i < FireTypes.length; ++i)
           FireTypes[i] = none;
     }
  }
  

8. 既然我们已经处理好了内存问题,接下来我们将继续改变武器开火的方式。Enforcer被设置为一个碰撞扫描武器,因此当武器完成一次扫描跟踪后它将会调用ProcessInstantHit()函数。我们重载这个函数,添加当有碰撞需要处理时我们的武器如何做出反应的逻辑。

  simulated function ProcessInstantHit(byte FiringMode, ImpactInfo Impact)
  {
  }
  

在这个特定的示例中,我们没有在这里添加父类函数的调用,因为我们不需要发生父类的逻辑。

9. 现在我们声明我们的代理。当武器需要处理碰撞时将会调用这个代理。

  delegate OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact);
  
  simulated function ProcessInstantHit(byte FiringMode, ImpactInfo Impact)
  {
     OnProcessInstantHit(self, FiringMode, Impact);
  }
  

10. 我们已经处理了当武器开火时应该发生的动作,现在我们可以解决添加处理玩家在各种开火类型之间如何改变的问题。记得在我们的默认属性中,我们添加了一行把次级开火状态改为WeaponSwitching的代码。这个默认属性是当二级开火模式被激活时武器将要进入的状态的名称。因为这个属性通常和鼠标右键绑定,所以我们使用这个属性来处理玩家在不同开火类型间的改变。

  simulated state WeaponSwitching
  {
  }
  

11. 我们需要在这个状态中做一些内务工作,所以我们将添加和UTWeap_Enforcer的WeaponBursting中包含的一样的一组函数。

  simulated state WeaponSwitching
  {
     simulated function TrackShotCount();
  
     simulated function RefireCheckTimer();
  
     simulated function bool TryPutDown()
     {
        bWeaponPutDown = true;
        return true;
     }
  }
  

12. 武器使用计时器来处理动画。计时器允许我们基于UnrealScript的内部架构来创建事件,而不必求助于Tick()中的昂贵的跟踪。当玩家改变开火类型时我们希望发生的动作是玩家放下手枪(enforcer)然后通过放入新的弹匣来重新装载弹药。幸运的是,虚幻竞技场3中关于手枪(enforcer)有一个这样的的动画。

  simulated state WeaponSwitching
  {
     simulated function TrackShotCount();
  
     simulated function RefireCheckTimer();
  
     simulated function bool TryPutDown()
     {
        bWeaponPutDown = true;
        return true;
     }
  
     simulated function BeginState(name PrevStateName)
     {
        TimeWeaponPutDown();
     }
  }
  

TimeWeaponPutDown()是一个父类中存在的函数。它设置一个计时器来触发及播放放下武器的动画。这个函数是在UTWeapon和Weapon类中实现的。简而言之,它处理了放下武器动画,并且当动画完成时,调用WeaponIsDown()函数。

13. 既然我们已经知道一旦放下武器后就会触发WeaponIsDown()函数,那么接下来我们拿回武器并播放一个重新装载弹药的动画。在这个极端,我们也需要处理开火类型状态的改变。

  simulated state WeaponSwitching
  {
     simulated function TrackShotCount();
     simulated function RefireCheckTimer();
  
     simulated function bool TryPutDown()
     {
        bWeaponPutDown = true;
        return true;
     }
  
     simulated function WeaponIsDown()
     {
        ClearTimer('WeaponIsDown');
        bLoaded = false;
        TimeWeaponEquipping();
        CurrentIndex++;
  
        if (CurrentIndex >= FireTypes.length)
           CurrentIndex = 0;
  
        AssignFireType();
     }
  
     simulated function BeginState(name PrevStateName)
     {
        TimeWeaponPutDown();
     }
  }
  

这里我们添加了针对这个状态的WeaponIsDown()函数。只要调用了这个函数,我们便清除和这个函数相关的计时器。设置bLoaded为假,因为在UTWeap_Enforcer中,当设置它为假时将会播放武器重新装载弹药的动画。然后我们调用TimeWeaponEquipping()函数,它和TimeWeaponPutDown()一样,因为它也将会播放一个动画并基于计时器触发一个函数。这个函数称为WeaponEquipped()。我们也会渐增CurrentIndex变量,来移动我们正在使用的开火类型的索引。当CurrentIndex的值大于等于FireTypes数组的长度时我们将会重置CurrentIndex。这将会为玩家创建一个供他们使用的循环选择系统。然后我们调用一个未定义的AssignFireType()函数。我在另一个地方完成了这个函数定义,因为我们可能需要在不同的地方基于CurrentIndex来处理分配开火类型。

14. 最后,一旦武器重新装载弹药动画播放完毕后,我们将再次返回到激活状态。如果我们不这样做,那么武器将永远地停留在这个状态,并且将不能再次开火。

  simulated state WeaponSwitching
  {
     simulated function TrackShotCount();
     simulated function RefireCheckTimer();
  
     simulated function bool TryPutDown()
     {
        bWeaponPutDown = true;
        return true;
     }
  
     simulated function WeaponIsDown()
     {
        ClearTimer('WeaponIsDown');
        bLoaded = false;
        TimeWeaponEquipping();
        CurrentIndex++;
  
        if (CurrentIndex >= FireTypes.length)
           CurrentIndex = 0;
  
        AssignFireType();
     }
  
     simulated function WeaponEquipped()
     {
        ClearTimer('WeaponEquipped');
        bLoaded = true;
        GotoState('Active');
     }
  
     simulated function BeginState(name PrevStateName)
     {
        TimeWeaponPutDown();
     }
  }
  

因此我们添加了一个新函数WeaponEquipped(),它简单地清除了和它相关的计时器,设置bLoaded为真(这样做是因为如果玩家切换到另一个武器然后又切换回这个武器,那么我们将不必再重新播放加载弹药动画了),然后在跳转到Active(激活)状态。

15. 现在我们应该定义AssignFireType()函数了。这个函数完成的功能是判断CurrentIndex是否可以用作为FireTypes中的有效索引,以及判断CurrentIndex指向的FireType是否有效。

  function AssignFireType()
  {
     if (CurrentIndex >= 0 && CurrentIndex < FireTypes.length && FireTypes[CurrentIndex] != none && FireTypes[CurrentIndex].WeaponClass != none)
     {
     }
  }
  

16. 我们进行那样的判断是很好的,但是为了获得任何真实的效果我们需要把它分配给我们的代理。

  function AssignFireType()
  {
     if (CurrentIndex >= 0 && CurrentIndex < FireTypes.length && FireTypes[CurrentIndex] != none && FireTypes[CurrentIndex].WeaponClass != none)
        OnProcessInstantHit = FireTypes[CurrentIndex].OnProcessInstantHit;
  }
  

记住,当第一次创建武器时,尽管我们的CurrentIndex是0,但是还没有为武器分配任何开火类型。因此,在我们的PostBeginPlay()函数中,我们也调用了AssignFireType()函数来完成这个功能。

  simulated function PostBeginPlay()
  {
     local int i;
  
     super.PostBeginPlay();
  
     if (FireTypeClasses.length > 0)
     {
        for (i = 0; i < FireTypeClasses.length; ++i)
           if (FireTypeClasses[i] != none)
              FireTypes.AddItem(new FireTypeClasses[i]);
     }
  
     AssignFireType();
  }
  

17. 现在我们已经完成了我们需要的所有逻辑。但是,我们还不知道怎么通知玩家武器应该设置为哪种开火类型。幸运的是,武器已经为我们提供了一些功能,它允许我们想HUD上描画东西。

  simulated function ActiveRenderOverlays(HUD H)
  {
     super.ActiveRenderOverlays(H);
  }
  

18. 为了让玩家知道选择了哪种开火类型,我们将描画一个那个开火类型模拟的武器的图标。我们描画这个图标的地方是弹药夹所在的地方。这意味着我们需要获得弹药夹的骨骼位置,把它投射到HUD上来获得有效的HUD坐标,然后我们渲染它。首先,我们需要第一人称网格物体的骨架网格物体组件。

  simulated function ActiveRenderOverlays(HUD H)
  {
     local SkeletalMeshComponent SkelMesh;
  
     super.ActiveRenderOverlays(H);
  
     if (H != none && H.Canvas != none)
     {
        SkelMesh = SkeletalMeshComponent(Mesh);
  
        if (SkelMesh != none)
        {
        }
     }
  }
  

19. 现在我们处理了估计网格物体,我们知道那个骨骼的位置名称是Bullet5(如果您启动虚幻编辑器并在动画编辑器中查看骨架网格物体,您会在那里找到那个骨骼的名称)并把它投射到HUD上。当描画图标时,通过基于分辨率进行百分比缩放计算最佳的宽度和高度可以为我们提供最好的结果。当基于先前计算的位置把图表居中后,然后我们便可以在屏幕上描画那个图标。Weapon类包含了图标的坐标,图标本身存储在一个合成的图标贴图中。

  simulated function ActiveRenderOverlays(HUD H)
  {
     local SkeletalMeshComponent SkelMesh;
     local vector BoneLocation;
     local float i_w;
     local float i_h;
     local color CanvasColor;
  
     super.ActiveRenderOverlays(H);
  
     if (H != none && H.Canvas != none)
     {
        SkelMesh = SkeletalMeshComponent(Mesh);
  
        if (SkelMesh != none)
        {
           BoneLocation = H.Canvas.Project(SkelMesh.GetBoneLocation('Bullet5', 0));
           i_w = H.Canvas.ClipX * 0.05f;
           i_h = H.Canvas.ClipY * 0.05f;
  
           CanvasColor = H.Canvas.DrawColor;
           H.Canvas.DrawColor =  class'UTHUD'.default.WhiteColor;
           H.Canvas.SetPos(BoneLocation.x - (i_w * 0.5f), BoneLocation.y - (i_h * 0.5f));
           H.Canvas.DrawTile(class'UTHUD'.default.IconHudTexture, i_w, i_h, FireTypes[CurrentIndex].WeaponClass.default.IconCoordinates.U, FireTypes[CurrentIndex].WeaponClass.default.IconCoordinates.V, FireTypes[CurrentIndex].WeaponClass.default.IconCoordinates.UL, FireTypes[CurrentIndex].WeaponClass.default.IconCoordinates.VL);
           H.Canvas.DrawColor = CanvasColor;
        }
     }
  }
  

指南 12.10 –武器设置器, 第三部分: MULTIENFORCER_BASE

基类本身并不是非常复杂。毕竟它仅是其它子类的基类而已。

1. 当UTWeap_MultiEnforcer想在屏幕上渲染图标时,您会注意到它询问武器类的开火类型。我们把它定义为一个全局常变量,以便子类可以在它们自己的默认属性中定义它。

  var const class<UTWeapon> WeaponClass;
  

2. 我们也需要定义一个UTWeap_MultiEnforcer使用的分配给它的代理的函数。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact);
  

指南 12.11 –武器设置器, 第四部分: MULTIENFORCER_BIO

这是生化开火类型。当子弹碰到任何东西时,它创建产生一个小的生化粘性物质的爆炸,并滴落一些生化粘性物质。

1. 首先,我们在默认属性中设置WeaponClass。

  defaultproperties
  {
     WeaponClass=class'UTWeap_BioRifle_Content'
     Name="Default__MultiEnforcer_Bio"
  }
  

2. 现在我们重载OnProcessInstantHit()函数。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
  }
  

3. 首先我们需要判断是否从碰撞中获得一个有效的HitActor。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     if (Impact.HitActor != none)
     {
     }
  }
  

4. 让我们从创建爆炸粒子特效开始。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local EmitterSpawnable es;
  
     if (Impact.HitActor != none)
     {
        es = Weapon.Spawn(class'EmitterSpawnable',,, Impact.HitLocation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_BioRifle.Particles.P_WP_Bio_Alt_Blob_POP');
     }
  }
  

我们使用Weapon来生成我们的EmitterSpawnable,因为仅可以在Actor中使用Spawn()函数而不能在Object中使用。

5. 然后加入爆炸声音。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local EmitterSpawnable es;
  
     if (Impact.HitActor != none)
     {
        Weapon.PlaySound(SoundCue'A_Weapon_BioRifle.Weapon.A_BioRifle_FireAltImpactExplode_Cue',,,, Impact.HitLocation);
        es = Weapon.Spawn(class'EmitterSpawnable',,, Impact.HitLocation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_BioRifle.Particles.P_WP_Bio_Alt_Blob_POP');
     }
  }
  

6. 接下来加入一些放射性伤害。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local EmitterSpawnable es;
  
     if (Impact.HitActor != none)
     {
        Weapon.PlaySound(SoundCue'A_Weapon_BioRifle.Weapon.A_BioRifle_FireAltImpactExplode_Cue',,,, Impact.HitLocation);
        es = Weapon.Spawn(class'EmitterSpawnable',,, Impact.HitLocation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_BioRifle.Particles.P_WP_Bio_Alt_Blob_POP');
  
        Weapon.HurtRadius(class'UTProj_BioGlob'.default.Damage, class'UTProj_BioGlob'.default.DamageRadius, class'UTProj_BioGlob'.default.MyDamageType, class'UTProj_BioGlob'.default.MomentumTransfer, Impact.HitLocation);
     }
  }
  

7. 最后我们生成一些生化粘性物质来完成这个开火类型。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local int g;
     local UTProj_BioShot NewGlob;
     local EmitterSpawnable es;
  
     if (Impact.HitActor != none)
     {
        Weapon.PlaySound(SoundCue'A_Weapon_BioRifle.Weapon.A_BioRifle_FireAltImpactExplode_Cue',,,, Impact.HitLocation);
        es = Weapon.Spawn(class'EmitterSpawnable',,, Impact.HitLocation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_BioRifle.Particles.P_WP_Bio_Alt_Blob_POP');
  
        for (g = 0; g < 6; ++g)
        {
           NewGlob = Weapon.Spawn(class'UTProj_BioGlobling',,, Impact.HitLocation);
  
           if (NewGlob != None)
              NewGlob.Velocity = (FRand() * 150.f) * (VRand() * 0.8f);
        }
  
        Weapon.HurtRadius(class'UTProj_BioGlob'.default.Damage, class'UTProj_BioGlob'.default.DamageRadius, class'UTProj_BioGlob'.default.MyDamageType, class'UTProj_BioGlob'.default.MomentumTransfer, Impact.HitLocation);
     }
  }
  

指南 12.12 –武器设置器, 第五部分: MULTIENFORCER_FLAK

这是一个高射炮开火类型。当子命中任何东西时,它创建一个高射炮炮弹爆炸并使得高射炮火到处飞行。

1. 首先我们在默认属性中设置WeaponClass变量。

  defaultproperties
  {
     WeaponClass=class'UTWeap_FlakCannon'
     Name="Default__MultiEnforcer_Flak"
  }
  

2. 现在我们重载OnProcessInstantHit()函数。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
  }
  

3. 首先我们需要判断我们是否从碰撞中获得一个有效的HitActor。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     if (Impact.HitActor != none)
     {
     }
  }
  

4. 我们首先添加爆炸粒子特效。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local EmitterSpawnable es;
  
     if (Impact.HitActor != none)
     {
        es = Weapon.Spawn(class'EmitterSpawnable',,, Impact.HitLocation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_FlakCannon.Effects.P_WP_Flak_Alt_Explosion');
     }
  }
  

我们使用Weapon来生成我们的EmitterSpawnable,因为仅可以在Actor中使用Spawn()函数而不能在Object中使用。

5. 然后我们加入要播放的音效。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local EmitterSpawnable es;
  
     Weapon.PlaySound(SoundCue'A_Weapon_FlakCannon.Weapons.A_FlakCannon_FireAltImpactExplodeCue',,,, Impact.HitLocation);
  
     if (Impact.HitActor != none)
     {
        es = Weapon.Spawn(class'EmitterSpawnable',,, Impact.HitLocation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_FlakCannon.Effects.P_WP_Flak_Alt_Explosion');
     }
  }
  

6. 接下来加入放射性伤害。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local EmitterSpawnable es;
  
     Weapon.PlaySound(SoundCue'A_Weapon_FlakCannon.Weapons.A_FlakCannon_FireAltImpactExplodeCue',,,, Impact.HitLocation);
  
     if (Impact.HitActor != none)
     {
        es = Weapon.Spawn(class'EmitterSpawnable',,, Impact.HitLocation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_FlakCannon.Effects.P_WP_Flak_Alt_Explosion');
     }
  
     Weapon.HurtRadius(class'UTProj_FlakShell'.default.Damage, class'UTProj_FlakShell'.default.DamageRadius, class'UTProj_FlakShell'.default.MyDamageType, class'UTProj_FlakShell'.default.MomentumTransfer, Impact.HitLocation);
  }
  

7. 最后,我们生成一些随机的高射炮块使它们到处飞行。为了完成这个功能,我们建立了一个小的迭代器,以便我们可以生成5个高射炮射弹。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local int i;
     local UTProj_FlakShard NewChunk;
     local EmitterSpawnable es;
  
     if (Impact.HitActor != none)
     {
        Weapon.PlaySound(SoundCue'A_Weapon_FlakCannon.Weapons.A_FlakCannon_FireAltImpactExplodeCue',,,, Impact.HitLocation);
  
        es = Weapon.Spawn(class'EmitterSpawnable',,, Impact.HitLocation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_FlakCannon.Effects.P_WP_Flak_Alt_Explosion');
  
        for (i = 0; i < 5; ++i)
        {
           NewChunk = Weapon.Spawn(class'UTProj_FlakShard',,, Impact.HitLocation);
  
           if (NewChunk != None)
           {
              NewChunk.bCheckShortRangeKill = false;
              NewChunk.Init((FRand() * 150.f) * (VRand() * 0.8f));
           }
        }
  
        Weapon.HurtRadius(class'UTProj_FlakShell'.default.Damage, class'UTProj_FlakShell'.default.DamageRadius, class'UTProj_FlakShell'.default.MyDamageType, class'UTProj_FlakShell'.default.MomentumTransfer, Impact.HitLocation);
     }
  }
  

指南 12.13 –武器设置器, 第六部分: MULTIENFORCER_ROCKET

这是火箭开火类型。当子弹命中任何东西时,它会产生非常强大的爆炸。

1. 首先我们在默认属性中设置WeaponClass。

  defaultproperties
  {
     WeaponClass=class'UTWeap_RocketLauncher'
     Name="Default__MultiEnforcer_Rocket"
  }
  

2. 接下来我们重载OnProcessInstantHit()函数。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
  }
  

3. 首先我们判断我们是否从碰撞中获得了有效的HitActor。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     if (Impact.HitActor != none)
     {
     }
  }
  

4. 产生爆炸粒子特效。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local EmitterSpawnable es;
  
     if (Impact.HitActor != none)
     {
        es = Weapon.Spawn(class'Engine.EmitterSpawnable',,, Impact.HitLocation, Rotator(Impact.HitNormal));
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_RocketLauncher.Effects.P_WP_RocketLauncher_RocketExplosion');
     }
  }
  

We use the Weapon to spawn our EmitterSpawnable because the Spawn() function is available within Actor and not Object. 我们使用Weapon来生成我们的EmitterSpawnable,因为仅可以在Actor中使用Spawn()函数而不能在Object中使用。

5. 接下来,我们将播放爆炸声音。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local EmitterSpawnable es;
  
     if (Impact.HitActor != none)
     {
        es = Weapon.Spawn(class'Engine.EmitterSpawnable',,, Impact.HitLocation, Rotator(Impact.HitNormal));
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_RocketLauncher.Effects.P_WP_RocketLauncher_RocketExplosion');
  
        Weapon.PlaySound(SoundCue'A_Weapon_RocketLauncher.Cue.A_Weapon_RL_Impact_Cue',,,, Impact.HitLocation);
     }
  }
  

6. 为了完成这个开火类型,我们需要进行一些放射性伤害。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     local EmitterSpawnable es;
  
     if (Impact.HitActor != none)
     {
        es = Weapon.Spawn(class'Engine.EmitterSpawnable',,, Impact.HitLocation, Rotator(Impact.HitNormal));
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_RocketLauncher.Effects.P_WP_RocketLauncher_RocketExplosion');
  
        Weapon.PlaySound(SoundCue'A_Weapon_RocketLauncher.Cue.A_Weapon_RL_Impact_Cue',,,, Impact.HitLocation);
        Weapon.HurtRadius(class'UTProj_Rocket'.default.Damage, class'UTProj_Rocket'.default.DamageRadius, class'UTProj_Rocket'.default.MyDamageType, class'UTProj_Rocket'.default.MomentumTransfer, Impact.HitLocation);
     }
  }
  

指南 12.14 –武器设置器, 第七部分: MULTIENFORCER_SHOCK

这是脉冲开火类型。当子弹命中任何东西时,它会产生一个强大的脉冲组合物爆炸。

1. 首先,我们在默认属性中设置WeaponClass。

  defaultproperties
  {
     WeaponClass=class'UTWeap_ShockRifle'
     Name="Default__MultiEnforcer_Shock"
  }
  

2. 接下来我们重载OnProcessInstantHit()函数。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
  }
  

3. 我们判断我们是否从碰撞中获得了有效的HitActor。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     if (Impact.HitActor != none)
     {
     }
  }
  

4. 产生组合物爆炸粒子特效。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     if (Impact.HitActor != none)
     {
        Weapon.Spawn(Class'UTGame.UTEmit_ShockCombo',,, Impact.HitLocation);
     }
  }
  

我们需要使用Weapon来生成列子特效,因为Spawn()是Actor类中的native函数,而不是Object的函数。Impact是个结构体,它包含了很多我们可以使用的有用信息。在这个例子中,我们使用了HitLocation。

5. 现在我们播放声效,无声的爆炸是非常无聊的。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     if (Impact.HitActor != none)
     {
        Weapon.Spawn(Class'UTGame.UTEmit_ShockCombo',,, Impact.HitLocation);
        Weapon.PlaySound(SoundCue'A_Weapon_ShockRifle.Cue.A_Weapon_SR_ComboExplosionCue',,,, Impact.HitLocation);
     }
  }
  

6. 为了完成这个开火类型,我们需要进行一些放射性伤害。

  function OnProcessInstantHit(UTWeapon Weapon, byte FiringMode, ImpactInfo Impact)
  {
     if (Impact.HitActor != none)
     {
        Weapon.Spawn(Class'UTGame.UTEmit_ShockCombo',,, Impact.HitLocation);
        Weapon.PlaySound(SoundCue'A_Weapon_ShockRifle.Cue.A_Weapon_SR_ComboExplosionCue',,,, Impact.HitLocation);
        Weapon.HurtRadius(class'UTProj_ShockBall'.default.ComboDamage, class'UTProj_ShockBall'.default.ComboRadius, class'UTProj_ShockBall'.default.ComboDamageType, class'UTProj_ShockBall'.default.ComboMomentumTransfer, Impact.HitLocation);
     }
  }
  

通过使用类的默认值,我们便节约了很多复制值的时间。

指南 12.15 –武器设置器, 测试

1. 启动一个即时作战的生死决战游戏。

2. 按下'Tab'键来打开控制台。从控制台中输入'giveweapon MasteringUnrealScript.UTWeap_Multienforcer'。这个命令将会给您一个武器。


图片 12.4 –控制台立即给予玩家一个期望的武器。

3. 现在您已经有了一个武器,您可以按下3键来切换到它。


Figure 12.5 –MultiEnforcer 武器现在处于激活状态。

4. 因为您已经选择了一个武器,我们可以看到有几个东西已经开始工作了。首先,出现了HUD图标来通知我们我们已经选择了火箭开火类型。可以开动武器来验证一下。


图片 12.6 –武器开火产生了火箭火焰类型。

5. 现在,我们测试开火类型切换功能。右击来切换到不同的开火类型。


图片 12.7 –武器切换了开火类型。

6. 太棒了,看上去我们切换到了脉冲开火类型。您可以启动武器验证它是否可以正常工作。


图片 12.8 –武器现在产生了脉冲开火类型。

在这个指南中您已经制作了一个可以允许玩家通过使用二级开火按钮来在不同的火焰类型间变换的武器。使用代理是完成这个功能的一种方法,但当然也有很多其它方法可以完成该功能。从这个例子中我们学习到的主要教训是当结合对象使用代理时确保清除到对象的引用是非常重要。

指南 12.16 –DELEGATES(代理) & KISMET, 第一部分: 简介 & 创建最初的类

在本指南中,我们将讨论如何在Kismet中使用代理。特别是,我们将为关卡设计人员创建一个特效生成器。Kismet逻辑允许关卡设计人员改变关卡运行时生成的特效。我们需要考虑的第一个事情是在这个强大的事物设计机制中如何实现这个功能。Kismet和关卡设计人员通常使用可放置的actors和画刷进行工作,因为我们或许应该避免使用关卡设计人员不能放置到关卡中进行调节的物体。因此,处理这个问题的最好的方法是使得特效生成器是可以放置在关卡中的,并且使得它的特效也是可以放置的。这可以帮助我们解决几个问题。关卡设计人员可以在定义了产生特效的位置和旋转值的某处放置一个单独的特效生成器。关卡设计人员可以把特效放置到地图中并调节特效来获得期望的效果。关卡设计人员是经常使用这种做事情的方法的。我们也需要书写一个Kismet准备节点,它允许关卡设计人员在特效生成器中设置代理。查看以下Kismet节点列表,还没有使用一个actor的Kismet节点,所以我们也需要制作它。到这里为止分析全部结束了,我们必须创建特效生成器类、特效基类、设置特效Kismet类、使用actor的Kismet类和我们要创建的特效的类。在这个指南中,我们将创建三个特效:爆炸、以成环的形式产生手榴弹(grenades)、gib(炸碎的血肉块)产生器。

1. 打开您最喜欢的文本编辑器,并在..\MasteringUnrealScript\Classes目录中创建新的UnrealScript脚本文件UTEffectsGenerator.uc、UTEffect.uc、SeqAct_SetEffect.uc、SeqAct_Use.uc、UTEffect_Explosion.uc、UTEffect_GrenadeRing.uc 和 UTEffect_Gibbage.uc。

2. 声明UTEffectsGenerator的类及它们的默认属性。

  class UTEffectsGenerator extends Actor
     placeable;
  
  defaultproperties
  {
     Name=”Default__UTEffectsGenerator”
  }
  

UTEffectsGenerator是可以放置的,因为我们希望关卡设计人员可以把它放到他们的关卡中。

3. 声明UTEffect的类及它们的默认属性。

  class UTEffect extends Actor
     abstract;
  
  defaultproperties
  {
     Name=”Default__UTEffect”
  }
  

UTEffect是抽象的,因为我们将要创建它的子类UTEffect_Explosion、UTEffect_GrenadeRing和UTEffect_Gibbage,因此关卡设计人员应该永远不应该产生或放置它们。

4. 声明SeqAct_SetEffect的类及它们的默认属性。

  class SeqAct_SetEffect extends SeqAct_SetSequenceVariable;
  
  defaultproperties
  {
     VariableLinks(1)=(ExpectedType=Class'Engine.SeqVar_Object',LinkDesc="Effect",PropertyName="Value",MinVars=1,MaxVars=255)
     ObjClassVersion=2
     ObjName="Set Effect"
     ObjCategory=”Effect Generator”
     Name=”Default__ SeqAct_SetEffect”
     ObjectArchetype=SeqAct_SetSequenceVariable'Engine.Default__SeqAct_SetSequenceVariable'
  }
  

SeqAct_SetEffect是SeqAct_SetSequenceVariable的子类,因为我们在Kismet中以同样的方式使用它。VariableLinks(1)指出了一个虚幻编辑器中的Kismet编辑器可以使用的链接元素。ObjName是将要出现在Kismet编辑器中的Kismet节点的名字。

5. 声明SeqAct_Use的类及它们的默认属性。

  class SeqAct_Use extends SequenceAction;
  
  defaultproperties
  {
     ObjName=”Use”
     ObjCategory=”Effect Generator”
     Name=”Default__SeqAct_Use”
     ObjectArchetype=SequenceAction'Engine.Default__SequenceAction'
  }
  

SeqAct_Use是SequenceAction的子类,因为我们在Kismet中以同样的方式使用它。ObjName是将要出现在Kismet编辑器中的Kismet节点的名字。ObjCategory用于对Kismet编辑器中的Kismet节点进行分类。

6. 声明UTEffect_Explosion的类及它们的默认属性。

  class UTEffect_Explosion extends UTEffect
     placeable;
  
  defaultproperties
  {
     Name=”Default_UTEffect_Explosion”
  }
  

UTEffect_Explosion是可以放置的,因为我们打算让关卡设计人员放置它。

7. 声明UTEffect_GrenadeRing的类及默认属性。

  class UTEffect_GrenadeRing extends UTEffect
     placeable;
  
  defaultproperties
  {
     Name=”Default__UTEffect_GrenadeRing”
  }
  

UTEffect_GrenadeRing是可以放置的,因为我们打算让关卡设计人员放置它。

8. 声明UTEffect_Gibbage的类及默认属性。

  class UTEffect_Gibbage extends UTEffect
     placeable;
  
  defaultproperties
  {
     Name=”Default__UTEffect_Gibbage”
  }
  

UTEffect_Gibbage是可以放置的,因为我们打算让关卡设计人员放置它。

指南 12.17 –DELEGATES(代理) & KISMET,第二部分: UTEFFECTSGENERATOR

1. 首先,我们声明特效生成器将要使用的代理函数。

  delegate OnGenerateEffects(UTEffectsGenerator InGenerator);
  

这个代理包含了一个参数,它存储着到UTEffectsGenerator的引用。在这个例子中,它将总是存放着到调用那个代理的UTEffectsGenerator的引用。这将允许其它类的实例首先访问调用这个代理的对象。

2. 现在我们已经有了代理函数,我们将需要书写一些逻辑来实际地调用它。

  function GenerateEffects()
  {
     OnGenerateEffects(self);
  }
  

我们把到OnGenerateEffects()的调用封装到GenerateEffects()中的原因实际是为了以后验证使用。尽管不能立即看出它有什么重要作用,但是如果您想获得一个禁用特效生成功能的方法或其它任何方法时这便有用了。这意味着我们仅需要调整GenerateEffects()即可,不必修改子类。

3. 现在我们需要一个调用GenerateEffects()函数的方法。在这个特定实例中,我们将使用存在于Actor. UsedBy()中的UsedBy()方法,当玩家按下Use按钮时便会调用UsedBy()方法。但在我们的实例中我们只需要一个通用的方法,以便Kismet可以和它进行交互。

  function bool UsedBy(Pawn User)
  {
     GenerateEffects();
     return super.UsedBy(User);
  }
  

因此,当玩家使用特效生成器时,该特效生成器将会调用GenerateEffects(),然后该方法将调用OnGenerateEffects()代理。

4. 现在我们需要一种方法来在任何我们喜欢的时候设置代理。

  function SetEffect(delegate<OnGenerateEffects> NewEffect)
  {
     OnGenerateEffects = NewEffect;
  }
  

从技术上讲,我把它封装到一个函数中是没有原因的,只是因为我可以从其它类中像访问变量那样来访问代理。但是,如果您把这个代理改为私有的或保护的代理时,那么您必须像那样修改所有使用那个代理的类。

5. 因为关卡设计人员将要把这个actor放到世界中,所以关卡设计人员需要可以看到特效生成器。因此,我们想默认属性中添加一些渲染组件,而这些组件仅能在编辑器中看到。

  defaultproperties
  {
     Begin Object Class=SpriteComponent Name=Sprite ObjName=Sprite Archetype=SpriteComponent'Engine.Default__SpriteComponent'
        HiddenGame=True
            AlwaysLoadOnClient=False
            AlwaysLoadOnServer=False
            Name="Sprite"
            ObjectArchetype=SpriteComponent'Engine.Default__SpriteComponent'
       End Object
       Components(0)=Sprite
  
     Begin Object Class=ArrowComponent Name=Arrow ObjName=Arrow Archetype=ArrowComponent'Engine.Default__ArrowComponent'
        ArrowColor=(B=255,G=200,R=150,A=255)
        Name="Arrow"
        ObjectArchetype=ArrowComponent'Engine.Default__ArrowComponent'
     End Object
     Components(1)=Arrow
  
     Name=”Default__UTEffectsGenerator”
  }
  

指南 12.18 - ELEGATES(代理) & KISMET, 第三部分: UTEFFECT

1. 我们需要做的第一件事是确保总是在这个类之前编译UTEffectGenerator。因为我们添加了到那个类(UTEffectsGenerator)定义的依赖。

  class UTEffect extends Actor
     dependson(UTEffectsGenerator)
     abstract;
  

2. 我们需要创建一个函数,使它作为所有子类的stub(桩)函数。这个stub(桩)函数也用作为定义在UTEffectsGenerator中的代理的重载代理。

  function Effect(UTEffectsGenerator InGenerator);
  

3. 接下来,我们创建一个函数,Kismet调用该函数来设置这种特效给特效产生器。

  function SetEffect(UTEffectsGenerator InGenerator)
  {
     if (InGenerator != none)
        InGenerator.SetEffect(Effect);
  }
  

正如前面所说的,代理赋值可以通过InGenerator.OnGenerateEffects = Effect的方式来完成。但是,那样做缺少了灵活度,因为如果把代理改变为私有或保护状态,那么这个类将不再有权访问它。所以,为了安全起见,我们使用了赋值函数。

4. 和UTEffectsGenerator的情况一样,因为关卡设计人员需要把这些特效放置到关卡中,所以我们需要向默认属性中添加一些渲染组件。

  defaultproperties
  {
       Begin Object Class=SpriteComponent Name=Sprite ObjName=Sprite Archetype=SpriteComponent'Engine.Default__SpriteComponent'
        HiddenGame=True
        AlwaysLoadOnClient=False
        AlwaysLoadOnServer=False
        Name="Sprite"
        ObjectArchetype=SpriteComponent'Engine.Default__SpriteComponent'
     End Object
     Components(0)=Sprite
  
     Name="Default_UTEffect"
  }
  

指南 12.19 –DELEGATES(代理) & KISMET, 第四部分: SEQACT_SETEFFECT

1. Kismet节点主要是通过native代码控制的。但是有一些事件,由native代码调用但也可以在UnrealScript中使用它们。在这个特例中,无论何时当代码被激活时,native代码调用名称为Activated()的事件。因此我们重载它来获得对它的访问权。

  event Activated()
  {
  }
  

2. 当在编辑器中把Kismet节点连接到用于存放到这些对象变量的引用的LinkedVariables(在每个VariableLinks中)时,这些对象变量应该包含了我们需要的UTEffectsGenerator和UTEffect的对象引用。所以,首先我们只要判断这些假设是否正确即可。

  event Activated()
  {
     if (VariableLinks.length >= 2 && VariableLinks[0].LinkedVariables.length > 0 && VariableLinks[0].LinkedVariables[0] != none && VariableLinks[1].LinkedVariables.length > 0 && VariableLinks[1].LinkedVariables[0] != none)
     {
     }
  }
  

在这个if语句中,我们首先判断了VariableLinks数组是否大于等于2。因为我们需要两个对象(UTEffectsGenerator and UTEffect),所以应该有两个VariableLinks存在。在每个VariableLinks中,LinkedVariables数组都应该大于零。最后,在LinkedVariables的第一个索引处应该有一个对象引用。

3. 因为LinkedVariables被存储为SequenceVariables,但实际上它应该是SeqVar_Objects,所以我们首先需要把我们找到的LinkedVariables类型转换为SeqVar_Objects。然后我们希望SeqVar_Objects包含了到UTEffectGenerators 和 UTEffect的引用。

  event Activated()
  {
     local SeqVar_Object sv_obj;
  
     if (VariableLinks.length >= 2 && VariableLinks[0].LinkedVariables.length > 0 && VariableLinks[0].LinkedVariables[0] != none && VariableLinks[1].LinkedVariables.length > 0 && VariableLinks[1].LinkedVariables[0] != none)
     {
        sv_obj = SeqVar_Object(VariableLinks[0].LinkedVariables[0]);
  
        if (sv_obj != none)
        {
        }
  
        sv_obj = SeqVar_Object(VariableLinks[1].LinkedVariables[0]);
  
        if (sv_obj != none)
        {
        }
     }
  }
  

正如这里所看到的,我们添加了一个局部变量来临时地存储类型转换的结果。这样我们才能判断类型转换是否有效,这样将会较少以后出现的问题(比如,如果关卡设计人员把我们不希望出现的东西放到链接中)。

4. 现在,我们已经把它们类型转换为SeqVar_Object了,然后我们可以尝试查找已经连接到这个Kismet节点上的UTEffectsGenerator 和 UTEffect的关卡实例了。我们创建两个局部变量存储到UTEffectsGenerator 和 UTEffect的引用,并且我们通过GetObjectValue()函数来从sv_obj中获得它们。GetObjectValue()返回一个对象引用,所以我们需要把这些转换为适当的类型。

  event Activated()
  {
     local SeqVar_Object sv_obj;
     local UTEffectsGenerator ut_effects_generator;
     local UTEffect ut_effects;
  
     if (VariableLinks.length >= 2 && VariableLinks[0].LinkedVariables[0] != none && VariableLinks[1].LinkedVariables[0] != none)
     {
        sv_obj = SeqVar_Object(VariableLinks[0].LinkedVariables[0]);
  
        if (sv_obj != none)
           ut_effects_generator = UTEffectsGenerator(sv_obj.GetObjectValue());
  
        sv_obj = SeqVar_Object(VariableLinks[1].LinkedVariables[0]);
  
        if (sv_obj != none)
           ut_effects = UTEffect(sv_obj.GetObjectValue());
     }
  }
  

5. 最后,我们有了UTEffectsGenerator 和 UTEffect的关卡实例。但是,在我们使用它们之前,我们最好判断一下这些引用是否真正地有效。因此,我们进行none检测,如果它们不是none,我们将告诉UTEffect的引用设置它的特效给UTEffectsGenerator的引用。我们通过调用UTEffect中的SetEffect()函数来完成这个过程,反过来它将会调用UTEffectsGenerator中的SetEffect()函数。

  event Activated()
  {
     local SeqVar_Object sv_obj;
     local UTEffectsGenerator ut_effects_generator;
     local UTEffect ut_effects;
  
     if (VariableLinks.length >= 2 && VariableLinks[0].LinkedVariables[0] != none && VariableLinks[1].LinkedVariables[0] != none)
     {
        sv_obj = SeqVar_Object(VariableLinks[0].LinkedVariables[0]);
  
        if (sv_obj != none)
           ut_effects_generator = UTEffectsGenerator(sv_obj.GetObjectValue());
  
        sv_obj = SeqVar_Object(VariableLinks[1].LinkedVariables[0]);
  
        if (sv_obj != none)
           ut_effects = UTEffect(sv_obj.GetObjectValue());
  
        if (ut_effects_generator != none && ut_effects != none)
           ut_effects.SetEffect(ut_effects_generator);
     }
  }
  

现在,我们完成了这个Kismet节点。

指南 12.20 –DELEGATES(代理) & KISMET, 第五部分: SEQACT_USE

1. 和SeqAct_SetEffect类似,我们再次重载Activated()事件。

  event Activated()
  {
  }
  

2. 和SeqAct_SetEffect类似,我们遍历我们的VariableLinks 和它们的LinkedVariables来查找和这个Kismet节点相连接的Actor实例。

  event Activated()
  {
     local SeqVar_Object sv_obj;
     local Actor a;
  
     if (VariableLinks.length >= 1 && VariableLinks[0].LinkedVariables.length > 0 && VariableLinks[0].LinkedVariables[0] != none)
     {
        sv_obj = SeqVar_Object(VariableLinks[0].LinkedVariables[0]);
  
        if (sv_obj != none)
        {
           a = Actor(sv_obj.GetObjectValue());
        }
     }
  }
  

3. 现在我们已经有了actor引用,我们进行none检测并调用UsedBy()函数。这是一个有用的Kismet节点并且任何实现那个函数的Actor都可以使用它。

  event Activated()
  {
     local SeqVar_Object sv_obj;
     local Actor a;
  
     if (VariableLinks.length >= 1 && VariableLinks[0].LinkedVariables.length > 0 && VariableLinks[0].LinkedVariables[0] != none)
     {
        sv_obj = SeqVar_Object(VariableLinks[0].LinkedVariables[0]);
  
        if (sv_obj != none)
        {
           a = Actor(sv_obj.GetObjectValue());
  
           if (a != none)
              a.UsedBy(none);
        }
     }
  }
  

4. 现在我们已经完成了我们需要的基类和Kismet节点,我们现在继续更加有趣的部分…创建特效!

指南 12.21 - DELEGATES (代理)& KISMET,第六部分: UTEFFECT_EXPLOSION

1. 首先,我们重载父类中的Effect函数。

  function Effect(UTEffectsGenerator InGenerator)
  {
  }
  

2. 对于这个特效来说,我们需要产生一个类似于火箭爆炸的爆炸粒子特效并播放爆炸声效。这个特效可以对某东西的定时爆炸工作的很好,比如坍塌的建筑物。所以,我们创建一个局部变量来保存到这个处理我们粒子生成的发射器的引用。

  function Effect(UTEffectsGenerator InGenerator)
  {
     local EmitterSpawnable es;
  }
  

3. 接着,我们生成我们的发射器并设置粒子系统模板。因为我们想在UTEffectsGenerator 所在处产生发射器,所以我们将使用UTEffectsGenerator的位置及旋转值。再次声明,在使用它之前,我们需要判断它是否为none。

  function Effect(UTEffectsGenerator InGenerator)
  {
     local EmitterSpawnable es;
  
     if (InGenerator != none)
     {
        es = Spawn(class'EmitterSpawnable',,, InGenerator.Location, InGenerator.Rotation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_RocketLauncher.Effects.P_WP_RocketLauncher_RocketExplosion');
     }
  }
  

4. 现在要完成我们需要的特效所要做的便是添加爆炸声效。

  function Effect(UTEffectsGenerator InGenerator)
  {
     local EmitterSpawnable es;
  
     if (InGenerator != none)
     {
        InGenerator.PlaySound(SoundCue'A_Weapon_RocketLauncher.Cue.A_Weapon_RL_Impact_Cue');
        es = Spawn(class'EmitterSpawnable',,, InGenerator.Location, InGenerator.Rotation);
  
        if (es != none)
           es.SetTemplate(ParticleSystem'WP_RocketLauncher.Effects.P_WP_RocketLauncher_RocketExplosion');
     }
  }
  

5. 第一个特效完成了。作为一个额外练习,您可以尝试使得声效和粒子系统都可以在编辑器中进行配置。

提示:您可以通过声明存储可编辑的声效和粒子系统的引用的全局变量来完成。

指南 12.22 –DELEGATES(代理) & KISMET, 第七部分: UTEFFECT_GRENADERING

在这个特效中,我们会从UTEffectsGenerator中以环状的方式抛出手榴弹。关卡设计人员可以配置每个手榴弹的步进角(step angle)。

1. 首先,我们声明一个编辑器全局变量。

  var(GrenadeRing) int Angle;
  

当关卡设计人员打开属性窗口时,这段代码将会把Angle放到GrenadeRing类别中。

2. 和以前一样,我们再次重载了父类的Effect函数。

  function Effect(UTEffectsGenerator InGenerator)
  {
  }
  

3. 因为我们希望在UTEffectsGenerator中使得产生的环状居中,所以我们进行none判断并判断Angele的值,因为关卡设计人员可能会输入我们不能使用的值。

  function Effect(UTEffectsGenerator InGenerator)
  {
     if (InGenerator != none && Angle > 0 && Angle < 65535)
     {
     }
  }
  

4. 设置声效。

  function Effect(UTEffectsGenerator InGenerator)
  {
     if (InGenerator != none && Angle > 0 && Angle < 65535)
     {
        InGenerator.PlaySound(SoundCue'A_Weapon_RocketLauncher.Cue.A_Weapon_RL_GrenadeFire_Cue');
     }
  }
  

5. 我们需要做的第一个事情是基于关卡设计人员设置的脚步长来生成环状物。因为将要抛出手榴弹,我们或许也可以在一个地方产生它们,并仅确保它们的速率使它们看上去是以环状的方式产生的一样。(如果您真的像这样做,也是可以的,您可以从环状物的起始位置开始产生手榴弹)。记住虚幻引擎中的最大旋转值是65535,所以我们添加一个计数到65535的for迭代器。每次迭代将增加角步长。

  function Effect(UTEffectsGenerator InGenerator)
  {
     local int i;
  
     if (InGenerator != none && Angle > 0 && Angle < 65535)
     {
        InGenerator.PlaySound(SoundCue'A_Weapon_RocketLauncher.Cue.A_Weapon_RL_GrenadeFire_Cue');
  
        for (i = 0; i < 65535; i = i + Angle)
        {
        }
     }
  }
  

6. 现在我们已经建立了迭代器,然后我们可以产生每个手榴弹并计算它的旋转值了。我们设置一个局部变量来存储每次迭代中的旋转值。在每次迭代中,我们仅需要改变rotator(旋转向量)的Yaw的值。我们也创建一个局部手榴弹变量来存储到我们产生的手榴弹的引用。

  function Effect(UTEffectsGenerator InGenerator)
  {
     local int i;
     local rotator r;
     local UTProj_Grenade grenade;
  
     if (InGenerator != none && Angle > 0 && Angle < 65535)
     {
        InGenerator.PlaySound(SoundCue'A_Weapon_RocketLauncher.Cue.A_Weapon_RL_GrenadeFire_Cue');
        r.Pitch = 0;
        r.Roll = 0;
  
        for (i = 0; i < 65535; i = i + Angle)
        {
           r.Yaw = i;
           grenade = Spawn(class'UTGame.UTProj_Grenade',,, InGenerator.Location, r);
        }
     }
  }
  

7. 最后,我们设置每个手榴弹的速度。通过使用我们的rotator(旋转向量)的值,我们把它转换为向量,并将该向量和一个浮点数相乘来获得速度值。设置浮点值增高或降低将会增加或降低手榴弹发射的远度。

  function Effect(UTEffectsGenerator InGenerator)
  {
     local int i;
     local rotator r;
     local UTProj_Grenade grenade;
  
     if (InGenerator != none && Angle > 0 && Angle < 65535)
     {
        InGenerator.PlaySound(SoundCue'A_Weapon_RocketLauncher.Cue.A_Weapon_RL_GrenadeFire_Cue');
        r.Pitch = 0;
        r.Roll = 0;
  
        for (i = 0; i < 65535; i = i + Angle)
        {
           r.Yaw = i;
           grenade = Spawn(class'UTGame.UTProj_Grenade',,, InGenerator.Location, r);
  
           if (grenade != none)
              grenade.Velocity = vector(r) * 300.f;
        }
     }
  }
  

现在,我们有了以环状形式抛出手榴弹的特效。

指南 12.23 - DELEGATES (代理)& KISMET, 第八部分: UTEFFECT_GIBBAGE

在这个特效中,我们将会生成很多从各个方向飞出的gib(炸碎的血肉块)。我们允许关卡设计人员控制产生的gib(炸碎的血肉块)的数量。

1. 首先,我们声明一个编辑器全局变量。

  var(Gibbage) int Amount;
  

This will put the Amount variable within the Gibbage category when the level designer opens the properties window. 当关卡设计人员打开属性窗口时,这段代码将会把Amount变量放到Gibbage类别中。

2. 和以前一样,我们重载了父类的Effect函数。

  function Effect(UTEffectsGenerator InGenerator)
  {
  }
  

3. 因为所有的gib(炸碎的血肉块)都是从UTEffectsGenerator实例中产生的,所以我们需要对其进行none检测。我们也要判断Amount值,以确保它大于0。

  function Effect(UTEffectsGenerator InGenerator)
  {
     if (InGenerator != none && Amount > 0)
     {
     }
  }
  

4. 我们需要设置一个迭代器,使它从零迭代到Amount值。

  function Effect(UTEffectsGenerator InGenerator)
  {
     local int i;
  
     if (InGenerator != none && Amount > 0)
     {
        for (i = 0; i < Amount; ++i)
        {
        }
     }
  }
  

5. 因为我们可以产生一系列的gib(炸碎的血肉块),所以我们可以设置一个函数来随机地选择gib(炸碎的血肉块)类供我们使用。创建一个称为GetRandomGibClass()的新函数,它将返回一个UTGib_Human类。

  function class<UTGib_Human> GetRandomGibClass()
  {
  }
  

6. 添加一个Switch语句,使用Rand(5)作为它的参数。我们这里使用5是因为有五种类型的人的gib(炸碎血肉块)可以使用。

  function class<UTGib_Human> GetRandomGibClass()
  {
     switch(Rand(5))
     {
     }
  }
  

7. 因为有5种类型的人的gib(炸碎血肉块)可以选择,所以根据随之值的不同可以返回这5个类。

  function class<UTGib_Human> GetRandomGibClass()
  {
     switch(Rand(5))
     {
        case 0:
           return class'UTGib_HumanArm';
  
        case 1:
           return class'UTGib_HumanBone';
  
        case 2:
           return class'UTGib_HumanChunk';
  
        case 3:
           return class'UTGib_HumanHead';
  
        case 4:
        default:
           return class'UTGib_HumanTorso';
     }
  }
  

8. 现在,我们生成一些gibs(血肉块)!我们定义了一个名称为gib的局部变量,以便它可以存放到我们刚刚创建的gib(血肉块)的临时引用。

  function Effect(UTEffectsGenerator InGenerator)
  {
     local int i;
     local UTGib_Human gib;
  
     if (InGenerator != none && Amount > 0)
     {
        for (i = 0; i < Amount; ++i)
        {
           gib = Spawn(GetRandomGibClass(),,, InGenerator.Location, InGenerator.Rotation);
        }
     }
  }
  

9. 在虚幻竞技场3中,gibs(血肉块)实际是通过使用物理引擎PhysX进行物理仿真的。这意味着它们使用某种真实的物理来到处地弹跳及和世界发生碰撞。这意味着我们将必须设置gib(血肉块)的速度并初始化它们,以便PhysX仿真它们。

  function Effect(UTEffectsGenerator InGenerator)
  {
     local int i;
     local UTGib_Human gib;
  
     if (InGenerator != none && Amount > 0)
     {
        for (i = 0; i < Amount; ++i)
        {
           gib = Spawn(GetRandomGibClass(),,, InGenerator.Location, InGenerator.Rotation);
  
           if (gib != none)
           {
              gib.Velocity = Vector(RotRand()) * RandRange(200.f, 400.f);
  
              if (gib.GibMeshComp != none)
              {
                 gib.GibMeshComp.WakeRigidBody();
                 gib.GibMeshComp.SetRBLinearVelocity(gib.Velocity, false);
                 gib.GibMeshComp.SetRBAngularVelocity(VRand() * 50.f, false);
              }
           }
        }
     }
  }
  

我们首先为gib(血肉块)设置一个随机速度,通过获取一个随机旋转值,并把该值和200-400之间的一个随机值相乘来实现。然后我们判断gib(血肉块)是否具有PhysX要求的网格物体组件。如果gib(血肉块)具有网格物体组件,那么我们将激活它,以便PhysX可以接管它并设置随机速度。SetRBLinearVelocity()设置对象的线性运行速度。SetRBAngularVelocity()设置对象的旋转速度。这样,我们便获得一个非常精细的飞溅的血肉块效果了。

指南 12.24 –DELEGATES(代理) & KISMET, 第九部分: 建立测试床

1. 编译脚本,启动虚幻编辑器。

2. 当已经加载虚幻编辑器时,打开DM-Chapter12-Kismet。正如您看到的,它仅是一个小的梗概关卡,它具有几个装饰性的静态网格物体、几个光源和一个player start。


图片 12.9 –DM-CH_12_Kismet 地图.

3. 当加载完地图后,然后我们需要把那些类放到我们的代码包中,以便我们可以想地图中添加新的类(UTEffectsGenerator、 UTEffect_Explosion、 UTEffect_Gibbage、 UTEffect_GrenadeRing)。这些类应该会自动地出现在Actor浏览器中。如果没有出现,我们则需要加载脚本包。要想完成这个操作,您需要打开Generic Browser(通用浏览器),跳转到Actor Classes(Actor类别)标签,点击File(文件)然后点击Open(打开)。这将会弹出一个文件对话框,您可以从中选择您要打开的包。


图片 12.10 –File(文件)->Open(打开)命令允许您加载脚本包。

当已经加载了脚本包后,您应该会在Acrtor Browser(Actor浏览器)中看到这些新的类。


图片 12.11 –在Actor浏览器的类别树种出现了那些新类。

4. 现在我们可以选择地图中的某处并放置我们的UTEffectGenerator了。我们把这个网格物体放在右侧房间的地板中间处。然后把我们的UTEffectsGenerator恰好放在它的上方。在ActorBrowser(Actor浏览器)中选择UTEffectsGenerator,然后右击关卡视口中您想放置它的地方。在关联菜单中,点击'Add UTEffectsGenerator Here(添加UTEffectsGenerator到这里)'菜单项。


图片 12.12 –把UTEffectGenerator 添加到地图中。

5. 新添加的UTEffectsGenerator应该放在了那个像管子一样的网格物体上方。


图片 12.13 –UTEffectGenerator actor的放置。

6. 我们将需要调整UTEffectsGenerator的rotation(旋转值)属性。现在选中它,按下F4键来打开它的属性窗口。


图片 12.14 –调整了UTEffectGenerator 的Rotation (旋转值)属性。

7. 接下来我们需要在关卡中添加我们的UTEffect实例。我们把它放到关卡的角落上,以便我们可以很容易地找到它们。按照和添加UTEffectsGenerator一样的方式来添加它们,从Actor浏览器中选择每个实例然后右击关卡,通过关联菜单来添加它们。


图片 12.15 –UTEffect actor放在了关卡几何体的外面。

8. 在这个实例中,我们需要一个物理体积(Physics Volume)来触发我们的UTEffectsGenerator。首先,我们设置一个构建画刷来覆盖和我们门口一样的区域。这些设置应该很好地覆盖门口。


图片 12.16 –红色构建画刷的放置。

9. 现在我们已经把构建画刷放在了我们期望的位置。从左侧菜单中点击'Add Volume(添加体积)'按钮,然后选择'PhysicsVolume'来把它添加到地图中。


图片 12.17 –从Add Volume (添加体积)菜单中选择了PhysicsVolume 。

10. 现在我们的地图中已经有了一个物理体积。通过点击它来选中它。如果您在3D视口中选中它们感觉比较困难,可以切换到2D视口中,来从那里选择它。


图片 12.18 –选中PhysicsVolume (物理体积)

11. 通过点击虚幻编辑器主工具条上的Kismet按钮打开Kismet。您现在应该看到Kismet编辑器了。


图片 12.19 –Kismet编辑器。

12. 右击它弹出关联菜单。我们使用Physics Volume(物理体积)创建一个Touch事件(记住在进行这个操作时选中物理体积)。这将会创建一个Kismet节点,当接触Physics Volume(物理体积)时便会触发这个Kismet节点。该节点对两个特定的引擎事件作出响应,它们是touched(接触)和untouched(未接触)。当actor的碰撞在接触了它拥有者的碰撞时触发Touched(接触)事件。相反则触发Untouched(未接触)事件。一旦调用了Touched(接触) 和Untouched(未接触)事件,那么actors将不会继续接触另一个actor。


图片12.20 –创建了Touch事件。

13. 我们需要对Touched事件节点做一些调整。默认情况下,它设置MaxTriggerCount为1,意味着它仅能被触发一次,然后它便会禁用它本身。在这个特定实例中,我们像是它是一直可以触发的。在底部左下角处,有一个每个选中节点的属性部分。如果您向下滚动,您会看到一个MaxTriggerCount值,设置这个值为0。


图片 12.21 –设置MaxTriggerCount 为0。

14. 当已经接触物理体积时我们需要做一些事情。我们创建一个使用一些东西的Action节点。这实际上是我们先前创建的SeqAct_Use节点。再次浏览整个关联菜单,从Effect Generator类别中添加Use动作节点。


图片 12.22 –添加了Use动作节点。

15. 现在我们从其中一个关卡视口中选择我们的UTEffectsGenerator。


图片 12.23 –选中了UTEffectGenerator 。

16. 返回到Kismet编辑器窗口,我们可以通过在Kismet中右击工作区并选择New Object Var Using UTEffectGenerator_0(使UTEffectGenerator_0用新建对象变量)来添加到选中的UTEffectsGenerator引用。

注意:实际的actor的名称在地图中可能变化的。


图片 12.24 –已经创建了UTEffectGenerator 对象变量。

17. 现在我们把所有的东西连到一起。点击并拖拽PhysicsVolume_0 Touch节点的右侧的Touched的黑色方框,把它和Use节点的左侧的In的黑色方框相连接。然后点击并拖拽Use节点底部的Target的紫色方框,把它连接到UTEffectsGenerator_1上。这告诉Kismet当接触了Physics Volume(物理体积)时触发Use节点,然后Use节点将会使用UTEffectsGenerator_1。


图片 12.25 –已经完成了适当的连接。

*18.*这将会处理我们需要的基本动作。但是我们还需要处理把特效附加到UTEffectsGenerator上的过程。因为我们有三个特效,所以我们想随机地把它们附加到UTEffectsGenerator上。我们通过使用一个Random(随机)开关节点来选择每次附加哪个特效。右击打开关联菜单,通过选择Add Action(添加动作)->Switch->Random来添加Random开关节点。


图片 12.26 –已经添加了Random Swicth 节点。

19. 我们需要改变random switch(随机开关)节点的属性。看一下左下角,滚动并把LinkCount的值从1改为3。


图片 12.27 –设置LinkCount 为3。

20. 右击打开关联菜单,从Add Action(添加动作) 下的Effect Generator(特效生成器)类别中添加三个Set Effect (设置特效)Kismet节点。这实际上使我们先前制作的SeqAct_SetEffect。添加完三个Set Effect(设置特效)Kismet节点之后,把它们连接到Random开关Kismet节点上,和之前的方法一样。


图片 12.28 –把Set Effect 动作连接到Random Switch上。

21. 现在我们把其他的东西也连接起来。所有的Set Effect Kismet节点都需要连接到UTEffectsGenerator上,以便它们知道把特效设置到哪个UTEffectsGenerator上。同时,把Use Kismet节点的Out端连接到Random Kismet的In端。因此,当触发了Use Kismet节点时,然后它将接着触发Random Kismet节点,而该节点又会触发其中一个Set Effect Kismet节点。


图片 12.29 –制作其它的连接。

22. 返回到关卡视口,并选择其中一个我们放置到关卡中的UTEffects。当选中其中一个时,把它作为一个对象添加到Kismet中,按照和先前添加UTEffectsGenerator一样的方式。右击打开关联菜单,那么您选中的那个UTEffect便会出现在关联菜单中。


图片 12.30 –创建了UTEffect 对象变量。

23. 对其它两个UTEffects重复这个过程。一旦把三个特效都放在了Kismet编辑器窗口中,把它们每个分别和Kismet相连接。


图片 12.31 –三个UTEffect 变量都连接到了Set Effect 动作上。

24. 现在我们已经完成了大部分逻辑。但是,当第一次加载关卡时,还没有为UTEffectsGenerator分配UTEffect。所以,我们先修复这个问题。右击打开关联菜单,并创建一个新的event(事件)Kismet节点,当加载完关卡并且关卡可见时则触发这个事件。


图片 12.32 –已经添加了Level Loaded and Visble(关卡加载并可见) 事件。

25. 现在我们需要做的便是把Level Loaded And Visible Kismet节点和Random Kismet节点相连接。我们这样做是因为当关卡加载时我们仅需要为UTEffectsGenerator设置一个特效而不需要做其它事情。


图片 12.33 –把新的事件连接到Random Switch 上。

26. 这便是我们需要针对Kismet做作的一切。现在我们需要测试所有东西。按下主工具条上的Build All(构建所有)按钮来构建整个关卡。

27. 一旦完成关卡构建,点击PIE按钮来启动一个小的窗口,在这里您可以测试您的关卡。

28. 穿过您放置到关卡中的体积。您应该看到其中的一个特效。这说明它是有效的!


图片 12.34 –激活了其中一个特效。

29. 因为我们在UTEffect_Gibbage 和 UTEffect_GrenadeRing中创建了可编辑变量,它们都显示在虚幻编辑器中。通过使用这些变量,关卡设计人员可以修改特效的工作方式。通过暴露类似于这样的变量,您的特效对于程序员来说将变得更加灵活。您可以随意地返回到那里,调整这些变量来创建不同的特效。


图片 12.35 –Gibbage(血肉块)和Grenade(手榴弹)特效的可编辑属性。

在本指南中,我们学习了如何一同使用delegate(代理)和Kismet。因为Kismet的方法是更加具有面向对象的特性,事实上绑定到一个实例中的函数代理使得这个示例按照它的方式工作。这是很重要的,因为我们想允许关卡设计人员不仅可以通过特效生成器来改变产生哪种特效,而且我们希望提供一种比较简单使用的方法来调整每种特效。尽管有很多方法完成这个功能,但是delegate(代理)不仅使得这个任务变得简单而且这种方式也非常灵活。

12.10 –总结

在这章中,我们进一步深入地学习了delegate(代理),我希望您已经从本章中学习了一些东西。一般当代码的执行需要发生很多改变时或者您仅需要书写一些特别灵活的代码时可以使用代理。Delegates(代理)是UnrealScript中的一个有用的工具,可以把它作为完成某种特定类型人物的可行方法。在虚幻竞技场3中,它们大多数应用于GUI,它可以简单地允许第三方改变特定事件的行为。正如这章的指南中演示的,delegate(代理)几乎在任何地方都很有用。

不幸的是,代理的使用在很大程度上都依赖于经验。尽管这种方法比其它几个方法更加简洁,但是因为虚幻引擎必须判定一个代理映射到那个函数上,所以这是处理事情时一种相对较慢的方法。

附加文件