Unity Making player pieces move in turns after rolling die

Viewed 468

I'm new to unity and I've been following and combining examples from tutorials to put together a simple boardgame-like demo using a 3d die from this tutorial: https://www.youtube.com/watch?v=LHQ4ynQhPLY and a boardgame tile setup using this tutorial: https://www.youtube.com/watch?v=d1oSQdydJsM along with a turn-based system from this tutorial: https://www.youtube.com/watch?v=W8ielU8iURI

While I have a fully working 3d die and I can get my pieces to move the appropriate number of spaces, I find myself having a difficult time incorporating the turn-based aspect.

My project file can be downloaded here: https://drive.google.com/drive/folders/1Odj3iqeYAaO3lnkzGOjwyxIdL0g00Xge?usp=sharing

For those not willing to download the project file, I will try to elaborate here:

I have six main scripts: Dice.cs, DiceSide.cs, ButtonHandler.cs, PlayerPiece.cs, Route.cs and GameControl.cs.

I excluded DiceSide.cs since this code isn't broken. It merely detects which side is on the ground so that I can detect that the opposite side is the die's value, which is stored in the diceValue variable in the code below.

I also excluded Route.cs since this also isn't broken. It defines the pathing route for the player pieces based on the position of the children objects of the "Board" game object.

Finally, I also excluded ButtonHandler.cs which is just a simple function that tells a button when it is clicked to roll the dice using the RollDice() function found in Dice.cs

Dice.cs (attached to the "Die" prefab):

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

public class Dice : MonoBehaviour
{
    Rigidbody rb;

    bool hasLanded;
    bool thrown;

    Vector3 initPosition;
    
    public int diceValue;

    public DiceSide[] diceSides;

    public bool IsDoneRolling;

    public int whosTurn = 1;


    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        initPosition = transform.position;
        rb.useGravity = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (rb.IsSleeping() && !hasLanded && thrown)
        {
            hasLanded = true;
            rb.useGravity = false;
            rb.isKinematic = true;

            SideValueCheck();

            if (whosTurn == 1)
            {
                GameControl.MovePlayer(1);
            } 
            else if (whosTurn == -1)
            {
                GameControl.MovePlayer(2);
            }
            whosTurn *= -1;
        }
        else if (rb.IsSleeping() && hasLanded && diceValue == 0)
        {
            RollAgain();
        }
    }

    public void RollDice() 
    {
        if (!thrown && !hasLanded)
        {
            IsDoneRolling = false;
            thrown = true;
            rb.useGravity = true;
            rb.AddTorque(Random.Range(0,250), Random.Range(0,250), Random.Range(0,250));
        }
        else if (thrown && hasLanded)
        {
            Reset();
        }
    }

    void Reset()
    {
        transform.position = initPosition;
        thrown = false;
        hasLanded = false;
        rb.useGravity = false;
        rb.isKinematic = false;
        IsDoneRolling = true;
    }

    void RollAgain()
    {
        Reset();
        IsDoneRolling = false;
        thrown = true;
        rb.useGravity = true;
        rb.AddTorque(Random.Range(0,250), Random.Range(0,250), Random.Range(0,250));
    }

    void SideValueCheck()
    {
        diceValue = 0;
        foreach (DiceSide side in diceSides)
        {
            if (side.OnGround())
            {
                diceValue = side.sideValue;
                Debug.Log(diceValue + " has been rolled!");
            }
        }
    }
}

PlayerPiece.cs (attached to each of the two player gameobject prefabs):

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

public class PlayerPiece : MonoBehaviour
{
    public Route currentRoute;

    int routePosition;

    public bool isMoving;
    public bool moveAllowed;

    public static int steps = 0;

    // Update is called once per frame
    void Update()
    {
        if (!isMoving)
        {
            StartCoroutine(Move());
            moveAllowed = false;
        }
    }

    IEnumerator Move()
    {
        if (isMoving)
        {
            yield break;
        }
        isMoving = true;

        while (steps > 0)
        {
            Debug.Log("Route position: "+routePosition);
            
            routePosition++;
            routePosition %= currentRoute.childNodeList.Count;

            Vector3 nextPos = currentRoute.childNodeList[routePosition].position;
            while (MoveToNextNode(nextPos)) { yield return null; }

            yield return new WaitForSeconds(0.1f);
            steps--;
        }

        isMoving = false;
    }

    bool MoveToNextNode(Vector3 goal)
    {
        return goal != (transform.position = Vector3.MoveTowards(transform.position, goal, 8f * Time.deltaTime));
        
    }
}

GameControl.cs (attached to an empty gameobject):

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

public class GameControl : MonoBehaviour
{
    private static GameObject player1, player2;

    public static int diceSideThrown = 0;

    // Start is called before the first frame update
    void Start()
    {
        player1 = GameObject.Find("Player1-Piece");
        player2 = GameObject.Find("Player2-Piece");

        player1.GetComponent<PlayerPiece>().moveAllowed = false;
        player2.GetComponent<PlayerPiece>().moveAllowed = false;
    }

    // Update is called once per frame
    void Update()
    {
       if (player1.GetComponent<PlayerPiece>().moveAllowed)
        {
            //Move the player 1 piece... code in PlayerPiece.cs
        }


        if (player2.GetComponent<PlayerPiece>().moveAllowed)
        {
            //Move the player 2 piece... code in PlayerPiece.cs
        }
    }

   public static void MovePlayer(int playerToMove)
    {
        switch (playerToMove)
        {
            case 1:
                player1.GetComponent<PlayerPiece>().moveAllowed = true;
                break;

            case 2:
                player2.GetComponent<PlayerPiece>().moveAllowed = true;
                break;
        }
    }
}

