Animation Issues

[IGNORE THE 2 COMMENTS BELOW, BUT NOT THE LAST ONE. I SOLVED BOTH PROBLEMS, BUT HAVEN’T SOLVED THE ONE IN THIS TITLE YET]

Hello folks. Anyone knows how to fix this animation issue, where the player unexpectedly makes a weird twist? I tried everything I know, I can’t figure it out unfortunately… Here’s a video demo to show what I mean (focus on the scene tab. It’ll show the problem a lot more clearly):

https://drive.google.com/drive/folders/1Ik1HY8tHsdHCWKKBazQ0Y3YLg7BNABNG?usp=sharing

(Ignore the fact that the horse takes half the hits… I am working on offsetting the projectiles from the right hand, based on which animal the player is driving. Will it look ugly? Most likely. Will it fix a problem drastically, yes it will)

and a side-question. What’s the drawbacks of using too many static variables? I’m trying not to, but I almost got myself into a tight couple, and to evade that, I used the static namespace I created a while ago to create a new variable to handle a specific situation, and it’s only getting bigger moving forward…

I’m getting a little worried about memory allocation, and more importantly, the code architecture

So here’s the specific example I used a static variable for. In WeaponConfig.cs, I wanted to differentiate how to fire arrows, based on what the player’s current state is at, so he can fire straight arrows when in freelook state on the back of an animal, and fire precise arrows in his aiming state, so he can have some wild shots here and there

Unfortunately, without a static class, I can’t see myself out of a tight couple, so… yeah, that’s what I kinda did here:

  1. In my own ‘RPG.Statics’ namespace, I created this new variable:
public static class IsRangerAimingStaticClass
{
    // The man-in-the-middle that will allow the player to fire a straight arrow
    // if he's not aiming at anyone, whilst on the back of an animal, and fire precise arrows when he's aiming at
    // someone
    public static bool IsRangerAiming;
}
  1. I set this up in ‘InputReader.cs’ accordingly:
    public void OnRangerAim(InputAction.CallbackContext context)
    {
        if (context.performed) 
        {
            RangerAimEvent?.Invoke();
            RangerOnAnimalAimEvent?.Invoke();
            IsRangerAiming = true;
            IsRangerAimingStaticClass.IsRangerAiming = true; // essential, so that 'WeaponConfig.LaunchProjectile (with 4 variables, for freelook) can classify between firing straight, freelook-style arrows, and firing precise arrows when the player is precisely aiming with his 'RangerAimingState.cs'
            Debug.Log($"IsRangerAiming: {IsRangerAiming}");
        }

        else if (context.canceled) 
        {
            RangerAimCancelEvent?.Invoke();
            RangerOnAnimalCancelEvent?.Invoke();
            IsRangerAiming = false;
            IsRangerAimingStaticClass.IsRangerAiming = false; // essential, so that 'WeaponConfig.LaunchProjectile (with 4 variables, for freelook) can classify between firing straight, freelook-style arrows, and firing precise arrows when the player is precisely aiming with his 'RangerAimingState.cs'
            Debug.Log($"IsRangerAiming: {IsRangerAiming}");
        }
    }

and 3. Use that in ‘LaunchProjectile.cs’ (the 4-variable variant, for freelook arrow shots):

        // for cases of firing a projectile when we don't have a target (freelook state). Also used when you're driving an animal:
        public void LaunchProjectile(Transform rightHandTransform, Transform leftHandTransform, GameObject instigator, float damage, Animal animal = null)
        {
            Transform correctTransform = GetTransform(rightHandTransform, leftHandTransform);
            Projectile projectileInstance;

            if (AnimalMountManager.isOnAnimalMount && IsRangerAimingStaticClass.IsRangerAiming)
            {
                // Compensation because on animals, the animations can get messy and the instantiation point can get terrible
                projectileInstance = Instantiate(projectile, correctTransform.position + new Vector3(0.5f, 0.5f, 0.5f) /* <-- convert to an 'AnimalOffset' Vector3 in 'Animal.cs', and refer to it from there */, Quaternion.identity);

                Camera mainCamera = Camera.main;
                Ray ray = mainCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
                if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity))
                {
                    projectileInstance.transform.LookAt(hit.point);
                    projectileInstance.transform.forward = ray.direction;
                }
                else
                {
                    projectileInstance.transform.forward = ray.direction;
                }
            }
            else
            {
                projectileInstance = Instantiate(projectile, correctTransform.position, Quaternion.identity);
                projectileInstance.transform.forward = instigator.transform.forward;
            }

            projectileInstance.SetupNoTarget(instigator, damage);
            Debug.Log($"Projectile Launched");
        }

Usually I only create these variable for one-time uses only, so it doesn’t get messy, otherwise I can see how hard it will be to debug those variables

It does exactly what needs to be done, but… I’m getting worried about the dependencies and the code architecture with the dependencies going everywhere :sweat_smile: (which is why I’m asking if there’s better approaches than this).

I’m trying my absolute best to stay away from using static variables, because I know how dangerous they can get if unmonitored, hence why I seek better approaches

After the comment above (I know these 2 comments have nothing to do with my animation problem, so bear with me), I suffered a very similar problem, where I was unable to accumulate a value. Instead of taking the static approach, though, I took the Interface approach with this one (I don’t understand interfaces much, so please, review this part for me and let me know if this works out or not).

My problem was, I wanted to get an ‘offset’ value that I attached to my ‘Animal.cs’ Script (it’s a custom code that I created for my animals, so they can hold valuable information that I can accumulate, like type, offset of projectile firing, etc), and I wanted to decouple the code a little bit, without having to rely on static variables

So, here’s what I did:

  1. I created a brand new Interface (I called the file for my unique interfaces as ‘External Interfaces’), called ‘IAnimal’, (for now) solely to get the offset values:
using UnityEngine;

public interface IAnimal 
{
    Vector3 GetProjectileOffset {get;}
}
  1. In ‘Animal.cs’, which holds the offset value that I want to get (and goes on all animals in my game scene), I implemented this interface, so I can have a hold of the offsets of all the animals:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace RPG.Animals {

public enum AnimalType
{
    Horse,
    Raven,
}

public class Animal : MonoBehaviour, IAnimal
{
    [SerializeField] private AnimalType animalType;
    public AnimalType Type => animalType;
    [SerializeField] public Transform AnimalPlayerRangerCameraTransform;
    [Tooltip("Is there any offset that's supposed to occur when firing a projectile from the back of this animal? Used in 'WeaponConfig.LaunchProjectile()' (freelook edition) to make sure the projectile doesn't accidentally hit the animal")]
    [SerializeField] public Vector3 OnThisAnimalProjectileOffset;

    public Vector3 GetProjectileOffset => OnThisAnimalProjectileOffset; // Implementing the 'IAnimal.cs' Interface rules
}

}
  1. I went to ‘LaunchProjectile’ in ‘WeaponConfig.cs’ (which I got from @Brian_Trotter 's RPG and Third Person merge course), and added an OPTIONAL parameter in the end of the parameters, of type ‘IAnimal’ (the interface), and officially set it to null

Inside the function, I check for it first before getting the code to act accordingly, as follows (this is no longer a 4-line function, it expanded because of my specific needs):

        // for cases of firing a projectile when we don't have a target (freelook state). Also used when you're driving an animal:
        public void LaunchProjectile(Transform rightHandTransform, Transform leftHandTransform, GameObject instigator, float damage, IAnimal animal = null)
        {
            Transform correctTransform = GetTransform(rightHandTransform, leftHandTransform);
            Projectile projectileInstance;

            if (AnimalMountManager.isOnAnimalMount && IsRangerAimingStaticClass.IsRangerAiming && animal != null)
            {
                // Compensation because on animals, the animations can get messy and the instantiation point can get terrible
                // projectileInstance = Instantiate(projectile, correctTransform.position + new Vector3(0.5f, 0.5f, 0.5f) /* <-- convert to an 'AnimalOffset' Vector3 in 'Animal.cs', and refer to it from there */, Quaternion.identity);

                Vector3 offset = animal.GetProjectileOffset;
                projectileInstance = Instantiate(projectile, correctTransform.position + offset, Quaternion.identity);

                Camera mainCamera = Camera.main;
                Ray ray = mainCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
                if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity))
                {
                    projectileInstance.transform.LookAt(hit.point);
                    projectileInstance.transform.forward = ray.direction;
                }
                else
                {
                    projectileInstance.transform.forward = ray.direction;
                }
            }
            else
            {
                projectileInstance = Instantiate(projectile, correctTransform.position, Quaternion.identity);
                projectileInstance.transform.forward = instigator.transform.forward;
            }

            projectileInstance.SetupNoTarget(instigator, damage);
            Debug.Log($"Projectile Launched");
        }
  1. In ‘Fighter.cs’, where I actually call the ‘LaunchProjectile.cs’ function, from ‘Shoot()’ (which is called from the animation event line), I added a line to get the type of animal, as follows:
            IAnimal currentAnimal = AnimalMountManager.isOnAnimalMount ? GetComponent<PlayerOnAnimalStateMachine>().GetAnimal() /* only the player holds 'PlayerOnAnimalStateMachine.cs', so alls good */ : null;

(Note: I already had an animal getter in my ‘PlayerOnAnimalStateMachine.cs’ variant of my state machine, which only works when the player is mounting an animal, and replacing the original state machine in that case)

and then I plugged that value into my ‘Shoot → LaunchProjectile’ call:

            if (targetObject != null)
            {
                // use the 'LaunchProjectile' function with 5 arguments (as we do have a target)
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, targetObject.GetComponent<Health>(), gameObject, damage);
            }
            else
            {
                // use the 'LaunchProjectile' function with 4 arguments (as we don't have a target), the last one (added 14/6/2024) is to offset the arrows based on the animal being driven
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, gameObject, damage, currentAnimal);
            }

Quite the learning journey with this one…

I’ll repeat the last 2 steps to get the Aim Reticle on my screen to act properly as well :slight_smile:

(I still have the animation problem from the title of this topic :sweat_smile:)

Just an update about the problem… does this matter? It’s in the animation itself which isn’t working, and if it does, how can I reassign them?

Missing Values

Another problem to mention, is I can’t get my player to properly rotate his upper body on the top of the horse. The entire body? Possible. Just the upper body, it’s an Inverse Kinematics Nightmare… @Brian_Trotter or @bixarrio if anyone of you guys knows how to deal with any of the 2 issues in this post (please ignore my first and second responses, they were for a different problem. So from a total of 5 replies here so far, please only investigate 1, 4 and 5), please help me out

To get my player to rotate his ENTIRE body whilst aiming on the back of the horse, here’s what I came up with:

    private void HandleUpperBodyRotation()
    {
        if (stateMachine.PlayerBodyRoot != null)
        {
            // Get the forward direction of the camera
            Vector3 targetDirection = Camera.main.transform.forward;

            // Flatten the target direction on the horizontal plane to avoid unwanted pitch
            targetDirection.y = 0;

            if (targetDirection.sqrMagnitude > 0.001f)
            {
                // Calculate the target rotation for the upper body
                Quaternion targetRotation = Quaternion.LookRotation(targetDirection);

                // Smoothly interpolate the spine's local rotation towards the target rotation
                stateMachine.PlayerBodyRoot.rotation = targetRotation;
            }
        }
    }

(That code works btw)

and here’s my attempt at making only the upper body rotate:

    private void HandleUpperBodyRotation()
    {
        if (stateMachine.PlayerBodyRoot != null)
        {
            // Get the forward direction of the camera
            Vector3 targetDirection = Camera.main.transform.forward;

            // Flatten the target direction on the horizontal plane to avoid unwanted pitch
            targetDirection.y = 0;

            if (targetDirection.sqrMagnitude > 0.001f)
            {
                // Calculate the target rotation for the upper body
                Quaternion targetRotation = Quaternion.LookRotation(targetDirection);

                // Smoothly interpolate the spine's local rotation towards the target rotation
                // stateMachine.PlayerBodyRoot.rotation = targetRotation; // TEST ALT BELOW

                // TEST:
                List<Transform> upperBodyParts = new List<Transform>();
                upperBodyParts.Add(stateMachine.Animator.GetBoneTransform(HumanBodyBones.Spine));
                upperBodyParts.Add(stateMachine.Animator.GetBoneTransform(HumanBodyBones.Chest));
                upperBodyParts.Add(stateMachine.Animator.GetBoneTransform(HumanBodyBones.Neck));
                upperBodyParts.Add(stateMachine.Animator.GetBoneTransform(HumanBodyBones.Head));

                foreach (Transform part in upperBodyParts)
                {
                    if (part != null)
                    {
                        part.rotation = targetRotation;
                    }
                }
            }
        }
    }

This one twists the direction in an absurdly weird way which I don’t even know how to explain, and dealing with local angles instead is even more disastrous. Anyone knows what’s going on here…?

For reference, here’s what’s going on:

(this is so wrong on so many levels)

Unfortunately, my computer bricked itself yesterday, so I’m answering questions on a laptop without access to any of my projects to test this out on, but let’s consider for a moment the way that twisting a connected body works…

So imagine you turn the Spine towards the target rotation… since the Chest is connected to the spine, it’s local rotation does not change but it rotates with the spine anyways because the LOCAL rotation is relative to the parent transform (which is relative to it’s parent transform, etc)…
So if you then rotate the Chest you’ve effectively rotated it MORE than it was already rotated by rotating the spine… but wait, now you’ve rotated the Neck, which was already rotated twice before by the chest and the spine… then finally, you’ve rotated the head which was already rotated three times before…

Congratulations, you’ve murdered your character a la Vecna from Stranger Things.

I’m very mindful of all of this with my neck condition (especially now that the neck is essentially fused). In order for me to turn and look at you, I turn my spine, which turns my shoulders, neck, and head because those are all along for the ride.

how did I somehow end up needing a master’s degree in Biological Kinematics to solve a programming problem… :sweat_smile: - honestly speaking, Brian, this whole idea of ‘Mounted Archery’, and honestly proper archery in and of itself, has challenged me in ways that I never thought I’d have to deal with one day (and the broken animation in the title of this post is the biggest headache of all)… but one day or another I was eventually going to have to deal with this anyway. Getting the OnFoot variant working by itself was honestly a miracle for me to begin with, because the Quaternions on that things were a nightmare before I figured out that it has to do with Unity’s internal angle workings that had to be compensated for

and there are a few broken things that I just couldn’t explain as to why they’re broken, and will probably need some rewriting

I did not see this coincidence happening at all :sweat_smile: - I’m just hoping you’re coping well at the very least. On a side note, do you have any suggestions on what I should try next to see if we can solve the problem? That would be highly appreciated, xD

Well, my first suggestion would be to point out that you’re tending towards Feature Aquisition Syndrome again… Too many features, not enough engineers. Remember that these sorts of problems are often solved by teams of programmers…

Most of the time, aim offsets are managed using a single bone… In this case, I’d probably say the spine or the chest. If you want a more natural each bone curves some approach, you’ll have to figure out what percentage of a turn to look belongs to each affected bones.

Just be nice to our poor models. The one in the picture you posted is going to require costly surgeries, assuming he didn’t break his neck altogether.

Trust me, feature-wise I’m almost done. It’s only a matter of time before I stop engineering this thing and actually start turning what we have worked so hard for into a proper game, and hopefully by then, it’ll be a lot of fun to play :slight_smile:

I tried freezing the rotation axis using just the spine on the x, y and z axis and use just the spine, and for some reason he’s pointing downwards as he rotates, with absolutely no intentions of ever looking back up. Here’s the code if you want to have a look:

    private void HandleUpperBodyRotation()
    {
        if (stateMachine.PlayerBodyRoot != null)
        {
            // Get the forward direction of the camera
            Vector3 targetDirection = Camera.main.transform.forward;

            // Flatten the target direction on the horizontal plane to avoid unwanted pitch
            targetDirection.y = 0;

            if (targetDirection.sqrMagnitude > 0.001f)
            {
                // Calculate the target rotation for the upper body
                Quaternion targetRotation = Quaternion.LookRotation(targetDirection);

                // Smoothly interpolate the spine's local rotation towards the target rotation
                // stateMachine.PlayerBodyRoot.rotation = targetRotation; // TEST ALT BELOW

                // TEST (ENTIRE REST OF THIS FUNCTION):
                List<Transform> upperBodyParts = new List<Transform>();
                upperBodyParts.Add(stateMachine.Animator.GetBoneTransform(HumanBodyBones.Spine));
                // upperBodyParts.Add(stateMachine.Animator.GetBoneTransform(HumanBodyBones.Chest));
                // upperBodyParts.Add(stateMachine.Animator.GetBoneTransform(HumanBodyBones.Neck));
                // upperBodyParts.Add(stateMachine.Animator.GetBoneTransform(HumanBodyBones.Head));

                targetRotation.x = 0; // <-- the modified line

                foreach (Transform part in upperBodyParts)
                {
                    if (part != null)
                    {
                        part.rotation = targetRotation;
                    }
                }
            }
        }
    }

