Mikameel
Mikameel4w ago

networked buttons / votes

I have 2 buttons in my Unity game One for team blue and one for team red. each player can vote which team is winning. But you can only vote for 1 team. So its either team red or team blue. I want to show the livescore in text that is networked. I want to network the current votes for each time. So the only thing that is networked should be the amount of votes each team has. and a player can only vote for one team.
11 Replies
kapa.ai
kapa.ai4w 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 networked buttons for team voting in your Unity game using Normcore, you can create a custom RealtimeComponent to synchronize the vote counts. Here's how you can approach this: 1. First, create a custom RealtimeModel to store the vote counts:
using Normal.Realtime;
using UnityEngine;

[RealtimeModel]
public partial class VoteCountModel
{
[RealtimeProperty(1, true)]
private int _blueVotes;

[RealtimeProperty(2, true)]
private int _redVotes;
}
using Normal.Realtime;
using UnityEngine;

[RealtimeModel]
public partial class VoteCountModel
{
[RealtimeProperty(1, true)]
private int _blueVotes;

[RealtimeProperty(2, true)]
private int _redVotes;
}
2. Then, create a custom RealtimeComponent to manage the voting logic:
using Normal.Realtime;
using UnityEngine;
using UnityEngine.UI;

public class VoteManager : RealtimeComponent<VoteCountModel>
{
public Text scoreText;
public Button blueButton;
public Button redButton;

private bool hasVoted = false;

private void Awake()
{
blueButton.onClick.AddListener(() => Vote(true));
redButton.onClick.AddListener(() => Vote(false));
}

public void Vote(bool forBlue)
{
if (hasVoted) return;

if (forBlue)
model.blueVotes++;
else
model.redVotes++;

hasVoted = true;
UpdateUI();
}

protected override void OnRealtimeModelReplaced(VoteCountModel previousModel, VoteCountModel currentModel)
{
if (previousModel != null)
{
previousModel.blueVotesDidChange -= UpdateUI;
previousModel.redVotesDidChange -= UpdateUI;
}

if (currentModel != null)
{
currentModel.blueVotesDidChange += UpdateUI;
currentModel.redVotesDidChange += UpdateUI;
UpdateUI();
}
}

private void UpdateUI()
{
scoreText.text = $"Blue: {model.blueVotes} - Red: {model.redVotes}";
}
}
using Normal.Realtime;
using UnityEngine;
using UnityEngine.UI;

public class VoteManager : RealtimeComponent<VoteCountModel>
{
public Text scoreText;
public Button blueButton;
public Button redButton;

private bool hasVoted = false;

private void Awake()
{
blueButton.onClick.AddListener(() => Vote(true));
redButton.onClick.AddListener(() => Vote(false));
}

public void Vote(bool forBlue)
{
if (hasVoted) return;

if (forBlue)
model.blueVotes++;
else
model.redVotes++;

hasVoted = true;
UpdateUI();
}

protected override void OnRealtimeModelReplaced(VoteCountModel previousModel, VoteCountModel currentModel)
{
if (previousModel != null)
{
previousModel.blueVotesDidChange -= UpdateUI;
previousModel.redVotesDidChange -= UpdateUI;
}

if (currentModel != null)
{
currentModel.blueVotesDidChange += UpdateUI;
currentModel.redVotesDidChange += UpdateUI;
UpdateUI();
}
}

private void UpdateUI()
{
scoreText.text = $"Blue: {model.blueVotes} - Red: {model.redVotes}";
}
}
3. In your Unity scene, add a Realtime component to an empty GameObject. 4. Create UI elements for the blue and red buttons, and a Text element for the score display. 5. Add the VoteManager component to a GameObject in your scene, and assign the UI elements in the inspector. This implementation will synchronize the vote counts across all clients connected to the same Normcore room. Each player can only vote once, and the live score will be updated in real-time for all players. For more information on creating custom RealtimeComponents and synchronizing data with Normcore, you can refer to the following resources: Synchronizing Custom Data RealtimeComponent Remember to compile your VoteCountModel after creating it, as explained in the Normcore documentation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4w ago
<@365574199844274177> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Mikameel
MikameelOP4w ago
It cannot resolve blueVotesDidChange It cannot resolve blueVotesDidChange @Normcore Assistant
kapa.ai
kapa.ai4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I apologize for the confusion in my previous response. You're right that the blueVotesDidChange and redVotesDidChange events are not automatically generated in Normcore 2. Let me provide you with the correct implementation: To resolve the issue with blueVotesDidChange, we need to modify our approach slightly. In Normcore 2, we can use the PropertyChanged event instead. Here's how you can update the VoteManager script:
using Normal.Realtime;
using UnityEngine;
using UnityEngine.UI;

