Player Behaviour
The Player
The player in Jump King consists of a en Entity (PlayerEntity.cs), and a series of Components - one of the more common ones being Node Trees (also referred to as a Behaviour Tree). The majority of the logic of how the player interacts with the world around them is handled by the ‘Body Component’ (BodyComp.cs).
Each Entity has an ‘Update’ method, which is called each frame, and is then followed by a the update of any ‘Enabled’ component also being called.
As part of the work to refactor Jump King and make it suitable for modding the various steps of the player’s behaviour, the BodyComp’s logic was split into a series of ‘Behaviours’ (IBodyCompBehaviour.cs) covering each operation needing to be performed in a given frame. Data is passed between behaviours through a provided ‘BehaviourContext’ object.
The aforementioned node tree contains various other nodes controlling ways of managing player velocity and animations. This velocity is then used by the BodyComp to drive the player’s interaction with the world.
You can access the PlayerEntity from most of your code through the Entity Manager
PlayerEntity player = EntityManager.instance.Find<PlayerEntity>();
You can get the BodyComp of a PlayerEntity (or any other node) like so:
BodyComp bodyComponent = player.GetComponent<BodyComp>();
You can access a specific Node from the behaviour tree a few ways depending on what you care about:
// The behaviour tree is contained within a Behaviour Tree Component
BehaviourTreeComp behaviourTreeComponent = player.GetComponent<BehaviorTreeComp>();
BTmanager behaviourTree = behaviourTreeComponent.GetRaw();
// This will get you the parent of the node you specify, useful if you want to add steps to run before/after it
// as there is no way to traverse 'up' a node tree
IBTcomposite walkSequencor = behaviourTreeComponent.FindParentNodeOf<WalkAnim>() as IBTcomposite;
// This will get you the first instance of the specified node, useful if you want to modify its behaviour in some way
WalkAnim walkAnimNode = behaviourTreeComponent.FindNode<WalkAnim>();
To summarise:
- Player Entity (PlayerEntity)
- Components
- Body Component (BodyComp)
- Collection of Behaviours (IBodyCompBehaviour)
- Input Component (InputComponent)
- Node Tree (BehaviourTreeComp)
- Walk Velocity Node (Walk)
- Walk Anim Node (WalkAnim)
- Jump Charging Node (JumpState)
- etc
- Body Component (BodyComp)
- Components
BodyComp Behaviours
The base behaviour of the BodyComp is comprised of the following behaviours in the following order:
- CacheCollisionStateBehaviour
- Caches initial states of the player’s collision at the start of the frame. Used to set some states of the base game blocks
- Sets the
StartOfFrameCollisionInfo
of theBehaviourContext
object passed to other behaviours
- WindVelocityUpdateBehaviour
- If the current screen has wind enabled it updates the X velocity of the player based on the current wind velocity
- CacheLastScreenBehaviour
- Caches the current screen index the player is on to the
LastScreen
property ofBodyComp
- Caches the current screen index the player is on to the
- WaterParticleSpawningBehaviour
- If we were on a water block last frame and aren’t this frame (or vice versa) this creates an appropriate water ‘splash’ particle
- CacheVelocityBehaviour
- Caches the current velocity to the
LastVelocity
property ofBodyComp
- Caches the current velocity to the
- CollisionCheckEarlyExitBehaviour
- If the player is currently colliding with a block then the previous frame didn’t fully resolve things and we early exit, preventing further behaviours from running
- UpdateXPositionFromVelocityBehaviour
- Updates the
Position
property of theBodyComp
based on the currentVelocity
- Registered
IBlockBehaviour
implementations have an opportunity to modify the velocity prior to it being applied via theirModifyXVelocity
methods
- Updates the
- CapPositionBehaviour
- Caps the player’s X position between bounds of the screen
- Doesn’t run if the current screen has Teleporting somewhere in it
- If it runs it adds a “CappedXPosition” flag to the
BehaviourContext
which is reset at the start of the next frame
- HandlePlayerTeleportBehaviour
- If the current screen allows teleporting and the player’s position exceeds the bounds of the screen it handles teleporting the player to the approproiate end position
- If it runs it adds a “TeleportedPlayer” flag to the
BehaviourContext
which is reset at the start of the next frame
- CachePreCollisionResolutionCollisionStateBehaviour
- Saves the current collision state of the player to the
PreResolutionCollisionInfo
property of theCollisionInfo
in theBehaviourContext
- Saves the current collision state of the player to the
- ResolveXCollisionBehaviour
- Resolves any collisions affecting the player by moving them along the X axis until they are no longer colliding with any solid blocks
- It moves the player by a step (1 pixel) in the appropriate direction iteratively checking the collision state of the player after each movement.
- Registered
IBlockBehaviour
implementations can require additional checks to be performed through theAdditionalXCollisionCheck
method - If the player touched a slope as part of this flow the “TouchedXSlope” flag is added to the
BehaviourContext
which is reset at the start of the next frame - If the player is being put in a ‘Knocked’ state from this operation, the “PlayBumpSFX” flag is added to the
BehaviourContext
which is reset at the start of the next frame
- UpdateYPositionFromVelocityBehaviour
- Updates the
Position
property of theBodyComp
based on the currentVelocity
- Registered
IBlockBehaviour
implementations have an opportunity to modify the velocity prior to it being applied via theirModifyYVelocity
methods
- Updates the
- CachePreCollisionResolutionCollisionStateBehaviour
- Aggregates the current collision state of the player to the
PreResolutionCollisionInfo
property of theCollisionInfo
in theBehaviourContext
- The
PreResolutionCollisionInfo
property ends up being a collection of every block that the player “has” collided with this frame, regardless of whether it has then resolved that collision or not
- Aggregates the current collision state of the player to the
- ResolveYCollisionBehaviour
- Resolves any collisions affecting the player by moving them along the Y axis until they are no longer colliding with any solid blocks
- It moves the player by a step (1 pixel) in the appropriate direction iteratively checking the collision state of the player after each movement.
- Registered
IBlockBehaviour
implementations can require additional checks to be performed through theAdditionalYCollisionCheck
method
- ApplyGravityBehaviour
- Applies Gravity to the
Velocity
of theBodyComp
- Gives all registered
IBlockBehaviour
implementations a chance to modify the gravity using theirModifyGravity
method
- Applies Gravity to the
- ExecuteBlockBehaviours
- Calls
ExecuteBlockBehaviour
on all registeredIBlockBehaviour
implementations - This is how we have custom blocks run any special logic they need
- Calls
- GuardtowerSoulBugFixBehaviour
- Deals with a specific edge case with Slope and Y collisions that caused some issues in the base game around the Guardtower
- PlayBumpSFXBehaviour
- If the
BehaviourContext
has the “PlayBumpSFXFlag” set it plays the appropriate bump SFX
- If the
Adding/Removing Behaviours
You will likely want to add your own behaviours to the BodyComp, or in some cases, replace/remove existing ones!
To add a new behaviour you simply need to call BodyComp.RegisterBehaviour
with an instance of an IBodyCompBehaviour
class.
PlayerEntity player = EntityManager.instance.Find<PlayerEntity>();
player.m_body.RegisterBehaviour(new MyCoolModBehaviour());
If you want your custom behaviour to run at a specific time in the update loop, you can call the RegisterBehaviourBefore
or RegisterBehaviourAfter
methods. To get a list of existing behaviours call the GetBehaviourList
method
PlayerEntity player = EntityManager.instance.Find<PlayerEntity>();
// Get a list of all current behaviours
IReadOnlyCollection<IBodyCompBehaviour> existingBehaviours = player.m_body.GetBehaviourList();
foreach (var behaviour in existingBehaviours)
{
if (behaviour is ResolveXCollisionBehaviour)
{
// Make our new behaviour
var newBehaviour = new MyCoolPreCollisionBehaviour();
// Register it to run before the X collision resolve
player.m_body.RegisterBehaviourBefore(behaviour, newBehaviour);
break;
}
}
You can remove a specific behaviour through the RemoveBehaviour
function
PlayerEntity player = EntityManager.instance.Find<PlayerEntity>();
// Get a list of all current behaviours
IReadOnlyCollection<IBodyCompBehaviour> existingBehaviours = player.m_body.GetBehaviourList();
foreach (var behaviour in existingBehaviours)
{
if (behaviour is ResolveXCollisionBehaviour)
{
// Remove the collision behaviour
player.m_body.RemoveBehaviour(behaviour);
break;
}
}