Endturn Button Multiplayer(Netcode for GameObjects)

So I‘m following this course and trying to make Multiplayer. So far it worked but with the Endturn Button I was struggeling. I wanted to make a NetworkList with Unit classes in it, that would check if every Unit pressed Endturn. If true then it would skip, but later after trying it I found out that a NetworkVariable can only store int,bool,float, etc. but no classes.
Why I didn‘t just use int and add 1 everytime somebody wants to end the Turn? Because I wanted to show who wants to end the Turn and who doesn‘t.
I made it by making NetworkVariables for every Unit, but sometimes it doesn‘t react and it ends the turn but it immediatly skippes again. All sorts of bugs, but they never come consistent, sometimes it works sometimes not.
So if someone knows what or how to solve this problem, I would appreciate

1 Like

If that help, heres the code Im using

private void Unit_OnNextTurnStateChanged(object sender, EventArgs e)
    {
        int wantToSkipUnits = 0;
        int allUnits = 0;

        foreach (Unit unit in LevelGrid.Instance.GetAllUnitList())
        {
            if (TurnSystem.Instance.IsTeamOnTurn(unit.GetCurrentTeamId()))
            {
                allUnits++;
            }
            if (TurnSystem.Instance.IsTeamOnTurn(unit.GetCurrentTeamId()) && unit.GetWantsToSkipState())
            {
                wantToSkipUnits++;
            }
        }

        Debug.Log(wantToSkipUnits + "/" + allUnits);

        if (wantToSkipUnits == allUnits && allUnits > 0)
        {
            if(IsHost)
                TurnSystem.Instance.NextTurn();

            foreach (Unit unit in LevelGrid.Instance.GetAllUnitList()) //NOTE FOR ME: Use GetOwnerUnit(), could maybe fix some problems
            {
                unit.ClearWantsToSkipList();

                    unit.SetWantsToSkipFalseServerRpc();
            }
        }
    }

    public void PlayerWantsToSkipTurn()
    {
        if (GetOwnership())
        {
            SetSkipStateServerRpc();

            if (GetWantsToSkipState() && !unitsThatWantToSkipList.Contains(this) && TurnSystem.Instance.IsTeamOnTurn(GetCurrentTeamId()))
            {
                foreach (Unit unit in LevelGrid.Instance.GetAllUnitList())
                {
                    unit.AddWantsToSkipList(this);
                }
            }
            else if (!GetWantsToSkipState() || unitsThatWantToSkipList.Contains(this) || !TurnSystem.Instance.IsTeamOnTurn(GetCurrentTeamId()))
            {
                foreach (Unit unit in LevelGrid.Instance.GetAllUnitList())
                {
                    unit.RemoveWantsToSkipList(this);
                }
            }

            OnNextTurnStateChanged?.Invoke(this, EventArgs.Empty);
        }

        OnNextTurnStateChanged?.Invoke(this, EventArgs.Empty);
    }

and the local List of Units and NetworkList

