UDK – Input commands

In this paper, we’ll have a look at controls and input commands. 

In other words, we’ll learn how to define game actions, and map them to buttons (or keys). 

As there is unfortunately no hint of documentation on the topic on UDN and most of the system’s mechanics

 are hidden somewhere in native code, what’s written below is the result of experimentation.

When reading through the list of UnrealEngine 3 Exec functions, we can notice something interesting. 

We see functions called Jump, StartFire, Use, etc. Which strangely sounds like things we can do by pressing the right buttons/keys.

And that’s basically how input commands work. They’re just exec commands. 

This means that with a little bit of imagination, we could do a text-based game playable from the console.

Command Bindings

We now know that exec functions are called when we do something with our controller.

 All we need to do now is bind the functions to the controller keys. However, there is an intermediate step we must complete to make things properly.

Game Bindable Actions (GBAs)

“The Unreal Way” consists in defining all the actions in the same place, and then assigning keys to theses actions. 

These are defined in the DefaultInput.ini file, and look like this (don’t forget to read the UDN page on config files):

1
.Bindings=(Name="GBA_Use",Command="use")

Simple as that. You give it a name (according to conventions, you should prefix it with 

“GBA_”, and append it with “_Gamepad” if you need a slightly different version for that kind of controller), 

and specify the name of an exec function. Of course, you can get more complex than that.

If the exec function accepts parameters, you can pass them like this:

1
.Bindings=(Name="GBA_SwitchWeapon2",Command="switchweapon 2")

You can also call several function at the same time, by placing a pipe “|” between each function:

1
.Bindings=(Name= "GBA_Jumping_Fire" ,Command= "StartFire | Jump" )

Note that ALL the functions will be called at the same time, so don’t go calling contradictory functions.

With these bindings, the function is called when the key is pressed,

 i.e. it won’t be called again until you release the button and press again. 

If you need a function to be called when the key is released add the “OnRelease” keyword before the name of the function.

 if you need to implement a “hold button” type of command, you must call a function that will start a behaviour on press, 

and another function that will stop it on release, pretty much like the weapon firing system:

1
.Bindings=(Name="GBA_Fire",Command="StartFire | OnRelease StopFire")

Analogic input

A bit of a special case here. When you have to handle analogic input, you don’t call an exec function.

 Instead, you set a value in the PlayerInput class (which we’ll talk about a little bit more further down), then you use these values wherever you need to.

The following code is taken from PlayerInput.uc (UDK November 2010) and shows the list of variables you can use to store analog information:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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;

Now don’t get your hopes up, even if you can actually map to the last four variables,

 you can’t plug in your Dual Shock 3 on PC and use the Sixaxis sensors (at the very least, not without sacrificing another input axis. I tried.)

If you really need your own variable, you can declare in your own PlayerInput subclass a float variable with the “input” specifier.

 This will allow you to map an axis to that variable.

Below is the default axis mapping for the Xbox 360 gamepad, or to be more accurate, XInput “powered” controllers:

udk_xbox_axes

Note that by default, the triggers are treated as normal buttons.

The values provided by the controller are as follows:

  • Joysticks: Return values in the [-1,1] range, from left to right, and from down to up.
  • Triggers: Return 0 if released or in the dead zone, then a value in the [1,2] range, depending on how much you press.
  • Buttons (and keyboard keys): Return 0 if released, 1 if pressed.

Now, to assign an axis to one of these float values above, the command is:

Axis VariableName [Speed=float_value] [AbsoluteAxis=float_value] [DeadZone=float_value]

  • VariableName: must be an input float variable.
  • Speed: basically a multiplier of the value returned by the controller. Negative values are fine. If this value is not specified, a speed of 1.0 will be assumed.
  • DeadZone: well, the size of the dead zone (the point until which the stick/trigger is considered untouched, because a stick in rested position is never at 0 sharp)
  • AbsoluteAxis: this one is quite mysterious. From the various tests I’ve conducted,
  •  it seems to be a percentage value by which the input value will be multiplied (on top of the Speed multiplication).
  •  However, with an AbsoluteAxis value of 100, a joystick fully pressed returns 1.16 instead of 1. That, and I actually can’t figure out the point of this setting.

Example:

1
.Bindings=(Name="GBA_MoveForward",Command="Axis aBaseY Speed=1.0 DeadZone=0.2")

Unless I missed something, you can’t call exec functions with joysticks (except by clicking them),

 whereas you can with triggers.

A note on contextual actions

As you can’t (as far as I know) change the bindings during the game,

 if a button can have several uses depending on the context, you need to plan everything beforehand. For instance:


