Search code examples
c#unity-game-enginephoton

Duplicate Values in Dictionary When Logging Distances in Photon Fusion


I'm developing a multiplayer game in Unity using Photon Fusion.

I have a dictionary, playerDistances:

[Networked]
[Capacity(4)] // Sets the fixed capacity of the collection
[UnitySerializeField] // Show this private property in the inspector.
private NetworkDictionary<NetworkString<_32>, float> playerDistances => default;

where I record the distance of each player to the finish line. I update these distances in the FixedUpdateNetwork() method

Dist=CalculateDistancePercentage();
If (playerDistances.ContainsKey(PlayerPrefs.GetString("PlayerName"))) {
    // If the player is already in the dictionary, then we should update his distances instead of adding new key pairs
    playerDistances.Set(PlayerPrefs.GetString("PlayerName"), Dist).
} else {
    // If the player is not already in the dictionary, then we should add a new key-value pair
    playerDistances.Add(PlayerPrefs.GetString("PlayerName"), Dist).
}

and print them when pressing the 'U' key in the Update() method.

If (Input.GetKeyDown(KeyCode.U))
{
    foreach(var kvp in playerDistances)
    {
        Debug.Log($"{playerDistances.Count}/{playerDistances.Capacity} Key:{kvp.Key} Value:{kvp.Value}").
    }
}

this is my CalculateDistancePercentage:

private float CalculateDistancePercentage()
{
    //Vector3 closestPointOnBounds = finishCollider.boun ds.ClosestPoint(transform.position).
    float currentDistance = Vector3.Distance(transform.position,finishObject.transform.position).
    //Debug.Log($"Absolute distance to the finish edge: {currentDistance}").
    return currentDistance.
}

However, when I press 'U', it prints duplicate values for different keys

2/6 Key:AC Value:69.639
2/6 Key:BBB Value:69.639
2/6 Key:AC Value:22.622
2/6 Key:BBB Value:22.622

even though each key (which represents a unique player) should have a different value like this:

2/6 Key:AC Value:69.639
2/6 Key:BBB Value:22.622

Solution

  • Not solving your issue but in general: It is enough to use only Set as per API Sets the value for the given key. Will add a new key if the key does not already exist. so your check is redundant ;)


    Then it sounds and looks to me like you are not checking for the local authority

    => You run both, the writing of things into the dictionary as well as the printing locally for both objects.

    Further the playerDistances seems to be bound to a specific instance of your player objects so you actually have two independent dictionaries, one on each player component.

    So what happens is for each player you do

    CalculateDistancePercentage();
    

    which always uses the correct according transform's distance

    BUT then you always store it using

    PlayerPrefs.GetString("PlayerName")
    

    as key, which no matter which component this runs on will always return the same local name.


    So my guess is that in the one dictionary let's say on player "AC" you store

    2/6 Key:AC Value:69.639
    

    and since you also run the code on the other client's ("BBB") component in that dictionary you add

    2/6 Key:AC Value:22.622
    

    The same happens on the other client's side where the component on "AC" adds

    2/6 Key:BBB Value:69.639
    

    and the component of "BBB" adds

    2/6 Key:BBB Value:22.622
    

    Now they get synchronized and both dictionaries contain both keys with the different distances.

    And again for printing you actually print both independent dictionaries


    So to solve this (disclaimer: No Photon expert whatsoever, I'm just googling this together) you either do not want to use a dictionary at all, but just synchronize a single float on the respective component itself

    [Networked] [field: SerializeField] public float distance { get; private set; }
    

    and then make sure to only calculate this on the local client

    if(HasStateAuthority)
    {
        distance = CalculateDistancePercentage();
    }
    

    Or you could make sure you only use one single dictionary for both players - namely one that is associated with the session itself rather than each player - Custom Session Properties each player can write to SessionInfo.UpdateCustomProperties and read from SessionInfo.Properties

    e.g. somewhat like

    if(HasStateAuthority)
    {
        var distance = CalculateDistancePercentage();
        var properties = SessionInfo.Properties;
        properties[PlayerPrefs.GetString("PlayerName")] = distance;
        SessionInfo.UpdateCustomProperties(properties);
    }
    

    and then for printing

    if (HasInputAuthority && Input.GetKeyDown(KeyCode.U))
    {
        var properties = SessionInfo.Properties;
        foreach(var kvp in properties)
        {
            Debug.Log($"Key:{kvp.Key} Value:{kvp.Value}").
        }
    }
    

    if using more of those properties you might want to add a dedicated prefix to the keys so you know this is about a player distance.