#Client Prediction For Non-Owned Objects

61 messages · Page 1 of 1 (latest)

surreal ridge
#

The attached video:
Player is the white circle. CSP is working great for player movement.
The yellow puck is a PredictedObject rigidbody2D, also working fine.

I'm trying to find a way to simulate on the client an interaction that happens with a non-owned object. In this case, when I click the mouse, it's sending an RPC with RunLocally=true through a player controller to the server which applies the following:

    public void ProcessHit()
    {
        if (IsServer)
        {
            _rigidbody.velocity = new Vector2();
            _rigidbody.AddForce(new Vector2(100, 100));
        }

        // Flash Red
        _flashTimer = _flashTimerMax;
        visualObject.GetComponent<SpriteRenderer>().color = _flashColor;

    }

As can be seen in the video with the puck immediately changing color, I can predict the action about to happen to the core. However I can't find a way to predict the movement that's about to happen to the core. All the examples I've found are just relying on collision for this prediction.

The [Replicate] / [Reconcile] pattern seems to only worked for owned objects, and the player doesn't own the puck.

Trying to add the velocity on the client side in ProcessHit doesn't work either, as the physics simulation is out of whack on the client (Physics is constantly running extra steps from reconciling the player, so applying forces locally is completely unpredictable).

I'm really stumped on how to get this working as it SHOULD be doable... somehow.

surreal ridge
#

Made some progress by doing [Replicate] / [Reconcile] in a script attached to an object the player owns, and having it control the other object that's being predicted. This makes sense after giving it all some thought.

However, it seems like after I modify the velocity client side, it's receiving/processing a stale update still which causes jitter. I've tried doing ClearReplicateCache() without success. Is there a way to get it to ignore replicates for a short time?

surreal ridge
#

For more context, here's my replicate/reconcile from my player controller now. Ignore the non cached component gets:

    [Replicate]
    private void RHitCore(MoveData md, bool asServer, Channel channel = Channel.Unreliable, bool replaying = false)
    {
        if (md.Strike)
        {
            Debug.Log("Hit Core!");
            GameObject.FindObjectOfType<CoreController>().ProcessHit();
            strikeCooldownTimer = strikeCooldownMax;
        }
        
    }

    [Reconcile]
    private void Reconciliation(ReconcileData rd, bool asServer, Channel channel = Channel.Unreliable)
    {
        Rigidbody2D rb = GameObject.FindObjectOfType<CoreController>().GetComponent<Rigidbody2D>();
        rb.velocity = rd.Velocity;
        rb.position = rd.Position;
    }
surreal ridge
#

$20 bounty for anyone who can help me figure this out 🙏

Specifically:
Predicting a rigidbody2d that also reacts to external forces that aren't collision/physic calculated.

sonic inlet
#

Are you taking ownership of it and applying forces?

#

then removing ownership?

#

wondering why its changing colors

surreal ridge
#

No, server owns it entirely.

It's changing color on both client and server as part of the Replicate:

    public void ProcessHit()
    {

        _rigidbody.velocity = new Vector2();
        _rigidbody.AddForce(new Vector2(100, 100));
        

        // Flash Red
        _flashTimer = _flashTimerMax;
        visualObject.GetComponent<SpriteRenderer>().color = _flashColor;

    }
sonic inlet
#

well you shouldnt be clearing the cache, thats really only if you want to teleport, change owner, ect

surreal ridge
#

My best guess is that my issue is that I apply the forces on the client, but then because it's a synced rb2d, it's still receiving old rigidbody data

sonic inlet
#

who is triggering the force, server or client?

surreal ridge
#

Client. Client is basically saying "Hit Core" then server processes. Trying to get client to predict that hit though.

#

I can clean this all up and make a demo project if it helps

sonic inlet
#

hmm okay I have an idea of what may be the problem

#

oh, you do have it in your replicate...

#

I mean, that should resolve the issue

#

because when the server sends data the client resets to it when reconciling, but it would re-apply forces during the replay

#

this could be part of the problem maybe

