I am building a turn based game with Unity and Netcode.
My connections work and I receive expected behavior on the NetworkManager.
Except for one thing...
I have a server variable, that should be aligned on all clients. It's the index of the player (turn) in the game (CurrentPlayerIndex). The turn should change after a action has been done, but for testing I just made it a button action, so that all clients can change turn (change the CurrentPlayerIndex).
I made it work, so that CurrentPlayerIndex changes correctly on all clients, when one client change the CurrentPlayerIndex. It works so that the variable in a visual label updates with the same variable on all clients.
The problem is that the variable doesn't get stored on the client, so when I increase the CurrentPlayerIndex on one client, it shows the correctly increased CurrentPlayerIndex in the label BUT when I increase the same (already increased) variable on the same client, it just starts from the local index instead.
Let me explain...
I have two clients. Client1 and Client2. CurrentPlayerIndex starts on 0. When I click "increase" three times on Client1, CurrentPlayerIndex increases to 3. This is displayed on both Client1 and Client2. But when I click "increase" 2 times on Client2, the CurrentPlayerIndex starts from 0 again and increases to 3. When I click "increase" on Client1 again, the CurrentPlayerIndex increases to 4 and so on...
I expect that I misunderstand something about the local client and server behavior, but in this case I just want a "global" variable, that can be affected by all clients and aligned on all clients.
Here's the code snippet:
[SerializeField]
public NetworkVariable<int> CurrentPlayerIndex = new NetworkVariable<int>();
public override void OnNetworkSpawn() {
CurrentPlayerIndex.OnValueChanged += OnCurrentPlayerIndexChanged;
}
public override void OnNetworkDespawn() {
CurrentPlayerIndex.OnValueChanged -= OnCurrentPlayerIndexChanged;
}
public void OnCurrentPlayerIndexChanged(int previous, int current) {
UIManager.Instance.gameMenuLabel.text = "Player " + CurrentPlayerIndex.Value + "'s turn";
}
[ServerRpc(RequireOwnership = false)]
public void ChangeTurnServerRpc(int nextPlayerIndex) {
CurrentPlayerIndex.Value = nextPlayerIndex;
}
public void ChangeTurn() {
int nextPlayerIndex = 0;
if (CurrentPlayerIndex.Value+1 < 5) {
nextPlayerIndex = CurrentPlayerIndex.Value+1;
}
ChangeTurnServerRpc(nextPlayerIndex);
}
Related
Currently I am using Toggles to select a list of images and rather ran into an interesting problem.
I am using a Toggle Group called Radio Group and have 3 toggles under it. Each time when a toggle is selected the command
PlayerPrefs.SetInt("SaveToggleId", id);
is run. In this the id number is 0 for toggle 1, 1 for toggle 2 and so on.
So when I try to read this data the next time , the following set of code always reads 0 when used in Start and the correct value when used in Awake
toggleGroupId = PlayerPrefs.GetInt("SaveToggleId");
toggleGroupObject = GetComponent<ToggleGroup>();
SelectStartingToggle(toggleGroupId);
When I used this code in conjuction with the Debug.log() statements in various places what I found is when used in Start , it first reads from the function associated when the first toggle is selected and therby stores 0 . But when I use it in Awake it reads the right value stored in PlayerPrefs and selects the correct initial value
My explanation would be that because Awake is executed before Start , it has ample time to read from PlayerPrefs which gives the correct value. Also when I used only the number in the Start() as follows
SelectStartingToggle(3);
it correctly selected the right toggle whereas when I used PlayerPref instead of number ,it chose the wrong value.
Is my explanation correct or am I missing something?Also how to make sure the code execution is halted until the data from PlayerPref is read. Here is the full code:
public class RadioButtonSystem : MonoBehaviour
{
ToggleGroup toggleGroupObject;
private int toggleGroupId;
// Start is called before the first frame update
private void Awake()
{
toggleGroupId = PlayerPrefs.GetInt("SaveToggleId");
toggleGroupObject = GetComponent<ToggleGroup>();
Debug.Log("SaveToggleId........" + toggleGroupId);
SelectStartingToggle(toggleGroupId);
}
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void onSelectedToggle1()
{
SaveToggleId(0);
}
public void onSelectedToggle2()
{
SaveToggleId(1);
}
public void onSelectedToggle3()
{
SaveToggleId(2);
}
public void SelectStartingToggle(int id)
{
Toggle[] toggles = GetComponentsInChildren<Toggle>();
toggles[id].isOn = true;
}
public void SaveToggleId(int id)
{
PlayerPrefs.SetInt("SaveToggleId", id);
Debug.Log("SaveToggleId........saving..........." + PlayerPrefs.GetInt("SaveToggleId"));
}
/* Toggle GetSelectedToggle()
{
Toggle[] toggles = GetComponentsInChildren<Toggle>();
foreach (var t in toggles)
if (t.isOn) return t; //returns selected toggle
return null; // if nothing is selected return null
}*/
}
Playerprefs are saved upon OnApplicationQuit(). If you want to save it immediately, call PlayerPrefs.Save(). After PlayerPrefs.SetInt().
Btw, from the unity scripting api:
This function will write to disk potentially causing a small hiccup, therefore it is not recommended to call during actual gameplay.
In the edit/project settings there is a tab called script execution order, where you can set the order in which your scripts will be executed. For example, you have a "LoadManager" script, you set its priority to 1, and you set everything that relys on it to a greater number like 10.
If you fo this, nothing eill start executing until your manager script finished.
I'm currently working on a chatting program and the idea is to make it a secret one (Kind of like Facebook has the secret chat function).
My messages are sent to a listBox component and I want that every 10 or 'n' seconds the oldest message would get deleted. I
was trying to mark every message with an index but didn't quite understand how that works.
What I'm asking if maybe you guys know a function or could help me write one that does just that. I'm using Visual Studio 2015 Windows Forms, C#.
Well, when you have a ListBox, the items are all indexed since it's an object collection (an array of objects). Starting from 0 and going upwards for newer entries.
So let's say we add 3 items to our ListBox
listBox1.Items.Add("Item 1"); //Index 0
listBox1.Items.Add("Item 2"); //Index 1
listBox1.Items.Add("Item 3"); //Index 2
All you would have to do, is create a thread that runs in the background that deletes the item at index 0 (the oldest entry) each time.
new Thread(() =>
{
while(true)
{
if(listBox1.Items.Count > 0) //Can't remove any items if we don't have any.
{
Invoke(new MethodInvoker(() => listBox1.Items.RemoveAt(0))); //Remove item at index 0.
//Needs invoking since we're accessing 'listBox1' from a separate thread.
}
Thread.Sleep(10000); //Wait 10 seconds.
}
}).Start(); //Spawn our thread that runs in the background.
In C# WinForms a ListBox contains ListBoxItems which are a ObjectCollection (msdn-link)
So you can add any Object you like, the message which will be displayed comes from the DisplayMember
So for example
public class MyMessage {
public DateTime Received { get; set; }
public string Message { get; set; }
public string DisplayString
{
get { return this.ToString(); }
}
public string ToString() {
return "[" + Received.ToShortTimeString() + "] " + Message;
}
}
can be added as ListBoxItem.
Setting the DisplayMember to "DisplayString" (more here) will get you the correct output.
now you can iterate through the ListBoxItems, cast them as MyMessage and check the time when they were received.
I don't know if you thought about this but here's a way you could achieve this task.
First create an List of strings
List<string> list1 = new List<string>();
To use the List feature you will have to include collections in the form
using System.Collections;
Now comes the tricky part.
First declare a static integer variable globally i.e. outside all classes.
static int a;
Whenever you receive a message(considering your messages will be in string format) you've to add that string to list1 which you created.
list1.Add("the received message");
Now you've to declare a Timer (If you're new, check out how timers work). Windows forms already has timers, using that will be preferable.
The timer sends a Tick event after the desired time.
private void timer1_Tick(object sender, EventArgs e)
{
a = list1.Count() - 1; //Count will return the number of items in the list, you subtract 1 because the indexes start from 0
list1.RemoveAt(a);
listBox.Items.Clear();
foreach(string x in list1)
{
listBox.Items.Add(x);
}
}
What this code will do is, at every Tick event of the timer it will refresh the listbox, remove the last element from the array, and refill the listbox with the rest.
To use the timer just drag and drop it on the form. It's all GUI based and easy to figure out.
Let me know if you've doubts.
Tip: Make maximum use of try{} & catch{} blocks to avoid app crashes.
Here is my dilemma:
public void checkCountProgress(int countProgress, int totalDataSize){ //work done here}
public void func1(int passVar)
{
for(int = 0; i<= 2000; i+=64){
//do work
checkCountProgress(i, 2000);
}
}
Everytime when I iterate through the for-loop, i obviously changes by increments of 64. The checkCountProgressFunction gives me the value of i at whichever point my client connection to the remote host gets disconnected. So I want to continue writing and sending data to the network from where I left off. I would manually stop my server, change the value of i in the parameter of the calling function, run, update service reference from client side and continue but now I need to automate that because I now have to include multiple connections to the host. When I look at some examples, it usually refers to passing a parameter from one function, getting its value and using it in another method (How to pass value from one method to another? ) which is entirely different to what I want to do. How can I achieve this?
You mean using settings to store the value at which the loop was stopped?
If so, here's an example of how to use them
In your project's Properties, in the Settings tab add a new setting of you desired value type and set the scope to User. For my example I created one named ValueToResume of type Int and initial value of 0.
static void Main(string[] args)
{
int initialLoopValue = Properties.Settings.Default.ValueToResume;
for (int i = initialLoopValue; i <= 2000; i += 64)
{
//do work
checkCountProgress(i, 2000);
}
Properties.Settings.Default.Reset(); //Loop was completed, we can reset the setting to 0
}
public static void checkCountProgress(int countProgress, int totalDataSize)
{
if (countProgress > 600)
{
Properties.Settings.Default.ValueToResume = countProgress;
Properties.Settings.Default.Save();
Environment.Exit(0); //force application exit
}
}
}
On the second run, the loop will be initialized at value 640
I am trying to Network a small 2 player Unity game. This is my first go at networking, so please feel free to correct me on anything I am doing wrong. I am attempting to make a simple matchmaking service where, on the main menu of the game, a player can click the Find Match button. This will execute the following function
private HostData[] hostList;
private const string typeName = "UniqueGameName";
private const string gameName = "RoomName";
public void FindMatch() {
RefreshHostList();
if (hostList != null) {
JoinServer(hostList[0]);
} else {
StartServer();
}
}
And here are the corresponding methods called in that function.
private void RefreshHostList()
{
MasterServer.RequestHostList(typeName);
}
void OnMasterServerEvent(MasterServerEvent msEvent)
{
if (msEvent == MasterServerEvent.HostListReceived)
hostList = MasterServer.PollHostList();
}
private void JoinServer(HostData hostData)
{
Network.Connect(hostData);
}
private void StartServer()
{
Network.InitializeServer(2, 25000, !Network.HavePublicAddress());
MasterServer.RegisterHost(typeName, gameName);
}
The idea is, when the player clicks Find Match, it will refresh the host list. If it finds a host, it will join their server and begin a match. If it doesn't, it will begin its own server and wait for another player. When testing this, the first player that clicks Find Match is able to start a server, but then the second player receives the error message "...Is the listen port already in use?". When I debug and step through my program, the second player's hostList is always null, even with the first players successful server creation and registration.
Are you using both instances of app on same machine? If you occupy network port once then you are not able to initialize server on same port on the same machine.
In general I would recommend looking into SingalR technology for your needs, it uses websockets.
I'm making a simple Guess-The-Number game with a GUI. I need to wait on a loop waiting for the user to input a number in a text box and press "OK". How do I wait for an event inside a loop?
Note: I don't want message boxes. This is done in the main window, hence the need to wait for input.
EDIT: I should have explained myself better. I know that there's a loop inside the GUI. What I want is another loop inside a method. Maybe there's a better way to do this. I could code stuff inside the button's event handler, now that I think about it. Although I'd need global variables. Whataver, I'll think about it, but I hope my question is clearer now.
EDIT 2: Sorry that my question wasn't clear and the edit didn't do much help. First of all, the code is too big to be posted here. I'd probably have to post a screenshot of the GUI, so it wouldn't be of much use. Basically, I have two fields, "Max number" and "Number of allowed guesses". The user enters these two and clicks "Play". A new panel becomes available, with a text box and a "Guess" button. The user enters a guess, and the program checks to see if it's correct.
The purpose of the second infinite loop is to avoid global variables. See, each time the user clicks "Play", the game has to generate a new random number as the correct guess. If everything is done inside a method, no problem. But if the "Guess" button's event handler is called multiple times, the number has to be stored as an instance variable of the Form. Sure, it's not big deal, but I think the number should be a property of the method directing the current game, not of the Form.
I'd also have to keep track of the remaining number of guesses outside of the method. Again, it's no big deal. I just want to avoid globals if I can.
Again, I'm sorry that my question wasn't too clear. I'm kind of tired, and I didn't feel like writing too much. If this still isn't clear, then don't bother. I'll think of something.
C# automatically loops infinitely waiting for events until your form is closed. You just need to respond to the button click event.
Jason Down's suggestion is wise, create a new GuessingGame class and add it to your project. I know you're worried about "global variables" (which everyone is taught in school never to use unless you absolutely have to), but think about your design specifications for a minute.
But if the "Guess" button's event handler is called multiple times, the number has to be stored as an instance variable of the Form. Sure, it's not big deal, but I think the number should be a property of the method directing the current game, not of the Form.
As an alternative, store an instance of your GuessingGame class in the form. This is not a global variable! You said so yourself, the point of the game is keep track of the guesses and generate new numbers to guess every time "Play" is clicked. If you store an instance of the game in the form then open another form (e.g. a Help or About box), then the game's instance would not be available (thus, not global).
The GuessingGame object is going to look something like:
public class GuessingGame
{
private static Random _RNG = new Random();
private bool _GameRunning;
private bool _GameWon;
private int _Number;
private int _GuessesRemaining;
public int GuessesRemaining
{
get { return _GuessesRemaining; }
}
public bool GameEnded
{
get { return !_GameRunning; }
}
public bool GameWon
{
get { return _GameWon; }
}
public GuessingGame()
{
_GameRunning = false;
_GameWon = false;
}
public void StartNewGame(int numberOfGuesses, int max)
{
if (max <= 0)
throw new ArgumentOutOfRangeException("max", "Must be > 0");
if (max == int.MaxValue)
_Number = _RNG.Next();
else
_Number = _RNG.Next(0, max + 1);
_GuessesRemaining = numberOfGuesses;
_GameRunning = true;
}
public bool MakeGuess(int guess)
{
if (_GameRunning)
{
_GuessesRemaining--;
if (_GuessesRemaining <= 0)
{
_GameRunning = false;
_GameWon = false;
return false;
}
if (guess == _Number)
{
_GameWon = true;
return true;
}
else
{
return false;
}
}
else
{
throw new Exception("The game is not running. Call StartNewGame() before making a guess.");
}
}
}
This way, all the data related to the game is encapsulated within the class. Hooking up the events is easy in the codebehind of the form:
GuessingGame game = new GuessingGame();
private void btnPlay_Click(object sender, EventArgs e)
{
int numberOfGuesses = Convert.ToInt32(txtNumberOfGuesses.Text);
int max = Convert.ToInt32(txtMax.Text);
game.StartNewGame(numberOfGuesses, max);
}
private void btnGuess_Click(object sender, EventArgs e)
{
int guess = Convert.ToInt32(txtGuess.Text);
bool correct = game.MakeGuess(guess);
if (correct)
lblWin.Visible = true;
if (game.GameEnded)
{
// disable guess button, show loss label
}
}
You should probably look for a book to actually learn windows programming.
The very basics:
1) There is already an infinite loop deep down in the windows code somewhere. Any windows program is constantly looping and scanning for input.
2) Once input is found, this loop fires off an Event.
3) Your mission, should you choose to accept it, is to write event handlers to handle those events.
you are most likely doing it wrong as it has already been pointed out, but you can use this
Application.DoEvents();
to process events when you are on an actual loop
to do it the right way
- don't use a loop
- use an edit box for the input, then a button
- implement the button onclick event
Yes, and What if I am waiting for Speech events, it could happen anytime event when a function is running, I need to handle that without recursively call a function