My first player turn is being skipped

Hello everyone, I’m starting to learn to do some Unity with a simple game where players change turns after selecting a card. When the turn is done, the next player get to choose the card. When the turn goes through 1 to 4 there is no problem, but when the 4th takes a card, the first player chooses automatically its card and passes the turn to the second player.

I’ve being trying to figure out what is happening, but the checking I’ve being doing tells me that the turn is being properly set, but somehow the first player gets to choose a card without pressing any key.

My guess is best to paste here the logic I’ve being doing to explain myself better. Hope the approach isn’t very nonsensical.

I have a Coordinator class that in the Update() uses this method to set the player turn, at the beginning the player turn is set to -1 to be able to choose the starting player at random:

private void SetPlayerTurn()
    {
        if (playerTurn < 0)
        {
            playerTurn = Utilities.GiveStartingTurn(numPlayers);
            players[playerTurn].GetComponent<Player>().ToggleMyTurn();
        }
       //Ask if the player has chosen a card
        if (players[playerTurn].GetComponent<Player>().TurnDone())
        {
            // Remove this players turn
            players[playerTurn].GetComponent<Player>().ToggleMyTurn();
            // Change the turn to the next player
            playerTurn++;
            // If the last player played, set it to the first one again
            playerTurn = playerTurn >= numPlayers ? 0 : playerTurn;
            // Give that player its turn
            players[playerTurn].GetComponent<Player>().ToggleMyTurn();
        }
    }

At the Player class, while the turn is set to true, the update is listening to the players input with Input.KeyDown(). From documentation I learned that once pressed it would not return true even if it still pressed in the next frames.

void Update()
    {
        if (myTurn && !turnPlayed)
        {
            ChooseCard();
        }
    }
private void ChooseCard()
    {
        if (Input.GetKeyDown("1"))
        {          
            turnPlayed = true;
        }
}

ToggleTurn just sets if the player can choose a card or not, called when its the player’s turn or when its over.

public void ToggleMyTurn()
    {
        myTurn = !myTurn;
    }

TurnDone is a method that is always being called by the coordinator to check if the player has choosen a card, to skip to the next player.

public bool TurnDone()
    {
        bool turnPlayedTemp = turnPlayed;
        turnPlayed = false;
        return turnPlayedTemp;
    }

When checking the player turn after choosing a card from 4th to 1st, the playerTurn is set properly to 0, but when it checks if the player has pressed a key, somehow it seems to think that it did, and so the next player takes its turn.

9234519--1290198--upload_2023-8-19_16-10-58.png

Maybe after posting this question I get what is happening, but I’ve been with this situation for some time.

Thank you very much for your time.

9234519--1290195--upload_2023-8-19_16-9-51.png

What I could change is not using Update to set whose turn is now. It’s a main problem here because the game reset after the last player turns. Start from a simple GameManager, set inside a function that all players can use to end their turn. Your turn will end exactly when you wanted and is not dependent on the Update refresh rate.

1 Like

It seems I still don’t fully understand the Update proper usage. I will try the approach you describe and see if I can manage.

Thank you very much for your quick response!

The situation just got worse. I’m not sure if I used the suggestion by AngryProgrammer properly, I would like to share the classes I’m using where I made the Coordinator a Singleton class to be able to call the PlayedCard method to change the turn only when the player chooses one.

With this approach I see in the console that now, instead of the next player choosing the card without input, now more players execute at the same time.

Testing, I tried throwing an Exception and somehow the exception was thrown twice.

9235338--1290300--upload_2023-8-20_1-57-42.png

I feel awful having to paste everything just to ask for help, but I still can not figure out why this could be happening.
The Player Prefab is an empty object with the Player script.
The Card Prefab us just an empty object with nothing for now.

GameCoordinator

using System.Collections.Generic;
using UnityEngine;

public class GameCoordinator : MonoBehaviour
{
    public List<string> colorDecks = new() { "R", "B", "Y", "G" };
    public GameObject playerPrefab;
    public GameObject cardPrefab;
    public List<GameObject> players = new List<GameObject>();
    public List<GameObject> deck = new List<GameObject>();
    public static int maxCardsPerPlayer = 5;
    public int numPlayers = 4;
    public int playerTurn;

