Perhaps a video will communicate what I am seeing more clearly and whether this comes down to user or configuration error and / or is expected behaviour:
Once I start the platform moving (by adding an initial force), there is a period of time where upon landing, the player slides a bit. Is this intended? Thanks!
Is your rolling animation root motion-based? If not, and if youāre open to using root motion, you can simply enable the Use Root Motion property during the roll animation and disable it once the rolling ends. One advantage of root motion is that the animationās velocity completely overrides the āproceduralā velocity.
Alternatively (no root motion), in your input-related code (e.g., the Update method), you can check if the character is rolling and set its movement direction to a previously cached direction (e.g., cache the direction at the start of the roll) to maintain a consistent forward roll:
private void Update()
{
// Regular input code here...
// Overrides movement direction while rolling
if (IsRolling())
{
SetMovementDirection(cachedRollingDirection);
}
}
Another approach is to override the CalcDesiredVelocity method to return a rolling velocity (e.g., cachedRollingDirection * rollingSpeed) while rolling.
Actually, this is the method where root motion velocity typically overrides procedural velocity, so a similar approach can work here too.
This is how the current implementation based on differential equations works in this case: to calculate the platformās velocity, the difference in position (delta position) is divided by the elapsed time (delta time). This represents the average velocity between two frames, so at least one previous frame and the current one are needed to calculate these deltas.
This effect is more or less pronounced depending on the speed of the platform as well as the characterās properties for ābrakingā or adjusting its velocity upon landing. Additionally, the cameraās tracking speed, as mentioned before, also affects the perception of this effect.
For example, in the video you shared, youāre clearly using very high friction and/or deceleration (instant/hard stop) and possibly some camera lag.
In contrast, in the following video (using default character settings and no camera lag), the effect is less pronounced. This is also observable in included examples like planet walk, etc.
For further clarity, if you push one of the green cubes (physics platforms) and jump onto it, youāll notice the effect is almost imperceptible compared to when using high deceleration and friction values (as seen in your test video). This is why, in games where action takes place on moving platforms (e.g., Super Smash Brothers), itās common to āhookā the character to the platform so they maintain their speed even when not in direct contact with it or to use alternative implementations that may require custom coding (e.g., KCC).
That said, as part of an upcoming update currently in development, I plan to conduct additional tests and explore alternative implementations in case any unforeseen issues exist.
Fantastic, thank you so much! I went with the root motion option, and it works really well! I do like how your default ājumpā behaviour imparts some lateral control to the player, so I may look to extend my ārollā function to do the same.
One other thing Iāve been thinking about is my use of Animation Events to call the StopRolling() method. I really dislike Animation Events - they feel like a āhackā and put a weird dependency on, and coupling of, Animation Clip / Animator and my Player Character component. Itās also a pain when I want to swap out animation clips.
Do you have any opinion on Animation Events, from a ābest practiceā perspective? I feel that the Player Character state model should āknowā the player state - for example, when the player is rolling. It feels important when determining whether the character can transition to other states, for example. Iām looking at adding other āActionsā, like picking up objects, attacking, interacting with doors etc, and I think Iāll end up encountering this āproblemā again in the future. If there are other, ābetterā solutions to this, Iād be really interested to hear.
One of my āanalysis paralysisā problems in my GameDev journey has been around the fact that there are so many different ways to do one thing - I understand that thereās often no ābestā solution, and that consistency is key, but I also have a desire to write āgoodā maintainable, extensible code, and sometimes experience is the only way to establish what good looks like. I really value your opinion in that respect!
Thatās great; Iām glad to hear the suggested solution worked well for you!
About your question:
While animation events are useful and have their purposes, for this and similar cases, you could use StateMachineBehaviour, which is ātiedā to the animation state rather than the animation clip. This allows direct access to the character, enabling modifications as needed. For example, to implement a ājump attackā using root motion:
public class RootMotionJump : StateMachineBehaviour
{
// Cached Character
private Character _character;
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// On RootMotionJump state enter...
// Cache ECM2 Character
if (_character == null)
_character = animator.transform.root.GetComponent<Character>();
// Set Flying movement mode to use root-motion vertical velocity
_character.SetMovementMode(MovementMode.Flying);
}
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// On RootMotionJump state exit...
// Exit Flying movement mode.
// It's perfectly fine to use Falling as our exit movement mode since it is automatically handled based on the character's ground status.
_character.SetMovementMode(MovementMode.Falling);
}
}
In this example, entering the animation state activates the flying movement mode; otherwise, the root motionās vertical velocity would be discarded. Once the state is completed, it exits flying movement mode by setting MovementMode.Falling, which is automatically managed in response to grounding status, making it a great choice as an āexitā state.
This same approach can be applied to different states that need to interact with the Character. By creating custom StateMachineBehaviour scripts, you can modularize specific behaviors, such as attacking, rolling, or any other custom movement, in a way thatās both flexible and easy to manage.
Brilliant, thanks again! This works great. Iāve also used the same technique to add audio to the animation states. Great stuff!
My Third Person controller is looking and feeling really good. Iāve just started on an NPC class, using your NavMeshCharacter component, inheriting from my Character enhancements, reusing my CharacterAnimator and animation controller without any changes. It all just works.
Iām delighted with EMC2 and your help - thank you again!
Thatās great, thanks for the update and kind comments! Iām glad to hear everythingās working well for you. If you have any more questions, feel free to reach out. Happy coding!
Iām really sorry to keep bothering you - Iām enjoying getting stuck in to ECM2! I have switched my Character to root motion, setting useRootMotion to true in the inspector, and adding a RootMotionController component.
Iāve added code into my Character component to subscribe to the MovementModeChanged base class event, and in that Iām setting useRootMotion to IsWalking(). So the moment the player jumps and switches to āFlyingā mode, root motion should be toggled off and the jump fully controlled outside of root motion.
This kind of works, but when I press jump the result is inconsistent: The character jumps, but with what feels like only a small amount of forward velocity. But every 4th or 5th jump, I suddenly get a lot of forward motion and the character jumps much further forward.
I believe this could be related to animation velocity. When using root motion, the animation velocity is applied directly, without any modifications, filters, or clamping. This ārawā velocity is then adjusted when root motion is disabled. For example, if the velocity exceeds the max speed, the CalcVelocity method will apply ābraking,ā forcing it to adhere to the max speed.
I recommend using the CalcDesiredVelocity method to visualize the animation velocity (try drawing a ray) so you can better understand what might be causing the issue. Additionally, you could try the included toggle root motion example to compare results.
Awesome, thanks again! I think it might actually to have been to do with the geometry in the scene and / or some weird collider thing. I seem to have fixed it, not quite sure how. But that works for me!
Iāve been messing with the new Behavior package in Unity 6. Your movement, controller and navmesh components are so well designed, I just drop them onto a character model, hook them up to behavior actions, and Iāve got highly configurable animal, creature and human AIs wandering around my scene! Iāve said it before and Iāll say it again, ECM2 is just brilliant, thank you again!
Hello! I use SetMovementDirection and maxFlySpeed to move my jetpack character in the air. How can i implement a dodge roll 5 meters in the left and right direction smoothly? What is the best way for that? Thanks.
The simplest approach is to apply an impulse to your character in the desired direction. This can be achieved using the AddForce or LaunchCharacter function.
Another commonly used method is leveraging root motion. However, this requires your animations to support root motion for it to work effectively.
For a more advanced and robust Dash implementation, refer to the Walkthrough 4.3 example. This demonstrates how to create a custom movement mode specifically for executing a dash.
The Unity 6 version includes two different examples for handling character input with Input System. One demonstrates managing custom actions in a way similar to the 1.3.x versions, which is used in most of the included examples and demos, ie (ThirdPersonInput, CharacterInput, etc)
An additional example showcases how to control a character (first or third person) using the PlayerInput component. These are covered in the Walkthrough - Controlling a Character section.
While both approaches are similar, each offers unique advantages depending on your project needs.
Hey Krull,
Not sure if Iām being dumb but I canāt seem to import the Unity 6 version. Iāve started a fresh project, imported ECM through the package manager, but it seems to be the Unity 2021+ version, as it uses the old Cinemachine namespace etc. Any help is appreciated.
Also thank you very much for the continued updates on this amazing asset.
Another user reported this issue, and it appears to be related to Unity serving an older version of the package.
Itās worth noting that the Unity 6 version of the package was uploaded using the latest available Unity 6 release at the time, version 6000.0.26f1. As a result, using a Unity version older than this might default to downloading a 2021+ version of the package.
I recommend updating to the latest Unity 6 version and forcing a re-download of the package within Unity 6. If the package was previously downloaded with an older Unity version, it will likely default to the 2021+ version.
In the meantime, if the issue persists, please contact me through the support email (please include your invoice number). Iāll be happy to provide the Unity 6 version directly.
ECM2 does not include an AI āmanagerā since this functionality is highly specific to each game.
By default, a Character uses an āauto-simulationā feature implemented via a LateFixedUpdate coroutine. For scenarios requiring a large number of characters, it is recommended to disable this auto-simulation by setting enableAutoSimulation to false. You can then manage character updates manually by calling the Simulate method within your Manager.
This approach works well when combined with the NavMeshCharacter component, which enhances a standard Character with navigation capabilities using Unityās NavMesh and NavMeshAgents.
Thank you Krull, I downloaded the latest version of Unity 6 and it solved the issue, I didnāt realise I was still using 6000.0.25f1 so it was my mistake.