So am currently developing a way where players in a current room are in a waiting room entering or selecting their specific item. When each player has completed a button is clicked which is 'I am Ready!'...When a player clicks I am ready then an Int variable ReadyPlayers Updates according to how many have clicked the I am ready Button. If the readyplayers (int) match the number of players in the room then a countdown starts. The problem I am facing is configuring the custom properties. I wrote this code hoping that it would solve this problem but I end up getting logical errors where the readyplayers (int) are not counted from the players.
using Hashtable = ExitGames.Client.Photon.Hashtable;
//Variables
public int ReadyPlayers = 0;
//added this to the button on the inspector : onclick event
public void OnClickIamReady()
{
ReadyPlayers = (int)PhotonNetwork.LocalPlayer.CustomProperties["PlayerReady"];
ReadyPlayers++;
Hashtable hash = new Hashtable() { { "PlayerReady", ReadyPlayers } };
PhotonNetwork.LocalPlayer.SetCustomProperties(hash);
//when a player clicks this button deactivate it
IamReadyButton.gameObject.SetActive(false);
foreach (Player player in PhotonNetwork.PlayerList)
{
Debug.Log(player.NickName.ToString() + " " + " is Ready. " + " " + " Players Who are Ready : " + player.CustomProperties["PlayerReady"].ToString());
}
}
It makes no sense that every player uses his own custom properties in order to store how many ready players he is aware of in total.
Instead you rather want to set only yourself to ready.
Then check of everyone is ready and start the match. But you don't want to make this check on the clients themselves. What about network lag? What if two clients press the button at the same time and the properties are not synced yet?
-> I would rather only check on the Master client within OnPlayerPropertiesUpdate
So something like
using System.Linq;
...
public void OnClickIamReady()
{
// Do not use a new hashtable everytime but rather the existing
// in order to not loose any other properties you might have later
var hash = PhotonNetwork.LocalPlayer.CustomProperties;
hash["Ready"] = true;
PhotonNetwork.LocalPlayer.SetCustomProperties(hash);
IamReadyButton.gameObject.SetActive(false);
if(!PhotonNetwork.IsMasterClient) return;
CheckAllPlayersReady ();
}
public override void OnPlayerPropertiesUpdate (Player targetPlayer, Hashtable changedProps)
{
if(!PhotonNetwork.IsMasterClient) return;
if(!changedProps.ContainsKey("Ready")) return;
CheckAllPlayersReady();
}
public override void OnMasterClientSwitched(Player newMasterClient)
{
if(newMasterClient != PhotoNework.LocalPlayer) return;
CheckAllPlayersReady ();
}
private void CheckAllPlayersReady ()
{
var players = PhotonNetwork.PlayerList;
// This is just using a shorthand via Linq instead of having a loop with a counter
// for checking whether all players in the list have the key "Ready" in their custom properties
if(players.All(p => p.CustomProperties.ContainsKey("Ready") && (bool)p.CustomProperties["Ready"]))
{
Debug.Log("All players are ready!");
}
}
Related
Apologies I am a newbie, ill try to explain as best as I can. Im creating a game for a university assignment. My game is a maths game that asks the user maths questions, they get a point for answering correctly and lose a life for answering incorrectly. I have created the game over multiple forms namely: Main Menu, Game (actual game), Rules and Leaderboard. My question focuses on the game and leaderboard forms. The two inputs the user give are their 'usernames' as a string input and the amount of points they get once they finish a round of answering questions as a interger input.
I am attempting to display the 'Username' and 'Score' on a listbox (leaderboard) permanetly so that if multiple players play, they can compare scores when opening the leaderboard at any time (leaderboard does not need to keep its data if the program is stopped and started again, only the data it acquires while its running). At the moment, once a player has finished their round, the leaderboard pops up and displays the information correctly, but when the player goes back to main menu and reopens the leaderboard or plays another round, the leaderboard is empty again, as if the array had cleaned itself.
I managed to succesfully keep the data in the listbox when I use one form only for the inputs, array and listbox, the issue comes in when trying to do it over two different forms, in my case, the inputs (name and score) are inputted on the game form, then are transferred over to the leaderboard form and stored in an array. The array is then displayed to the listbox output for the leaderboard.
Example of what I would like:
Player 1 plays 1 round and gets a score of 7. This is outputted to the listbox leaderboard as follows:
Username: Player 1 | Score: 7
Player 2 proceeds to play 1 round and gets a score of 9. Then the listbox leaderboard is as follows:
Username: Player 2 | Score: 9
Username: Player 1 | Score: 7
etc. At the moment, the usernames and scores are displayed on the leaderboard the first time the player views it which is right after their round. Then when the player closes the leaderboard it gets deleted/refreshed off the leaderboard so that the leaderboard is empty which is not what I want.
Things to note:
I am required to use an array, cannot use List
Id prefer not to write to a textfile but will if its my last resort
Some of the comments may not make sense, but it is required for marks
Hope that was clear enough. Any help would be greatly appreciated :)
My code sofar is as follows:
Leaderboard code:
namespace Educational_Boardgame_Group_Assignment
{
public partial class Leaderboard : Form
{
//Variables
string userName;
int userScore;
public Leaderboard()
{
InitializeComponent();
}
public Leaderboard(string nameValue, int numValue)
{
InitializeComponent();
this.nameValue = nameValue;
this.scoreValue = numValue;
}
public string nameValue { get; set; }
public int scoreValue { get; set; }
//Back to main menu method
private void btnMainMenu_Click(object sender, EventArgs e)
{
//Closes Leaderboard Form
this.Close();
}
//Loads users name and score from the game they played
private void Leaderboard_Load(object sender, EventArgs e)
{
//Variables
string[] Username = new string[20];
int[] Score = new int[20];
int index = 0;
//Input
userName = nameValue;
userScore = scoreValue;
//Process | Displays user's name and score from the array within it is stored.
if (index < Username.Length && index < Score.Length)
{
Username[index] = userName;
Score[index] = userScore;
lstHighScoreOutput.Items.Add("Username: " + Username[index] + " Score: " + Score[index]);
}
}
}
}
Game Code (Acquiring Username):
//Variable
bool boolTryAgain = false;
//Process | Dialog Pop-Up Box for username entry by user in order to save their highscore
do
{
//Variable
userName = UserPopUpBox.GetUserInput("Enter your name below:", "Username Entry");
//Process
if (userName == "")
{
DialogResult dialogResult = MessageBox.Show("You did not enter anything. Try again?", "Error", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
boolTryAgain = true; //will reopen the dialog for user to input text again
}
else if (dialogResult == DialogResult.No)
{
//exit/cancel
MessageBox.Show("Your highscore will not be saved.");
boolTryAgain = false;
}//end if
}
else
{
if (userName == "cancel")
{
MessageBox.Show("Your highscore will not be saved.");
}
else
{
MessageBox.Show("Your username is: '" + userName + "'");
}
}
} while (boolTryAgain == true);
Game Code (Acquiring User Score):
//Process | Determines whether user input is correct. Displays specific output based on user input.
int userEntered = Convert.ToInt32(txtAnswer.Text);
if (userEntered == total)
{
lblStatus.Text = "Correct!";
lblStatus.ForeColor = Color.Lime;
score += 1;
lblScore.Text = "Score: " + score;
SetUpGame();
}
else
{
lblStatus.Text = "Incorrect!";
lblStatus.ForeColor = Color.Red;
lives -= 1;
lblLives.Text = "Lives: " + lives;
SetUpGame();
}
Game Code (Sending data to the leaderboard form):
//Process | If user answers incorrectly 3 times, game ends and output displayed.
if (lives == 0)
{
this.Close();
MessageBox.Show("You have run out of lives! \nYour final score is: " + score);
//Shows user score on leaderboard
Leaderboard frm3 = new Leaderboard(userName, score);
frm3.ShowDialog();
}
Welcome to managing state in an application!
A couple things to point out first (some you already know):
Your Leaderboard has nameValue and scoreValue as public properties so that they are accessible outside of the Leaderboard class and are available to the rest of the class (like it's methods etc).
Your string[] Username and int[] Score are scoped to the Leaderboard_Load method. That is, these variable exist during the method's execution and no where else. You can't access these variables in btnMainMenu_Click, for example.
The actual leadership data does load into Username and Scrore, but is then loaded into lstHighScoreOutput. Once that method finishes, Username and Scrore are removed from memory. So the only place where your data is actually present once the UI is loaded, is in the UI component of lstHighScoreOutput.
Once the form Leaderboard closes, it disposes (clears out of memory) all of it's referenced components, including lstHighScoreOutput. So at that point, the data is removed from memory, which is why when you open the UI again, you don't see previous values.
So what you want is the data to exist and be managed outside of the Leaderboard UI, because this is state for your game. Ok so you don't need it to persist between runs, that's fine. But you want it to be available no matter how many times you run the game portion and show the leaderboard.
A simple place to put this is in your main/calling window (probably your main menu in this case). So you store the data there, update it
after the game is complete. The data is then passed to your Leaderboard. So the Leaderboard becomes a "dumb" component which just renders what it's given.
OR
Let the Leaderboard compute the high scores, which works but requires the UI to be opened to adjust the scores. If a user don't run the leaderboard, the scores aren't updated. So try the first approach.
A more 'correct' way of storing leaderboard data is in it's own class, one that holds both score and user arrays. Then you're not passing 2 arrays around the place, you just pass the leaderboard instance.
public class LeaderboardData
{
public string[] UserNames { get; set; }
public int[] UserScores { get; set; }
}
You can even take this further with the Singleton Pattern. This is a special design pattern which lets your class only ever have once instance.
With a singleton LeaderboardData, you don't even need to pass references to the class. It will hold onto and look after it's own data for the lifetime of your running game. This is the approach I would take.
I am trying to configure this type of game where I have 6 players connected to the same room. On game start, the Master Client (first player turn) starts where the UI button should be enabled only to him and it should be disabled to all other players and after the first player click the button it will get disabled and for the 2nd player alone it should be enabled and so on to all other players.
So i have this idea where a loop should iterate through the list of players when a button is clicked by the first or master client then getNext() that is the next player as per the actor number. The problem is how do i put in code?
I was also recommended to use Custom properties but it has been a huge problem to me since i don't understand them. Watched some of the tutorials and also some documentation but I seem not to understand.
I am currently not using photonview component.
let me just write a short code of what i mean;
public class GameManager : MonoBehaviourPunCallbacks
{
private ExitGames.Client.Photon.Hashtable _myCustomProperties = new ExitGames.Client.Photon.Hashtable();
private void Start()
{
//onstart the master client should only be the one t view the button and click it
//his name should be displayed when it his turn
if (PhotonNetwork.IsMasterClient)
{
Player_Name.text = PhotonNetwork.MasterClient.NickName + " " + "it's your turn";
button.SetActive(true);
}
}
//onclcik the Button
public void TurnButton()
{
for(int i = 0; i<PhotonNetwork.Playerlist.length; i++)
{
//so every click of the button a loop should iterate and check the player if is the next to see the button and click it
//a name should also be displayed on his turn as per his nickname
}
}
}
There is no need for custom player properties here.
I would rather use room properties for that
public class GameManager : MonoBehaviourPunCallbacks
{
private const string ACTIVE_PLAYER_KEY = "ActivePlayer";
private const string ACTIVE_ME_FORMAT = "{0}, you it's your turn!!";
private const string ACTIVE_OTHER_FORMAT = "Please wait, it's {0}'s turn ...";
private Room room;
[SerializeField] private Button button;
[SerializeField] private Text Player_Name;
#region MonoBehaviourPunCallbacks
private void Awake()
{
button.onClick.AddListener(TurnButton):
button.gameObject.SetActive(false);
Player_Name.text = "Connecting ...";
// Store the current room
room = PhotonNetwork.CurrentRoom;
if (PhotonNetwork.IsMasterClient)
{
// As master go active since usually this means you are the first player in this room
// Get your own ID
var myId = PhotonNetwork.LocalPlayer.ActorNumber;
// and se it to active
SetActivePlayer(myId);
}
else
{
// Otherwise fetch the active player from the room properties
OnRoomPropertiesUpdate(room.CustomProperties);
}
}
// listen to changed room properties - this is always called if someone used "SetCustomProperties" for this room
// This is basically the RECEIVER and counter part to SetActivePlayer below
public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
{
// Maybe another property was changed but not the one we are interested in
if(!propertiesThatChanged.TryGetValue(ACTIVE_PLAYER_KEY, out var newActiveID)) return;
// if we got a value but it's not an int something is wrong in general
if(!(newActiveID is int newActvieIDValue))
{
Debug.LogError("For some reason \"ACTIVE_PLAYER_KEY\" is not an int!?");
return;
}
// otherwise apply the ID
ApplyActivePlayer(newActvieIDValue);
}
// Optional but might be important (?)
// In the rare case that the currently active player suddenly leaves for whatever reason
// as the master client you might want to handle this and go to the next player then
//public override void OnPlayerLeftRoom (Player otherPlayer)
//{
// if(!PhotonNetwork.IsMasterClient) return;
//
// if(!propertiesThatChanged.TryGetValue(ACTIVE_PLAYER_KEY, out var currentActiveID) && currentActiveID is int currentActiveIDValue) return;
//
// if(currentActiveIDValue != otherPlayer.ActorNumber) return;
//
// var nextPlayer = Player.GetNextFor(currentActiveIDValue);
// var nextPlayerID = nextPlayer.ActorNumber;
// SetActivePlayer(nextPlayerID);
//}
#endregion MonoBehaviourPunCallbacks
// Called via onClick
private void TurnButton()
{
// this gets the next player after you sorted by the actor number (=> order they joined the room)
// wraps around at the end
var nextPlayer = Player.GetNext();
// Get the id
var nextPlayerID = nextPlayer.ActorNumber;
// and tell everyone via the room properties that this is the new active player
SetActivePlayer(nextPlayerID);
}
// This writes the new active player ID into the room properties
// You can see this as kind of SENDER since the room properties will be updated for everyone
private void SetActivePlayer(int id)
{
var hash = new ExitGames.Client.Photon.Hashtable();
hash[ACTIVE_PLAYER_KEY] = id;
room.SetCustomProperties(hash);
}
// this applies all local changes according to the active player
private void ApplyActivePlayer(int id)
{
// get the according player
var activePlayer = Player.Get(id);
// Am I this player?
var iAmActive = PhotonNetwork.LocalPlayer.ActorNumber == id;
// Set the button active/inactive accordingly
button.gameObject.SetActive(iAmActive);
// Set the text accordingly
Player_Name.text = string.Format(iAmActive ? ACTIVE_ME_FORMAT : ACTIVE_OTHER_FORMAT, activePlayer.NickName):
}
}
I'm using a TMP input field and would like to override some default behaviour. Specifically, when a letter is held down, it repeats. Similarly, when backspace (or delete) is held down, it removes multiple characters.
I would like it so that if a letter is held down, that letter is only entered once. I would also like backspace to only remove one character if it is held down.
I am already implementing Input.GetKeyDown() to perform a certain task, so I believe I will need to do something similar here, but I can't quite work out how.
Thanks.
EDIT:
Thanks #daniel-m. This works sometimes, but other times it allows for 1 or 2 repeating letters.
I added extra Debug.Logs to investigate, like so:
private void Update()
{
if (IsFieldSelected)
{
// if a key is pressed
if (Input.anyKeyDown)
{
// save the the current text to lastText (this key will have been entered)
lastText = inputField.text;
Debug.Log("Key was pressed! inputField = " + inputField.text + ", inputField = " + lastText);
}
// if a key is held down
if (Input.anyKey)
{
Debug.Log("Key held down, part 1! inputField = " + inputField.text + ", inputField = " + lastText);
inputField.text = lastText;
Debug.Log("Key held down, part 2! inputField = " + inputField.text + ", inputField = " + lastText);
}
}
}
And I held down the keys the keys 1 to 6 consecutively with the following Keydown and Keyup timestamps (recorded in a different script):
The output into the text box was 1122334456. You can see in the debug logs below that a repetition is added to the inputField.text. However, it doesn't always show up in the input field - numbers 1-4 repeated but 5 and 6 didn't. And when the numbers do repeat, they don't show up in the input field until the key is released (even though the debug log says it's already in there).
I have tested a little bit around and I think I have a solution for your problem.When a key is pressed I save the current text into lastText. If a key is held down I set the current text inside the input field to lastText. So the code essentially sets the text field to the last text that was saved.
public class Text : MonoBehaviour
{
public TMP_InputField inputField;
private bool IsFieldSelected;
private string lastText;
private void Update()
{
if (IsFieldSelected)
{
if (Input.anyKeyDown)
{
lastText = inputField.text;
}
if (Input.anyKey)
{
Debug.Log(inputField.text);
inputField.text = lastText;
}
}
}
public void IsSelected()
{
IsFieldSelected = true;
}
public void IsNotSelected()
{
IsFieldSelected = false;
}
}
These are the events inside the InputField (TMP) gameobject.
I am new to Unity, and many of these principals I am just blanking on.
I build a dialogue system and its working great, but I want to make a confirmation box.
I have managed to get the confirmation box to pull up, but I cant seem to figure out how to wait for one of the Yes / No buttons are pushed before the code is returned to the Dialogue manager with the proper bool.
Right now the code pushes on behind the dialogue box. Which is expected.
I tried an enumerator but I must have been doing it wrong because it did not matter.
I just cleaned up the code to try to start from scratch.
public bool AskForTakingMoney(){
takingMoneyBox.SetActive(true);
goldText.text = "Current Gold: " + GameManager.instance.currentGold.ToString() + "G";
questionText.text = "Pay the " + DialogManager.instance.goldAmount + "G?";
//Wait here until yes or no button pushed
return saysYes;
}
public void SaysYes(){
saysYes = true;
selectedAnswer = true;
takingMoneyBox.SetActive(false);
}
public void SaysNo(){
saysYes = false;
selectedAnswer = true;
takingMoneyBox.SetActive(false);
}
I really just need the return function to not go back until the yes or no button is pushed.
I am at a complete loss.
If you want to show a popup, then the popup should be responsible for what happens next.
So your AskForTakingMoney should never return a value. Instead you need to assign the onclick events for the Yes and No button.
Your code should simply be:
public void AskForTakingMoney(){
takingMoneyBox.SetActive(true);
goldText.text = "Current Gold: " + GameManager.instance.currentGold.ToString() + "G";
questionText.text = "Pay the " + DialogManager.instance.goldAmount + "G?";
}
public void SaysYes(){
takingMoneyBox.SetActive(false);
// Your withdraw money code here.
}
public void SaysNo(){
takingMoneyBox.SetActive(false);
}
Then from within the Editor, click on your Yes Button.
You should see an onclick field like the one in the picture. Click on the plus sign, then drag in your MonoBehaviour object which is holding the code with above functions.
After dragging it in, You should be able to select SaysYes from the dropdown that appears under 'YourScriptName' -> 'SaysYes()'
Do the same for your No Button and you should be all set.
You would either need the function to not care about returning what they clicked, and each frame check (in the update method, or in a coroutine) if the button has been pressed by checking your selectedAnswer variable.
Or you could run some code in a separate thread, and use WaitHandle's, to cause the thread to pause until the button has been pressed.
Example using your code:
using System.Threading;
using UnityEngine;
public class Manager : MonoBehaviour
{
EventWaitHandle handle = new EventWaitHandle(false,EventResetMode.AutoReset);
Thread _thread;
public void StartThread()
{
_thread = new Thread(BackgroundThreadFunction);
_thread.Start();
}
public void BackgroundThreadFunction()
{
if (AskForTakingMoney())
{
//Yes
}
else
{
//No
}
}
public bool AskForTakingMoney()
{
takingMoneyBox.SetActive(true);
goldText.text = "Current Gold: " + GameManager.instance.currentGold.ToString() + "G";
questionText.text = "Pay the " + DialogManager.instance.goldAmount + "G?";
//Wait here until yes or no button pushed
handle.WaitOne();
return saysYes;
}
public void SaysYes()
{
saysYes = true;
selectedAnswer = true;
takingMoneyBox.SetActive(false);
handle.Set();
}
public void SaysNo()
{
saysYes = false;
selectedAnswer = true;
takingMoneyBox.SetActive(false);
handle.Set();
}
}
Make Two Buttons, Set them to active at the end of AskForTakingMoney(), Make those buttons lead to SaysYes(); and SaysNo();
Edit: Comment if you need clarification. I may not be understanding you clear enough. Do you even have buttons set up?
I have game objects which is button (Called as tiles), when the button is clicked it will be inactive and there's a condition to be tested if the condition is true the button will be destroyed but if its false the button will back to its active status.
My game Manager script (The condition):
public void checkword()
{
wordBuilded = displayer.text.ToString();
if (txtContents.Contains(wordBuilded))
{
Debug.Log(wordBuilded + " Is on the list");
wordScore++;
Debug.Log(wordScore);
}
else
{
Debug.Log("Someting is wrong..");
FindObjectOfType<LetterTiles>().tileStatus();
}
}
On my Button (Tile) script (Script attached to the buttons):
public void letterClick()
{
gameManager.currentWord += letter;
gameObject.SetActive(false);
}
public void tileStatus()
{
gameObject.SetActive(true);
}
When an object is deactivated Unity won't find it using FindObjectOfType.
either keep a local reference to the object [the button] in the game manager before you disable it or hide the button in a different way.
Since you are using a Button, you can use the interactable property of the button to make it disabled [and if you want it to be hidden then change the disabled color to transparent] like:
gameobject.GetComponent<Button>().interactable = false;