public class VoteManager : RealtimeComponent<VoteCountModel>
{
public Text scoreText;
public Button blueButton;
public Button redButton;

private bool hasVoted = false;

private void Awake()
{
blueButton.onClick.AddListener(() => Vote(true));
redButton.onClick.AddListener(() => Vote(false));
}

public void Vote(bool forBlue)
{
if (hasVoted) return;

if (forBlue)
model.blueVotes++;
else
model.redVotes++;

hasVoted = true;
UpdateUI();
}

protected override void OnRealtimeModelReplaced(VoteCountModel previousModel, VoteCountModel currentModel)
{
if (previousModel != null)
{
previousModel.PropertyChanged -= OnModelPropertyChanged;
}

if (currentModel != null)
{
currentModel.PropertyChanged += OnModelPropertyChanged;
UpdateUI();
}
}

private void OnModelPropertyChanged(IRealtimeModel model, string propertyName)
{
if (propertyName == "blueVotes" || propertyName == "redVotes")
{
UpdateUI();
}
}

private void UpdateUI()
{
scoreText.text = $"Blue: {model.blueVotes} - Red: {model.redVotes}";
}
}
using Normal.Realtime;
using UnityEngine;
using UnityEngine.UI;

public class VoteManager : RealtimeComponent<VoteCountModel>
{
public Text scoreText;
public Button blueButton;
public Button redButton;

private bool hasVoted = false;

private void Awake()
{
blueButton.onClick.AddListener(() => Vote(true));
redButton.onClick.AddListener(() => Vote(false));
}

public void Vote(bool forBlue)
{
if (hasVoted) return;

if (forBlue)
model.blueVotes++;
else
model.redVotes++;

hasVoted = true;
UpdateUI();
}

protected override void OnRealtimeModelReplaced(VoteCountModel previousModel, VoteCountModel currentModel)
{
if (previousModel != null)
{
previousModel.PropertyChanged -= OnModelPropertyChanged;
}

if (currentModel != null)
{
currentModel.PropertyChanged += OnModelPropertyChanged;
UpdateUI();
}
}

private void OnModelPropertyChanged(IRealtimeModel model, string propertyName)
{
if (propertyName == "blueVotes" || propertyName == "redVotes")
{
UpdateUI();
}
}

private void UpdateUI()
{
scoreText.text = $"Blue: {model.blueVotes} - Red: {model.redVotes}";
}
}
In this updated version, we're using the PropertyChanged event instead of specific events for each property. This event is fired whenever any property in the model changes. We check if the changed property is either blueVotes or redVotes, and if so, we update the UI. This approach is consistent with Normcore 2's implementation of RealtimeModel. For more information on working with RealtimeModel and property changes in Normcore 2, you can refer to the Synchronizing Custom Data documentation. Remember to ensure that your VoteCountModel is correctly defined and compiled as shown in the previous response. If you're still encountering issues, double-check that your Normcore version is up to date and that the model has been properly compiled. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4w ago
<@365574199844274177> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Mikameel
MikameelOP4w ago
@Normcore Assistant The Propertychanged doesnt work as well. This is my code, it works except that it's not updated unless a local button is pressed. Can I use RPC events for this? I want to set up RPC anyway
kapa.ai
kapa.ai4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I apologize for the confusion in my previous responses. You're right that the PropertyChanged event might not be working as expected in your case. While Normcore generally discourages the use of RPC (Remote Procedure Call) events, they do provide a way to implement RPC-like functionality using their datastore. This approach can be useful for one-shot effects or events that don't need to be synchronized for late-joining clients. Here's how you can implement an RPC-like event system for your voting scenario using Normcore: 1. First, let's create a model for our voting event:
using Normal.Realtime;
using UnityEngine;

[RealtimeModel]
public partial class VoteEventModel
{
[RealtimeProperty(1, true)] private int _trigger;
[RealtimeProperty(2, true)] private int _senderID;
[RealtimeProperty(3, true)] private bool _isBlueVote;

public void FireEvent(int senderID, bool isBlueVote)
{
this.trigger++;
this.senderID = senderID;
this.isBlueVote = isBlueVote;
}

public delegate void EventHandler(int senderID, bool isBlueVote);
public event EventHandler eventDidFire;

[RealtimeCallback(RealtimeModelEvent.OnDidRead)]
private void DidRead()
{
if (eventDidFire != null && trigger != 0)
eventDidFire(senderID, isBlueVote);
}
}
using Normal.Realtime;
using UnityEngine;