1
.Bindings=(Name="GBA_Jump",Command="Jump | Axis aUp Speed=+1.0 AbsoluteAxis=100")


Even though the aUp axis isn’t actually used for making the pawn jump, it’s still permanently set, because in UT,

 when the pawn is swimming, or you’re in a flying vehicle, you use the jump button to go up. Likewise,

 the code behind the jump function eventually covers for double jump situations.

Binding buttons to GBAs

For instance:


1
2
3
4
5
6
7
8
9
10
11
12
.Bindings=(Name="XboxTypeS_Start",Command="GBA_ShowMenu")
.Bindings=(Name="XboxTypeS_LeftX",Command="GBA_StrafeLeft_Gamepad")
.Bindings=(Name="XboxTypeS_LeftY",Command="GBA_MoveForward_Gamepad")
.Bindings=(Name="XboxTypeS_RightX",Command="GBA_TurnLeft_Gamepad")
.Bindings=(Name="XboxTypeS_RightY",Command="GBA_Look_Gamepad")
.Bindings=(Name="XboxTypeS_RightShoulder",Command="GBA_NextWeapon")
.Bindings=(Name="XboxTypeS_RightTrigger",Command="GBA_Fire")
.Bindings=(Name="XboxTypeS_LeftShoulder",Command="GBA_WeaponPicker")
.Bindings=(Name="XboxTypeS_LeftTrigger",Command="GBA_AltFire")
.Bindings=(Name="XboxTypeS_RightThumbstick",Command="GBA_SwitchToBestWeapon_Gamepad")
.Bindings=(Name="XboxTypeS_A",Command="GBA_Jump_Gamepad")
.Bindings=(Name="XboxTypeS_B",Command="GBA_ToggleMelee")


It’s the exact same syntax as for creating GBAs. Actually you could directly assign a button to a command, 

but that’s not very neat as several buttons can point to the same action.

 See the DefaultInput.ini file for the list of the buttons names. 

Note that in the example above, “XboxTypeS_RightThumbstick” is when you click the stick (the L3/R3 buttons, in the Playstation naming scheme).

Processing input

Exec commands

As the documentation on exec function tells us, when an exec command is called, 

the engine is looking in various places for a function matching the name, in a specific order.

 Making the function itself is relatively easy (depending on what you want to do). It’s just a matter of putting the function in the right place.

 Mostly, it’s a matter of common sense. In most situations, gameplay-related commands will go in the PlayerController or the PlayerInput, 

most GUI-related ones will go in the HUD, and so on.

PlayerInput class

This is an helper class, tied to the PlayerController, which is in charge of storing input data sent by the player, and possibly processing it. 

There’s basically only one function you should care about in this class is the PlayerInput() event.

 It’s the place where you’ll do all your input-only related code,

 such as checking for double click (by default, only supports double click on movement,

 for UT’s dodge mechanic), “special moves”, etc. I can’t really elaborate on the subject here as it’s highly dependent on your game.

You’ll note that the raw input values are saved in some buffer variables,

 because the function is going to modify to bound variables (don’t know why we’re not doing it the other around, though).

The move exception

As we’ve seen above, when we need to deal with axes and analogic input, we don’t map buttons to functions. 

The perfect example being how the player moves. Every tick (i.e., in most cases, every frame),

 the PlayerController calls the PlayerMove() function. As you can see in the base PlayerController class, 

its default, global implementation does nothing, but the function is (re)defined in every single of the controller’s states. 

Simply because, whether you’re walking, swimming, driving, flying or dead, you won’t hande the movement the same way.

The PlayerMove() function does some additional checks

 (such as “should I be making a double jump?” in the PlayerWalking state, or updtating the player’s 

acceleration according to the input) and then calls ProcessMove() which also has as many flavours as the controller has states.

The difference between PlayerMove() and ProcessMove() is that ProcessMove is only executed locally 

(on the controller’s owner’s computer), while what is done in PlayerMove is useful for replicating the movement on the other clients.

Examples

To illustrate the above, I’m going to do two (simple) things, using my 3rd person camera game as a basis:

  1. Make the player movement speed proportional to the joystick input (by default it always move at the same speed).
  2. On the press of a button, the “over the shoulder” camera will switch shoulder.

You’ll note that in this case, I don’t even need to subclass PlayerInput.

Proportional Movement

This is easy to do, this method looks too much hacky to my liking, but I can’t think of any better way of doing it.

Let’s have a look at this excerpt from the PlayerMove() function in PlayerController’s PlayerWalking state:


