The Motion Graph
The motion graph is the core movement system of NeoFPS. It provides a powerful but flexible tool for designers to control how the FPS character moves, and how the character's movement state affects other Unity systems such as audio and animation. The motion graph means designers aren't tied to a specific style of movement, and minimises the need for complex custom code when trying to achieve a specific FPS vision (though it is also designed to be highly extensible).
The NeoFPS movement system uses a MotionController behaviour attached to the FPS character, which drives a NeoCharacterController. The MotionController has parameters to select the desired motion graph ScriptableObject, along with a [MotionControllerData ScriptableObject] that the graph queries for parameters such as movement speed and strafe multipliers. By separating the motion controller from the graph and data, NeoFPS allows for a huge amount of flexibility in creating separate characters with unique movement styles and traits in a single game. This can be very useful if the game design involves character classes or RPG style stats.
States are the building blocks of the motion graph. Each state is a style of movement and has complete control of the character's MotionController.
For more information, see Motion Graph States.
Connections handle the relationship between movement states. Each update tick, all of the outbound connections of the current state are checked in sequence. If an outbound connection is found to be valid, then that state in turn is checked until the final state in the chain is reached. That final state becomes the new "current" state, and is then updated.
In order for a connection to be valid, the conditions attached to it must return true (all or any depending on the setting).
Connections can also be muted by checking the Mute checkbox above the conditions list. A muted connection will never be traversed and will show as red in the viewport. A muted connection will also be greyed out in the out-connections list of the source element.
Connections are evaluated for parent sub-graphs as well to allow for grouping logic. The connection checks are handled in order from the highest level sub-graph containing the current state, down the sub-graph hierarchy, and lastly the current state. An example of how his can be useful is switching between grounded and airborne movement. You can group all of the grounded movement states (running, walking, crouching, etc) and connections together in their own sub-graph, and group all of the airborne states and connections together in another sub-graph. Connecting these 2 sub-graphs using a ground contact check condition means that these connections are much higher priority than the connections inside the respective sub-graphs. It doesn't matter what connections exist and conditions are met for grounded movement if the character is no longer touching the ground. Without this ordering and grouping of nodes into sub-graphs, every grounded movement state would require a connection to the airborne states.
Conditions are a one off test that returns either true or false. The tests can be based on the graph state, graph parameters, or external systems such as physics and character health. Connections can be set to traverse when all or any conditions are true. If you need some combination of all/any then you can use condition groups.
For more information, see Motion Graph Conditions.
Sub-Graphs allow states to be logically grouped together into smaller graphs. They have inbound and outbound connections just like a state and are tested before the current state in order from the graph root down to the current sub-graph and finally the current state. This enables complex logic to designed through a simple interface. For example, most characters will have an Airborne and a Grounded sub-graph. Inside the Grounded sub-graph will be various states handling movement such as walking, sprinting and sliding, as well as the connections between them. If the character loses ground contact then it doesn't matter which of those states the character is in. The sub-graph connections will handle the connection into the Airborne sub-graph and then select the relevant airborne state.
Each sub-graph has a default state. On entering a sub-graph, all of the connections to child states are evaluated in sequence. If none of them are valid then the default state automatically becomes the current state.
Parameters And Data
Motion graph parameters are similar to the parameters inside the Unity AnimatorController. Any number of parameters can be added to the motion graph and then referenced by key or hash. You can also store the parameter reference for quicker access.
Certain parameters such as triggers and switches can be blocked, either by graph behaviours, or from outside the graph.
The motion graph also allows for an event parameter. This can be subscribed to from outside the graph and triggered by graph behaviours, allowing the graph to interact with components outside of the motion graph system.
Motion data behaves similarly but cannot be changed from outside the graph, except for using an override asset. Motion data are referenced in graph states and behaviors, and are completely optional.
For more information, see Motion Graph Parameters And Data.
Motion graph behaviours add extra functionality to the graph. They can process logic on entering or exiting a state or sub-graph and/or when a state is updated. Certain behaviours can only be attached to states, and others can only be attached to sub-graphs, while most can be attached to both.
Motion graph behaviours allow for simple logic such as resetting or modifying parameters. They can also be complex systems on their own, such as the motion graph behaviour based footsteps.
For more information, see Motion Graph Behaviours.
For characters that have an animated humanoid body, you can also make use of root motion to drive movement using animations. To make this work you need to add a FirstPersonBodyRootMotion component to the character's body. This intercepts the root motion offsets from the animation and, instead of then moving the character model, it sends those offsets to the motion controller instead. This allows you to blend root motion in and out, and use it selectively on specific movement states.
To leverage the root motion information you can add the SetRootMotionStrength motion graph behaviour to your states or subgraphs, or you can use the RootMotion motion graph state. The latter sends the player input to the character's body animator for use in a blend tree.