- Mastering CryENGINE
- Sascha Gundlach Michelle K. Martin
- 6590字
- 2021-07-16 12:07:27
Game actions
There are two options any game system has when it wants to react to user input. In the simplest form, the code will listen to a specific input event, such as the user pressing the Space bar on the keyboard. Whenever this event is received, the code can react with the appropriate response, for example by making the player character jump.
This direct link between a key on the keyboard and a reaction in the game world is very straightforward, but also very limited. The code will need to check whether the player character is currently allowed to jump. Triggering jumps might need to be prevented during a cut-scene, while in a vehicle, or when a menu is currently shown.
Also, nowadays it is customary to allow players to customize the keyboard layout. The key to trigger a jump might not be the Space bar after the player has personalized the controls and the code responding to the input event needs to account for that.
In addition to keyboard input, many games also offer support for game controllers. For a multiplatform project, this could even be considered a must. Game controllers might also be present on a PC, and the user might prefer using them instead of mouse and keyboard. In our example, the code that triggers the jump would also have to listen for events from those game controllers. And of course, the controller layout is usually customizable by the player as well.
All this adds up to a fair bit of managing code that would need to be written for each class that wanted to react to input events. To solve this, another layer of abstraction between the input events and the responses to them is provided by CryENGINE: the Game Action system that manages the so-called Action Map table.
The Game Action system is an additional event system that links input events to Action events. The core of the system is the ActionMapManager that manages the Action Map tables and sits in CryAction. The system works as a simple mapping. One or more input events are mapped to an Action, for example, the Space bar on the keyboard, the A button on the Xbox controller, and the X button on the PS3 controller are all mapped to an Action event with the name jump
. Whenever the Game Action system receives either of these three input events, it will send out the jump
Action event. Game systems can listen to Action events and respond to them, rather than listen to the input events directly.
Action Maps
As described in the preceding player-jumping example, listening to Actions has the benefit that the code responding to the events doesn't need to be concerned with controller and input mapping. Since the mapping can be changed, this is a distinct advantage over listening for? input events directly. This is all done in the Game Actions system within the CryAction DLL.
The mapping of input events to Action events, that is, which keys and controller buttons trigger which Actions, is done inside a large table called an Action Map. Action Maps are stored in the XML file format.
One of the advantages of Action Maps is that they can be modified by the player. Actions can be rebound to different input events and the modified Action Map can be saved. Every game might not offer this functionality, of course, and you will need to decide whether this will make sense for your specific project. The User profiles section will explain how to modify an ActionMap at runtime, as well as saving and loading them.
Regardless of whether your game will allow the controls to be customized by the players, it will need to ship with a default ActionMap, a default mapping of input events to Action events. For this, you will need a list of Action events that can be triggered and a mapping that binds input events to them. You will not need to create this from scratch, however, since the CryENGINE SDK ships with both. You can extend and modify the existing files to meet the needs of your project.
The default Action Map is stored in the defaultprofile.xml
file at Game/Libs/config
. This file contains the default mapping for all the Action events you need, and it will not change after shipping. This is an example entry from this file:
<action name="jump" onPress="1" keyboard="space" xboxpad="xi_a" ps3pad="pad_cross"/>
In the preceding excerpt, an Action with the name jump
is linked to three different input events, that is, it will trigger on either of them. It is mapped to the Space bar on the keyboard, the A button on the Xbox controller, and the X button on the PS3 controller. The entry also defined when the Action event should be sent out with the onPress="1"
attribute. The exact syntax of an Action mapping is explained in detail in the section Creating a new Action.
If you take a closer look at the defaultprofile.xml
, you will notice that there are several Action Maps contained within the file, each with a different name. This is no mistake; there is a very good reason for this.
In every game or simulation, the same key or button is commonly used for multiple actions. During gameplay, the A button on a controller might, for example, trigger a jump, but in a menu it would select an option on the interface. If the game has an inventory for the player, pressing A while it is open might select an item. And if the player is inside a vehicle, the A button could trigger the handbrake. Similar multiple assignments can be found for lots of other input events, especially since the number of buttons on a game controller is fairly limited.
To accommodate this, the defaultprofile.xml
contains several entries that map the same button to various Action events. One example is the A button on the Xbox controller, which is represented by the keyword xi_a
. This is mapped to the jump
event as well as the vehicle brake v_brake
and a range of menu actions. When this button is pressed, all these Action events might be triggered. Creating individual Action event entries for each of these actions is sensible, as it allows the player to customize their controls individually. The player might choose to keep the default assignment for jumping, but prefer the B button to be the handbrake inside vehicles. Splitting the actions up into separate events allows this kind of freedom.
But how can we prevent the code responsible for jumping from making the player jump while they are in a vehicle or while they are navigating their inventory or an in-game menu? Of course, the code could perform checks and only trigger the jump when none of these conditions apply. However, this would require an unnecessary amount of overhead as the code that should only be concerned with the player jumping now also needs to know about menus, inventories, and the vehicle system. The overhead code would get larger with each new system that reacted to inputs.
Instead, CryENGINE separates all Actions into multiple, separate Action Maps that can be individually activated or deactivated. Action events from an Action Map will only trigger if that map is currently active. For example, one Action Map would only handle the player inside a vehicle. It contains the mappings for all vehicle controls, such as acceleration and brakes. This Action Map is only activated once the player enters a vehicle (as a driver). The rest of the time it is disabled and none of the vehicle control Action events will trigger. The code handling the vehicle Actions would not need to perform any unnecessary checks. Accordingly, when a vehicle is entered, Action Maps to control swimming or player locomotion are disabled.
The CryENGINE SDK uses different Action Maps for various game modes, UI menus, different camera modes, singleplayer/multiplayer, and debug actions that are used only during project development. Some of Crytek's previously released games even used special Action Maps for some boss fights to change the controls as per the specific needs of the fight. Of course, more than one Action Map can be active at a time.
If you write a completely new system for your game project that comes with its own set of controls, you might consider creating an entirely new Action Map inside defaultprofile.xml
. To do so, create a new actionmap
tag node inside the XML file and then place your mapped actions inside as follows:
<actionmap name="newName">
<action … />
<action … />
<action … />
</actionmap>
All maps will be automatically loaded, so you won't need to add any extra managing code for it. You can enable and disable your Action Map via the ActionMapManager inside your system, as shown in the following code:
#include <IActionMapManager.h> // retrieve a pointer to the Action Map Manager IActionMapManager* pActionMapMan = g_pGame->GetIGameFramework()->GetIActionMapManager(); // enable an Action Map pActionMapMan->EnableActionMap("vehicle_general", true); // disable an Action Map pActionMapMan->EnableActionMap("player ", false);
The preceding sample code is written for use inside the CryGameSDK
DLL, where the global variable g_pGame
is defined. When working with ActionMaps, this is the DLL in which you will spend most of your time. If you are working with a full source code and want to access the ActionMapManager from there, use gEnv->pGame
instead. Inside the CryAction DLL, you may also use CCryAction::GetCryAction()->GetIActionMapManager();
to retrieve the pointer to the ActionMapManager.
Creating a new Action
The CryENGINE SDK is shipped with a large range of Actions already set up and implemented. Most of these Actions will be relevant to many types of games, such as player movement, shooting, weapon selection, and vehicle controls. While you will be keeping those in place, you will most certainly want to extend the system with your own Actions as well. This section will cover how to create a new Action in the Game Actions system and map input events to it.
There are two steps to setting up a new Action:
- Create the new Action event.
- Set up a mapping for it inside an ActionMap.
To create a new Action event, you will need to create a new ActionId for it. This is basically just a string containing the name of the event and it is how Actions are referenced inside the codebase. To simplify the process for this, all Action events are listed in one file: GameActions.actions
. This file is located inside the GameDll
folder under GameSDK
and is also included in the Visual Studio CryGameSDK project. This is an excerpt from this file:
DECL_ACTION(moveleft) DECL_ACTION(moveright) DECL_ACTION(moveforward) DECL_ACTION(moveback) DECL_ACTION(jump) DECL_ACTION(crouch) DECL_ACTION(sprint)
To add your own Action events, first choose an appropriate and descriptive name for your Action. It cannot contain whitespaces. Then, simply add it to the bottom of the file and surround your Action event with the DECL_ACTION()
macro. This is defined in GameActions.h
and the CGameActions
class in this file creates ActionIds for all events listed in the GameActions.actions
file. The new entry in the files could look something like this:
DECL_ACTION(your_new_action_name)
After you have added your Action in this file, you will need to recompile the CryGameSDK for both 64- and 32-bit before you can start using your new Action. But first you will need to map input events to it, which will be explained in the next section.
Once the Action event setup is done, you can add a mapping for your Action into one of the ActionMaps in the defaultprofile.xml
file. You can create your own ActionMap of course, but keep in mind that you will also need to take care of activating and deactivating it. In most cases, you will be adding to the existing player
ActionMap. This map is activated automatically at game start, so you will not need to add any extra managing code.
To add a new Action, open the defaultprofile.xml
file from Game/Libs/config
in a text editor. An editor that has syntax highlighting for a XML file is preferable, but not required. Since the file will be a few hundred lines long, the fastest way to find the ActionMap you want to modify is to do a quick search for its name, for example, player
.
New Actions have to be added inside the tags for the ActionMap. Each ActionMap has an opening and a closing tag as shown in the following code snippet:
<actionmap name="player "> ... <!-- New Actions go here, in between the actionmap tags --> ... </action>
Each Action mapping gets its own XML entry, which has to be the tag action
. There are a number of attributes that can be set on the node. Most are optional, but at the very least an entry has to have a name for the Action event it is supposed to trigger, one input event that is associated with it, and a trigger option.
<!-- Minimalistic Action mapping --> <action name="jump" keyboard="space" onPress="1" />
To specify which input events will trigger the Action, you need to specify the device and the name of the input event. There are three devices: xboxpad
, ps3pad
, and keyboard
. While the Xbox and PS3 controller are obvious, the keyboard
attribute is used to access both the keyboard and the mouse input, although these are technically two devices. The distinction is made internally based upon the name of the events.
You can specify one, two, or all three devices inside the action node. You will need to decide which are relevant for the platform you are developing. The devices and their events can be set either as attributes or as children of the action node:
<action name="jump" keyboard="space" onPress="1" /> // The above entry creates the same mapping as the one below <action name="jump" > <keyboard input="space" /> </action>
Using individual children rather than attributes inside the main action node allows you to bind more than one input event from the same device to an Action. This is especially useful for the Enter key that exists at two places on most standard keyboards (once on the main block, and once on the numeric keypad). Each of these keys generates an input event with a different name, yet it is expected that both keys work the same way, that is, they trigger the same Action. For example, if the Enter key would cause a dialog line to be skipped in your game, your players will most likely expect that the Enter key on the numeric keypad will have the same effect.
<action name="skip_dialogfragment" onPress="1" xboxpad="xi_b" > <keyboard> <inputdata input="enter"/> <inputdata input="np_enter"/> </keyboard> </action>
The preceding action mapping example would link the Action of skipping of a dialog line to both Enter keys. Note that it also maps it to the B button on the Xbox controller. This is done inside the main action
node. You can combine different input devices as attributes or children of the main action node.
In order to set up the appropriate input events inside the Action Map, you need to know their names. These are very straightforward for most keyboard keys, but not so for special keys such as the numeric keypad and function keys as well as the mouse and controller input events. You can find a table listing the most important input event names at the end of this chapter in the The input event names reference section.
There are a number of optional attributes that can be set on an action node to further specify when and how the Action event is supposed to be triggered. These options will be explained in this section.
A trigger option defines when the Action event should be sent out if one of the mapped input events is received. In all the examples listed so far, the trigger option was set to onPress="1"
, which will send the Action event once when the button or key is pressed down.
Keyboard keys and buttons on a mouse or a controller can trigger Actions when being pressed down, when they are released, and/or repeatedly while they are being held down. This is useful for many game mechanics. In a fighting game, for example, a player might have to press a key or button to enter a defensive block stance and then hold it to stay in that stance. He exits the stance as soon as he releases it again. In such a case, the Action would have the trigger options onPress
and onRelease
set up.
The preceding table lists the three available trigger options and describes their effects. You can set up more than one trigger option for an Action mapping. An entry in your Action Map could look like the following code:
<action name="jump" onPress="1" onRelease="1" keyboard="space"/>
You can also specify different trigger options for each device and input. For example, have the Action immediately triggered when the mapped key on the keyboard is pressed, but only trigger it on release of the mapped button on the Xbox controller. This is done using the following code snippet:
<action name="menu_open" > <keyboard input="x" onPress="1" /> <xboxpad input="xi_back" onRelease="1" /> </action>
Each of the three trigger options onPress
, onHold
, and onRelease
allow further parameters to be specified. All of these are optional and are not required for the option to work.
By default, the onPress
option will trigger the Action event immediately when the input event is received. However, this behavior can be altered and the event can be delayed. The following table lists the additional attributes you can specify. These must be set on the same node as the onPress
attribute:
When the option onHold
is specified, the Game Action system will repeatedly trigger the mapped Action event while the key or button is being held down. This option allows further parameters to set the frequency of the events and an initial delay.
A delay is a length of time between the initial key press and before the repeated Action events are being triggered. You can easily visualize what this means by opening a text editor and holding down a letter key on your keyboard. After typing the letter once, there will be a short delay before your screen will start filling up.
The onHold
parameters are specified as attributes in the same XML node where the onHold
attribute is set. Here is an example setup:
<action name="menu_up" onHold="1" holdTriggerDelay="0.5" holdRepeatDelay="0.15" > <keyboard input="up" /> </action>
When specifying onHold
inside an action tag, you will usually want to also specify one or more parameters to configure the trigger option. The following table lists the available parameters:
Unlike the other two trigger options, onRelease
does not have its own delay parameter. However, as mentioned in the description of the onPress
trigger option, the onRelease
trigger will be delayed automatically in case the onPress
trigger is delayed.
This ensures that the events are always being triggered in order. However, it is possible to prevent onRelease
to trigger the Action event altogether if a certain amount of time has passed since the key or button was pressed, or in other words, a trigger timeout.
This is done with the releaseTriggerThreshold
attribute. Like the other parameters, it needs to be set in the same node as the onRelease
option. The following is an example of how to use this parameter:
<action name="detonate" onRelease="1" releaseTriggerThreshold="3"> <keyboard input="h" /> </action>
With the preceding setup, the detonate
Action will only trigger if the h
key was pressed and released, but only if the key was not held down for more than 3
seconds.
A key on a keyboard or a button on a mouse only has two states it can be in, either up or down. The Xbox and PS3 game controllers, however, also have analog controls such as the thumb sticks and the trigger buttons. These controls have no distinct pressed/released states, but instead deliver a value that represents how far they are pressed or how far they have been moved from the center.
You can access the analog value of these inputs in the code that will react to the Action event and use them to modify the game mechanics. The mapping in the ActionMaps also allows us to do some preprocessing of these values.
By using the parameters for analog input handling, you can prevent the Action event from being triggered unless the thumb stick or trigger button are moved above a specified threshold. The following table explains the three attributes required for this. All three need to be specified if analog preprocessing is to be used.
By default, holding Shift, Alt, Ctrl, or the Windows key while pressing any key on the keyboard will be ignored. For example, an Action linked to the input event h
will trigger even if the Shift key is held, making the input into a capital letter H
.
By specifying noModifiers="1"
as an attribute, the Action requires that neither of these modifier keys must be pressed for the event to trigger. In the preceding example, the Action would not trigger when H is typed.
During development, it is often necessary to assign a number of debug keys to help with testing and debugging. These inputs can be assigned a range of different functionality, but one common purpose is to trigger console commands in the debug console. To make the development process easier, Action Maps allow input events to trigger console commands directly, without the need to create handling code or FlowGraphs.
The following Action mapping demonstrates how this can be set up. You will need to specify consoleCmd="1"
to indicate that this Action is a direct console command. The string inside the attribute name
will be executed in the console. You can use this to set console variables or call console commands with parameters.
<action consoleCmd="1" keyboard="f5" name="save" onPress="1" />
This functionality is mainly used to perform debugging and testing; but in the preceding example of QuickSave
, it can even be used as a game feature.
Reacting to Action events
Now that you have one or multiple new Action events set up and input events mapped to them, you need to learn how to listen to the Action so that you can respond to it when it is triggered. You can do this in code or in the FlowGraph.
When reacting to Actions, you can either set up your own class to receive the Action events, or you can extend the main existing Action event handler inside PlayerInput.cpp
. The next section will cover how to set up a new Action event listener from scratch. This can be useful if, for example, you are implementing a new FlowGraph node that is supposed to trigger when receiving specific Action events. In most cases, however, adding handler code for new Actions inside the PlayerInput
class is sufficient. Also, it is both faster and more convenient than setting up an entirely new listener. So, feel free to skip ahead to the Extending PlayerInput section at this time.
The Game Action system is implemented like a regular event system. This means it has a list of listener classes that it manages internally whenever an event is triggered, a callback function is called in every class in that list with the event name and some parameters. In order for any class to receive the events it sends out, it needs to derive from a listener interface base class and register itself with the system as a listener.
Note that the preceding diagram is only for demonstration. In fact, most classes are actually not derived from the IActionListener
interface directly. They either receive their triggers via the PlayerInput
class or are implemented as GameObject
, which is in turn derived from the IActionListener
interface.
For the Game Action system, the interface class that you will need to derive your system from is called IActionListener
. This is defined in the file IActionMapManager.h
, which you will need to include in your own header file as follows:
#include <IActionMapManager.h> class CMyOwnClass : public IActionListener { public: // ... };
After deriving from the interface, you will need to implement the callback function to receive the Action events. This function is called OnAction
and should be declared as follows:
virtual void OnAction(const ActionId& action, int activationMode, float value) {}
You can leave the body of the function empty for now. Before you can start catching the event, you will need to register your class as a listener with the ActionMapManager. Inside your classes initialization function, add the following lines of code:
IGameFramework* pGameFrmWk = gEnv->pGame->GetIGameFramework(); IActionMapManager* pActMan = pGameFrmWk->GetIActionMapManager(); pActMan->AddExtraActionListener(this);
Now, your OnAction()
function will be called whenever an Action event is being triggered. The parameters of this function are practically the same as they were for the handler functions when extending the PlayerInput class, so please see the next section for a detailed description.
The class CPlayerInput
inside PlayerInput.cpp
in the Game DLL handles almost all Action events that are related to the player. The player class CPlayer
itself is implemented as a GameObject
(meaning it has a GameObject
proxy), which is derived from the IActionListener
interface. The CPlayerInput
class registers itself with the player's GameObject
as the main Action handler class. This means the GameObject
will forward all received Action events to this class.
The CPlayerInput
class links a list of handler functions to specific Action events. If the class receives a certain Action event, it will call the linked function automatically.
It is very easy to add your own handler code in this class. For one, it is good practice to keep all code related to input inside one class, as it makes debugging simpler. But it also requires the least amount of work, since you don't need to write any management code to register or deregister yourself from the ActionMapManager
. All you need to add is one function to handle the specific Action event that you are interested in and a single line to register it inside CPlayerInput
.
To add a handler for your newly created Action, implement a new function inside the CPlayerInput
class. The function needs to follow a specific declaration:
bool NewHandlerFunction(EntityId entityId, const ActionId& actionId, int activationMode, float value);
Let's take a look at the parameters of this function. The first parameter is the entity ID of the player character. The second parameter is the name of the Action event. As your handler function will be linked directly to just one Action, this is irrelevant at this point. If you are implementing an Action listener from scratch or are using the same handler function for more than one Action event, you can use this variable to distinguish between different Actions.
The third parameter, activationMode
, contains the trigger options that spawned the event. This will correspond to the trigger options set up in the mapping, and the values are either eAAM_OnPress
, eAAM_OnRelease
, or eAAM_OnHold
.
The last parameter, value
, contains an analog value. What this value represents depends on the input device. For keyboard keys and mouse buttons, this value will either be 0 or 1. However, for game controllers' thumb sticks or trigger buttons, this represents how far the trigger or stick is pushed. For mouse movements and the mouse wheel, this contains a delta value.
You can use these values to modify your response to the Action event. The intensity with which a trigger button is pressed determines how fast a vehicle is accelerates. Otherwise, the distance of the thumb stick from the center determines whether the player is walking or running.
When working with these values, consider that due to the nature of analog sticks these hardly ever return to an exact 0.0 value when they are released. It might make sense to work with a so-called "dead zone", which is a small range around 0.0 in which your function simply ignores the Action and pretends the stick was at the center. This way you will prevent things like camera or player movement when the user is not even touching the thumb stick.
When you implement your handler function inside the source file, be sure to return true
to indicate that you have handled the event.
Before your handler function will be called, you will need to link it to the Action event you want it to handle. This is done in a single line in the CPlayerInput's constructor. All handler functions are registered here. This is an excerpt from the constructor:
ADD_HANDLER(moveforward, OnActionMoveForward); ADD_HANDLER(moveback, OnActionMoveBack); ADD_HANDLER(moveleft, OnActionMoveLeft); ADD_HANDLER(moveright, OnActionMoveRight);
The ADD_HANDLER
function has two parameters: the name of the Action event you intend to handle and the function that handles it. At the bottom of the constructor, add your own function and Action event.
ADD_HANDLER(NewAction, NewHandlerFunction);
Now, you are all set to receive Action events and respond to them.
If you are using your own custom ActionMaps and want to extend the CPlayerInput
class, you will need to complete an additional step in order to receive the events from your Action Map. This step is not required if you implemented your own IActionListener
interface from scratch.
Each ActionMap internally stores an EntityId
. When an Action event is triggered it is passed into the corresponding GameObject
. This is how CPlayerInput
receives its callbacks. So, the new Action Map must be told the EntityId
of the player so that the PlayerInput
will be able to handle its Action events.
If you have full source access, you can put this code inside the file CET_ActionMap.cpp
inside CryAction, where most ActionMaps are enabled and set up. Otherwise, place it somewhere inside the CryGameSDK
DLL, possibly inside the Reset()
function in PlayerInput.cpp
if you would like to keep all input related code in one place.
IGameFramework* pGameFrmWk = gEnv->pGame->GetIGameFramework();
IActionMapManager* pActMan = pGameFrmWk->GetIActionMapManager();
IActionMap* pActnMap = pActMan->GetActionMap("NewActionMap");
pActnMap->SetActionListener( m_pPlayer->GetEntityId() );
Tip
At times, during development, keys are mapped to help with debugging and QA testing. They can allow the developer to quickly jump to certain locations, trigger events, cut scene, give inventory items, ammo, and so on.
It can be helpful to gather all these debug mappings inside a debug Action Map. Then, simply remove or deactivate this Action Map before shipping. This way you won't need to remove the code for it and risk breaking your build in the process, but you also don't have to worry about players using your debug keys to cheat.
You don't need to meddle with the source code to react to Action events. The FlowGraph node Input:Action offers easy access to the Game Actions system. To use this node, you will only need to know the name of the Action you want to respond to and the ActionMap it is defined in.
Inside your FlowGraph, add the node Input:Action. The node can only react to onPress
and onRelease
triggers; the onHold
trigger will be ignored. The node looks like the following:
Before you can use this node, you will need to set up your Action event and ActionMap name. The node also requires the local player's entity to be assigned on it. You will also need to call the Enable port once to start listening to events. A possible setup can look like the following:
Generally, it is recommended that you don't use this FlowGraph node to listen to Action events, because the current implementation does not make use of the Game Actions system properly. It neither checks whether an ActionMap is active or not, nor does it respect the trigger options. If you need a proper Action event listening FlowGraph node for your project, you can use the information in this chapter to implement your own.
Filtering Actions
As mentioned before, it is possible to enable and disable entire Action Maps completely and block their Action events from triggering. This is useful for segments of the game when the controls need to change completely, such as if the player entered a vehicle or is currently in the pause menu.
However, sometimes it can become necessary to only prevent certain Action events from triggering, while leaving the rest of the ActionMap intact. The most common use case, for example, is when the player's movement is restricted. If the player is currently balancing over a broken tree or climbing a wall, they might still be able to move forward and backwards, but not left or right. Also, they might still be allowed to fire their weapon, but not enter crouch mode. Another common example is turret gameplay, where the player's movement is completely blocked, but their rotation or their ability to fire is not.
In these cases, instead of switching to an entirely new ActionMap completely, you can choose to temporarily filter out the unwanted Actions. This is done using Action Filters.
Action Filters are created in code, and they need to be registered with the ActionMapManager. The best place to do this is inside the CGameActions
class in GameActions.h
/GameActions.cpp
. This class has direct access to all registered Action events and it is accessible from the CryGameSDK
DLL. If you register your filter from another place, you should include GameActions.h
and refer to the Action events by using CGameAction::ActionEventName
. This section will take you through the process of creating a new Action Filter.
First, you need to retrieve a pointer to the ActionMapManager. Then you can create a new Action Filter. You will need to provide a name for the filter to access it again later and a filtering mode. There are two modes available:
Then, you can start adding Actions to the newly created filter. Here is some example code that creates a simple filter to block player movement:
IGameFramework* pGameFrmWk = gEnv->pGame->GetIGameFramework(); IActionMapManager* pActMan = pGameFrmWk->GetIActionMapManager(); IActionFilter* pNewFilter; pNewFilter = pActMan ->CreateActionFilter("no_move", eAFT_ActionFail); pNewFilter->Filter(jump); pNewFilter->Filter(moveleft); pNewFilter->Filter(moveright); pNewFilter->Filter(moveforward); pNewFilter->Filter(moveback);
Once the Action Filter has been created, it can be activated or deactivated either from code or from FlowGraph. Both mechanics are explained as follows:
- To enable a filter via code, you have two options. You can call the filter directly, provided you have a pointer to it, for example if you stored it after creation. Alternatively, you can use the ActionMapManager to access a filter via its name. Here are the functions at your disposal:
IActionMapManager::EnableFilter(const char *name, bool enable) IActionFilter::Enable(bool enable)
- In FlowGraph, you can enable or disable the filters via the node Actor:ActionFilter. (This node was called Input:ActionFilter in the previous releases of CryENGINE.) Like the Input:Action node, it requires the local player's entity to be set on the node in order to work. Here is how a setup with the no_move filter created in the previous section could look:
Reacting to Input events
Up to this point, Action events have been covered quite extensively as a layer of abstraction on top of the input system. In general, Actions are preferable to the raw input events due to the many advantages of Actions.
It is also possible to listen and react to input events from CryInput directly. This section will also cover how to listen and respond to these events in code and in the FlowGraph.
Like the Game Actions system, the input system is an event system. It sends out the input events to all classes that have registered themselves as listeners. To become a listener, a class needs to derive from the interface IInputEventListener
.
#include <IInput.h> class CMyOwnClass : public IInputEventListener { public: // ... };
To compile this without error, the class needs to implement the callback function that the input system will call with the input events. This function should return false
to allow other listeners to also receive the event. If true
is returned, the rest of the listeners will not be called.
virtual bool OnInputEvent( const SInputEvent &event );
This function will receive all input events dispatched by the input system, coming from all supported devices. The parameter event contains all information about the input event needed to process it. These are the members of the SInputEvent
struct and their descriptions:
Most key and button presses will trigger at least two input events: once when the key is pressed down and once again when it is released. A common implementation of the OnInputEvent()
function compares the keyName
to all input event names it wants to react to, and then it checks the state of the input control before it reacts to the input. The state check is necessary so that the code doesn't respond to the same key pressed twice. This is not necessary of course for the non-button events, such as the mouse wheel or mouse movement for example.
You can either check the keyName
directly by using a string compare or check the keyId
against the enum value of your input event. A table listing the most common input event names can be found at the end of this chapter in the section The input event names reference.
The FlowGraph also provides the option to react to input events, making it easy for level designers to prototype new ideas. The node for this is called Debug:InputKey (note that this was called Input:Key in the previous CryENGINE releases).
Having many of these nodes in a level will cause a lot of string compare operations in nearly every frame, which can take a bite out of performance. In addition to that, input events are not filtered (like Action events), nor can they be remapped easily at runtime. Therefore, it is advised that all functionality prototyped with this node should be converted into a code-based solution. Instead of input events, it would also make sense to use Actions in this implementation.
To encourage implementing Actions properly, this node has been moved into the Debug category by Crytek. This means it is not intended to be used in a shipped product, but only for development and debugging purposes. By default, it will not work outside of the Sandbox editor.
To make this node work in launcher, it must have the local player's entity assigned to it, the NonDevMode checkbox must be checked, and the Enable port has to be triggered. Depending on your system configuration, you might also need to set the console variable g_disableInputKeyFlowNodeInDevMode
to 0
inside your system.cfg
file.
The preceding example shows how to receive left and right mouse movements by using the Debug:InputKey FlowGraph node. To catch input events with this node, you need to provide the name of the input event in the Key port of the node. A table listing the most common input event names can be found at the end of this chapter in the section The input event names reference.