FirstGearGames / FishNet

FishNet: Unity Networking Evolved.
Other
1.32k stars 139 forks source link

Predicted network transforms jitter when refresh rate isn't 60 Hz. #293

Closed AlphaLul closed 1 year ago

AlphaLul commented 1 year ago

Unity version: 2021.3.20 LTS Fish-Networking version: 3.4.3

Whenever the monitor's refresh rate is set to anything but 60 Hz, the players look like they are jittering if using prediction with a network transform.

Steps to reproduce the behavior:

  1. Set monitor refresh rate to anything but 60 Hz
  2. Go to the network transform prediction example scene
  3. Start the server and client
  4. Move around

Expected behavior: Smooth movement the same way it looks when the refresh rate is at 60 Hz.

tedbarth commented 1 year ago

What is your NetworkManager's Tick Rate?

AlphaLul commented 1 year ago

I believe it's 30 in my project, but like I said, it happens even in the sample scene, which has the default settings. I feel like there definitely shouldn't be jitter with default settings. Also I'm pretty sure 30 is the default anyway.

hesham-akmal commented 1 year ago

Check this discord post, might be related https://discord.com/channels/424284635074134018/1072698621478318080

Basically the new prediction scenes and scripts move only OnTick, which I found to be very jittery as well on 165hz

The old demo csp scripts had a different client move approach which is tick independent, and moves on TimeManager Updates, which I found to be much smoother.