[RealtimeModel]
public partial class VoteEventModel
{
[RealtimeProperty(1, true)] private int _trigger;
[RealtimeProperty(2, true)] private int _senderID;
[RealtimeProperty(3, true)] private bool _isBlueVote;

public void FireEvent(int senderID, bool isBlueVote)
{
this.trigger++;
this.senderID = senderID;
this.isBlueVote = isBlueVote;
}

public delegate void EventHandler(int senderID, bool isBlueVote);
public event EventHandler eventDidFire;

[RealtimeCallback(RealtimeModelEvent.OnDidRead)]
private void DidRead()
{
if (eventDidFire != null && trigger != 0)
eventDidFire(senderID, isBlueVote);
}
}
2. Now, let's update our VoteManager to use this event system:
using Normal.Realtime;
using UnityEngine;
using UnityEngine.UI;

public class VoteManager : RealtimeComponent<VoteEventModel>
{
public Text scoreText;
public Button blueButton;
public Button redButton;

private bool hasVoted = false;
private int blueVotes = 0;
private int redVotes = 0;
private void Awake()
{
blueButton.onClick.AddListener(() => Vote(true));
redButton.onClick.AddListener(() => Vote(false));
}

protected override void OnRealtimeModelReplaced(VoteEventModel previousModel, VoteEventModel currentModel)
{
if (previousModel != null)
{
previousModel.eventDidFire -= OnVoteEventFired;
}

if (currentModel != null)
{
currentModel.eventDidFire += OnVoteEventFired;
}
}

private void Vote(bool forBlue)
{
if (hasVoted) return;

model.FireEvent(realtime.clientID, forBlue);
hasVoted = true;
}

private void OnVoteEventFired(int senderID, bool isBlueVote)
{
if (isBlueVote)
blueVotes++;
else
redVotes++;

UpdateUI();
}

private void UpdateUI()
{
scoreText.text = $"Blue: {blueVotes} - Red: {redVotes}";
}
}
using Normal.Realtime;
using UnityEngine;
using UnityEngine.UI;

public class VoteManager : RealtimeComponent<VoteEventModel>
{
public Text scoreText;
public Button blueButton;
public Button redButton;

private bool hasVoted = false;
private int blueVotes = 0;
private int redVotes = 0;
private void Awake()
{
blueButton.onClick.AddListener(() => Vote(true));
redButton.onClick.AddListener(() => Vote(false));
}

protected override void OnRealtimeModelReplaced(VoteEventModel previousModel, VoteEventModel currentModel)
{
if (previousModel != null)
{
previousModel.eventDidFire -= OnVoteEventFired;
}

if (currentModel != null)
{
currentModel.eventDidFire += OnVoteEventFired;
}
}

private void Vote(bool forBlue)
{
if (hasVoted) return;

model.FireEvent(realtime.clientID, forBlue);
hasVoted = true;
}

private void OnVoteEventFired(int senderID, bool isBlueVote)
{
if (isBlueVote)
blueVotes++;
else
redVotes++;

UpdateUI();
}

private void UpdateUI()
{
scoreText.text = $"Blue: {blueVotes} - Red: {redVotes}";
}
}
This implementation uses an RPC-like event system to synchronize votes across all clients. When a player votes, it fires an event that all clients receive. Each client then updates its local vote count and UI accordingly. Remember to compile your VoteEventModel after creating it. This approach should update the vote counts on all clients whenever any client votes, without requiring a local button press on each client. For more information on implementing RPC-like events in Normcore, you can refer to the RPC Events Recipe in the Normcore documentation. However, please note that Normcore generally recommends against using RPC messages for most scenarios, as stated in their documentation: Room > Room > RPCs "RPC messages in Normcore are available as a last resort. If you have players that can join a room late, RPC messages make consistent synchronization difficult. Any state modified in response to an RPC message will need to be manually synchronized by your own code." If you're experiencing issues with property synchronization, it might be worth revisiting your original implementation and ensuring that all components are properly set up and connected in your Unity scene. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4w ago
<@365574199844274177> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Mikameel
MikameelOP2d ago
Update on this for someone reading this later. I forgot to set the property in the realtimemodel to True on create subscribe event @kapa.ai I want to rechange my current system,. I want a voting system that keeps track of which player voted for which team. I want the team name to be dynamic and should be able to change. I want a button resot score. I want to log which player voted for which team. THere's always just 2 teams. What is a good design pattern for this?
kapa.ai
kapa.ai2d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To implement a more flexible voting system with the features you've described, we can use a combination of Normcore's RealtimeModel and RealtimeComponent. Here's a design pattern that should work well for your requirements: 1. First, let's create a VoteModel to represent individual votes:
[RealtimeModel]
public partial class VoteModel
{
[RealtimeProperty(1, true)] private int _playerID;
[RealtimeProperty(2, true)] private string _teamName;
}
[RealtimeModel]
public partial class VoteModel
{
[RealtimeProperty(1, true)] private int _playerID;
[RealtimeProperty(2, true)] private string _teamName;
}
2. Next, let's create a TeamModel to represent each team:
[RealtimeModel]
public partial class TeamModel
{
[RealtimeProperty(1, true)] private string _name;
[RealtimeProperty(2, true)] private RealtimeSet<VoteModel> _votes;
}
[RealtimeModel]
public partial class TeamModel
{
[RealtimeProperty(1, true)] private string _name;
[RealtimeProperty(2, true)] private RealtimeSet<VoteModel> _votes;
}
3. Now, let's create our main VotingSystemModel:
[RealtimeModel]
public partial class VotingSystemModel
{
[RealtimeProperty(1, true)] private RealtimeArray<TeamModel> _teams;
[RealtimeProperty(2, true)] private int _resetTrigger;
}
[RealtimeModel]
public partial class VotingSystemModel
{
[RealtimeProperty(1, true)] private RealtimeArray<TeamModel> _teams;
[RealtimeProperty(2, true)] private int _resetTrigger;
}
4. Finally, let's create a VotingManager component to handle the voting logic:
using Normal.Realtime;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;

