Editor presents differently than Build

for context; This is my first Unity Project(2021.3.33f1 LTS for Windows). I am trying to make a simple chess game with hope of implementing and comparing different algorithms for chess bots. I wanted to have a build for what i have right now because I think it is good progress. However, What i see in the editor isnt what appears in the build. Particularly, I use the “CreatePrimitive(PrimitiveType.Quad)” to make the tiles for the board. They show up when I use th editor but are replaced with a lighter purple in the build. I dont even know where to begin to debug this since it works as expected in the editor. Anyone have experience with such problems? I added the code for the tile and board and attached screenshots. Thank you in advance.

(edit) I should add that the platform I attempted to build for is “Windows, Mac, Linux” with the Target Platform being Windows.

// Board.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class BoardState
{
    public event Action<Vector2Int, Vector2Int> OnPieceMoved;
    private TileState[,] tileStates;
    
    private static Vector2Int minPoint, maxPoint;

    public const int n = 8; // Size of the board

    // to change and update state
    public Vector2Int MinPoint{
        get=>minPoint;
        set=>minPoint=value;
    }
    public Vector2Int MaxPoint{
        get=>maxPoint;
        set=>maxPoint=value;
    }
    public int N => n;

    public TileState[,] TileStates{
        get=>tileStates;
        set=>tileStates=value;
    }

    public BoardState(){}
    public BoardState(BoardState original){
        // Clone the tile states
        this.tileStates = new TileState[N, N];
        for (int yi = 0; yi < N; yi++)
            for (int xi = 0; xi < N; xi++)
                tileStates[yi, xi] = original.tileStates[yi, xi]?.Clone(); // Clone each tile
    }

    public BoardState Clone() => new BoardState(this); // Clone method

    public void CreateBoardState(PlayerState player1, PlayerState player2)
    {
        // Create and Add Tiles
        tileStates = new TileState[N, N];
        minPoint = new Vector2Int(0, 0); maxPoint = new Vector2Int(N-1, N-1);

        for (int yi = 0; yi < N; yi++){
            for (int xi = 0; xi < N; xi++){
                TileState tileState = new TileState();

                tileState.Colour = (yi + xi) % 2 == 1; // Alternate colours
                tileState.Min = minPoint.x; tileState.Max = maxPoint.x;
                tileState.Position = new Vector2Int(xi, yi); // Note the order here

                tileStates[yi, xi] = tileState;

            }
        }
        
        // Create and Add Pieces
        PopulateBoardState(player1, player2);
    }

    private void PopulateBoardState(PlayerState player1, PlayerState player2)
    {
        // string[] pieceTypes = { "Pawn", "Bishop", "Knight", "Rook", "Queen", "King" };
        string[] pieceTypes = { "King", "Queen", "Bishop", "Knight", "Rook",  "Pawn" }; // orderd so the King is indeed the first piece in the Player's Pieces List
        foreach (string pieceType in pieceTypes)
        {
            AddPieceStates(pieceType, player1, player2);
        }
    }

    private void AddPieceStates(string type, PlayerState player1, PlayerState player2)
    {
        switch (type)
        {
            case "King":
                AddPieceState(type, true, 3, player1);
                AddPieceState(type, false, 3, player2);
                break;
            case "Queen":
                // AddPieceState(type, true, 4, player1);
                // AddPieceState(type, false, 4, player2);
                break;
            case "Rook":
                AddPieceState(type, true, 0, player1);
                AddPieceState(type, true, 7, player1);
                AddPieceState(type, false, 0, player2);
                AddPieceState(type, false, 7, player2);
                break;
            case "Knight":
                // AddPieceState(type, true, 1, player1);
                // AddPieceState(type, true, 6, player1);
                // AddPieceState(type, false, 1, player2);
                // AddPieceState(type, false, 6, player2);
                break;
            case "Bishop":
                AddPieceState(type, true, 2, player1);
                AddPieceState(type, true, 5, player1);
                AddPieceState(type, false, 2, player2);
                AddPieceState(type, false, 5, player2);
                break;
            case "Pawn":
                for (int xi = 0; xi < n; xi++)
                {
                    AddPieceState(type, true, xi, player1); // Light
                    AddPieceState(type, false, xi, player2); // Dark
                }
                break;
            default:
                Debug.Log("Unknown piece type: " + type);
                break;
        }
    }

    void AddPieceState(string type, bool colour, int x, PlayerState player){
        int darkY = minPoint.y, lightY = maxPoint.y;
        PieceState pieceState;

        // Handle special case for Pawn
        if (type == "Pawn"){
            darkY++; lightY--;
            pieceState = new PawnState(colour, new Vector2Int(x, colour ? lightY : darkY), minPoint, maxPoint);
        }else{
            // Use Type.GetType to get the type of the piece state dynamically
            Type pieceStateType = Type.GetType(type + "State"); // Assumes the class names are in the format "KingState", "QueenState", etc.
            if (pieceStateType == null){
                Debug.LogError($"Could not find type: {type}State");
                return;
            }
            Vector2Int startPos = new Vector2Int(x, colour ? lightY : darkY);

            // Create an instance of the PieceState using reflection
            pieceState = (PieceState)Activator.CreateInstance(pieceStateType, new object[] { colour, startPos, minPoint, maxPoint });
            if (pieceState == null){
                Debug.LogError($"Failed to create instance of type: {type}State");
                return;
            }
        }

        // Set piece to tile
        int tileY = colour ? lightY : darkY;
        tileStates[tileY, x].pieceState = pieceState; // Adjust for array index

        // Give piece to player
        player.AddPieceState(pieceState);
    }



    public TileState GetTile(Vector2Int pos)
    {
        int xIndex = pos.x;
        int yIndex = pos.y; // Invert y coordinate for the array

        if (0 <= xIndex && xIndex < N && 0 <= yIndex && yIndex < N)
            return tileStates[yIndex, xIndex]; // Correct indexing for the array
        
        return null; // Return null if out of bounds
    }

    public TileState GetTile(int x, int y) // overload
    {
        int xIndex = x;
        int yIndex = y; // Invert y coordinate for the array

        if (0 <= xIndex && xIndex < N && 0 <= yIndex && yIndex < N)
            return tileStates[yIndex, xIndex]; // Correct indexing for the array
        
        return null; // Return null if out of bounds
    }

    public void MovePiece(Vector2Int from, Vector2Int to)
    {
        TileState fromTile = GetTile(from);
        TileState toTile = GetTile(to);

        if (fromTile != null && toTile != null)
        {
            //Debug.Log($"Moving piece from {from} to {to}");
            toTile.pieceState = fromTile.pieceState;
            fromTile.pieceState = null;
        }

        OnPieceMoved?.Invoke(from, to); // for the board tiles too
    }

    // Piece Movement Logic
    public bool InBounds(Vector2Int pos)=>Utility.InBounds(minPoint, maxPoint, pos);

}

