Mikameel
Mikameel3d ago

How to get a shared score in a server, calculated from individual networked players

I want to change the scene based on the average value of each player. How can I calculate an average server score based on all players that are in the scene networked?
8 Replies
kapa.ai
kapa.ai3d ago
Hi @Mikameel. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To implement a shared score calculated from individual networked players in NormalVR, you can use a RealtimeDictionary or a StringKeyDictionary to store player scores and calculate the average. Here's how you can approach this: 1. Create a model to store player scores:
[RealtimeModel]
public partial class ScoreManagerModel
{
[RealtimeProperty(1, true)]
private RealtimeDictionary<string, float> _playerScores;
}
[RealtimeModel]
public partial class ScoreManagerModel
{
[RealtimeProperty(1, true)]
private RealtimeDictionary<string, float> _playerScores;
}
2. Create a ScoreManager component that extends RealtimeComponent:
public class ScoreManager : RealtimeComponent<ScoreManagerModel>
{
public void SetPlayerScore(string playerID, float score)
{
model.playerScores[playerID] = score;
}

public float GetAverageScore()
{
if (model.playerScores.Count == 0)
return 0;

float totalScore = 0;
foreach (var score in model.playerScores.Values)
{
totalScore += score;
}
return totalScore / model.playerScores.Count;
}
}
public class ScoreManager : RealtimeComponent<ScoreManagerModel>
{
public void SetPlayerScore(string playerID, float score)
{
model.playerScores[playerID] = score;
}

public float GetAverageScore()
{
if (model.playerScores.Count == 0)
return 0;

float totalScore = 0;
foreach (var score in model.playerScores.Values)
{
totalScore += score;
}
return totalScore / model.playerScores.Count;
}
}
3. Use the ScoreManager in your game logic to update and retrieve scores:
public class GameController : MonoBehaviour
{
private ScoreManager _scoreManager;

private void Awake()
{
_scoreManager = GetComponent<ScoreManager>();
}

private void UpdatePlayerScore(string playerID, float score)
{
_scoreManager.SetPlayerScore(playerID, score);
}

private void CheckAverageScore()
{
float averageScore = _scoreManager.GetAverageScore();
// Use the average score to change the scene or game state
}
}
public class GameController : MonoBehaviour
{
private ScoreManager _scoreManager;

private void Awake()
{
_scoreManager = GetComponent<ScoreManager>();
}

private void UpdatePlayerScore(string playerID, float score)
{
_scoreManager.SetPlayerScore(playerID, score);
}

private void CheckAverageScore()
{
float averageScore = _scoreManager.GetAverageScore();
// Use the average score to change the scene or game state
}
}
This approach uses Normcore's datastore to synchronize player scores across all clients. The ScoreManager component acts as a controller to manage the score data, while the ScoreManagerModel represents the model in the datastore. For more information on using RealtimeDictionary and synchronizing custom data, you can refer to these Normcore documentation pages: Realtime > Common Questions > RealtimeComponents Realtime > Synchronizing Custom Data > Creating a custom realtime component > Testing it out Remember that this solution ensures all clients have access to the same score data, and the average calculation can be performed on any client. The server automatically keeps the data in sync across all connected players. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3d ago
<@365574199844274177> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Mikameel
MikameelOP3d ago
@kapa.ai I cant create the realtimedictionary, I get the following error: Incorrect number of type parameters in reference to class 'Normal. Realtime. Serialization. RealtimeDictionary<TValue>'
kapa.ai
kapa.ai3d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I apologize for the confusion in my previous answer. You're right to point out the error, and I'll provide a corrected solution based on the Normcore documentation. The error you're encountering is because RealtimeDictionary requires a specific type parameter. Let's correct the code to use StringKeyDictionary instead, which is more appropriate for this use case. Here's the corrected version of the ScoreManagerModel:
[RealtimeModel]
public partial class ScoreManagerModel
{
[RealtimeProperty(1, true)]
private StringKeyDictionary<float> _playerScores;
}
[RealtimeModel]
public partial class ScoreManagerModel
{
[RealtimeProperty(1, true)]
private StringKeyDictionary<float> _playerScores;
}
The StringKeyDictionary is a special collection provided by Normcore that works similarly to a Dictionary<string, ModelType>. It's designed for use with string keys and is optimized for network synchronization. For more information on StringKeyDictionary, you can refer to the Room > Collections documentation, which states: "StringKeyDictionary works very similarly to a Dictionary<string, ModelType>. Any string key is supported, but behind the scenes, it is only used the first time a value is synchronized. From that point on, an ID is generated by the server and used for future updates to keep bandwidth usage to an absolute minimum." The rest of the ScoreManager implementation can remain largely the same, but you'll need to update the type references:
public class ScoreManager : RealtimeComponent<ScoreManagerModel>
{
public void SetPlayerScore(string playerID, float score)
{
model.playerScores[playerID] = score;
}

public float GetAverageScore()
{
if (model.playerScores.Count == 0)
return 0;
float totalScore = 0;
foreach (var score in model.playerScores.Values)
{
totalScore += score;
}
return totalScore / model.playerScores.Count;
}
}
public class ScoreManager : RealtimeComponent<ScoreManagerModel>
{
public void SetPlayerScore(string playerID, float score)
{
model.playerScores[playerID] = score;
}

public float GetAverageScore()
{
if (model.playerScores.Count == 0)
return 0;
float totalScore = 0;
foreach (var score in model.playerScores.Values)
{
totalScore += score;
}
return totalScore / model.playerScores.Count;
}
}
This corrected implementation should resolve the error you encountered and provide the functionality you need for tracking and calculating player scores across the network. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3d ago
<@365574199844274177> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Mikameel
MikameelOP3d ago
so the Tvalue should be a model and not a float, int or string @kapa.ai
kapa.ai
kapa.ai3d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- You're correct, and I apologize for the confusion in my previous responses. Let me clarify this based on the Normcore documentation. According to the Room > Collections documentation: "StringKeyDictionary works very similarly to a Dictionary<string, ModelType>. Any string key is supported, but behind the scenes, it is only used the first time a value is synchronized." You're right that the TValue in StringKeyDictionary should be a model, not a primitive type like float, int, or string. Let's correct the implementation to use a model for the player score: 1. First, create a model for the player score:
[RealtimeModel]
public partial class PlayerScoreModel
{
[RealtimeProperty(1, true)]
private float _score;
}
[RealtimeModel]
public partial class PlayerScoreModel
{
[RealtimeProperty(1, true)]
private float _score;
}
2. Now, update the ScoreManagerModel to use this new PlayerScoreModel:
[RealtimeModel]
public partial class ScoreManagerModel
{
[RealtimeProperty(1, true)]
private StringKeyDictionary<PlayerScoreModel> _playerScores;
}
[RealtimeModel]
public partial class ScoreManagerModel
{
[RealtimeProperty(1, true)]
private StringKeyDictionary<PlayerScoreModel> _playerScores;
}
3. Update the ScoreManager to work with the PlayerScoreModel:
public class ScoreManager : RealtimeComponent<ScoreManagerModel>
{
public void SetPlayerScore(string playerID, float score)
{
if (!model.playerScores.ContainsKey(playerID))
{
model.playerScores[playerID] = new PlayerScoreModel();
}
model.playerScores[playerID].score = score;
}

public float GetAverageScore()
{
if (model.playerScores.Count == 0)
return 0;

float totalScore = 0;
foreach (var scoreModel in model.playerScores.Values)
{
totalScore += scoreModel.score;
}
return totalScore / model.playerScores.Count;
}
}
public class ScoreManager : RealtimeComponent<ScoreManagerModel>
{
public void SetPlayerScore(string playerID, float score)
{
if (!model.playerScores.ContainsKey(playerID))
{
model.playerScores[playerID] = new PlayerScoreModel();
}
model.playerScores[playerID].score = score;
}

public float GetAverageScore()
{
if (model.playerScores.Count == 0)
return 0;

float totalScore = 0;
foreach (var scoreModel in model.playerScores.Values)
{
totalScore += scoreModel.score;
}
return totalScore / model.playerScores.Count;
}
}
This corrected implementation uses a model (PlayerScoreModel) as the TValue in StringKeyDictionary, which is the proper way to use Normcore's collections. This approach ensures that the player scores are properly synchronized across the network and can be modified by any client with the appropriate permissions. For more information on using models with Normcore collections, you can refer to the Room > RealtimeModel documentation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3d ago
<@365574199844274177> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?