public class VotingManager : RealtimeComponent<VotingSystemModel>
{
public Text scoreText;
public Button team1Button;
public Button team2Button;
public Button resetButton;
public InputField team1NameInput;
public InputField team2NameInput;

protected override void OnRealtimeModelReplaced(VotingSystemModel previousModel, VotingSystemModel currentModel)
{
if (previousModel != null)
{
// Unsubscribe from previous model events
previousModel.teams.modelAdded -= OnTeamAdded;
previousModel.teams.modelRemoved -= OnTeamRemoved;
}
if (currentModel != null)
{
if (currentModel.teams.Count == 0)
{
// Initialize teams if they don't exist
currentModel.teams.Add(new TeamModel { name = "Team 1" });
currentModel.teams.Add(new TeamModel { name = "Team 2" });
}

// Subscribe to new model events
currentModel.teams.modelAdded += OnTeamAdded;
currentModel.teams.modelRemoved += OnTeamRemoved;

// Set up UI
SetupUI();
}
}

private void SetupUI()
{
team1Button.onClick.AddListener(() => Vote(0));
team2Button.onClick.AddListener(() => Vote(1));
resetButton.onClick.AddListener(ResetVotes);

team1NameInput.onEndEdit.AddListener((name) => ChangeTeamName(0, name));
team2NameInput.onEndEdit.AddListener((name) => ChangeTeamName(1, name));

UpdateUI();
}

private void Vote(int teamIndex)
{
if (teamIndex < 0 || teamIndex >= model.teams.Count) return;

var team = model.teams[teamIndex];
var existingVote = team.votes.FirstOrDefault(v => v.playerID == realtime.clientID);

if (existingVote != null)
{
// Player has already voted for this team
return;
}

// Remove vote from other team if exists
foreach (var otherTeam in model.teams)
{
var otherVote = otherTeam.votes.FirstOrDefault(v => v.playerID == realtime.clientID);
if (otherVote != null)
{
otherTeam.votes.Remove(otherVote);
break;
}
}

// Add new vote
team.votes.Add(new VoteModel { playerID = realtime.clientID, teamName = team.name });

UpdateUI();
}
private void ChangeTeamName(int teamIndex, string newName)
{
if (teamIndex < 0 || teamIndex >= model.teams.Count) return;

model.teams[teamIndex].name = newName;
UpdateUI();
}

private void ResetVotes()
{
foreach (var team in model.teams)
{
team.votes.Clear();
}
model.resetTrigger++; // This triggers a sync across all clients
UpdateUI();
}

private void UpdateUI()
{
if (model.teams.Count < 2) return;

team1NameInput.text = model.teams[0].name;
team2NameInput.text = model.teams[1].name;

scoreText.text = $"{model.teams[0].name}: {model.teams[0].votes.Count} - {model.teams[1].name}: {model.teams[1].votes.Count}";

// Log votes
Debug.Log("Current Votes:");
foreach (var team in model.teams)
{
foreach (var vote in team.votes)
{
Debug.Log($"Player {vote.playerID} voted for {vote.teamName}");
}
}
}

private void OnTeamAdded(TeamModel team, bool isLocal) => UpdateUI();
private void OnTeamRemoved(TeamModel team, bool isLocal) => UpdateUI();
}
using Normal.Realtime;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;