1
2
3
4
// Update acceleration.
NewAccel = PlayerInput.aForward*X + PlayerInput.aStrafe*Y;
NewAccel.Z = 0;
NewAccel = Pawn.AccelRate * Normal(NewAccel);


We can see that a vector is created from the input, normalized, and added to the pawn’s acceleration rate. 

First thing that annoys us here, is the normalization. Because of that, it won’t matter how much we push the joystick,

 the pawn will always move at the same speed. Second annoying thing is the fact that input affects Acceleration rather than Velocity.

 I want my joystick to be directly linked to the pawn’s velocity.

So, given the emplacement of that line of code (right in the middle of the function),

 and the call to ProcessMove at the end of the function (which makes use of NewAccel), 

we’ve got no choice but to copy/paste the entire function in our subclass and make the required change (removing the line). 

Don’t forget this is the PlayerMove() inside the PlayerWalking state we’re overriding, here.

We also need to modify the ProcessMove() function in the same way, but we’le be doing slightly more complex things.

 Here’s the default implementation of the function (UDK November 2010):


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function ProcessMove( float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
{
     if ( Pawn == None )
     {
         return ;
     }
 
     if (Role == ROLE_Authority)
     {
         // Update ViewPitch for remote clients
         Pawn.SetRemoteViewPitch( Rotation.Pitch );
     }
     Pawn.Acceleration = NewAccel;
     CheckJumpOrDuck();
}


We could just replace Pawn.Acceleration by Pawn.Velocity, but if we do that, it will screw up the “Jump” code quite badly. 

So we need to make sure the jump code behaves as it did before:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function ProcessMove( float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
{
     if ( Pawn == None )
     {
         return ;
     }
 
     if (Role == ROLE_Authority)
     {
         // Update ViewPitch for remote clients
         Pawn.SetRemoteViewPitch( Rotation.Pitch );
     }
 
     if (Pawn.Physics != PHYS_Falling) //The pawn's physics mode is different while jumping
     {
         Pawn.Velocity = NewAccel;
     }
     else
     {
         //This way, we get back to the behaviour of the default implementation
         Pawn.Acceleration = Pawn.AccelRate * Normal(NewAccel);
     }
 
     CheckJumpOrDuck();
}

Disclaimer: This implementation is far from bug-free. The jump distance will be affected

 by how fast you were moving before jumping (which isn’t necessarily bad, but might be unwanted),

 and you can get weird bugs such as the pawn moving on its own (very similar to a dead zone issue, except it’s not :) ) 

Fixing these requires knowledge about how the different Physics modes work, which I don’t have at the time of writing.

Camera switching

While this one is also fairly easy, it takes a bit a work to be done properly.

We basically need to send a command to the camera. However, 

the closest thing we can call exec functions on is the PlayerController. 

As a result, we need to propagate the call from the controller to the camera, 

which means modifying all the classes in-between. But let’s start from the beginning.

First, we define a new Game Bindable Action and assign it to the gamepad’s left thumbstick button (I’m doing this in UDKInput.ini):


1
Bindings=(Name="GBA_SwitchPOV",Command="SwitchPOV") Bindings=(Name="XboxTypeS_LeftThumbstick",Command="GBA_SwitchPOV")


Note that by default, that button is assigned to the Jump action, so you’ll need to remove the appropriate line.

Then, we create our exec function in our custom PlayerController:

1
2
3
4
exec function SwitchPOV()
{
    SandboxCamera(PlayerCamera).SwitchPOV();
}

And then, define that function in our camera:

1
2
3
4
function SwitchPOV()
{
    SandboxThirdPersonCamera(CurrentCamera).SwitchPOV();
}

Now, the above is VERY dirty. It works in my case as I only have a single camera mode.

 But if you have several, how can you know to which type you need to cast CurrentCamera to access its SwitchPOV() function?

 You can’t. That’s why you would need to extend GameCameraBase, 

to add a stub SwitchPOV() function. Which would allow you to write the following:


1
2
3
4
5
6
function SwitchPOV()
{
    SandboxCameraBase(CurrentCamera).SwitchPOV();
    //Now, each camera mode just needs to override SwitchPOV()
    //to implement the camera-specific behaviour
}


Finally, you need to define switch POV in your camera mode. 

In my case, it’s a very simple thing (please refer to the example 3rd  person camera in my camera basics tutorial):

1
2
3
4
function SwitchPOV()
{
    ThirdPersonCamOffsetY = -ThirdPersonCamOffsetY;
}

And that’s it!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值