public class Board : MonoBehaviour
{
    // PRIVATE
    BoardState state;
    private Tile[,] tiles;
    private const float tileSize = 5f;
 
    // Load all sprites from the Pieces.png
    static int sheetN = 1; // the piece sheet we use
    static Dictionary<int, float> pieceScaleMap = new Dictionary<int, float>
    {
        { 0, 1.25f },
        { 1, 1.25f },
    };
    float pieceScaleFactor = pieceScaleMap[sheetN]; // increase size of a piece also used to set collider of piece to reciprocal
    public static Dictionary<string, Sprite> sprites = new Dictionary<string, Sprite>();


    public float TileSize
    {
        get { return tileSize; }
    }

    public BoardState State{
        get=>state;
        set{
            state=value;
            state.OnPieceMoved += MovePiece;
        }
    }

    public void CreateBoard(Player Player1, Player Player2)
    {
        // Create and Add Tiles
        tiles = new Tile[state.N, state.N];

        for (int yi = 0; yi < state.N; yi++)
        {
            for (int xi = 0; xi < state.N; xi++)
            {
                GameObject tileObject = GameObject.CreatePrimitive(PrimitiveType.Quad);
                Tile tile = tileObject.AddComponent<Tile>();
                tile.State = state.TileStates[yi, xi];
                tile.N = tileSize;

                tiles[yi, xi] = tile;

                // Position the tile in the scene
                tileObject.transform.position = new Vector3(xi * tileSize, yi * tileSize, 0);
                tileObject.transform.localScale = new Vector3(tileSize, tileSize, 1); // Flatten for board look
            }
        }

        // need tileSize for both player and bot moves
        Player1.State.TileSize = tileSize; Player2.State.TileSize = tileSize;
        
        // Create and Add Pieces
        PopulateBoard(Player1, Player2);
    }

    private void CenterCamera()
    {
        Camera.main.transform.position = new Vector3((state.N - 1) * tileSize / 2, (state.N - 1) * tileSize / 2, -1);
        Camera.main.orthographic = true; // Ensure it's set to Orthographic
        Camera.main.orthographicSize = (state.N * tileSize) / 2; // Adjust size based on board dimensions
    }

    public static Dictionary<string, Sprite> LoadSprites()
    {
        // Load all sprites from the Pieces.png
        Sprite[] allSprites = Resources.LoadAll<Sprite>($"Sprites/Pieces{sheetN}"); // Adjust path if needed
        
        foreach (var sprite in allSprites)
        {
            sprites[sprite.name] = sprite; // Map sprite names to the dictionary
        }
        return sprites;
    }