Something like this (not very clean code, but you'll get the gist)

`public CharacterController CharacterController { get; private set; }

    private MoveData _clientMoveData;

    private void Awake()
    {
        InstanceFinder.TimeManager.OnTick += TimeManager_OnTick;
        InstanceFinder.TimeManager.OnUpdate += TimeManager_OnUpdate;

        CharacterController = GetComponent<CharacterController>();
    }

    private void OnDestroy()
    {
        if (InstanceFinder.TimeManager != null)
        {
            InstanceFinder.TimeManager.OnTick -= TimeManager_OnTick;
            InstanceFinder.TimeManager.OnUpdate -= TimeManager_OnUpdate;
        }
    }

    public override void OnStartClient()
    {
        base.OnStartClient();
        CharacterController.enabled = IsServer || IsOwner;
    }

    private void TimeManager_OnTick()
    {
        if (IsOwner)
        {
            Reconciliation(default, false);
            CheckInput(out var md);
            Move(md, false);
        }

        if (IsServer)
        {
            Move(default, true);
            var rd = new ReconcileData(transform.position);

    Reconciliation(rd, true);
        }
    }

    private void TimeManager_OnUpdate()
    {
        if (IsOwner)
            MoveWithData(_clientMoveData, Time.deltaTime);
    }

    private void CheckInput(out MoveData md)
    {
        md = default;

        if (IsFrozen) return;

        var horizontal = X
        var vertical = Y

        if (horizontal == 0f && vertical == 0f)
            return;

        md = new MoveData
        {
            Horizontal = horizontal,
            Vertical = vertical
        };
    }

    [Replicate]
    private void Move(MoveData md, bool asServer, bool replaying = false)
    {
        //Sanity check!
        if (asServer)
            if (IsFrozen)
            {
                md.Horizontal = 0;
                md.Vertical = 0;
            }

        if (asServer || replaying)
            MoveWithData(md, (float)TimeManager.TickDelta);
        // ReSharper disable once ConditionIsAlwaysTrueOrFalse //Wrong ReSharper assumption
        else if (!asServer)
            _clientMoveData = md;
    }

    private void MoveWithData(MoveData md, float delta)
    {
        if (IsFrozen && IsClient)
            return;

        if (IsFrozen)
        {
            md.Horizontal = 0;
            md.Vertical = 0;
        }

        var direction = new Vector3(md.Horizontal, 0, md.Vertical).normalized + new Vector3(0,Physics.gravity.y,0);

        //Move char controller
        if (CharacterController.enabled)
            CharacterController.Move(direction * (_currentMoveRate * delta));
    }
  `
AlphaLul commented 1 year ago

I followed that script and it didn't fix the problem, and it actually made it look more jittery on non-host clients. Maybe it's because my controller is using a Rigidbody2D, but I adapted the script to work with a rigidbody and it still didn't work.

FirstGearGames commented 1 year ago

Issue is likely your controller. Lower refresh rates can maybe cause a single frame to simulate twice on occasion, only if the refresh rate is close to or lower than the tick rate. Higher frame rates will have no impact.

AlphaLul commented 1 year ago

Issue is likely your controller. Lower refresh rates can maybe cause a single frame to simulate twice on occasion, only if the refresh rate is close to or lower than the tick rate. Higher frame rates will have no impact.

It's not just my controller. Like I said, it happens in the sample as well. Also, when I tested it, never did I put the refresh rate below 30, which was the tick rate. I tested 50, 60, and 75, and it was only ever smooth on 60.

AlphaLul commented 1 year ago

Update: The issue seems to have to do with VSync. When I have VSync on, it looks jittery at anything but 60 frames, but when I have it off, everything looks as expected.

I'm not entirely sure if this is a FishNet issue or byproduct of Unity locking the fps as 60, but if it is something to do with FishNet, I think it would be nice if FishNet's interpolation supported VSync, people that like to use it won't reasonably be able to with all the jitter.

Maclay74 commented 1 year ago

Did you try to build the game? To remove Editor from the equation.

FirstGearGames commented 1 year ago

I agree to try a build and let us know.

AlphaLul commented 1 year ago

It happens in both the build and the editor if VSync is on.

FirstGearGames commented 1 year ago

Are you sure it's not a CSP issue? Does this happen with client auth?

This part particularly caught my eye...


    private void TimeManager_OnUpdate()
    {
        if (IsOwner)
            MoveWithData(_clientMoveData, Time.deltaTime);
    }

You're trying to move the object per frame rather than per tick. Please try with a client auth controller.

AlphaLul commented 1 year ago

It could be a CSP issue, but like I said, it happens even with the sample scene and the provided controller. CSP is a pretty important feature for my game to have, which is why I chose this networking solution in the first place. I can't just not use it.

FirstGearGames commented 1 year ago

This may have been a bug in the NT unrelated to refresh rate, but rather clientHost. Does this issue still persist in 3.5.6?

AlphaLul commented 1 year ago

Yes, it's still happening. When you were testing it, did you go to the network transform prediction sample, enable VSync, and set your monitor's refresh rate to something far away from 60, like 50 or 70?

FirstGearGames commented 1 year ago

Looks like NT hit a rough patch. Please try 3.5.8 just to be absolutely certain it's still a problem then I'll take a look.

AlphaLul commented 1 year ago

Yeah, it's still happening for me in the sample scene. Just based on how it looks, it seems like an interpolation problem, so maybe the interpolation just isn't taking VSync into account or something?

tedbarth commented 1 year ago

@AlphaLul Maybe we have an interpretation problem here. Could you make a high fps video of the problem? Also creating an isolating project helped me a lot to separate user error from bugs and identify the real cause/criteria.

AlphaLul commented 1 year ago

@AlphaLul Maybe we have an interpretation problem here. Could you make a high fps video of the problem? Also creating an isolating project helped me a lot to separate user error from bugs and identify the real cause/criteria.

The game starts jittering a bit when I start recording for some reason, so it's hard to get a video of it. I have created a new project where I've changed literally nothing, and when I go to the TransformPrediction sample scene, the problem still persists, so it's not anything to do with my player controller or project setup. I agree that it's likely an issue with FishNet's interpolation.

FirstGearGames commented 1 year ago

I tested using the CharacterController scene with VSync on and off. Tested refresh rates 60Hz and 140Hz.

Everything was smooth in all scenarios.

AlphaLul commented 1 year ago

I tested using the CharacterController scene with VSync on and off. Tested refresh rates 60Hz and 140Hz.

Everything was smooth in all scenarios.

I'm talking about the TransformPrediction scene in the Prediction example scenes folder, not just the normal character controller one.

FirstGearGames commented 1 year ago

I noticed the prefab lost it's configuration somehow in that example scene. The NetworkTransform was sitting on client-auth when it should not have been. After I assigned the NetworkTransform inside PredictedObject it worked fine. image

Tested in TransformPrediction with the same settings as my previous comment.

What I did see when it wasn't configured right was not jittering, but the transform fighting movement in a slow adjusting rubberband appearance.

AlphaLul commented 1 year ago

That's interesting, maybe it's something to do with my monitor or how my Unity project is set up by default. Are you up for me sending you a build of my version to see if it happens with that?

AlphaLul commented 1 year ago

Yeah I had a friend test it and they said it looks fine on their monitor on 75 Hz, which was the same refresh rate I was testing with. Seems like it's just my monitor lol.