Question by
Marcus_is_beast · Feb 23, 2019 at 11:14 AM ·
unity 5gameendgame
Adding An End Game Message The Returning Back To Main Menu
Hello, I am fairly new to unity, I have followed a tutorial on YouTube for a board game and I am expanding upon it. I have added a main menu I am just stuck on trying to detect when white or brown pieces have all made it and then end the game. The game is a recreation of the royal game of ur, Any help would be greatly appreciated.
AIPlayer.cs
using UnityEngine;
using System.Collections.Generic;
public class AIPlayer
{
public AIPlayer()
{
stateManager = GameObject.FindObjectOfType<StateManager>();
}
StateManager stateManager;
virtual public void DoAI()
{
// Do the thing for the current stage we're in
if(stateManager.IsDoneRolling == false)
{
// We need to roll the dice!
DoRoll();
return;
}
if(stateManager.IsDoneClicking == false)
{
// We have a die roll, but we need to pick a stone to move
DoClick();
return;
}
}
virtual protected void DoRoll()
{
GameObject.FindObjectOfType<DiceRoller>().RollTheDice();
}
virtual protected void DoClick()
{
// Pick a stone to move, then "click" it.
PlayerStone[] legalStones = GetLegalMoves();
if(legalStones == null || legalStones.Length == 0)
{
// We have no legal moves. How did we get here?
// We might still be in a delayed coroutine somewhere. Let's not freak out.
return;
}
// BasicAI simply picks a legal move at random
PlayerStone pickedStone = PickStoneToMove(legalStones);
pickedStone.MoveMe();
}
virtual protected PlayerStone PickStoneToMove( PlayerStone[] legalStones )
{
return legalStones[ Random.Range(0, legalStones.Length) ];
}
/// <summary>
/// Returns a list of stones that can be legally moved
/// </summary>
protected PlayerStone[] GetLegalMoves()
{
List<PlayerStone> legalStones = new List<PlayerStone>();
// If we rolled a zero, then we clearly have no legal moves.
if(stateManager.DiceTotal == 0)
{
return legalStones.ToArray();
}
// Loop through all of a player's stones
PlayerStone[] pss = GameObject.FindObjectsOfType<PlayerStone>();
foreach( PlayerStone ps in pss )
{
if(ps.PlayerId == stateManager.CurrentPlayerId)
{
if( ps.CanLegallyMoveAhead( stateManager.DiceTotal) )
{
legalStones.Add(ps);
}
}
}
return legalStones.ToArray();
}
}
MainMenu
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour{
public void PlayGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
public void QuitGame ()
{
Debug.Log("QUIT");
Application.Quit();
}
}
PlayerStone
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerStone : MonoBehaviour
{
// Use this for initialization
void Start()
{
theStateManager = GameObject.FindObjectOfType<StateManager>();
targetPosition = this.transform.position;
}
public Tile StartingTile;
public Tile CurrentTile { get; protected set; }
public int PlayerId;
public StoneStorage MyStoneStorage;
bool scoreMe = false;
StateManager theStateManager;
Tile[] moveQueue;
int moveQueueIndex;
bool isAnimating = false;
Vector3 targetPosition;
Vector3 velocity;
float smoothTime = 0.25f;
float smoothTimeVertical = 0.1f;
float smoothDistance = 0.01f;
float smoothHeight = 0.5f;
PlayerStone stoneToBop;
// Update is called once per frame
void Update()
{
if (isAnimating == false)
{
// Nothing for us to do.
return;
}
if (Vector3.Distance(
new Vector3(this.transform.position.x, targetPosition.y, this.transform.position.z),
targetPosition) < smoothDistance)
{
// We've reached the target position -- do we still have moves in the queue?
if(
(moveQueue == null || moveQueueIndex == (moveQueue.Length))
&&
((this.transform.position.y-smoothDistance) > targetPosition.y)
)
{
// We are totally out of moves (and too high up), the only thing left to do is drop down.
this.transform.position = Vector3.SmoothDamp(
this.transform.position,
new Vector3(this.transform.position.x, targetPosition.y, this.transform.position.z),
ref velocity,
smoothTimeVertical);
// Check for bops
if(stoneToBop != null)
{
stoneToBop.ReturnToStorage();
stoneToBop = null;
}
}
else
{
// Right position, right height -- let's advance the queue
AdvanceMoveQueue();
}
}
else if (this.transform.position.y < (smoothHeight - smoothDistance))
{
// We want to rise up before we move sideways.
this.transform.position = Vector3.SmoothDamp(
this.transform.position,
new Vector3(this.transform.position.x, smoothHeight, this.transform.position.z),
ref velocity,
smoothTimeVertical);
}
else
{
// Normal movement (sideways)
this.transform.position = Vector3.SmoothDamp(
this.transform.position,
new Vector3(targetPosition.x, smoothHeight, targetPosition.z),
ref velocity,
smoothTime);
}
}
void AdvanceMoveQueue()
{
if (moveQueue != null && moveQueueIndex < moveQueue.Length)
{
Tile nextTile = moveQueue[moveQueueIndex];
if (nextTile == null)
{
// We are probably being scored
// TODO: Move us to the scored pile
Debug.Log("SCORING TILE!");
SetNewTargetPosition(this.transform.position + Vector3.right * 10f);
}
else
{
SetNewTargetPosition(nextTile.transform.position);
moveQueueIndex++;
}
}
else
{
// The movement queue is empty, so we are done animating!
//Debug.Log("Done animating!");
this.isAnimating = false;
theStateManager.AnimationsPlaying--;
// Are we on a roll again space?
if(CurrentTile != null && CurrentTile.IsRollAgain)
{
theStateManager.RollAgain();
}
}
}
void SetNewTargetPosition(Vector3 pos)
{
targetPosition = pos;
velocity = Vector3.zero;
isAnimating = true;
}
void OnMouseUp()
{
// TODO: Is the mouse over a UI element? In which case, ignore this click.
MoveMe();
}
public void MoveMe()
{
// Is this the correct player?
if (theStateManager.CurrentPlayerId != PlayerId)
{
return;
}
// Have we rolled the dice?
if (theStateManager.IsDoneRolling == false)
{
// We can't move yet.
return;
}
if (theStateManager.IsDoneClicking == true)
{
// We've already done a move!
return;
}
int spacesToMove = theStateManager.DiceTotal;
if (spacesToMove == 0)
{
return;
}
// Where should we end up?
moveQueue = GetTilesAhead(spacesToMove);
Tile finalTile = moveQueue[ moveQueue.Length-1 ];
// TODO: Check to see if the destination is legal!
if(finalTile == null)
{
// Hey, we're scoring this stone!
scoreMe = true;
}
else
{
if(CanLegallyMoveTo(finalTile) == false)
{
// Not allowed!
finalTile = CurrentTile;
moveQueue = null;
return;
}
// If there is an enemy tile in our legal space, the we kick it out.
if(finalTile.PlayerStone != null)
{
//finalTile.PlayerStone.ReturnToStorage();
stoneToBop = finalTile.PlayerStone;
stoneToBop.CurrentTile.PlayerStone = null;
stoneToBop.CurrentTile = null;
}
}
this.transform.SetParent(null); // Become Batman
// Remove ourselves from our old tile
if(CurrentTile != null)
{
CurrentTile.PlayerStone = null;
}
// Even before the animation is done, set our current tile to the new tile
CurrentTile = finalTile;
if( finalTile.IsScoringSpace == false ) // "Scoring" tiles are always "empty"
{
finalTile.PlayerStone = this;
}
moveQueueIndex = 0;
theStateManager.IsDoneClicking = true;
this.isAnimating = true;
theStateManager.AnimationsPlaying++;
}
// Return the list of tiles __ moves ahead of us
public Tile[] GetTilesAhead( int spacesToMove )
{
if (spacesToMove == 0)
{
return null;
}
// Where should we end up?
Tile[] listOfTiles = new Tile[spacesToMove];
Tile finalTile = CurrentTile;
for (int i = 0; i < spacesToMove; i++)
{
if (finalTile == null)
{
finalTile = StartingTile;
}
else
{
if (finalTile.NextTiles == null || finalTile.NextTiles.Length == 0)
{
// We are overshooting the victory -- so just return some nulls in the array
// Just break and we'll return the array, which is going to have nulls
// at the end.
break;
}
else if (finalTile.NextTiles.Length > 1)
{
// Branch based on player id
finalTile = finalTile.NextTiles[ PlayerId ];
}
else
{
finalTile = finalTile.NextTiles[0];
}
}
listOfTiles[i] = finalTile;
}
return listOfTiles;
}
public Tile GetTileAhead( )
{
return GetTileAhead( theStateManager.DiceTotal );
}
// Return the final tile we'd land on if we moved __ spaces
public Tile GetTileAhead( int spacesToMove )
{
//Debug.Log(spacesToMove);
Tile[] tiles = GetTilesAhead( spacesToMove );
if(tiles == null)
{
// We aren't moving at all, so just return our current tile?
return CurrentTile;
}
return tiles[ tiles.Length-1 ];
}
public bool CanLegallyMoveAhead( int spacesToMove )
{
if( CurrentTile != null && CurrentTile.IsScoringSpace )
{
// This stone is already on a scoring tile, so we can't move.
return false;
}
Tile theTile = GetTileAhead( spacesToMove );
return CanLegallyMoveTo( theTile );
}
bool CanLegallyMoveTo( Tile destinationTile )
{
//Debug.Log("CanLegallyMoveTo: " + destinationTile);
if(destinationTile == null)
{
// NOTE! A null tile means we are overshooting the victory roll
// and this is NOT legal (apparently) in the Royal Game of Ur
return false;
// We're trying to move off the board and score, which is legal
//Debug.Log("We're trying to move off the board and score, which is legal");
//return true;
}
// Is the tile empty?
if(destinationTile.PlayerStone == null)
{
return true;
}
// Is it one of our stones?
if(destinationTile.PlayerStone.PlayerId == this.PlayerId)
{
// We can't land on our own stone.
return false;
}
// If it's an enemy stone, is it in a safe square?
if( destinationTile.IsRollAgain == true )
{
// Can't bop someone on a safe tile!
return false;
}
// If we've gotten here, it means we can legally land on the enemy stone and
// kick it off the board.
return true;
}
public void ReturnToStorage()
{
Debug.Log("ReturnToStorage");
//currentTile.PlayerStone = null;
//currentTile = null;
this.isAnimating=true;
theStateManager.AnimationsPlaying++;
moveQueue = null;
// Save our current position
Vector3 savePosition = this.transform.position;
MyStoneStorage.AddStoneToStorage( this.gameObject );
// Set our new position to the animation target
SetNewTargetPosition(this.transform.position);
// Restore our saved position
this.transform.position = savePosition;
}
}
AIPlayer_UtilityAl
using UnityEngine;
using System.Collections.Generic;
public class AIPlayer_UtilityAI : AIPlayer
{
Dictionary<Tile, float> tileDanger;
override protected PlayerStone PickStoneToMove( PlayerStone[] legalStones )
{
Debug.Log("AIPlayer_UtilityAI");
if(legalStones == null || legalStones.Length == 0)
{
Debug.LogError("Why are we being asked to pick from no stones?");
return null;
}
CalcTileDanger( legalStones[0].PlayerId );
// For each stone, we rank how good it would be to pick it, where 1 is super-awesome and -1 is horrible.
PlayerStone bestStone = null;
float goodness = -Mathf.Infinity;
foreach(PlayerStone ps in legalStones)
{
float g = GetStoneGoodness(ps, ps.CurrentTile, ps.GetTileAhead() );
if(bestStone == null || g > goodness)
{
bestStone = ps;
goodness = g;
}
}
Debug.Log("Choosen Stone Goodness: " + goodness );
return bestStone;
}
virtual protected void CalcTileDanger( int myPlayerId )
{
tileDanger = new Dictionary<Tile, float>();
Tile[] tiles = GameObject.FindObjectsOfType<Tile>();
foreach(Tile t in tiles)
{
tileDanger[t] = 0;
}
PlayerStone[] allStones = GameObject.FindObjectsOfType<PlayerStone>();
foreach(PlayerStone stone in allStones)
{
if(stone.PlayerId == myPlayerId)
continue;
// This is an enemy stone, add a "danger" value to tiles in front of it (unless safe)
for (int i = 1; i <= 4; i++)
{
Tile t = stone.GetTileAhead(i);
if( t == null )
{
// This tile (and subsequent tiles) are invalid, so we can just bail
break;
}
if( t.IsScoringSpace || t.IsSideline || t.IsRollAgain )
{
// This tile is not a danger zone, so we can ignore it.
continue;
}
// Okay, this tile is within bopping range of an enemy, so it's dangerous.
if(i == 2)
{
// 2 tiles is most likely, so most dangerous!
tileDanger[t] += 0.3f;
}
else
{
tileDanger[t] += 0.2f;
}
}
}
}
virtual protected float GetStoneGoodness( PlayerStone stone, Tile currentTile, Tile futureTile )
{
float goodness = 0;//Random.Range(-0.1f, 0.1f);
if( currentTile == null )
{
// We aren't on the board yet, and it's always nice to add more to the board to open up more options.
goodness += 0.20f;
}
if( currentTile != null && (currentTile.IsRollAgain == true && currentTile.IsSideline == false) )
{
// We are sitting on a roll-again space in the middle. Let's resist moving just because
// it blocks the space from our opponent
goodness -= 0.10f;
}
if( futureTile.IsRollAgain == true )
{
goodness += 0.50f;
}
if( futureTile.PlayerStone != null && futureTile.PlayerStone.PlayerId != stone.PlayerId )
{
// There's an enemy stone to bop!
goodness += 0.50f;
}
if( futureTile.IsScoringSpace == true )
{
goodness += 0.50f;
}
float currentDanger = 0;
if(currentTile != null)
{
currentDanger = tileDanger[currentTile];
}
goodness += currentDanger - tileDanger[futureTile];
// TODO: Add goodness for tiles that are behind enemies, and therefore likely to contribute to future boppage
// TODO: Add goodness for moving a stone forward when we might be blocking friendlies
return goodness;
}
}
CurrentPlayerDisplay
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI;
public class CurrentPlayerDisplay : MonoBehaviour {
// Use this for initialization
void Start () {
theStateManager = GameObject.FindObjectOfType<StateManager>();
myText = GetComponent<Text>();
}
StateManager theStateManager;
Text myText;
// TODO: Consider a humanizer library
string[] numberWords = { "White", "Red", "Three", "Four" };
// Update is called once per frame
void Update () {
myText.text = "Current Player: " + numberWords[theStateManager.CurrentPlayerId];
}
}
DiceRoller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class DiceRoller : MonoBehaviour
{
// Use this for initialization
void Start()
{
DiceValues = new int[4];
theStateManager = GameObject.FindObjectOfType<StateManager>();
}
StateManager theStateManager;
public int[] DiceValues;
public Sprite[] DiceImageOne;
public Sprite[] DiceImageZero;
// Update is called once per frame
void Update()
{
}
public void RollTheDice()
{
if (theStateManager.IsDoneRolling == true)
{
// We've already rolled this turn.
return;
}
// In Ur, you roll four dice (classically Tetrahedron), which
// have half their faces have a value of "1" and half have a value
// of zero.
// You COULD roll actual physics enabled dice.
// We are going to use random number generation instead.
theStateManager.DiceTotal = 0;
for (int i = 0; i < DiceValues.Length; i++)
{
DiceValues[i] = Random.Range(0, 2);
theStateManager.DiceTotal += DiceValues[i];
// Update the visuals to show the dice roll
// TODO: This could include playing an animation -- either 2D or 3D
// We have 4 children, each is an image of the die. So grab that
// child, and update its Image component to use the correct Sprite
if (DiceValues[i] == 0)
{
this.transform.GetChild(i).GetComponent<Image>().sprite =
DiceImageZero[Random.Range(0, DiceImageZero.Length)];
}
else
{
this.transform.GetChild(i).GetComponent<Image>().sprite =
DiceImageOne[Random.Range(0, DiceImageOne.Length)];
}
}
// If we had an animation, we'd have to wait for it to finish before
// we set doneRolling, but we can just set it right away
//theStateManager.DiceTotal = 15;
theStateManager.IsDoneRolling = true;
theStateManager.CheckLegalMoves();
//Debug.Log("Rolled: " + DiceTotal);
}
}
DiceTotalDisplay
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class DiceTotalDisplay : MonoBehaviour
{
// Use this for initialization
void Start()
{
theStateManager = GameObject.FindObjectOfType<StateManager>();
}
StateManager theStateManager;
// Update is called once per frame
void Update()
{
if (theStateManager.IsDoneRolling == false)
{
GetComponent<Text>().text = "= ?";
}
else
{
GetComponent<Text>().text = "= " + theStateManager.DiceTotal;
}
}
}
StateManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StateManager : MonoBehaviour
{
// Use this for initialization
void Start()
{
PlayerAIs = new AIPlayer[NumberOfPlayers];
PlayerAIs[0] = null; // Is a human player
//PlayerAIs[0] = new AIPlayer_UtilityAI();
PlayerAIs[1] = new AIPlayer_UtilityAI();
}
public int NumberOfPlayers = 2;
public int CurrentPlayerId = 0;
AIPlayer[] PlayerAIs;
public int DiceTotal;
// NOTE: enum / statemachine is probably a stronger choice, but I'm aiming for simpler to explain.
public bool IsDoneRolling = false;
public bool IsDoneClicking = false;
//public bool IsDoneAnimating = false;
public int AnimationsPlaying = 0;
public GameObject NoLegalMovesPopup;
public void NewTurn()
{
//Debug.Log("NewTurn");
// This is the start of a player's turn.
// We don't have a roll for them yet.
IsDoneRolling = false;
IsDoneClicking = false;
//IsDoneAnimating = false;
CurrentPlayerId = (CurrentPlayerId + 1) % NumberOfPlayers;
}
public void RollAgain()
{
Debug.Log("RollAgain");
IsDoneRolling = false;
IsDoneClicking = false;
//IsDoneAnimating = false;
}
// Update is called once per frame
void Update()
{
// Is the turn done?
if (IsDoneRolling && IsDoneClicking && AnimationsPlaying==0)
{
Debug.Log("Turn is done!");
NewTurn();
return;
}
if( PlayerAIs[CurrentPlayerId] != null )
{
PlayerAIs[CurrentPlayerId].DoAI();
}
}
public void CheckLegalMoves()
{
// If we rolled a zero, then we clearly have no legal moves.
if(DiceTotal == 0)
{
StartCoroutine( NoLegalMoveCoroutine() );
return;
}
// Loop through all of a player's stones
PlayerStone[] pss = GameObject.FindObjectsOfType<PlayerStone>();
bool hasLegalMove = false;
foreach( PlayerStone ps in pss )
{
if(ps.PlayerId == CurrentPlayerId)
{
if( ps.CanLegallyMoveAhead( DiceTotal) )
{
// TODO: Highlight stones that can be legally moved
hasLegalMove = true;
}
}
}
// If no legal moves are possible, wait a sec then move to next player (probably give message)
if(hasLegalMove == false)
{
StartCoroutine( NoLegalMoveCoroutine() );
return;
}
}
IEnumerator NoLegalMoveCoroutine()
{
// Display message
NoLegalMovesPopup.SetActive(true);
// TODO: Trigger animations like have the stones shake or something?
// Wait 1 second
yield return new WaitForSeconds(1f);
NoLegalMovesPopup.SetActive(false);
NewTurn();
}
}
StoneStorage
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StoneStorage : MonoBehaviour {
// Use this for initialization
void Start () {
// Create one stone for each placeholder spot
for (int i = 0; i < this.transform.childCount; i++)
{
// Instantiate a new copy of the stone prefab
GameObject theStone = Instantiate( StonePrefab );
theStone.GetComponent<PlayerStone>().StartingTile = this.StartingTile;
theStone.GetComponent<PlayerStone>().MyStoneStorage = this;
AddStoneToStorage(theStone , this.transform.GetChild(i) );
}
}
public GameObject StonePrefab;
public Tile StartingTile;
public void AddStoneToStorage( GameObject theStone, Transform thePlaceholder=null )
{
if( thePlaceholder == null )
{
// Find the first empty placeholder.
for (int i = 0; i < this.transform.childCount; i++)
{
Transform p = this.transform.GetChild(i);
if( p.childCount == 0 )
{
// This placeholder is empty!
thePlaceholder = p;
break; // Break out of the loop.
}
}
if(thePlaceholder==null)
{
Debug.LogError("We're trying to add a stone but we don't have empty places. How did this happen?!?!?");
return;
}
}
// Parent the stone to the placeholder
theStone.transform.SetParent( thePlaceholder );
// Reset the stone's local position to 0,0,0
theStone.transform.localPosition = Vector3.zero;
}
}
Title
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Tile : MonoBehaviour
{
// Use this for initialization
void Start()
{
}
public Tile[] NextTiles;
public PlayerStone PlayerStone;
public bool IsScoringSpace;
public bool IsRollAgain;
public bool IsSideline; // Is part of a player's private/safe area
// Update is called once per frame
void Update()
{
}
}
Comment