Trouble getting AI character to rotate in place...

DavidC

Active member
Basically, I want the npc to rotate toward the player before attacking, even if they don't move position.

I've tried a number of approaches now that all didn't work as expected. Main problems I ran into were...
1. NPC pitch & roll being changed too.
2. Physics jittering issues if I set eular x & z to zero at same time.
3. Tried aim & look at abilities, but the character only seems to rotate if they also have to move position some.

I'm sure Im going about this wrong. What should I be doing?

Thank you!
 
The Ultimate Character Controller is taking control of the rotation so you'll need to create a new ability which rotates. I haven't had a chance to add this ability yet but this thread is related: https://opsive.com/forum/index.php?threads/rotate-towards.5009/#post-25720
How is this being handled in the Behavior & Deathmatch AI? I own behavior, but I'm avoiding using it (for now), because I'm trying to make some very lightweight, performant, and dumb NPCs (in addition to better NPCs). Basically minions/trash and bosses. I have read over some of the code for behavior, but didn't spot anything for this.

I saw that forum post, the code doesn't work. I've spent an additional 4 hours trying to get it to work today (spent all day yesterday working on this problem, including time on that script), but I'm getting NREs on function calls that come from parent classes, and just don't know all the ins and outs of how the ability system works. I see a debug log once, and then get NREs on line 70...

