Showing posts with label Cacildes Adventure. Show all posts
Showing posts with label Cacildes Adventure. Show all posts

Jan 1, 2026

Hybrid AI Movement - A case of character controller + navmesh agent

One of the trickiest parts of the game is handling AI movement, mostly because I use a hybrid solution made of:

  • Root motion animations for busy actions such as attacks, spell casting, dodges, and parry reactions — mostly because there is no better movement than the one built directly into the animation itself, and this brings a much more natural fluidity to combat.
  • NavMesh pathfinding, which is activated specifically for the Patrol and Chase states, since these depend on navigating the scene’s NavMesh paths. This is used only as a guide for the third piece of the puzzle.
  • CharacterController, which acts as the actual motor that moves the AI around when root motion is not being used. This controller is fed with movement data coming from the NavMeshAgent’s pathfinding results.

This is the code I use to make all of this work together:


private void OnAnimatorMove()
{
    Vector3 gravity = characterGravity != null && characterGravity.ignoreGravity
        ? new Vector3(0, characterGravity.initialY, 0)
        : Physics.gravity;
    if (animator.applyRootMotion && characterController.enabled)
    {
        // If the agent is enabled and we are not performing an action,
        // use the NavMesh to guide the CharacterController
        if (agent.enabled && !isBusy)
        {
            Vector3 worldDeltaPosition = agent.nextPosition - transform.position;
            worldDeltaPosition.y = 0f;
            Vector3 direction = worldDeltaPosition.normalized + gravity;
            float speed = ShouldRun() ? chaseSpeed : patrolSpeed;
            if (!characterController.isGrounded)
            {
                direction += agent.transform.forward * 2f;
            }
            if (agent.velocity.magnitude <= 0.1f)
            {
                speed = 0f;
            }
            characterController.Move(speed * Time.deltaTime * direction);
            agent.nextPosition = transform.position;
            HandleAgentRotation();
        }
        else
        {
            // Apply animator root rotation
            transform.rotation *= animator.deltaRotation;
            // Apply root motion position and gravity
            Vector3 rootMotionPosition =
                animator.deltaPosition + gravity * Time.deltaTime;
            if (isCuttingDistanceToTarget)
            {
                HandleCuttingDistance(ref rootMotionPosition);
            }
            characterController.Move(rootMotionPosition);
        }
    }
}

HandleCuttingDistance is a helper function that makes the enemy dash toward the player, almost like a glide effect. This is great for making combat more challenging, since the enemy can close distances very quickly.


Why isn’t NavMesh alone enough?

Unity’s NavMeshAgent is great at pathfinding, but terrible if you want enemies to be pushed off the NavMesh area.

I iterated through multiple solutions, but none of them worked well. In the end, I found that only activating the NavMeshAgent for Patrol and Chase was enough, while letting other states be handled entirely by animations.

For example, if an enemy dodges using root motion, the NavMeshAgent is disabled, so there’s nothing preventing that enemy from falling off a cliff. Of course, this is one of the worse trade-offs of this approach — certain enemy actions can end up causing their own death.

There is definitely room for improvement here, such as turning dodge actions into proper state-machine states that enable the NavMeshAgent on state enter and disable it on state exit.


Common issues with this hybrid approach

One of the main issues I ran into was enemies getting stuck in certain areas. For this system to work properly, there are two very important things to take into account:

  • CharacterController dimensions must be smaller than the NavMeshAgent’s dimensions
    The controller’s radius and height must be smaller than the agent’s radius and height. Otherwise, the agent might find a valid path, but the CharacterController collides with the environment, causing the AI to get stuck — even though the agent itself “fits.”
  • Step height must match between the agent and the controller
    I was still having issues even with correct dimensions, and eventually found the cause: the NavMeshAgent step height was set to 0.5 (50 cm), while the CharacterController’s step offset was only 0.1 (10 cm). This meant the agent could plan paths over steps that the CharacterController physically could not climb.

Because of this mismatch, the agent wanted to move forward, but the controller refused — resulting in a stuck AI.


This fix should be included in the next patch for the game.


These are some hard-conquered lessons after many iterations on this problem 🙂 I hope this helps someone out there who is trying to build Soulslike AI movement using a hybrid solution with NavMesh and CharacterController.

Aug 3, 2025

Cacildes Adventure - Obsidian Samurai Boss Fight Improved



Here's the improved Obsidian Samurai boss fight from Cacildes Adventure v2.0!

In this video, you'll see that both the player and the enemy wield the same weapon — the Odachi — which means they also share the same animation move set!

