Keypress Interactions
KeyPress Interactions are pretty neat in my opinion. You can intercept on what type of press it is (Press, Hold, Release), or even pick up on axis movement.
First off, you need to make your mutator class that will create your interaction. See Creating An Interaction From A Mutator for this.
Done that? Good...
So thats the mutator part done. Next is the easy part!
A simple interaction:
Class MyInteraction extends Interaction; Function Initialize() { Log("Interaction Initialized"); }
Dont forget to add
bActive=True
Under defaultproperties (else this interaction wont do anything).
Now to see what is going on with these keypresses, add the following function under your initialize function.
function bool KeyEvent(EInputKey Key, EInputAction Action, FLOAT Delta ) { if (Action == IST_Press) ViewportOwner.Actor.ClientMessage("Key PRESSED:" @ Key); if (Action == IST_Release) ViewportOwner.Actor.ClientMessage("Key RELEASED:" @ Key); if (Action == IST_Hold) ViewportOwner.Actor.ClientMessage("Key HELD:" @ Key); if (Action == IST_Axis) ViewportOwner.Actor.ClientMessage("Key AXIS:" @ Key); return false; }
This will make a small white message appear in the chat box in the bottom left of your screen telling you what you did, and which key you pressed. The keys are represented as numbers (try using Chr(Key) to convert them into letters and numbers. Remember keys like "Home" wont display properly like this), though you can use the ones listed in Engine.Interactions
to match on a key that has been pressed.
Note the Return False;
. If you return "True" in this function, the InteractionMaster will cease to iterate through the interactions, it will stop at yours.
Another function, KeyType, can be used to build up a string from what keys have been pushed, as is demonstrated in Engine.Console
. I think that is all there is to it.
function bool KeyType( EInputKey Key, optional string Unicode ) { if (bIgnoreKeys) return true; if( Key>=0x20 ) { if( Unicode != "" ) TypedStr = TypedStr $ Unicode; else TypedStr = TypedStr $ Chr(Key); return( true ); } }
Now that you can see which does what, lets look at a practical use for it. I will be modifying ICU (which was created in the HUD Interactions page) to highlight enemys when you push Page Up, and Friendlys when you push Page Down.
I added this for the keypress function:
function bool KeyEvent(EInputKey Key, EInputAction Action, FLOAT Delta ) { if ((Action == IST_Press) && (Key == IK_PageUp)) bDrawEnemy = True; if ((Action == IST_Release) && (Key == IK_PageUp)) bDrawEnemy = False; if ((Action == IST_Press) && (Key == IK_PageDown)) bDrawFriendly = True; if ((Action == IST_Release) && (Key == IK_PageDown)) bDrawFriendly = False; return false; }
That, quite basically, sets bDrawEnemy to TRUE when PageUp is pressed, and to FALSE when it is released. The same for bDrawFriendly.
I have added a couple of extra lines to the PostRender function to account for this, and only draw the right people:
simulated function PostRender( canvas Canvas ) { local Pawn P; local vector CameraLocation, dir, ScreenLocation; local rotator CameraRotation; local float dist, draw_scale; If ((bDrawEnemy) || (bDrawFriendly)) //if none are true, dont do anything. Saves processing. { foreach ViewportOwner.Actor.DynamicActors(class'Pawn', P) { if (ViewportOwner.Actor.Pawn == None || P == None) Return; //A trace to tell if you can see this thing If ((Canvas.Viewport.Actor.FastTrace(P.Location, ViewportOwner.Actor.Pawn.Location)) && (P != ViewportOwner.Actor.Pawn) && (P.PlayerReplicationInfo != None) && (P.Health > 0)) { //Convert 3d location to 2d for display on the Canvas ScreenLocation = WorldToScreen(P.location); Canvas.GetCameraLocation(CameraLocation, CameraRotation); dir = P.Location - CameraLocation; dist = VSize(dir); //Distance between me and them if (dir dot vector(CameraRotation) > 0) { draw_scale = 512 / dist; //Calculate the drawscale, 512 is the "1:1" distance. //Set drawing params Canvas.SetPos(ScreenLocation.X - (32 * draw_scale), ScreenLocation.Y - (32 * draw_scale)); Canvas.Style = 3; Canvas.SetDrawColor(255,255,255); if (bDrawEnemy) //If PageUp is depressed (bDrawEnemy is true), see if the pawn is an enemy, if so, draw him! if ((P.PlayerReplicationInfo.Team.TeamIndex != ViewportOwner.Actor.Pawn.PlayerReplicationInfo.Team.TeamIndex) || (!GRI.bTeamGame)) Canvas.DrawIcon(texture'red', draw_scale); if (bDrawFriendly) //If PageDown is depressed (bDrawFriendly is true), see if the pawn is an friendly, if so, draw him! if (P.PlayerReplicationInfo.Team.TeamIndex == ViewportOwner.Actor.Pawn.PlayerReplicationInfo.Team.TeamIndex) Canvas.DrawIcon(texture'green', draw_scale); } } } } }
I must also make sure
bVisible=True
Is in defaultproperties, else any render functions will be ignored.
Now I have a mutator that highlights my enemys in red when I push PageUp, and people on my team in green when I push PageDown. Neat, huh?
Checkout Input Key Mapping for a full table of input keys, and their corresponding values.
With a little work, I figured out how to use these with the GUIUserKeyBinding class, to allow the user to select the keys used by the Interaction.
This part is in RadarInteraction.uc:
var int LastKey var int Zooming; var float ZoomDelta // KeyEvent will detect keyup and keydown events for any keys bound to our // zoom feature, and act accordingly function bool KeyEvent(EInputKey Key, EInputAction Action, float Delta) { local string tmp; // if the zoom key that is currently being held is released, end the zoom if(Action == IST_Release && Zooming !=0 && Key == LastKey) { LastKey=-1; Zooming=0; return True; // a key has been pressed } else if (Action == IST_Press) { // big ugliness here, we use console commands to get the name of the numeric // key, and then the alias bound to that keyname tmp = ViewportOwner.Actor.ConsoleCommand("KEYNAME"@Key); tmp = ViewportOwner.Actor.ConsoleCommand("KEYBINDING"@tmp); // if it's one of our two aliases (which don't actually exist), set the zoom // direction, save the key that started the zoom, and eat the event if (tmp == "tdzoomin") { LastKey = Key; Zooming = -1; return True; } else if (tmp ~= "tdzoomout") { LastKey = Key; zooming = 1; return True; } } // this event doesn't matter to us, so we pass it on for further processing return False; }
and this is from TacticalDisplayKeyBinding:
// Custom Key Bindings class TacticalDisplayKeyBinding extends GUIUserKeyBinding; defaultproperties { KeyData(0)=(Alias="",KeyLabel="Tactical Display Controls",bIsSection=True) KeyData(1)=(alias="tdzoomin",KeyLabel="Zoom In",bIsSection=False) KeyData(2)=(alias="tdzoomout",KeyLabel="Zoom Out",bIsSection=False) }
The GUIUserKeyBinding allows the user to bind the keys aliases in the "Controls" tab of their settings. When they press a key, the interaction checks to see if it is bound to one of the two aliases, and takes action. This is adapted from the same code that the Controls tab uses to load the user's keybindings.
Will: Any problems understanding this? Anything I could do to make it clearer? If so: tell me, or do it yourself
DJPaul: Not for me - once again, brilliantly written. Cheers.
Claw: Hmm, this is excellently written. And now I finally realize what WorldToScreen() means.
CheshireCat: Here's another KeyEvent example, that allows the user to select the keys used. Any questions?
Mysterial: Why would you want to do all that key binding stuff in KeyEvent() (in the last example)? Why not just use an exec function instead?
Mychaeel: Because exec functions only work in certain classes, and you might not want to create those classes. (It's a good idea not to replace game-relevant classes like xPlayer unless you absolutely have to for very good reasons other than doing things that could just as well be done less invasively, like what's described above.)
Mysterial: Ah, I was under the impression that Interactions were such a class. Obviously not
Is there a list anywhere of classes in which exec functions work?
Mychaeel: Since you can register any amount of Interaction objects simultaneously (instead of having to replace an existing one by yours), they're safe and neat for modding. (Though Joe Wilcox says that they weren't meant to be used for that.) – I don't think there's a list of places where "exec function"s work. Sounds like an idea for a new page though.
Tarquin: exec function the same as exec directive?
Mychaeel: No. See Function Syntax.
Nuleo: Has anyone figured out a better way to use the GUIUserKeyBindings without using exec commands just to get the key names? In my interaction I was originally using this method
if ((Action == IST_Press) && (Key == IK_PageUp)) //Do something
But when I changed to this method (with a class extending GUIUserKeyBindings)
tmp = ViewportOwner.Actor.ConsoleCommand("KEYNAME"@Key); tmp = ViewportOwner.Actor.ConsoleCommand("KEYBINDING"@tmp); // if it's one of our two aliases (which don't actually exist), set the zoom // direction, save the key that started the zoom, and eat the event if (tmp == "tdzoomin") //Do something
I noticed a reduce in game speed and the sound was chopping up a bit.
Ceej: I've just been experimenting with this. For new game types, one place to install a keypress interaction seems to be in the InitInputSystem() event of your PlayerController subclass. For example:
event InitInputSystem() { super.InitInputSystem(); Player.interactionMaster.AddInteraction("MyPackage.MyKeyInteraction", Player); }