    public static GameCoordinator Instance { get; private set; }

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else if (Instance != null && Instance != this)
        {
            Destroy(this);
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        CreatePlayers();
        CreateDeck();

        Utilities.SuffleDeck(deck);

        DealCards();

        playerTurn = Random.Range(0, numPlayers);
        players[playerTurn].GetComponent<Player>().ToggleMyTurn(true);
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void CreatePlayers()
    {
        for (int i = 0; i < numPlayers; i++)
        {
            GameObject newPlayer = Instantiate(playerPrefab, new Vector3(0, 0, 0), Quaternion.identity);
            newPlayer.name = "Player" + (i + 1);
            players.Add(newPlayer);
        }
    }

    private void CreateDeck()
    {
        for (int i = 1; i <= 8; i++)
        {
            foreach (string deckColor in colorDecks)
            {
                GameObject newCard = Instantiate(cardPrefab, new Vector3(0, 0, 0), Quaternion.identity);
                newCard.name = deckColor + i;
                deck.Add(newCard);
            }
        }
    }

    private void DealCards()
    {
        foreach (GameObject currentPlayer in players)
        {
            for (int i = 0; i < maxCardsPerPlayer; i++)
            {
                currentPlayer.GetComponent<Player>().AddCard(deck[0]);
                deck.RemoveAt(0);
            }
        }
    }

    private void SetNextPlayersTurn()
    {
        players[playerTurn].GetComponent<Player>().ToggleMyTurn(false);
        playerTurn++;
        playerTurn = playerTurn >= numPlayers ? 0 : playerTurn;
        players[playerTurn].GetComponent<Player>().ToggleMyTurn(true);
        throw new System.Exception();
    }

    public void PlayedCard()
    {
        SetNextPlayersTurn();
    }
}

Player

using System;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField]
    private List<GameObject> cardsOnHand = new List<GameObject>();
    [SerializeField]
    private bool turnEnabled = false;

    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        ChooseCard();
    }

    public void ToggleMyTurn(bool status)
    {
        turnEnabled = status;
    }

    public void AddCard(GameObject card)
    {
        cardsOnHand.Add(card);
    }

    private void ChooseCard()
    {
        if (!turnEnabled)
        {
            return;
        }

        if (Input.GetKeyDown(KeyCode.Q))
        {
            GameCoordinator.Instance.PlayedCard();
        }
        if (Input.GetKeyDown(KeyCode.W))
        {        
            GameCoordinator.Instance.PlayedCard();
        }
        if (Input.GetKeyDown(KeyCode.E))
        {      
            GameCoordinator.Instance.PlayedCard();
        }
        if (Input.GetKeyDown(KeyCode.R))
        {         
            GameCoordinator.Instance.PlayedCard();
        }
        if (Input.GetKeyDown(KeyCode.T))
        {
            GameCoordinator.Instance.PlayedCard();
        }
    }
}

Utilities

using System;
using System.Collections.Generic;
using UnityEngine;

public class Utilities
{
    public static List<GameObject> SuffleDeck(List<GameObject> deck)
    {
        if (deck == null)
        {
            return new List<GameObject>();
        }

        int totalCards = deck.Count;

        for (int i = totalCards - 1; i > 0; i--)
        {
            int j = UnityEngine.Random.Range(0, i);
            GameObject swapTemp = deck[i];
            deck[i] = deck[j];
            deck[j] = swapTemp;
        }

        return deck;
    }
}

Hello, I figured out after sleeping on it. If what I think is true, my problem was that at the same frame the player A choose a card, the coordinator enabled player B to choose, as it was still the same frame, the second player “was” still pressing that same key, so it automatically used its turn and so on at that same frame.

I added a new variable called previous Status that should hold the previous frame turn status, and change its value if the turn variable changes, so that the next frame it actually the next players turn.

I placed this piece of code at the beggining of the ChooseCard method and now it looks like is working perfectly.

        if (previousStatus != turnEnabled)
        {
            previousStatus = turnEnabled;
            return;
        }

Thank you everyone!

1 Like