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?
Related
I am trying to write a chess program in C# Windows Forms and I am writing a method GetMove() in this HumanPlayer class I have, which will return the Move from a player input of two clicks on separate squares on the board UI.
Could I have some help / advice on what I should use to implement this or if I am misunderstanding something else, explain that to me.
I've tried to add code snippets, but please let me know if I've done them wrong.
class HumanPlayer : Player
{
private Coords _selected;
public HumanPlayer(PieceColour colour) : base(colour)
{
_selected = new Coords();
}
public override ChessMove GetMove(Board board)
{
Coords Start = new Coords();
board.RaiseSquareClicked += ReceiveStartSquareClickInfo;
// Want to wait until that function is triggered by the event until continuing
Start = _selected;
board.RaiseSquareClicked -= ReceiveStartSquareClickInfo;
Coords End = new Coords();
board.RaiseSquareClicked += ReceiveEndSquareClickInfo;
// Want to wait until that function is triggered by the event until continuing
End = _selected;
board.RaiseSquareClicked -= ReceiveEndSquareClickInfo;
return new ChessMove(Start, End);
}
public void ReceiveStartSquareClickInfo(object sender, SquareClickedEventArgs e)
{
_selected = e.Square.Coords;
}
public void ReceiveEndSquareClickInfo(object sender, SquareClickedEventArgs e)
{
_selected = e.Square.Coords;
}
}
One thing I tried was using AutoResetEvent and WaitOne() and Set(), but this caused the UI to stop displaying.
I also tried to understand and use await and async, but I just confused myself and overcomplicated it and didn't get anywhere with it. So it might just be that I need someone to explain it to me.
This code that doesn't work might help someone understand what I misunderstand about asynchronous functions etc.
public async void Play()
{
_currentPlayer = _players[0];
_currentTurn = 1;
while (!GameOver())
{
ChessMove move= await _currentPlayer.GetMove(_board);
if (_currentPlayer == _players[1])
{
_currentTurn += 1;
_currentPlayer = _players[0];
}
else
{
_currentPlayer = _players[1];
}
}
}
class HumanPlayer : Player
{
private Coords _selected;
private TaskCompletionSource<bool> _squareClicked;
public HumanPlayer(PieceColour colour) : base(colour)
{
_selected = new Coords();
}
public override async Task<ChessMove> GetMove(Board board)
{
Coords Start = new Coords();
_squareClicked = new TaskCompletionSource<bool>();
board.RaiseSquareClicked += ReceiveStartSquareClickInfo;
_squareClicked.Task.Wait();
Start = _selected;
board.RaiseSquareClicked -= ReceiveStartSquareClickInfo;
Coords End = new Coords();
_squareClicked = new TaskCompletionSource<bool>();
board.RaiseSquareClicked += ReceiveEndSquareClickInfo;
_squareClicked.Task.Wait();
End = _selected;
board.RaiseSquareClicked -= ReceiveEndSquareClickInfo;
return new ChessMove(Start, End);
}
public async void ReceiveStartSquareClickInfo(object sender, SquareClickedEventArgs e)
{
_squareClicked.SetResult(true);
_selected = e.Square.Coords;
}
public async void ReceiveEndSquareClickInfo(object sender, SquareClickedEventArgs e)
{
_squareClicked.SetResult(true);
_selected = e.Square.Coords;
}
}
I've kinda been hesitant / nervous to post this question because I don't want people to get annoyed at me for posting a "duplicate question". Even though I've looked through several of the questions, it is confusing and frustrating not knowing whether the solution just doesn't apply to my situation or if I've added it in wrong. I'm sure I could find my solution answered in another question, but I feel it would take me a lot longer to find it and understand it than posting my own question.
Sorry if I've posted this question wrong or not followed the guidelines, this is my first post here.
If I've done anything wrong in formatting / communicating through this post, let me know and I'll try to fix it.
Good question, from what I can understand, your wanting to wait on a response from another method before executing yours.
Using async/await is the best option for this, if your getting confused at that, there are some tutorials and such you can follow
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/await
https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/
https://dotnettutorials.net/lesson/async-and-await-operator-in-csharp/
TaskCompletionSource is a class in C# that enables creating a Task object which can be manually completed with a result or exception.
class Example
{
TaskCompletionSource<string> taskCompletionSource = new();
public async Task DoStuff()
{
// Wait for the result of the TaskCompletionSource
var result = await taskCompletionSource.Task;
Console.WriteLine(result);
}
public void SetResult(){
taskCompletionSource.SetResult("Hello World!");
}
}
In this example, calling to DoStuff method will wait until the SetResult method is called, which will then set the result variable to "Hello World!"
Your post states that you want to wait for another method to be triggered before continuing and then describes three "states of play" so my first suggestion is to identify in code exactly the things we need to wait for in the chess game loop.
enum StateOfPlay
{
PlayerChooseFrom,
PlayerChooseTo,
OpponentTurn,
}
Game Loop
The goal is to run a loop that cycles these three states continuously, waiting at each step. However, the main Form is always running its own Message Loop to detect mouse clicks and key presses and it's important not to block that loop with our own.
The await keyword causes a waiting method to return immediately which allows the UI loop to keep running. But when "something happens" that we're waiting for, the execution of this method will resume on the next line after the await. A semaphore object says when to stop or go and is initialized here in the waiting state.
SemaphoreSlim _semaphoreClick= new SemaphoreSlim(0, 1);
When the game board is clicked during the players turn then the Release() method will be called on the semaphore, allowing things to resume. In terms of the specific question that you asked, this code snippet shows how to use the await keyword in your chess game loop.
private async Task playGameAsync(PlayerColor playerColor)
{
StateOfPlay =
playerColor.Equals(PlayerColor.White) ?
StateOfPlay.PlayerChooseFrom :
StateOfPlay.OpponentTurn;
while(!_checkmate)
{
switch (StateOfPlay)
{
case StateOfPlay.PlayerChooseFrom:
await _semaphoreClick.WaitAsync();
StateOfPlay = StateOfPlay.PlayerChooseTo;
break;
case StateOfPlay.PlayerChooseTo:
await _semaphoreClick.WaitAsync();
StateOfPlay = StateOfPlay.OpponentTurn;
break;
case StateOfPlay.OpponentTurn:
await opponentMove();
StateOfPlay = StateOfPlay.PlayerChooseFrom;
break;
}
}
}
Player's turn
Here we have to wait for each square to get clicked. A straightforward way to do this is with a SemaphoreSlim object and call Release() when the game board is clicked during the player's turn.
Square _playerFrom, _playerTo, _opponentFrom, _opponentTo;
private void onSquareClicked(object sender, EventArgs e)
{
if (sender is Square square)
{
switch (StateOfPlay)
{
case StateOfPlay.OpponentTurn:
// Disabled for opponent turn
return;
case StateOfPlay.PlayerChooseFrom:
_playerFrom = square;
Text = $"Player {_playerFrom.Notation} : _";
break;
case StateOfPlay.PlayerChooseTo:
_playerTo = square;
Text = $"Player {_playerFrom.Notation} : {_playerTo.Notation}";
richTextBox.SelectionColor = Color.DarkGreen;
richTextBox.AppendText($"{_playerFrom.Notation} : {_playerTo.Notation}{Environment.NewLine}");
break;
}
_semaphoreClick.Release();
}
}
Opponents turn
This simulates a computer opponent processing an algorithm to determine its next move.
private async Task opponentMove()
{
Text = "Opponent thinking";
for (int i = 0; i < _rando.Next(5, 10); i++)
{
Text += ".";
await Task.Delay(1000);
}
string opponentMove = "xx : xx";
Text = $"Opponent Moved {opponentMove}";
richTextBox.SelectionColor = Color.DarkBlue;
richTextBox.AppendText($"{opponentMove}{Environment.NewLine}");
}
It might be helpful to look at another answer I wrote that describes how to create the game board with a TableLayoutPanel and how to interact with mouse to determine the square that's being clicked on.
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):
}
}
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!");
}
}
I have an object with a script that moves it along a path. On the same object is a script that, if activated, sends it back to its start point. The mover script is default active and the home is by default inactive. I want it so that if I press "2", mover deactivates and home activates, and if I press "1", the scripts go back to how they were. states 1 & 2 should be exclusive. I have created a script, on the same object, as follows:
public class activator : MonoBehaviour
{
bool mover = new bool();
bool home = new bool();
void Start ()
{
mover = true;
home = false;
}
void Update ()
{
if (Input.GetButtonDown("1"))
{
mover = true;
home = false;
}
if (Input.GetButtonDown("2"))
{
mover = false;
home = true;
}
if(mover == true)
{
GetComponent<router>().enabled = true;
GetComponent<routeRepairs>().enabled = false;
}
else if(home == true)
{
GetComponent<router>().enabled = false;
GetComponent<routeRepairs>().enabled = true;
}
}
}
note the actual scripts I'm trying to activate and deactivate from the object are router and routeRepairs. When I actually run the game in playmode nothing happens after the button presses.
There is a question that stackoverflow is telling me is similar but the solutions given aren't doing anything for me. So, any help with how to get this working would be greatly appreciated! Thanks in advance!
one more thing: I hope to add more movement scripts that should be activated and deactivated in the same way, so solutions that only apply to two states (ie moving or home) aren't ideal. probably not important but it just occurred to me that it was worth qualifying..?
Are the inputs actually recognized? Put a print inside the input if to see if it actually gets the input. If not, maybe your input is not configured correctly. Or you can try using KeyCode instead like:
if (Input.GetKeyDown(KeyCode.Alpha1))
Also, you don't need the extra booleans or the extra if statements. Just get the scripts in start and then directly enable/disable them
router router;
routerRepairs routerRepairs;
void Start ()
{
router = GetComponent<router>();
routerRepairs = GetComponent<routerRepairs>();
}
void Update ()
{
if (Input.GetKeyDown(KeyCde.Alpha1))
{
router.enabled = true;
routerRepairs.enabled = false;
}
if (Input.GetKeyDown(KeyCde.Alpha2))
{
router.enabled = false;
routerRepairs.enabled = true;
}
}
So basically what I have right now is a form with hidden labels in it, and I'm using cases along with a simple counter (counting the clicks on a picturebox) to move through the cases. What I need to happen is when I get to case 2 I need the labels to pop up for a certain amount of time (1 second or so) and the goal is for the user to memorize 2 of those and put it in a text box. I'm just confused on how I use a timer or a do while loop to make the labels only pop up for a certain amount of time as with cases it only checks it once right when it's clicked. Also I'm using enums to store the "Answers" but I'm not 100 percent sure how to crosscheck what the user inputs and what the enum is. Sorry if it's a really easy answer, I'm quite new to C#, and especially loops and enums. Any help will be greatly appreciated. Thank you!
Cases
private void pbMummy_Click(object sender, EventArgs e)
{
counter++;
switch (counter)
{
case 1:
MessageBox.Show("Help me! I lost my passwords. Can you try and just get 2 of them?");
break;
case 2:
lblc1.Visible = true;
lblc2.Visible = true;
lblc3.Visible = true;
lblc4.Visible = true;
lblc5.Visible = true;
System.Threading.Thread.Sleep(1500);
lblc1.Visible = false;
lblc2.Visible = false;
lblc3.Visible = false;
lblc4.Visible = false;
lblc5.Visible = false;
break;
}
}
Enums
public enum Memorize
{
boo92134,
spooky93,
grim432,
fangs9981,
cobweb439
}
public class Mummy
{
public Memorize answer { get; set; }
}
You could use async task for this as it's a really simple task:
async void ShowLabelForCertainTime( Label label, int milliseconds )
{
label.Visible = true;
await Task.Delay( milliseconds ); //Time in milliseconds
label.Visible = false;
}
Just call it in case you want:
case 2:
ShowLabelForCertainTime( lbcl1 );
Edit: not sure why but I have been working with "buttons" instead of "labels" but the principle is the same
You can use Timers to set your buttons to visible after some time. Use a dictionary so you can retrieve which buttons has launched the event. Here's a code sample just as proof of concept, it needs some refactoring.
//run this on the click of the button
int button_id = 1; //or whatever way you want to reference your button
//myButtons[button_id].Visible = false;
var btnTimer = new System.Timers.Timer(2000);
btnTimer.Elapsed += myTestClass.HideButton;
btnTimer.Enabled = true;
//keep a reference to that button so you know who the timer belongs to
myTestClass.myTimers[btnTimer] = button_id;
public static class myTestClass
{
public static Dictionary<System.Timers.Timer, int> myTimers = new Dictionary<System.Timers.Timer, int>();
public static void HideButton(object sender, System.Timers.ElapsedEventArgs e)
{
System.Timers.Timer tmr = (System.Timers.Timer)sender;
if(myTimers.ContainsKey(tmr))
{
//here you get your reference back to the button to which this timer belongs, you can show/hide it
//var btn_id = myButtons[myTimers[tmr]];
//myButtons[btn_id].Visible = false;
Console.WriteLine(myTimers[tmr]);
}
tmr.Enabled = false;
}
}