public class VotingManager : RealtimeComponent<VotingSystemModel>
{
public Text scoreText;
public Button team1Button;
public Button team2Button;
public Button resetButton;
public InputField team1NameInput;
public InputField team2NameInput;

protected override void OnRealtimeModelReplaced(VotingSystemModel previousModel, VotingSystemModel currentModel)
{
if (previousModel != null)
{
// Unsubscribe from previous model events
previousModel.teams.modelAdded -= OnTeamAdded;
previousModel.teams.modelRemoved -= OnTeamRemoved;
}
if (currentModel != null)
{
if (currentModel.teams.Count == 0)
{
// Initialize teams if they don't exist
currentModel.teams.Add(new TeamModel { name = "Team 1" });
currentModel.teams.Add(new TeamModel { name = "Team 2" });
}

// Subscribe to new model events
currentModel.teams.modelAdded += OnTeamAdded;
currentModel.teams.modelRemoved += OnTeamRemoved;

// Set up UI
SetupUI();
}
}

private void SetupUI()
{
team1Button.onClick.AddListener(() => Vote(0));
team2Button.onClick.AddListener(() => Vote(1));
resetButton.onClick.AddListener(ResetVotes);

team1NameInput.onEndEdit.AddListener((name) => ChangeTeamName(0, name));
team2NameInput.onEndEdit.AddListener((name) => ChangeTeamName(1, name));

UpdateUI();
}

private void Vote(int teamIndex)
{
if (teamIndex < 0 || teamIndex >= model.teams.Count) return;

var team = model.teams[teamIndex];
var existingVote = team.votes.FirstOrDefault(v => v.playerID == realtime.clientID);

if (existingVote != null)
{
// Player has already voted for this team
return;
}

// Remove vote from other team if exists
foreach (var otherTeam in model.teams)
{
var otherVote = otherTeam.votes.FirstOrDefault(v => v.playerID == realtime.clientID);
if (otherVote != null)
{
otherTeam.votes.Remove(otherVote);
break;
}
}

// Add new vote
team.votes.Add(new VoteModel { playerID = realtime.clientID, teamName = team.name });

UpdateUI();
}
private void ChangeTeamName(int teamIndex, string newName)
{
if (teamIndex < 0 || teamIndex >= model.teams.Count) return;

model.teams[teamIndex].name = newName;
UpdateUI();
}

private void ResetVotes()
{
foreach (var team in model.teams)
{
team.votes.Clear();
}
model.resetTrigger++; // This triggers a sync across all clients
UpdateUI();
}

private void UpdateUI()
{
if (model.teams.Count < 2) return;

team1NameInput.text = model.teams[0].name;
team2NameInput.text = model.teams[1].name;

scoreText.text = $"{model.teams[0].name}: {model.teams[0].votes.Count} - {model.teams[1].name}: {model.teams[1].votes.Count}";

// Log votes
Debug.Log("Current Votes:");
foreach (var team in model.teams)
{
foreach (var vote in team.votes)
{
Debug.Log($"Player {vote.playerID} voted for {vote.teamName}");
}
}
}

private void OnTeamAdded(TeamModel team, bool isLocal) => UpdateUI();
private void OnTeamRemoved(TeamModel team, bool isLocal) => UpdateUI();
}
This design pattern addresses all your requirements: 1. It keeps track of which player voted for which team using the VoteModel. 2. Team names are dynamic and can be changed using the input fields. 3. There's a reset button to clear all votes. 4. It logs which player voted for which team (in the UpdateUI method). 5. It's designed for exactly two teams, but could be easily extended if needed. To use this system: 1. Create a new GameObject in your scene and add the Realtime component to it. 2. Add the VotingManager component to the same GameObject. 3. Set up your UI elements (buttons, text fields, input fields) and assign them in the inspector to the corresponding fields in the VotingManager component. This implementation uses Normcore's RealtimeModel and RealtimeComponent system to ensure that all changes are properly synchronized across all clients. The use of RealtimeSet and RealtimeArray ensures that additions and removals of votes and teams are properly tracked and synchronized. For more information on working with custom RealtimeModels and RealtimeComponents in Normcore, you can refer to the Synchronizing Custom Data documentation. Remember to compile your models (VoteModel, TeamModel, and VotingSystemModel) after creating them, as explained in the Normcore documentation. Realtime > Synchronizing Custom Data This design should provide a robust and flexible voting system that meets all your requirements while leveraging Normcore's synchronization capabilities. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2d 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?