#
           strikeCooldownTimer = strikeCooldownMax;```
#

although I suspect that would just break how often they can 'strike'

surreal ridge
#

ah, yeah, that shouldn't be in there, it's not really being used for anything atm

sonic inlet
#

I would also pass in the replaying bool to ProcessHIt

#

and dont reset flashtimer/visuals if replaying

#

but that also shouldnt cause the issues

surreal ridge
#

ah, yeah, good call out. but yeah

sonic inlet
#

nothing is standing out to me atm assuming you have a PO on the puck and its setup right with graphical as child

#

and you arent using something else that could be interfering like ontriggers

surreal ridge
sonic inlet
#

I'm not entirely sure at this moment sorry. I've a bit to do over the next few days but I can try to make a basic demo tomorrow. At the very least I'll be able to confirm if it's a problem or not.

surreal ridge
#

I'll put a demo of this project, cleaned up, on github tonight... hopefully it either helps you/someone find my issue (likely the cause 🙂 ), or find an issue with FN

surreal ridge
#

Very slim project containing basically only the necessary stuff to showcase my issue

sonic inlet
#

okay. ty

surreal ridge
#

Just some more context on my design goal here

If I can figure the above issue out, then I can switch the logic to be:
1: Client attempts to hit puck
2: Client saw that it hit the puck, so predict
3: Server receives client's attempt to hit puck, runs logic on its end
4: Server also sees the hit (or doesn't), and processes it
5: If server saw the same hit, then the prediction is correct. If server did not see the same hit, fix the client's sim.

The logic of setting the velocity directly from the client right now is just a prototype, but that logic above is going to require this CSP.

surreal ridge
#

Oh wow, so it appears you can only do one [Replicate] [Reconcile] in your scene or things get really wonky. So doing it for player movement AND for the puck movement is a no-go.

A solution I'm seeing then is to run one master replicate/reconcile which handles CSP for every single object you want to CSP at once.

And you replicate/reconcile the entire scene at once... but you don't really have all motor history for the spectated puck, unless you count your predictions, which kills the point of reconciling the predictions...

#

but I'm unsure how you'd get a client initiated trigger to interact with that correctly since you can't replicate/reconcile (Not owner, and already doing a rep/rec on the player) ... 🤔

sonic inlet
#

I'll be sure to put that under limitations

surreal ridge
#

Got it working!

#

Going to share some very ugly PoC code for anyone else who runs into this

rocky sky
#

oh nice, did u make "A solution I'm seeing then is to run one master replicate/reconcile which handles CSP for every single object you want to CSP at once."?

surreal ridge
#

Yep, I have everything running in a single Replicate/Reconcile

#

My structs look like:

#

Inputs

    public struct MoveData :IReplicateData
    {
        public float Horizontal;
        public float Vertical;
        public bool Strike;

        public MoveData(float horizontal, float vertical)
        {
            Horizontal = horizontal;
            Vertical = vertical;
            Strike = false;
            _tick = 0;
        }

        private uint _tick;
        public void Dispose() { }
        public uint GetTick() => _tick;
        public void SetTick(uint value) => _tick = value;
    }

Reconcile Data

    public struct ReconcileData : IReconcileData
    {
        public Vector3 Position;
        public Quaternion Rotation;
        public Vector3 Velocity;
        public Vector3 PuckPosition;
        public Vector3 PuckVelocity;

        public ReconcileData(Vector3 position, Quaternion rotation, Vector3 velocity)
        {
            Position = position;
            Rotation = rotation;
            Velocity = velocity;
            PuckPosition = Vector3.zero;
            PuckVelocity = Vector3.zero;
            _tick = 0;
        }

        private uint _tick;
        public void Dispose() { }
        public uint GetTick() => _tick;
        public void SetTick(uint value) => _tick = value;
    }
#

Then the Rep/Rec functions (These are ugly doing gameobject finds, I'll turn this into a cached reference in the future, it's a PoC 🙂 ):

    [Replicate]
    private void Move(MoveData md, bool asServer, Channel channel = Channel.Unreliable, bool replaying = false)
    {

        // Player
        //Add extra gravity for faster falls.
        float moveRate = _maxSpeed * 10;
        Vector3 forces = new Vector3(md.Horizontal, md.Vertical) * moveRate;
        _rigidbody.AddForce(forces);
        _rigidbody.velocity = Vector2.ClampMagnitude(_rigidbody.velocity, _maxSpeed);

        // Puck
        if (md.Strike)
        {
            GameObject.FindObjectOfType<PuckController>().ProcessHit(replaying);
        }

    }

    [Reconcile]
    private void Reconciliation(ReconcileData rd, bool asServer, Channel channel = Channel.Unreliable)
    {        
        //Player
        transform.position = rd.Position;
        transform.rotation = rd.Rotation;
        _rigidbody.velocity = rd.Velocity;
    }
#

It's currently built off the player CSP stuff which is why it's modifying transform directly, it's attached to the player. I'll likely make a CSPManager of some sort to handle all objects that need to be predicted, and attach this to that instead.

rocky sky
#

Anyway, its good research

#

Good job

surreal ridge
#

Making constant improvements as I wrap my head around this more

sonic inlet
shell echo
#

@surreal ridge I'm trying to do knockback for my combat game. I want to apply forces for non-owned object on client then tell server to do as well. Do you have any ideas?

surreal ridge
#

For non owned objects, the way that example handles the cars is 👍

shell echo
#

@surreal ridge Sorry to bother you again. Is there a specific script that relate my question?

#

This one is bit messy I can't quite understand

atomic cave
#

@surreal ridge Thank you for providing the solution & code snippets. Legend

sullen vaporBOT
#

Reznok received thanks.

minor cradle
#

@orchid turtle solution is here