public NetworkVariable<bool> wantsToSkip = new NetworkVariable<bool>(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
 public List<Unit> unitsThatWantToSkipList = new List<Unit>();

I tried :sweat_smile:

And the RPC´s

[ServerRpc(RequireOwnership = false)]
    private void SetWantsToSkipFalseServerRpc()
    {
        wantsToSkip.Value = false;
    }

    [ServerRpc(RequireOwnership = false)]
    private void SetSkipStateServerRpc()
    {
        wantsToSkip.Value = !wantsToSkip.Value;
    }

Since you can only pass primitives, an int still may be the way to go, thanks to the magic of bit masking.

Each bit position represents a player… suppose you have 4 players, from 0 to 3, then each player’s bitmask is
1 << playerNumber
Which will look like this to the computer
Player0 = 0b0001
Player1 = 0b0010
Player2 = 0b0100
Player3 = 0b1000

int bitMask = 0;

So suppose no players have indicated that they are done with their turn, and now Player 2 wishes to end their turn,.

bitMask = bitMask | (1<<2)

or in more generic terms,

bitMask = bitMask | (1<<playerNumber)

To test if a given player has his flag set, you use the & operator

if(bitMask & (1<<playerNumber)>0) //Will be true if playerNumber's bit is set.
1 Like

So like an Id for each player?
I was thinking of that but I wasn‘t fully sure if its what I want.
But can I make a NetworkVariable with BitMask

I hope it fixes these bugs, I will test it today. But what is the diference between making an int Id or a BitMask?

Maybe its a connection problem, because the same happend as I was implementing a Landmine. Player1(the player that places it) could see it but the Player2 couldn‘t. I did it with Spawn(true); so it should be on the network

This is separate from the Network id that Mirror uses. Basically, as a player logs into the server, assign the player an integer id, in order… i.e. 0, then 1, then 2, then 3, etc.

I don’t believe so, but you can use an integer.

a Bitmask is just an integer, with each bit that comprises the integer representing a number. Integers are composed of binary digits. For example
0b0001 = 1
0b0010 = 2
0b0011 = 3 (1 + 2)
0b1000 = 8
0b1111 = 15

We can get and set individual bits in a bitmask (integer) by shifting 1 to the index of the bit you’re interested in. So if the player’s number is 0, then the bitmask is
1 << 0, which takes
0b0001 and shifts the 1 0 spaces to the left, which is still 0b0001
Player number 1 is 1<<1 so 0b0001 (1) becomes 0b0010 (2)
Player number 2 is 1<<2 so 0b0001 (1) becomes 0b0100 (4)
Player number 3 is 1<<3 so 0b0001 (1) becomes 0b1000 (8)

Using the | (or) and | (and) operators you can set (with the |) or read a bit with the &.

Suppose player0, player1, and player3 have all committed, but player 2 has not, then the bitmask will be the bit for each of those players, or
0b1011 (remember the lowest bits represent the lowest number) so the integer that would be passed is 8 + 2 + 1 or 11.

This is very similar to the way Unity handles Layer Masks.

I can’t help much with the actual networking part. @Yitzchak_Cohen, perhaps you can read in on this?

There are also errors with placing mines, when I am Host: I place the mine and it all works everbody sees it. But on Clientside I only see the mine, I used a ServerRpc. So it should work

I´m getting even weirder bugs:

public void PlayerWantsToSkipTurn()
    {
        if (GetOwnership() && IsClient)
        {
            Debug.Log("Client " + IsClient + "; Server " + IsServer + "; Host " + IsHost);
            SetSkipStateServerRpc();

            //if (GetWantsToSkipState() && !unitsThatWantToEndTurnList.Contains(this))
            //{
            //    foreach (Unit unit in LevelGrid.Instance.GetAllUnitList())
            //    {
            //        unit.AddWantsToSkipList(this);
            //    }
            //}

            //if (!GetWantsToSkipState() && unitsThatWantToEndTurnList.Contains(this))
            //{
            //    foreach (Unit unit in LevelGrid.Instance.GetAllUnitList())
            //    {
            //        unit.RemoveWantsToSkipList(this);
            //    }
            //}

            //while (beforechangeWantsToSkipState == GetWantsToSkipState())
            //{
                
            //}

            foreach(Unit teamTurnUnits in LevelGrid.Instance.GetTeamTurnUnits())
            {
                if (teamTurnUnits.GetWantsToSkipState())
                    AddWantsToSkipList(teamTurnUnits);
                if(!teamTurnUnits.GetWantsToSkipState())
                    RemoveWantsToSkipList(teamTurnUnits);
                Debug.Log(teamTurnUnits.GetWantsToSkipState() + "|Actually:" + GetWantsToSkipState());
            }

            Debug.Log(unitsThatWantToEndTurnList.Count);
            CheckEndTurnUnits();
        }
    }

The Rpc isn´t even getting called anymore, what is happening?
On the Host it works, but not on the Client and I checked with Debug.Log().
Its even worse, somehow there is no error like that you can´t call an ServerRpc as a Server, no errors but it just never gets to run why is that?
This is the ServerRpc:

   [ServerRpc(RequireOwnership = false)]
    private void SetSkipStateServerRpc()
    {
        Debug.Log("1. " + wantsToSkip.Value);
        wantsToSkip.Value = !wantsToSkip.Value;
        Debug.Log("2. " + wantsToSkip.Value);
    }

Maybe this isn´t even anymore anything related to the course, because I think I`ve done some big mistakes

It’s out of my knowledge zone, sorry.

Hi there,
Is it possible this is an issue with your attributes?
I’m not sure Mirror uses the [ServerRPC] attribute. In order to send a command from the client to the server you want to use the [Command] attribute.

If you are using Unity netCode then ServerRPC is valid.

I am using Netcode, I was even thinking of getting the multiplayer course but I used Netcode and didn‘t want to switch to Mirror.

Okay, are you getting any errors when calling the RPC?

No, its just straight up never getting called. I also waited for 15min, but its not even delayed. I mean 15min should be enough time

Is the Debug right before it gets called firing?

Yes

I’m sorry I am not familiar enough with Unity Netcode to help much more than that. It sounds like it could be a network configuration issue if the RPC isn’t firing. Are there any settings in the network manager that are related to RPCs?

I dont think so. Watched some CM videos about Netcode and he didn’t warn about something like that

The only other thing I could think of is getting rid of that RequireOwnership = false parameter. Since you are checking for ownership anyway. Not sure if that would make any difference. Sorry I am not more help.

Privacy & Terms