- Home /
How to properly serialize fields and store them for use in play mode?
Hello,
I'm stuck with this for ~3 days already, and I'm browsing tons of questions in UnityAnswers. Sadly, I'm still stuck.
I'm working on a mesh generator based off the terrain, so in future it will be used as an A* navigation graph. The mesh seems to be working fine (I'm using a "OnDrawGizmos()" function to check it), but as soon as I hit the play button, all the data is gone. I could go the brute-force way and store it in a file after generation and load it as soon as the game starts, but... I want to do it the Unity way.
So, to sum it up: How can I properly declare the variables so they get saved when I'm done with my graph and stored for use when I press play?
The code is quite lengthy, so I'll only paste the class definitions - tell me if I need to post more.
using UnityEditor;
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
//---------------------------------------------------------------
// Since Unity currently lacks a Tuple<T> implementation,
// lets use a simple one for coordinates
//---------------------------------------------------------------
[System.Serializable]
public class Coordinate : IEquatable< Coordinate >
{
[SerializeField] private int _x;
[SerializeField] private int _z;
public int x { get { return _x; } set { _x = value; } }
public int z { get { return _z; } set { _z = value; } }
public Coordinate() {}
public Coordinate(int first, int second)
{
this.x = first;
this.z = second;
}
public override bool Equals( object obj )
{
var other = obj as Coordinate;
if( other == null ) return false;
return Equals (other);
}
public override int GetHashCode()
{
int hash = 13;
hash = (hash * 7) + x;
hash = (hash * 11) + z;
return hash;
}
public bool Equals( Coordinate other )
{
if ( other == null ) return false;
if ( GetType() != other.GetType() ) return false;
if ( ReferenceEquals (this, other) ) return true;
if ( x == other.x && z == other.z ) return true;
return false;
}
public override string ToString()
{
return string.Format ("[Coordinate: x={0}, z={1}]", x, z);
}
}
//---------------------------------------------------------------
// This is our GridNode class. The A* mesh will be stored here.
//---------------------------------------------------------------
[System.Serializable]
public class GridNode
{
[SerializeField] private bool _Valid;
[SerializeField] private Vector3 _Position;
[SerializeField] private List< Coordinate > _Neighbours;
public bool Valid { get { return _Valid; } private set { _Valid = value; } } // Whether this node is usable or not.
public Vector3 Position { get { return _Position; } set { _Position = value; } } // Spatial position
public List< Coordinate > Neighbours { get { return _Neighbours; } set { _Neighbours = value; } } // We store our neighbours here.
public void MarkAsValid() { this.Valid = true; } // This seems to be kinda stupid, but lets just be VERY safe here.
public void MarkAsInvalid() { this.Valid = false; }
public GridNode()
{
Valid = false;
Position = Vector3.zero;
}
}
[ExecuteInEditMode]
public class GridGenerator : MonoBehaviour
{
#region Variable Declarations
// The editor will complaint these should be either private or Capitalized. Keep as is for the Editor counterpart.
[SerializeField] private bool m_bDrawGridArea = true; // Should we display the grid on the editor?
[SerializeField] private bool m_bDrawMeshNodes = true; // Should we display the nodes on the editor?
[SerializeField] private bool m_bDrawMesh = true; // Should we display the mesh on the editor?
public int m_iSquareSideSize = 50; // Max size of the graph (and the matrix it generates ;) )
public int m_iStartNodeX = 0; // Column coordinate
public int m_iStartNodeZ = 0; // Row coordinate
public float m_flScale = 1.0f; // Unit scale!
public float m_flGroundDistance = 0.1f; // Desired ground distance. This is NEVER scaled!
public bool m_bBuildingGraph = false; // Using this, we control the grid build stage (Internal control only, don't touch please.)
public float m_flGroundTraceSize = 10.0f; // Haw far can we search for the ground (halved for each axis)
public float m_flMaxMeshNodeStraightDistance = 1.42f;
public float m_flMaxMeshNodeDiagonalDistance = 2.0f;
// Both variables are temporary and used for Mesh generation
private HashSet<Coordinate> m_Closed; // Used in Grid generation
private Queue<Coordinate> m_ProcessQueue; // Used in Grid generation
[SerializeField]
private GridNode[,] _m_Grid; // Our sexy grid storage
public GridNode[,] m_Grid
{
get { return _m_Grid; }
set { _m_Grid = value; }
}
#endregion
// Use this for initialization
void Start ()
{
// Debug.Log("test: " + m_Grid[0,0].Position);
}
// Update is called once per frame
void Update()
{
// Debug.Log("test: " + m_Grid[0,0].Position);
}
}
For initialization (for tests, etc.) you can use this:
for ( int f = 0; f < m_iSquareSideSize; f++ )
for ( int g = 0; g < m_iSquareSideSize; g++ )
{
m_Grid[ f, g ] = new GridNode();
m_Grid[ f, g ].Neighbours = new List< Coordinate >();
}
Thank you in advance! I really hope someone out there knows a solution for this :) Also improvements are welcome!
PS: I found this link prior posting this question, but I couldn't find what's wrong with my code :(
Update: I edited my code and, if I replace GridNode[,] with GridNode[], it works. Any clue why a bidimensional array doesn't get saved, but a single dimension array does ?
Good old Unity serialization - you are stuck with writing your own mapping function for a 1d to 2d array.
Yeah... Sad I'm stuck for 3 days with this :(
I'm already converting the code to use mapping on a single dimension: y * m_iSquareSideSize + x
Thank you :)
I've got an implementation of Tuple I wrote - though your specific looks fine to me (and probably has a better version of GetHashCode).
So only 1D arrays and lists serialize (no dictionaries, queues, hashsets etc) and also watch references to your serializable classes, multiple references to the same thing get deserialized as separate instances (unless the object inherits from ScriptableObject and you do the necessary rain dance).
Answer by TheZoc · Mar 26, 2014 at 06:13 AM
The class and variables declaration are fine - Sadly, Unity can't serialize bidimensional arrays. As a workaround, I changed my GridNode[,]
to GridNode[]
and I'm using this auxiliary function:
private int SolveGridIndex( int x, int z )
{
return x * m_iSquareSideSize + z;
}
I hope this save some time to someone having the same issue I had :)
Your answer
Follow this Question
Related Questions
Why can't a MonoBehaviour subtype with generic arguments be serialized in a list since Unity 4.5? 0 Answers
[SerializeField] Not Working Properly? 1 Answer
How to make a serialized editor only field 2 Answers
[Solved]Why doesn't my ScriptableObject based asset save using a custom inspector ? 1 Answer