So what should happen is that which I click the button, the RollDice() function in Dice.cs fires (working) which rolls the dice and generates the value of spaces the player has to move (working) and then only player 1 should move that number of spaces (not working, obviously because this part isn't connected in GameControl.cs however the movement code in PlayerPiece.cs is demonstrated to work in my non-turnbased testing). After Player 1 finishes moving, clicking the same button should roll the dice, repeating the steps except only player 2 should move the new number of spaces generated.

I know all the pieces are there, but I'm at a loss as to how to get them to fit together to work as intended.

Your help is greatly appreciated.

1 Answers

Looks like you're fairly close, just have a few gaps that need filled in, and a bit of cleaning up/reorganizing.

Let's start with the Dice script. Dice shouldn't care whos turn it is, so we'll remove that from here and add it to the GameControl. And rather than passing which player to move, we'll pass the number of steps to move.

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

public class Dice : MonoBehaviour
{
    Rigidbody rb;

    bool hasLanded;
    bool thrown;

    Vector3 initPosition;
    
    public int diceValue;

    public DiceSide[] diceSides;

    public bool IsDoneRolling;
    // Assign game object through editor
    public GameObject gameControllerGameObject; 
    private GameController gameController;


    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        initPosition = transform.position;
        rb.useGravity = false;
    }
    void Awake()
    {
        gameController = gameControllerGameObject.GetComponent<GameController>();
    }

    // Update is called once per frame
    void Update()
    {
        if (rb.IsSleeping() && !hasLanded && thrown)
        {
            hasLanded = true;
            rb.useGravity = false;
            rb.isKinematic = true;

            SideValueCheck();
            
            gameControl.MovePlayer(diceValue);
            
        }
        else if (rb.IsSleeping() && hasLanded && diceValue == 0)
        {
            RollAgain();
        }
    }

    public void RollDice() 
    {
        if (!thrown && !hasLanded)
        {
            IsDoneRolling = false;
            thrown = true;
            rb.useGravity = true;
            rb.AddTorque(Random.Range(0,250), Random.Range(0,250), Random.Range(0,250));
        }
        else if (thrown && hasLanded)
        {
            Reset();
        }
    }

    void Reset()
    {
        transform.position = initPosition;
        thrown = false;
        hasLanded = false;
        rb.useGravity = false;
        rb.isKinematic = false;
        IsDoneRolling = true;
    }

    void RollAgain()
    {
        Reset();
        IsDoneRolling = false;
        thrown = true;
        rb.useGravity = true;
        rb.AddTorque(Random.Range(0,250), Random.Range(0,250), Random.Range(0,250));
    }

    void SideValueCheck()
    {
        diceValue = 0;
        foreach (DiceSide side in diceSides)
        {
            if (side.OnGround())
            {
                diceValue = side.sideValue;
                Debug.Log(diceValue + " has been rolled!");
            }
        }
    }
}

Next we'll take a look at PlayerPiece. Adding steps as a parameter to Move so we can call it with the number of steps to move, and cleaning up some variables we shouldn't need, as well as taking the logic out of Update so we can call Move directly from GameControl

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

public class PlayerPiece : MonoBehaviour
{
    public Route currentRoute;

    int routePosition;

    // Remove unnecessary variables
    public bool isMoving;

    // Update is called once per frame
    void Update()
    {
        
    }
    // Make this public so we can call it from GameControl
    // Add number of steps to move as parameter
    public IEnumerator Move(int steps)
    {
        if (isMoving)
        {
            yield break;
        }
        isMoving = true;

        while (steps > 0)
        {
            Debug.Log("Route position: "+routePosition);
            
            routePosition++;
            routePosition %= currentRoute.childNodeList.Count;

            Vector3 nextPos = currentRoute.childNodeList[routePosition].position;
            while (MoveToNextNode(nextPos)) { yield return null; }

            yield return new WaitForSeconds(0.1f);
            steps--;
        }

        isMoving = false;
    }

    bool MoveToNextNode(Vector3 goal)
    {
        return goal != (transform.position = Vector3.MoveTowards(transform.position, goal, 8f * Time.deltaTime));
        
    }
}

And lastly, GameControl to tie it all together. Add references to the PlayerPiece scripts so you're not retrieving them each time you want to call a method or variable. Best to do that in Awake rather than Start to make sure they're ready. Add whosTurn so that GameControl can keep track of it. Call Move() on the player piece whos turn it is, and pass it the number of steps.

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

public class GameControl : MonoBehaviour
{
    // Be careful about using static variables when they're not necessary
    private GameObject player1, player2;
    // Add references to the player piece scripts
    private PlayerPiece player1Piece;
    private PlayerPiece player2Piece;

    private whosTurn = 1;

    // Start is called before the first frame update
    void Awake()
    {
        player1 = GameObject.Find("Player1-Piece");
        player2 = GameObject.Find("Player2-Piece");

        // Set the reference to the script so you don't have to call GetComponent() each time
        player1Piece = player1.GetComponent<PlayerPiece>();
        player2Piece = player2.GetComponent<PlayerPiece>();
    }

    // Update is called once per frame
    void Update()
    {
       // Clean this out and we'll handle movement directly in the PlayerMove
    }
   // Change variable to handle steps to move
   public void MovePlayer(int steps)
    {
        switch (whosTurn)
        {
            case 1:
                StartCoroutine(player1Piece.Move(steps));
                break;

            case 2:
                StartCoroutine(player2Piece.Move(steps));
                break;
        }

        whosTurn *= -1;
    }
}

I think that should get everything... moving... I haven't tested this code, so if you run into any trouble let me know

Related