This is where it's NRE (line number is literally the function signature)
Code:
    public override void UpdateRotation()
    {
        base.UpdateRotation();

The problem happens regardless if I start the ability once and immediately stop it, or if I don't try to stop it. I have it set to manual start (using a trystartability function call). And have tried both manual stop and automatic stop.

I have also looked over the documentation on creating abilities for awhile now. This is a lot of wasted time for something as simple as turning a character, and I am frustrated...
 
The LocalLookSource Target property is set, which will then be used by the Aim/Use abilities to face the target towards that look source target.

This is where it's NRE (line number is literally the function signature)
That can't be - it sounds like the line numbers are somehow getting corrupted.
 
Justin said:
The LocalLookSource Target property is set, which will then be used by the Aim/Use abilities to face the target towards that look source target.


That can't be - it sounds like the line numbers are somehow getting corrupted.
I swear, literally the moment I make a forum post, I do the *one thing* that fixes the problem. In this case, I shut down everything, ate lunch, came back, turned the computer back on, started working, and zero NREs...

I am using the Update Rotation () every frame to have the npc turn towards me (they also walk until they hit stopping distance). I have the A* integration. The NPC isn't following at all. I'll add code in a minute.

Code:
IEnumerator TurnAndAttack()
{
RotateTowards();
Debug.Log("Coroutine started");
for (int i = 0; i < rotationFrames; i++)
{
yield return null;
}
RotateTowardsStop();
UseItem();
StopCoroutine(TurnAndAttack());
}

Everything else is working: starting and stopping of coroutine, UseItem();

Code:
public void RotateTowards()
{
if (rotateTowards != null) characterLocomotion.TryStartAbility(rotateTowards);
}

public void RotateTowardsStop()
{
if (rotateTowards != null) characterLocomotion.TryStopAbility(rotateTowards, true);
}

Getting proper debug.log for starting and stopping

Code:
using System.Collections;
using System.Collections.Generic;
using Opsive.UltimateCharacterController.Character;
using UnityEngine;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Utility;

public class RotateTowards : Ability
{
public GameObject target;
public LocalLookSource localLookSource;

public override void Awake()
{
base.Awake();
localLookSource = m_CharacterLocomotion.gameObject.GetComponent<LocalLookSource>();
}

public override bool CanStartAbility()
{
if (!base.CanStartAbility())
{
return false;
}
return true;
}

public override bool CanStopAbility()
{
return !m_CharacterLocomotion.Grounded;
}

protected override void AbilityStarted()
{
base.AbilityStarted();
Debug.Log("Start.");
target = localLookSource.Target.gameObject;

}

public override void UpdateRotation()
{
base.UpdateRotation();
if (target == null)
{
return;
}
var targetRotation = Quaternion.RotateTowards(m_GameObject.transform.rotation, target.transform.rotation, 360);
m_CharacterLocomotion.DeltaRotation = (Quaternion.Inverse(m_Transform.rotation) * targetRotation).eulerAngles;
}

protected override void AbilityStopped(bool force)
{
base.AbilityStopped(force);
Debug.Log("Stop.");
}
}

I've been stepping through the debugger, and this is what my code is spitting out at the end of UpdateRotation()
1622920217042.png

But watching for several frames now, the value hasn't changed off -267 (+93) even a bit. Needless to say, in full play mode, I can stand behind the NPC and he'll attack straight ahead of them without turning towards me at all.
1622920231382.png

Even if I just put this in UpdateRotation(), with any values at all, nothing seems ot happen.
Code:
m_CharacterLocomotion.DeltaRotation = new Vector3(0, 180, 0);
 
Last edited:
this code works, but I'm assigning directly instead of working through the opsive locomotion system...

Code:
    protected override void AbilityStarted()
    {
        base.AbilityStarted();
        Debug.Log("Start.");
        target = localLookSource.Target.gameObject;
        targetDirection.transform.SetPositionAndRotation(m_CharacterLocomotion.gameObject.transform.position, m_CharacterLocomotion.gameObject.transform.rotation);
        targetDirection.transform.LookAt(target.transform);
        targetDirection.transform.rotation.eulerAngles.Set(0f, targetDirection.transform.rotation.eulerAngles.y, 0f);

    }

    public override void UpdateRotation()
    {

        base.UpdateRotation();
        if (target == null)
        {
            return;
        }
        m_CharacterLocomotion.transform.rotation = Quaternion.RotateTowards(m_CharacterLocomotion.gameObject.transform.rotation, targetDirection.transform.rotation, rotateSpeed * Time.deltaTime);
    }
 
Glad you're making progress! I'm actually surprised that your code works though as the transform rotation value is overwritten. Here's how I do it within Aim.cs:

Code:
            // Determine the direction that the character should be facing.
            var lookDirection = m_LookSource.LookDirection(m_LookSource.LookPosition(), true, m_CharacterLayerManager.IgnoreInvisibleCharacterLayers, false, false);
            var rotation = m_Transform.rotation * Quaternion.Euler(m_CharacterLocomotion.DeltaRotation);
            var localLookDirection = MathUtility.InverseTransformDirection(lookDirection, rotation);
            localLookDirection.y = 0;
            lookDirection = MathUtility.TransformDirection(localLookDirection, rotation);
            var targetRotation = Quaternion.LookRotation(lookDirection, rotation * Vector3.up);
            m_CharacterLocomotion.DeltaRotation = (Quaternion.Inverse(m_Transform.rotation) * targetRotation).eulerAngles;

LookSource.LookDirection will return the target if there is a Target set on the Local Look Source.
 
Glad you're making progress! I'm actually surprised that your code works though as the transform rotation value is overwritten. Here's how I do it within Aim.cs:

yes, that Aim.cs is the code I was working with (also from that forum post you linked). The problem I'm having is that it is NOT working as expected. (From the post above)
I've been stepping through the debugger, and this is what my code is spitting out at the end of UpdateRotation()
1622920217042.png


But watching for several frames now, the value hasn't changed off -267 (+93) even a bit. Needless to say, in full play mode, I can stand behind the NPC and he'll attack straight ahead of them without turning towards me at all.
1622920231382.png


Even if I just put this in UpdateRotation(), with any values at all, nothing seems ot happen.
Code:
m_CharacterLocomotion.DeltaRotation = new Vector3(0, 180, 0);

I don't see why assigning to DeltaRotation doesn't work. Is it possible it's because of the A* integration overwriting or something? The stopping distance is high enough that A* isn't moving the character, but maybe it's overwriting deltarotation with 0 before it's actually applied to the character?
 
Have you tried going the route of setting the Target with the LocalLookSource and the activating the aim ability? It's tough for me to be able to say what it is doing without stepping through it with A*.
 
Have you tried going the route of setting the Target with the LocalLookSource and the activating the aim ability? It's tough for me to be able to say what it is doing without stepping through it with A*.
That was one of the first things I tried. I had forgotten, but I had also tried disabling/unchecking all other abilities including A*, and that didn't seem to fix anything, so it probably isn't A*.

When I write to deltaRotation, well this is one of the many strange behaviors I get...


This is using the AIM ability. Also, for whatever reason, it's absolutely killing the frame rate.

On a related note, when is the LookAt ability used? I've looked at the code and it doesn't seem to interact with the LocalLookSource and so I'm not sure what the use case is?

I really don't know why it's not working, I just don't have a deep enough understanding of the character controller and Quaternions in a 3d environment to understand what's happening while stepping through the debugger. Since writing directly to the transform is working (at least for now), I guess I'll just move on.
 
Last edited:
Wait, is there an ability like this already included in the Movement pack? As I said, I'm not using behavior atm, but I'd buy it for the premade abilities.
 
This is using the AIM ability. Also, for whatever reason, it's absolutely killing the frame rate.
Interesting - have you tried profiling it to see what is the bottleneck?

On a related note, when is the LookAt ability used? I've looked at the code and it doesn't seem to interact with the LocalLookSource and so I'm not sure what the use case is?
The LookAt ability uses a custom target transform instead of the LookSource. In the demo scene this is used in the Interact room to have the character's head look at the target. It's a subtle feature.

I really don't know why it's not working, I just don't have a deep enough understanding of the character controller and Quaternions in a 3d environment. Since writing directly to the transform is working (at least for now), I guess I'll just move on.
I think that's a good approach for now, and I've made a note to showcase an AI agent looking at a target in one of the demo scenes.

Wait, is there an ability like this already included in the Movement pack? As I said, I'm not using behavior atm, but I'd buy it for the premade abilities.
No, the Movement Pack integration doesn't include any integrations.
 
Interesting - have you tried profiling it to see what is the bottleneck?
It's scripts (player loop) and the animator that spike when it's turned on. I'm totally guessing here, but perhaps all the iterative math on the transforms causes a compounding problem with having more abilities active? The character would be using A star AI Agent Movement, Aim, and "Use" while also having a Local Look Source Target (I have this set to the player and it never changes, atm).

Something else I noticed is sometimes I feel like the state system falls behind several frames. Is this something you've ever noticed?
 
It's scripts (player loop) and the animator that spike when it's turned on. I'm totally guessing here, but perhaps all the iterative math on the transforms causes a compounding problem with having more abilities active? The character would be using A star AI Agent Movement, Aim, and "Use" while also having a Local Look Source Target (I have this set to the player and it never changes, atm).
If you can drill into the call stack that should be able to point more into what method is specifically causing the slowdown.

Something else I noticed is sometimes I feel like the state system falls behind several frames. Is this something you've ever noticed?
No, the state system changes the same frame that it is triggered.
 
Top