    private void PopulateBoard(Player Player1, Player Player2)
    {
        // string[] pieceTypes = { "Pawn", "Bishop", "Knight", "Rook", "Queen", "King" };
        string[] pieceTypes = { "King", "Queen", "Bishop", "Knight", "Rook",  "Pawn" }; // orderd so the King is indeed the first piece in the Player's Pieces List
        foreach (string pieceType in pieceTypes)
        {
            AddPieces(pieceType, Player1, Player2);
        }
    }

    private void AddPieces(string type, Player Player1, Player Player2)
    {
        switch (type)
        {
            case "King":
                AddPiece(type, true, 3, Player1);
                AddPiece(type, false, 3, Player2);
                break;
            case "Queen":
                // AddPiece(type, true, 4, Player1);
                // AddPiece(type, false, 4, Player2);
                break;
            case "Rook":
                AddPiece(type, true, 0, Player1);
                AddPiece(type, true, 7, Player1);
                AddPiece(type, false, 0, Player2);
                AddPiece(type, false, 7, Player2);
                break;
            case "Knight":
                // AddPiece(type, true, 1, Player1);
                // AddPiece(type, true, 6, Player1);
                // AddPiece(type, false, 1, Player2);
                // AddPiece(type, false, 6, Player2);
                break;
            case "Bishop":
                AddPiece(type, true, 2, Player1);
                AddPiece(type, true, 5, Player1);
                AddPiece(type, false, 2, Player2);
                AddPiece(type, false, 5, Player2);
                break;
            case "Pawn":
                for (int xi = 0; xi < state.N; xi++)
                {
                    AddPiece(type, true, xi, Player1); // Light
                    AddPiece(type, false, xi, Player2); // Dark
                }
                break;
            default:
                Debug.Log("Unknown piece type: " + type);
                break;
        }
    }

    void AddPiece(string type, bool colour, int x, Player Player)
    {
        int darkY = state.MinPoint.y, lightY = state.MaxPoint.y;

        GameObject PieceObject = new GameObject(type + (colour ? "W" : "B") + (type == "Pawn" ? x : ""));
        // Convert the type string to a Type object
        Type pieceType = Type.GetType(type);
        Piece piece = PieceObject.AddComponent(pieceType) as Piece;
        if (piece == null){
            Debug.LogError($"Failed to add component of type: {type}");
            return;
        }

        if(type=="Pawn"){
            darkY++; lightY--;
        }
        int tileY = colour ? lightY : darkY;
        
        piece.State = state.TileStates[tileY, x].pieceState;
        piece.TileSize = tileSize;
        piece.PieceSprite = sprites[$"{type}"];
        piece.PieceColliderSize = 1 / pieceScaleFactor;

        // Set piece to tile
        tiles[tileY, x].piece = piece; // Adjust for array index
        //Debug.Log(piece);
        //Debug.Log(tiles[tileY, x].piece + " "+ tiles[tileY, x].piece.State.Type + " on tile " + x + " " + tileY);

        // Set UI
        PieceObject.transform.position = new Vector3(x * tileSize, tileY * tileSize, 0);
        PieceObject.transform.localScale = new Vector3(tileSize * pieceScaleFactor, tileSize * pieceScaleFactor, 1); // Adjust based on sprite size

        // Give piece to player
        Player.AddPiece(piece);
    }


    public Tile GetTile(Vector2Int pos)
    {
        int xIndex = pos.x;
        int yIndex = pos.y; // Invert y coordinate for the array

        if (0 <= xIndex && xIndex < state.N && 0 <= yIndex && yIndex < state.N)
        {
            return tiles[yIndex, xIndex]; // Correct indexing for the array
        }
        return null; // Return null if out of bounds
    }
    public Tile GetTile(int x, int y) // overload
    {
        int xIndex = x;
        int yIndex = y; // Invert y coordinate for the array

        if (0 <= xIndex && xIndex < state.N && 0 <= yIndex && yIndex < state.N)
        {
            return tiles[yIndex, xIndex]; // Correct indexing for the array
        }
        return null; // Return null if out of bounds
    }

    public void MovePiece(Vector2Int from, Vector2Int to)
    {
        Tile fromTile = GetTile(from);
        Tile toTile = GetTile(to);

        if (fromTile != null && toTile != null)
        {
            Debug.Log($"Moving piece from {from} to {to}");
            toTile.piece = fromTile.piece;
            fromTile.piece = null;
        }
    }


    
    void Awake()
    {
        // create Sprite dict
        if(sprites.Count==0)
            LoadSprites();
    }
  
    // Start is called before the first frame update
    void Start()
    {
        CenterCamera();
    }

    // Update is called once per frame
    void Update()
    {
        // Additional logic if needed
    }
}




// Tile.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System; // for basic arrays


