Unity 掷骰子后让玩家的棋子轮流移动
Posted
技术标签:
【中文标题】Unity 掷骰子后让玩家的棋子轮流移动【英文标题】:Unity Making player pieces move in turns after rolling die 【发布时间】:2021-08-03 04:06:28 【问题描述】:我是 Unity 的新手,我一直在关注并结合教程中的示例,使用本教程中的 3d 骰子组合一个简单的棋盘游戏样演示:https://www.youtube.com/watch?v=LHQ4ynQhPLY 和使用本教程的棋盘游戏瓷砖设置:@ 987654322@ 以及本教程中的回合制系统:https://www.youtube.com/watch?v=W8ielU8iURI
虽然我有一个完全可用的 3D 骰子,并且我可以让我的棋子移动适当数量的空间,但我发现自己很难融入回合制方面。
我的项目文件可以在这里下载:https://drive.google.com/drive/folders/1Odj3iqeYAaO3lnkzGOjwyxIdL0g00Xge?usp=sharing
对于那些不愿意下载项目文件的人,我将在此处尝试详细说明:
我有六个主要脚本:Dice.cs、DiceSide.cs、ButtonHandler.cs、PlayerPiece.cs、Route.cs 和 GameControl.cs。
我排除了 DiceSide.cs,因为此代码没有损坏。它只是检测哪一边在地上,这样我就可以检测到另一边是骰子的值,它存储在下面代码中的 diceValue 变量中。
我还排除了 Route.cs,因为它也没有损坏。它根据“棋盘”游戏对象的子对象的位置定义玩家棋子的路径。
最后,我还排除了 ButtonHandler.cs,它只是一个简单的函数,它告诉按钮何时被单击以使用 Dice.cs 中的 RollDice() 函数掷骰子
Dice.cs(附加到“Die”预制件):
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(附加到两个玩家游戏对象预制件中的每一个):
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(附加到一个空的游戏对象):
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;
所以应该发生的是我单击按钮,Dice.cs 中的 RollDice() 函数触发(工作),它掷骰子并生成玩家必须移动(工作)的空间值,然后只有玩家1 应该移动该数量的空格(不起作用,显然因为这部分在 GameControl.cs 中没有连接,但是 PlayerPiece.cs 中的移动代码在我的非回合测试中被证明可以工作)。玩家 1 完成移动后,单击相同的按钮应该掷骰子,重复这些步骤,但只有玩家 2 应该移动新生成的空格数。
我知道所有部件都在那里,但我不知道如何让它们组合在一起以按预期工作。
非常感谢您的帮助。
【问题讨论】:
GetComponent
并不便宜,考虑缓存一次并使用缓存的值。也许使用数组或列表而不是单个变量,这样您就可以拥有两个以上的玩家。投票决定何时搬入更新似乎不是最好的主意。最好根据某些事件触发运动,例如掷骰子。那么你只需要一个值来跟踪下一个应该移动的玩家的索引。
对...我是unity新手,所以你用的那些词的一半对我来说意义不大。我需要知道如何做你所建议的事情,就像你所建议的想法一样。您提出的事件建议本质上是实现的:当掷骰子时,它会设置允许玩家移动的开关。问题是,他们不动,因为我不知道如何让 GameControl 实现这一点。
你现在有了一些零件,虽然它们可能有点分散。为什么movePlayer
不让玩家直接移动而不是设置一个变量并希望它在下一次更新中被拾取?
因为我不知道更好。我是新来的团结。我正在寻找一种让所有这些部分协同工作的方法。此刻,他们根本不动。感谢您的帮助。
【参考方案1】:
看起来您已经很接近了,只是需要填补一些空白,并进行一些清理/重组。
让我们从 Dice 脚本开始。 Dice 不应该关心轮到谁了,所以我们将从这里删除它并将它添加到 GameControl。我们将传递要移动的步数,而不是传递要移动的玩家。
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!");
接下来我们来看看 PlayerPiece。将步骤作为参数添加到 Move,以便我们可以使用要移动的步骤数来调用它,并清理一些我们不需要的变量,以及从 Update 中取出逻辑,以便我们可以直接从 GameControl 调用 Move
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));
最后,GameControl 将它们结合在一起。添加对 PlayerPiece 脚本的引用,这样您就不会在每次要调用方法或变量时都检索它们。最好在“清醒”而不是“开始”中这样做,以确保他们准备好了。添加 whosTurn 以便 GameControl 可以跟踪它。在轮到的玩家棋子上调用 Move(),并将步数传递给它。
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;
我认为这应该让一切都...动起来...我没有测试过这段代码,所以如果您遇到任何问题,请告诉我
【讨论】:
我在 GameControl 的第 34,37,4145 行遇到与 whosTurn 变量以及 StartCoroutine(player1Piece.Move(steps)) 相关的错误;和 StartCoroutine(player2Piece.Move(steps));说“非静态字段、方法或属性需要对象引用”player2.GetComponent<PlayerPiece>();
未捕获返回值。应该是player2Piece = player2.GetComponent<PlayerPiece>();
@Sweepster,您的错误是因为您将 MovePlayer 设置为静态。这里不需要静态(或者几乎在我看到的任何代码中)。固定示例
谢谢...现在我在 Dice.cs 的第 44 行收到一个错误,提示“非静态字段、方法或属性 'GameControl.MovePlayer( int)'"... 显然我还有很多东西要学,但我们快到了!
是的,我只是在看 - 我注意到您实际上并没有对 GameControl 脚本的引用。我会更新以上是关于Unity 掷骰子后让玩家的棋子轮流移动的主要内容,如果未能解决你的问题,请参考以下文章