setting variable on element from array not working

Hi, I’ve been banging my head against this problem for over a day now, and I’m sure I’m missing something really straightforward, please help!

I’ve tried to simplify the problem, so made the test script below which is called in start:

    void Test(int q, int r, int elevation)
    {
        HexCoord h = GetHexAt(q, r);
        h.elevation = elevation;
        Debug.Log($"elevation {h.elevation}");  //this outputs 1
        Debug.Log($"elevation {hexes[q,r].elevation}"); //this outputs 0
        //hexes[q, r].elevation = elevation;

        UpdateHexVisuals();
    }

    public HexCoord GetHexAt(int q, int r) {
        return hexes[q, r];
    }

I would expect both of my debug.log entries to be 1, but they aren’t.

The commented code below the debug.log works, and everything behaves correctly. I realise in this instance there isn’t much benefit in factoring out this code, but it is useful in the bigger picture.

I’d be grateful if someone could explain why assigning elevation via this method doesn’t seem to be actually changing the elevation of the object in the hexes array.

Full code below.

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

public class MapAxial : MonoBehaviour
{
    public GameObject HexPrefab;

    public Mesh meshWater;
    public Mesh meshFlat;
    public Mesh meshRaised;
    public Mesh meshCliff;

    public Material MatDesert;
    public Material MatRock;
    public Material MatGrass;
    public Material MatWater;
    public Material MatCliff;

    public int mapHeight; //in hexes
    public int mapWidth; //in hexes
 
    protected HexCoord[,] hexes;
    protected Dictionary<HexCoord, GameObject> hexToGameObjectMap;

    void Start()
    {
        GenerateMap();

        Test(2,2,1);
    }

    void Test(int q, int r, int elevation)
    {
        HexCoord h = GetHexAt(q, r);
        h.elevation = elevation;
        Debug.Log($"elevation {h.elevation}");  //this outputs 1
        Debug.Log($"elevation {hexes[q,r].elevation}"); //this outputs 0
        //hexes[q, r].elevation = elevation;

        UpdateHexVisuals();
    }

    public HexCoord GetHexAt(int q, int r) {
        return hexes[q, r];
    }

    virtual public void GenerateMap() {

        hexes = new HexCoord[mapWidth, mapHeight]; //create array to store hexCoord structs
        hexToGameObjectMap = new Dictionary<HexCoord, GameObject>(); //create dictionary


        //Generate a flat map

        for (int column = 0; column < mapWidth; column++)
        {
            for (int row = 0; row < mapHeight; row++)
            {
                //add new HexCoord to array
                HexCoord h = new HexCoord(column, row); 
                hexes[column, row] = h;

                //instantiate hex GameObject and position it using method in HexCoord
                GameObject hex_go = (GameObject)Instantiate(HexPrefab, h.WorldPosition(), Quaternion.identity, this.transform); 

                //name the new hex GameObject in the hierarchy
                hex_go.name = "hex_" + column + "_" + row; 

                //map the game object to the data structure in the dictionary
                hexToGameObjectMap[h] = hex_go;

                StaticBatchingUtility.Combine(this.gameObject);
            }
        }
        UpdateHexVisuals();
    }

    public void UpdateHexVisuals() 
    {
        for (int column = 0; column < mapWidth; column++)
        {
            for (int row = 0; row < mapHeight; row++)
            {
                HexCoord h = hexes[column, row];
                GameObject hex_go = hexToGameObjectMap[h];

               //get meshrenderer of hex gameObject and assign it default desert material 
                MeshRenderer mr = hex_go.GetComponentInChildren<MeshRenderer>(); 
                mr.material = MatDesert;
                if (h.elevation > 0)
                {
                    Debug.Log($" Q {h.Q} R{h.R}");
                    mr.material = MatGrass;
                }

                //get meshfilter of hex gameobject and assign it flat mesh as a default
                MeshFilter mf = hex_go.GetComponentInChildren<MeshFilter>();  
                mf.mesh = meshFlat;
            }
        }
    }

HexCoord, adapted from this code on GitHub (GitHub - akhra/HexCoord: Hexagon Grid Coordinate System for Unity/C#)

public struct HexCoord
{

    /// <summary>
    /// Position on the q axis.
    /// </summary>
    [SerializeField]
    public int Q;
    /// <summary>
    /// Position on the r axis.
    /// </summary>
    [SerializeField]
    public int R;


    public float elevation;

    /// <summary>
    /// Initializes a new instance of the <see cref="Settworks.Hexagons.HexCoord"/> struct.
    /// </summary>
    /// <param name="q">Position on the q axis.</param>
    /// <param name="r">Position on the r axis.</param>
    public HexCoord(int q, int r)
    {
        this.Q = q;
        this.R = r;
        this.elevation=0;
        Debug.Log("this code called");
    }

    /// <summary>
    /// Position on the cubic z axis.
    /// </summary>
    /// <remarks>
    /// The q,r coordinate system is derived from an x,y,z cubic system with the constraint that x + y + z = 0.
    /// Where x = q and y = r, this property derives z as <c>-q-r</c>.
    /// </remarks>
    public int S
    {
        get { return -Q - R; }
    }

    /// <summary>
    /// Offset x coordinate.
    /// </summary>
    /// <remarks>
    /// Offset coordinates are a common alternative for hexagons, allowing pseudo-square grid operations.
    /// Where y = r, this property represents the x coordinate as <c>q + r/2</c>.
    /// </remarks>
    public int O
    {
        get { return Q + (R >> 1); }
    }

    /// <summary>
    /// Unity position of this hex.
    /// </summary>
    public Vector2 Position()
    {
        return Q * Q_XY + R * R_XY;
    }

    public Vector3 WorldPosition() {

        float x = Position().x;
        float y = 0; //could change to elevation??
        float z = Position().y;

        return new Vector3(x, y, z);
    }

    static readonly Vector2 Q_XY = new Vector2(SQRT3, 0);
    static readonly Vector2 R_XY = new Vector2(SQRT3 / 2, 1.5f);
    static readonly Vector2 X_QR = new Vector2(SQRT3 / 3, 0);
    static readonly Vector2 Y_QR = new Vector2(-1 / 3f, 2 / 3f);
}

The reason your outputs are different is that HexCoord is a struct, not a class. When you assign one class to another, all you are doing is assigning a reference to that class (some people say “pointer” instead of “reference”). So, in this code, the output is the same at Line 14 as it is at Line 13, and it is the same at Line 19 as it is at Line 18, because box2 and box1 contain references to the same object of class DataBox:

public class Demo
{
    public void Start()
    {
        DataBox box1 = new DataBox();
 
        box1.x = 3;
 
        DataBox box2;
 
        box2 = box1;
 
        print(box1.x);
        print(box2.x);
 
        box1.x = 4;
 
        print(box1.x);
        print(box2.x);
    }
}

public class DataBox
{
    public int x;
}

This outputs the following:

3
4
4```

But, if you make DataBox a struct, assigning it to another struct *copies* the values in it, not just a reference to it. After that assignment, you have two *different* copies of the struct, so changing one does not change the other.

```public struct DataBox
{
public int x;
}```

The above gets you this output:

```3
3
4
3```

This is all because classes are *reference* types, and structs are *value* types. There is a lot of information on the internet about this. I advise you to limit your reading to reliable sources (stuff written by Microsoft, for example), as this is one of the most misunderstood, yet fundamental, concepts you need to know when programming in C#.

Good luck!
1 Like

Thanks very much, that’s a super clear explanation. I’ll go away and do some reading.

1 Like