public class TileState
{
    // Start is called before the first frame update
    private Vector2 position;
    private bool colour;
    private float min,max,minx,miny,maxx,maxy;

    public PieceState pieceState;

    public Vector2 Position{
        get{return position;}
        set{position = value;}
    }

    public bool Colour{
        get{return colour;}
        set{colour = value;}
    }

    public float Min{
        get{return min;}
        set{
            min=value;
            minx=min; miny=min;
        }
    }
    public float Max{
        get{return max;}
        set{
            max=value;
            maxx=max; maxy=max;
        }
    }

    public TileState(){}

    // Copy constructor
    public TileState(TileState original)
    {
        this.position = original.position;
        this.colour = original.colour;
        this.Min = original.min;
        this.Max = original.max;
        
        // Clone the piece state if it exists
        pieceState = original.pieceState?.Clone();
    }

    public TileState Clone() => new TileState(this); // Clone method

    public bool HasPieceState(){
        return pieceState!=null;
    }
}

public class Tile : MonoBehaviour
{
    TileState state;
    private float n; // length of tile
    private Material tileMaterial; //store for reuse

    public Piece piece;
    
    public TileState State{
        get=>state;
        set=>state=value;
    }
    public float N{
        get{return n;}
        set{
            if(state.Min<value && value<state.Max){
                n=value;
                //ScaleTile();
            }
        }
    }
    private void RenderTileColour(){
        if(tileMaterial==null)
        {
            tileMaterial = new Material(Shader.Find("Unlit/Color")); // Use an unlit shader
            GetComponent<Renderer>().material = tileMaterial; // set material once
        }
        tileMaterial.color = state.Colour ? Color.white : Color.black;
    }

    private void ScaleTile()=>transform.localScale = new Vector3(n, n, 1); // Adjust scale for visual representation


    void Start()
    {
        RenderTileColour();
        ScaleTile();
        
    }

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


Usually a bright pink material means there’s either no shader or an issue with the shader. So potentially the default shaders are getting stripped in the build?

Could either use a simple prefab in lieu of creating the primitive, or potentially put the shader that would be used in the always included shaders in the graphics settings: Unity - Manual: Graphics

2 Likes

To add to Spiney’s post:

On line 514 you create a new material that uses a shader that Unity doesn’t know you’re using because it’s not referenced anywhere in the scene and so it isn’t included in the Build. So instead create the material in the editor and drop it onto the board or make a reference to the material from within your script like so:

Material tileMaterial;
1 Like

Thank you @spiney199 !

You were 100% right it was a shader problem. I had to go into the Player settings and search for " Always Included Shaders" and manually add the shader in was using ‘Unlit/Color’ to the list of shaders

Thank you so much you’re a lifesaver.

1 Like

No problem!

Zulo brings up good points as well. It would make more sense to reference this material via the inspector somehow. As it stands, you’re instancing a material per-tile and also not cleaning them up, which will cause a memory leak.

Easiest solution would be to Object.Destroy the materials in OnDestroy() on the tile’s, but worth looking into ways to only require one/two materials (one for each tile colour) instead.

2 Likes

As Spiney points out, this is not really a good practice. It’s only a matter of time before you take this code somewhere else for your next project and forget about this conversation and have to track it all back down again.

Drag it in, it’s the Unity Way™. Using Shader.Find() is just as bad as GameObject.Find(), plus the failures can be far more subtle and deeper into the presentation.

You also do a lot of string testing, your switch statements and all, inviting massive typing mistakes. Constants or enums are better for typing, but enums have their own issues.

Enums enums are bad in Unity3D if you intend them to be serialized:

It is much better to use ScriptableObjects for many enumerative uses. You can even define additional associated data with each one of them, and drag them into other parts of your game (scenes, prefabs, other ScriptableObjects) however you like. References remain rock solid even if you rename them, reorder them, reorganize them, etc. They are always connected via the meta file GUID.

Collections / groups of ScriptableObjects can also be loaded en-masse with calls such as Resources.LoadAll<T>();

Best of all, Unity already gives you a built-in filterable picker when you click on the little target dot to the right side of a field of any given type… bonus!

Finally, about strings in general:

Remember the first rule of GameObject.Find():

Do not use GameObject.Find();

More information: Regarding GameObject.Find · UnityTipsRedux

In general, DO NOT use Find-like or GetComponent/AddComponent-like methods unless there truly is no other way, eg, dynamic runtime discovery of arbitrary objects. These mechanisms are for extremely-advanced use ONLY.

If something is built into your scene or prefab, make a script and drag the reference(s) in. That will let you experience the highest rate of The Unity Way™ success of accessing things in your game.

“Stop playing ‘Where’s GameWaldo’ and drag it in already!”

Here’s why all this stuff is CRAZY code:

1 Like