They’re even wearing the same armor. This marks a big milestone for Cacildes Adventure: starting from v2.0, both AI enemies and players will share the same items, weapons, and even skills!

So if you spot an enemy using a weapon, from now on you can be sure — you’ll be able to claim it for yourself. :-)

Hope you enjoy it!


~~~


Aqui está a luta melhorada contra o boss Samurai de Obsidiana em Cacildes Adventure v2.0!

Neste vídeo, vais ver que tanto o jogador como o inimigo usam a mesma arma — a Odachi — o que significa que partilham exatamente os mesmos movimentos e animações!

Ambos usam também a mesma armadura. Isto marca um passo importante em Cacildes Adventure: a partir da versão 2.0, os inimigos controlados pela IA e os jogadores passam a partilhar os mesmos itens, armas e até habilidades!

Por isso, sempre que vires um inimigo a usar uma arma, já sabes — a partir de agora vais poder apanhá-la para ti. :-)

Espero que gostes!

Jul 3, 2024

Cacildes Adventure - Preview of Sewers in Cecily Town

 Working on new content for Cacildes Adventure!

I'm doing some improvements on AI and combat so that it will be more dynamic with enemies cutting distance to attack you!





The way the cutting distance works is we have two functions, MoveTowardsTarget and StopMoveTowardsTarget, where we define in the animation clip where we want the enemy to start moving towards us, and then we stop on a keyframe later. This allows for the animation to decide if the enemy should move closer.

This is all just a flag in code, that allows special behaviour to occur in the OnAnimatorMove() function of our AI character:


So if we are cutting distance, let's use the navmesh to do it. Otherwise, just sync the agent and character controller to the root motion of the animation playing (which is the default behaviour).


That's a nice way to handle this, and it makes combat a lot more fun!

Anwyay, expect more news soon :-) Stay tuned!

Jun 5, 2024

Cacildes Adventure: New work begins with a character creator

 As I begin plans for another expansion for Cacildes Adventure, first thing I'm working on is a character customization tool to customize Cacildes!




The way it works (or I thought about it) was creating body parts container game objects to store all the available customization pieces:

This works great with my existing equipment logic for armors, because if a helmet requires us to hide the hair of the character, for example, we just need to hide the parent container "HairContainer", and voilà, it is agnostic to whatever hair is selected because that's a child of the HairContainer.


You'll be able to edit many details of the model and also the name of the character.

I'm also adding localization! More on that later.

Stay tuned for more :-)


May 29, 2024

Cacildes Adventure: Expanding the combat and the Mimic Chest

Couple of new additions to the next update of the game!

Enemies can now follow-up attacks with more combos. Before, they could follow-up any attack with an additional combo, but now it's 3 or even 4 attacks in a row!


Also working on some new enemies for the Sunkenland expansion (coming this summer). Take a look at the mimic chest!



 

May 27, 2024

Cacildes Adventure - 20% off on Steam this week!

20% off on Steam this week! Check it out :-D

Man, it's been a while! My game has been released to Steam this April and I didn't even post about it here on the blog, so I'm doing it now.


It's currently 20% off on steam, and it's been getting better with each weekly update. For every bug I fix, I try to add some new weapon or spell or something new to the game. I think it's going to be like that for a while, but I have plans for a small expansion this summer. Anyway, do check it out! It plays great on the steam deck as well. ;)

















Jun 21, 2023

Cacildes Adventure - Full game release!

 

Hello everyone. I am pleased to announce the full game is now available for download!
After a year and a half of development (it started in January 2022), I am very happy to see this project concluding in what I believe is a high-note. I want to thank my girlfriend, Cátia, which was crucial to get me going during the highs and lows of developing this. A special thanks to Synty Studios, which assets I have been buying for many years and finally got the chance to put to good use! 
And thanks Unity for a game engine which has allowed me to fullfill my life's dream: to create my own RPG!
Please report any bugs that you find in the discussion board. I am available to help if you get stuck during your playthrough. :-)
I hope you have a delightful experience playing with Cacildes and his companions!




~~~~


(Game gone gold obligatory picture)





 

May 4, 2023

UNITY PERFORMANCE: FindObjectOfType => outside of start() or awake() is problematic






 Just a heads up on something I was unaware. As I added many climbable ladders to my city, I started noticing the frames dropping and while using Profiler, I noticed it was due to the Update loop. There was a reference in the Update loop to the FindObjectOfType call, which I thought was done only once during the gameobject initialization.

I put the FindObjectOfType references in the Awake() method instead, and my framerate doubled, from 60 to 120. So, heads up! :-P


BEFORE:


AFTER: