Procedural first person headbob gives weird values

making an fps controller script
everything works however my headbobbing gives weird results.

the setup is
player(charactercontroller)as root and then a child Cameraroot(empty actor) ,then headbob(emptyActor) and last child is the maincamera

i think it has to do with not getting the correct localvelocity. but im not sure

i used ValuesSum because flax doesnt have magnitude of a Vector3?(i might be wrong)
float velocitySize = velocity.ValuesSum;//magnitude?

the bobbing works but i get sometimes extreme results and sometimes very minimal.

using System;
using System.Collections.Generic;
using FlaxEngine;

namespace Game
{
    /// Player Script.
   
    public class Player : Script
    {
        public Actor camRoot;
        public Actor headBob;
        private float rotationX;
        public float lookSpeed = 10f;
        private CharacterController controller;

              
        private Vector3 targetVelocity = Vector3.Zero;
        private Vector3 targetDirection = Vector3.Zero;
        private Vector3 moveDirection = Vector3.Zero;

        public float normalSpeed = 6f;
        public float runSpeed = 12f;
        public float crouchSpeed = 2f;
        private float speed = 0f;
        private float wantedSpeed = 0f;

        public float jumpHeight = 8f;
        public float runJumpHeight = 12f;
        public float jumpInterval = 1f;
        private float nextJump = 1.2f;
        public float Gravity = 20f;
        private bool prevGrounded;

        //footsteps and headbob
        public float headBobFrequency = 1.5f;
        public float headBobHeight = .35f;
        private float headBobSwayAngle = .5f;
        public float headBobSideMovement = .075f;
        public float bobHeightSpeedMultiplier = .35f;
        public float bobStrideSpeedLengthen = .35f;
        public float jumpLandMove = 2f;
        public float jumpLandTilt = 35f;
        public float springElastic = 1.25f;
        public float springDampen = .77f;
        private float springPos, springVelocity, headBobFade;
        private float headBobCycle;
        private float xPos;
        private float yPos;
        private float xTilt;
        private float yTilt;
        private float xVelocity = 0.0f;
        private Vector3 prevVelocity, prevPosition;
        private Vector3 bobStartPosition;
        private Quaternion bobStartRotation;



        public override void OnStart()
        {
            speed = normalSpeed;
            Screen.CursorVisible = false;
            Screen.CursorLock = CursorLockMode.Locked;
            controller = Actor as CharacterController;
            bobStartPosition = headBob.LocalPosition;
            bobStartRotation = headBob.LocalOrientation;

        }
        
        public override void OnEnable()
        {
            // Here you can add code that needs to be called when script is enabled (eg. register for events)
        }

        /// <inheritdoc/>
        public override void OnDisable()
        {
            // Here you can add code that needs to be called when script is disabled (eg. unregister from events)
        }
        public override void OnFixedUpdate()
        {
            
            Vector3 forward = camRoot.Transform.Forward;
            Vector3 right = new Vector3(forward.Z,0f,-forward.X);
            
            //inputmovement
            bool isRunning = Input.GetAction("Run");
            bool isCrouching = Input.GetAction("Crouch");
            var hor = Input.GetAxis("Horizontal");
            var ver = Input.GetAxis("Vertical");
            Vector3 velocity = controller.Velocity;
            Vector3 localVelocity = Transform.WorldToLocalVector(velocity);
            bool isMovingForward = localVelocity.Z > 0.5f;
            targetDirection = (hor * right) + (ver * forward);
            targetDirection = targetDirection.Normalized;
            targetVelocity = targetDirection;


            if (controller.IsGrounded)
            {
                if (Input.GetAction("Jump") && Time.GameTime > nextJump)
                {
                    nextJump = Time.GameTime + jumpInterval;
                    if (isRunning)
                    {
                        moveDirection.Y = runJumpHeight;

                    }
                    else
                    {
                        moveDirection.Y = jumpHeight;
                    }
                    //feetaudio.PlayJumpingSound();
                    //playjumpsound here
                }
            }
            else
            {
                //not grounded
                //airTime += Time.DeltaTime;
                moveDirection.Y -= Gravity * Time.DeltaTime;
                nextJump = Time.GameTime + jumpInterval;



            }
            if (isRunning && isMovingForward)
            {
                speed = runSpeed;
            }
            else if (isCrouching)
            {

                speed = crouchSpeed;
            }
            else
            {
                speed = normalSpeed;
            }

            wantedSpeed = Mathf.Lerp(wantedSpeed, speed, 2f * Time.DeltaTime);
            targetVelocity *= wantedSpeed;
           
            moveDirection.Z = targetVelocity.Z;
            moveDirection.X = targetVelocity.X;

            controller.Move(moveDirection);

            //playerrotation
            rotationX += Input.GetAxis("Mouse Y") * lookSpeed * Time.DeltaTime;
            rotationX = Mathf.Clamp(rotationX, -85f, 85f);
            camRoot.LocalOrientation = Quaternion.Euler(rotationX, 0f, 0f);
            Actor.Orientation *= Quaternion.Euler(0, Input.GetAxis("Mouse X") * lookSpeed * Time.DeltaTime, 0f);

            doHeadBob();
            /*if (prevGrounded == false && controller.isGrounded)
            {
                feetaudio.PlaylandingSound();
                nextStep = headcontroller.headBobCycle + stepInterval;


            }

            if (headcontroller.headBobCycle > nextStep)
            {
                nextStep = headcontroller.headBobCycle + stepInterval;

                if (controller.isGrounded || climbladder)
                {
                    feetaudio.playfootsound();
                }


            }
            */
            prevGrounded = controller.IsGrounded;
        }
        public override void OnUpdate()
        {
            // Here you can add code that needs to be called every frame
        }
        public void doHeadBob()
        {
            Vector3 velocity = (Actor.Position - prevPosition) / Time.DeltaTime;
            Vector3 deltaVelocity = velocity - prevVelocity;
            prevPosition = Actor.Position;
            prevVelocity = velocity;
            Vector3 localvelocity = Actor.Transform.WorldToLocalVector(velocity);
            //Debug.Log(localvelocity);
            /*if (climbladder == false)
            {
                velocity.Y = 0f;
            }
            */
            velocity.Y = 0f;
            springVelocity -= deltaVelocity.Y;
            springVelocity -= springPos * springElastic;
            springVelocity *= springDampen;
            springPos += springVelocity * Time.DeltaTime;
            springPos = Mathf.Clamp(springPos, -.32f, .32f);

            if (Mathf.Abs(springVelocity) < .05f && Mathf.Abs(springPos) < .05f)
            {
                springVelocity = springPos = 0f;
            }

            float velocitySize = velocity.ValuesSum;//magnitude?
            float flatVelocity = velocitySize;
            /*
            if (Controller.climbladder)
            {
                flatVelocity *= 4f;
            }
            else if (Controller.climbladder == false && charactercontrol.isGrounded == false)
            {
                flatVelocity /= 4f;
            }
            */

            float strideLengthen = 1f + flatVelocity * bobStrideSpeedLengthen;
            headBobCycle += (flatVelocity / strideLengthen) * (Time.DeltaTime / headBobFrequency);

            float headBobCyclePi = headBobCycle * Mathf.Pi * 2f;
            float bobFactor = Mathf.Sin(headBobCyclePi);
            float bobSwayFactor = Mathf.Sin(headBobCyclePi + Mathf.Pi * .5f);
            bobFactor = 1f - (bobFactor * .5f + 1f);
            bobFactor *= bobFactor;

            headBobFade = Mathf.Lerp(headBobFade, (velocitySize < .1f) ? 0f : 1f, Time.DeltaTime);
            headBobFade = Mathf.Abs(headBobFade);
            float speedHeightFactor = 1f + (flatVelocity * bobHeightSpeedMultiplier);

            xPos = -headBobSideMovement * bobSwayFactor * headBobFade;
            yPos = springPos * jumpLandMove + bobFactor * headBobHeight * headBobFade * speedHeightFactor;
            xTilt = springPos * jumpLandTilt;
            yTilt = bobSwayFactor * headBobSwayAngle * headBobFade;


            float zTilt = Mathf.SmoothDamp(headBob.LocalPosition.X , localvelocity.X, ref xVelocity, Time.DeltaTime / 0.5f);


            Quaternion wantedrotation = Quaternion.Euler(xTilt, yTilt, -zTilt);
            Vector3 wantedposition = new Vector3(xPos, yPos, 0f);
            headBob.LocalPosition = bobStartPosition + wantedposition;
            headBob.LocalOrientation = bobStartRotation * wantedrotation;
        }
      
    }
}

You can calculate the magnitude of 3D vector with sqrt( x * x +y * y + z * z) thought I am not sure if this will fix your issue.

tnx! that gives correct result but the result is way too big.
i need Time.fixedDeltaTime too to make it work

now i tried multiplying deltatime by 60 to get correct results(i dont know why i get good result with this) as Time.FixedDeltaTime isnt an option in flax

Vector3 velocity = (Actor.Position - prevPosition) / (Time.DeltaTime * 60f);

I am new to flax actually but I believe using time.Deltatime in an onFixedUpdate is not really a solid way to get consistent behavior, I believe deltatime is for the frame update time, for sure you can declare a constant for fixedupdatetime in your code 1/60 (probably you can access the time setting for the fixedupdate by code too)… however any special reason why you don’t put all the code in a regular update(), do you need absolutely this logic to be framerate independent and at a fixed regular interval ? Just curious… :stuck_out_tongue:

yes i need the physics time to get the correct results.

so how do i code fixed deltatime?

You can set the PHYSICS FPS to whatever you want in TIME.SETTINGS, i believe it is used also for the fixedupdate, it seems to be set to 60 fps by default, so your “UNITY” Time.fixedDeltaTime should be in FLAX => 1/60 (0.01666666)

yes fixedupdate is 60 fps

ive allready tried 1/60 as time value, however that doesnt work , i get huge values.

Time.DeltaTime is the same as “Time.FixedDeltaTime” if you are accessing it from FixedUpdate().