ehh I’m not looking for something too advanced. All I really want is when he’s rotating on the back of the horse, don’t rotate your legs (unless maybe you do a full 180 rotation… but let’s not get into this now, and honestly speaking, I probably won’t even bother with it if it gets too hard for what it does), because it won’t make sense if your legs are rotating with you inside the horse. That’s just it :slight_smile:

just let go of the right mouse button, he’ll heal himself :stuck_out_tongue: - it’s what happens when you’re holding the right mouse button, with a bow in your hand (it’s quite important, because that button either defends you, or acts as a bow, depending on what you’re holding), that bothers me the most

Chilling Player
and, oh look, he’s chilling. Don’t worry, he doesn’t need a surgery :smiley:

(I tried rotating the hips for that one, and that’s what I got…!)

So… I did a little bit of further thinking, and decided “Wait a second… why don’t we rotate the entire body, and then bring the legs back to their original transform?”

Whilst that worked for a little bit at first, essentially when the player does a full 180 turn, the legs hide inside the horse, before turning around and returning as the player returns

So, instead of solving the problem, it just seemed like it was slowing the problem down from being visible, which did not help at all. Here’s what I came up with, though:

    private void HandleUpperBodyRotation()
    {
        if (stateMachine.PlayerBodyRoot != null)
        {
            // Get the forward direction of the camera
            Vector3 targetDirection = Camera.main.transform.forward;

            Quaternion currentUpperLegLRotation = stateMachine.PlayerBodyRoot.FindGrandChild("UpperLeg_L").transform.rotation;
            Quaternion currentUpperLegRRotation = stateMachine.PlayerBodyRoot.FindGrandChild("UpperLeg_R").transform.rotation;

            // Flatten the target direction on the horizontal plane to avoid unwanted pitch
            targetDirection.y = 0;

            if (targetDirection.sqrMagnitude > 0.001f)
            {
                // Calculate the target rotation for the upper body
                Quaternion targetRotation = Quaternion.LookRotation(targetDirection);

                // Smoothly interpolate the spine's local rotation towards the target rotation
                // stateMachine.PlayerBodyRoot.rotation = targetRotation;

                // TEST 1:
                stateMachine.PlayerBodyRoot.rotation = targetRotation;
                stateMachine.PlayerBodyRoot.FindGrandChild("UpperLeg_R").transform.rotation = currentUpperLegRRotation;
                stateMachine.PlayerBodyRoot.FindGrandChild("UpperLeg_L").transform.rotation = currentUpperLegLRotation;
            }
        }
    }

Please send help, I want to move on from this insanity already :sweat_smile:

This one is actually well outside of my wheelhouse. Double the fun in that, as I may have mentioned, my system is down, and the laptop I’m working on just can’t handle Unity.

Just keep experimenting, and apologize to your model on a regular basis.

I ended up keeping it as is. Apparently the new attempt may work on the horsey, but on the Raven, oh my god it was a disaster… So I ended up keeping it as is, when I noticed how much of a disaster it would be with all the other animals after that. The animation in the title is something I desperately wish I could fix though, but alls good for now

In the meanwhile I managed to get my projectiles to ignore the animal I’m driving from the driver (i.e: the player in this prototype. So if the player fires an arrow at the Raven he’s flying for example, the Raven will ignore it. This is the alternative solution I have instead of doing offsets and what not, and the Aim Reticle was not negotiating with me when it comes to taking offsets for some reason… I did it using Interfaces again… I’m slowly getting comfy with these wild concepts, xD), and boy oh boy did this suddenly become a hell of a lot more fun. Literally, a warzone, but with animals instead of planes, and it’s a ton of fun xD (but with some issues that I’m asking Malbers about, since he solved it in his own way) :stuck_out_tongue:

You honestly ought to try this lol, it’s the most fun I’ve had in a long time, but with a big smile on my face because it’s kinda wild to believe that I’m the creator of this amazing scene, lol

Not going to lie though, I loved the feeling of firing arrows from above the flying Raven on the poor peasants down there that are fighting because I set them up against each other :stuck_out_tongue:

Anyway, time to go lock up the Aiming system with the last few steps. It’s been a wild week with this system, and I want to move on to Dynamic Respawning next :slight_smile:

You honestly taught me well, Brian, and your absence was an amazing chance for me to actually try out new ideas (I don’t mean this in any bad way btw, don’t get me wrong. I mean this in the best way possible!). Thank you, but I’ll still ask for help and nag you from time to time :slight_smile:

Privacy & Terms