Ⅰ,Simple keypress detection in UDK using Kismet
UDK中最简单的输入实现
UDK中处理消息相应,可以自行编辑,处理相应的UScript,
但这里,我们首先从最简单的开始
Well, there are a lot of tutorials here, I thought I would donate one of mine. It's up on my blog, but being nice, here it is:
1. Close UDK.
关闭UDK
2. Open \UDK\(version)\UTGame\Config\UTInput.ini
打开UTGame,Config中的UTInput.ini文件;
3. ctrl+F, enter the key you want to edit. (in my case, it was the E key)
Ctrl+F,找到你想要响应的键位输入,如E
4. You should find something like this:
找到下代码,这里即是你想要处理的位置
Bindings=(Name="E",Command="GBA_Use")
whereas the Name is the key you want,
5. Append the Command text with " | causeevent something you will remember" (I used epress for that)
加入响应的指令
6. It should look like this:
如下所示
Bindings=(Name="E",Command="GBA_Use | causeevent epress")
7. In kismet, right-click on the grey area. Scroll over Misc and click on "Console Event"
打开UDK,在Kismet的空白区域添加Misc(杂项)中的"Console Event"消息
8. Under console event's settings, check "Client Side Only?"
在设置面板中选择"Client Side Only"
9. Replace the None in console event name to the "something you will remember" (so I put, epress there.)
输入前面取的指令名字
10. Connect out with what you want done once the key is pressed.
Done. Now when you build, it should do what you connected it to once you press the key.
在Kismet加入相应的事件响应
Troubleshooting:
Q: There is no binding thing for my key
A: Create one in this format:
Bindings=(Name="KEYLETTER",Command=" causeevent Thatlongassvariable")
Q: Nothing happens:
A: 1. Make sure you close UDK and open the UDK after saving the .ini file.
2. make sure you saved the .ini file
3. Make sure the letter is correct (try other things, such as E)
4. Make sure what the Console Event is connected to actually does something. Test this by connecting the level load event to it.
如无法响应,请确保:
1, 保证UDK在编写Ini、后完成了重启;
2, 保证Ini文件已经保存;
3, 保证输入了正确的字符;
4, 确保消息响应存在。
Ⅱ,UDK中脚本输入处理
(注,新版本脚本有所不同,这里仅作参照)
首先,我们打开UDK的安装目录下面的Development\Src\Engine\Input.uc文件。这里面处理了用户的所有输入。那么下面从代码开始。
struct native KeyBind
{
var config name Name;
var config string Command;
var config bool Control,
Shift,
Alt; /** if true, the bind will not be activated if the corresponding key is held down */
var config bool bIgnoreCtrl, bIgnoreShift, bIgnoreAlt; structcpptext
{
FKeyBind()
: Name()
, Control(FALSE), Shift(FALSE), Alt(FALSE)
, bIgnoreCtrl(FALSE), bIgnoreShift(FALSE), bIgnoreAlt(FALSE)
{}
}
}; var config array<KeyBind> Bindings;
这是声明在脚本中的一个结构体,我们在DefaultInput中配置的其实就是这个结构体。那么,看看DefaultInput.ini中的配置吧:
.Bindings=(Name="TurnRight",Command="Axis aBaseX Speed=+1.0")
.Bindings=(Name="StrafeLeft",Command="Axis aStrafe Speed=-1.0")
.Bindings=(Name="StrafeRight",Command="Axis aStrafe Speed=+1.0")
.Bindings=(Name="W",Command="MoveForward")
.Bindings=(Name="S",Command="MoveBackward") .Bindings=(Name="D",Command="TurnRight")
.Bindings=(Name="Q",Command="StrafeLeft")
.Bindings=(Name="E",Command="StrafeRight")
这和其它Config类型的处理并没有什么不同,这样的.Bindings只是往脚本中Bindings数组中添加一个结构体元素罢了。
在Unreal中每一个按键包括字母、数字、鼠标等等都有对应的名称,比如当我们按下鼠标右键的时候,系统将会自动在Bindings中搜索鼠标右键的名称对应的Command,Command是一个字符串,然后将这个字符串交给Actor的Exec函数来处理。Exec函数会分析字符串的内容然后做相关处理。这是通过下面两个函数来实现的:
native function string GetBind(const out Name Key);
void ExecInputCommands(const TCHAR* Cmd,class FOutputDevice& Ar);
你可以在Input.uc中发现它们。我们甚至可以写出伪码(GetBind是natve函数):
string command = GetBind( Key )
if( isvalid(command) )
ExecInputCommands( command, GlobalLog )
else
super handler.
end if.
这样处理比直接将某个按键和某个功能直接绑定灵活的多,当然付出的代价就是效率会降低。
让我们PlayerInput.uc,也在Engine下面,它是Input的派生类。在其中我们可以看到一种用input声明的变量,如:
// Input axes.
var input float aBaseX;
var input float aBaseY;
var input float aBaseZ;
var input float aMouseX;
var input float aMouseY;
var input float aForward;
var input float aTurn;
var input float aStrafe;
var input float aUp;
var input float aLookUp; // analog trigger axes
var input float aRightAnalogTrigger;
var input float aLeftAnalogTrigger; // PS3 SIXAXIS axes
var input float aPS3AccelX;
var input float aPS3AccelY;
var input float aPS3AccelZ;
var input float aPS3Gyro;
这些input标签将会为该变量赋予新的含义,我们可以直接在Defaultinput.ini中配置对它们的操作。比如.Bindings=(Name="TurnRight",Command="Axis aBaseX Speed=+1.0")
那么当我们触发TurnRight事件的时候,aBaseX就会改变,而变化的速度就是1.0。
它是如何做到的呢?
首先可以看到Input.uc中声明了这样的两个变量:
var native const Map{FName,void*} NameToPtr;
var native const init array<pointer> AxisArray{FLOAT};
这两个就保存了带有input标签的变量的地址,它们两个保存的内容并不相同。AxisArray中保存的是所有的带input标签且是float变量的元素,它们每个tick都会被设置成0.
cpptext部分中还有两个函数:
BYTE* FindButtonName(const TCHAR* ButtonName);
FLOAT* FindAxisName(const TCHAR* ButtonName);
这两个函数将会在必要的时候反射出所有的带有input标签的变量,并把它们的地址保存下来,这样当我们看到对应的Command到来的时候就可以处理了。下面来分析一个例子:
Bindings=(Name="MouseX",Command="Count bXAxis | Axis aMouseX")
这段配置可以在BaseInput.ini中找到,它对应的是鼠标移动。每次系统Tick的时候,底层都会去检查我们的鼠标是否移动了,如果是,那么就会进入鼠标移动处理的流程。这种移动是二维的,也就是X、Y两个方向。而鼠标X轴移动的名称是固定的,那就是MouseX。除了MouseX之外,系统还定义了MouseY、MouesScrollUp、MouseScrollDown、LeftMouseButton、RightMouseButton、MiddleMouseButton、Tab、Enter、BackSpace、Escape、CapsLock、PageUp、PageDown、End、Home、Left、Right、Down、Zero、One、Two、Three、Four、.. Nine、A、B、C、D..Z、NumPadZero..NumPadNine、Multiply、Add、Subtract、Decimal、Divide、F1、F2...F12、Semicolon、Equals、Comma、Underscore、RightShift等等等的名称。这些你都可以在配置文件中使用,具体还有什么大家可以看UDN上面的文档这里就不墨迹了(其实已经很墨迹了,⊙﹏⊙b汗)。
继续说处理流程,那么MouseX的名称将会用于GetBind函数,它将会返回我们的命令"Count bXAxis | Axis aMouseX",这两个命令实际上两个不同的命令的集合。
我们可以找到aMouseX和bXAxis的定义:
var input byte bXAxis;
var input float aMouseX;
这两个变量定义在PlayerInput.uc中。Count命令将会导致系统为PlayerInput对象的bXAxis加1。而Axis命令的处理相对复杂一点,首先发现是Axis命令,那么系统就知道这下要更新鼠标轴的状态了。那么它会寻找几个附加的命令来更新这个aMouseX属性。这几个附加的属性是:
Speed Invert DeadZone AbsoluteAxis。
我们的这个例子并没有用到这些附加属性,所以它们都将采用系统的默认值。而上面着红色的配置就使用了这些属性。刚才我说过了,带input标签的变量的实际地址都会被保存下来,所以可以通过这种方式得到aMouseX变量在内存中的位置。看看Input.uc中有个函数叫:
virtual void UpdateAxisValue( FLOAT* Axis, FLOAT Delta );
这就是更新这个值专用的函数。其内部可以简单的表示为*Axis += Delta。当然,PlayerInput重载了这个函数添加了自己的一些处理。
实现这样的自动更新完全依赖于Unreal3强大的反射系统,这带来了无与伦比的灵活性,当然,代价也是效率上的付出。
正是因为Unreal3的所有输入都会先转换成Command然后再交给Exec函数来运行,而Exec又可以将这个Command视为另外一个事件来处理,这就带来了另外一种灵活性,比如说上面的例子(我着色为红色),其实TurnRight其实并不是一个按键,但是它是另外一个Input的输出,所以这样就形成了一个嵌套的结构。这又是如何做到的呢?
如下面这个例子:
Bindings=(Name="BackSpace",Command="Jump")
.Bindings=(Name="P",Command="BackSpace")
比如我们按下P,那么系统首先会去Bindings下面搜索Command,得到的Command字符串为BackSpace,首先系统将BackSpace这个字符串视为一个普通的命令或者函数进行Exec,如果我们没有其它某个函数叫BackSpace(比如脚本中有exec function BackSpace()),那么系统将会最终将"BackSpace"视为一个新的Bindings的事件,然后去Bindings中搜索,那么如上面的例子将会得到Command为Jump,那么Jump就是跳跃,这样一个嵌套的处理流程就实现了。
现在我们应该大概知道Unreal3对输入处理的大概流程了吧~~让我们放手去做吧~~