Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
1
Question by Bovine · Sep 27, 2011 at 10:12 PM · c#arrayserializationclassnull

Why is my C# array of objects fully populated when some of them should be null?

Hi All

Aplogies if this is more a c# query, but I can't find any details generally so far.

I have a 2D map that represents legal positions for creatures and players in the world. The world is 3d but movement is tile based. I have a serializable class for each tile and a 1d array I treat as 2d via a GetTile() method. Some of my tiles are not accessible and so I wanted to set the array location to null, but it doesn't appear to work - when I inspect the array on load, each index contains a tile class, although where the tiles should be null they are only partially constructed?

Any help greatly apprechiated!

[Edit] Thanks for all the help so far. Here's the code with the bodies edited out

 [System.Serializable]
 public class BVMapTile
 {  // [EDIT] clearly a tile isn't empty but the contents probably don't tell you much
 }
 
 [System.Serializable]
 public class BVMap2D : MonoBehaviour
 {   // map data
     public int MaxX = 0;
     public int MaxZ = 0;
     public BVMapTile[] Tiles = null; // array is single but treated like 2D
 
     // gets a tile
     public BVMapTile GetTile(int x, int z)
     {  // return the tile specified
        return Tiles[x * MaxZ + z];
     }
     
     public void SetTile(int x, int z, BVMapTile tile)
     {  // set the tile
        Tiles[x * MaxZ + z] = tile;
     }
 }
 
 [CustomEditor(typeof(BVMap2D))]
 public class BVMap2DEditor : Editor
 {
     static bool m_render_boxes = true;
     
     void OnSceneGUI()
     {  // [EDIT] I'm drawing some handles here
     }
     
     [DrawGizmo(GizmoType.NotSelected)]
     static void RenderLevelHullGizmo(GameObject obj, GizmoType type)
     {  // if the selection is same we might care
        if(!Selection.Contains(obj)) return;
         
        // [EDIT] I'm drawing some gizmos here, I look for the component type on the GameObject and bail if its not there
     }
     
     Vector3 m_move_height = new Vector3(0, 1, 0);   // if we collide at this height we cannot pass between squares
     Vector3 m_sight_height = new Vector3(0, 1.5f, 0);  // if we collide at this height we cannot see between squares
     
     // map of calculated grounds positions
     Dictionary<BVMapTile, Vector3> m_calculated_ground_positions = new Dictionary<BVMapTile, Vector3>();
     
     public override void OnInspectorGUI()
     {  // get the map
         BVMap2D map = target as BVMap2D;
         Transform t = map.transform;
         
         // build map button
         if(GUILayout.Button("Build Map"))
         {  // warning!
            // clear map of ground positions
            m_calculated_ground_positions.Clear();
             
            // calculate the size of the map, from the handles in the scene
            Vector3 size = map.UpperBounds;
            size -= map.LowerBounds;
            int max_x = (int) ((size.x + 0.2f) / BVMap2DConstants.TileSize);
            int max_z = (int) ((size.z + 0.2f) / BVMap2DConstants.TileSize);
             
            // create a new map!
            map.Tiles = new BVMapTile[max_x * max_z];
            map.MaxX = max_x;
            map.MaxZ = max_z;
             
            // [EDIT]
            // *** The Relevant Collision Models are parsed to decide whether the tile position is accessible. ***
             
            // the final pass will decides whether the tile is closed
            for(int x=0; x<max_x; ++x)
            {  // for each z tile
               for(int z=0; z<max_z; ++z)
               {  // get  the tile we are on
                  BVMapTile tile = map.GetTile(x, z);
                  tile.DetermineClosed();
                     
                  // if the tile has an entry, set the ground position
                  tile.HaveGroundPosition = tile.HaveGroundPosition || m_calculated_ground_positions.TryGetValue(tile, out tile.GroundPosition);
                  if(!tile.HaveGroundPosition || !tile.TileOpen)
                  {  // this sets the tile in the array to null
                     map.SetTile(x,z, null);
                  }
               }
            }
         }
         
         if(GUI.changed)
             EditorUtility.SetDirty(target);
     }
 }

Apologies if the code is a bit odd looking - I have trouble pasting code into Unity Answers. Hopefully I didn't edit away too much.

Some points to note:

  • I am using a public 1d array to benefit from Unity's serialisation (2d arrays don't get serialised)

  • It seems I may not want to benefit from Unity's in built serialisation given answers to date...

  • The tiles are serializable classes

  • I'm setting the contents of the array in a custom editor which boils collision models within an array defined by two handles down to a 2D grid.

This looks like so in the editor for anyone who might be interested:

Editor Screenshot

I'll respond to comments below, but it looks like the Unity default serialisation is getting in my way a little - how can I work around this?

Thanks Ian H

Comment
Add comment · Show 5
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image J3-Gaming · Sep 27, 2011 at 10:23 PM 0
Share

Can u post your current code?

avatar image Bovine · Sep 27, 2011 at 10:29 PM 0
Share

I'll see if I can post an edited version as it's several files... $$anonymous$$orrow tho as it's very late here!

avatar image syclamoth · Sep 28, 2011 at 06:51 AM 1
Share

Ins$$anonymous$$d of setting your inaccessible tiles to 'null', you should put a boolean in them which deter$$anonymous$$es whether they are walkable or not. This neatly sidesteps the problem you have been having, and still allows you to use serialization. As an added bonus, you could put in a cast definition which returns the 'walkable' boolean if you refer to the tile as a bool (not quite sure how to do this, assu$$anonymous$$g it's possible).

avatar image Bovine · Sep 28, 2011 at 07:40 AM 0
Share

@syclamoth - that's essentially what I have now but there's no point having a structure exist that I won't use. I'm deploying for iOS so I want to keep memory usage low, having a smaller data structure to serialise and deserialize will also speed up loading times. At present the structures are too heavy weight so I need to boil them down to using enum flags where possible which should greatly reduce the overhead and perhaps mean that there is less necessity to have the array scarcely populated.

Null is still the cheapest and most efficient option however.

avatar image Bovine · Sep 28, 2011 at 09:06 AM 0
Share

Boiling the tile data down it amounts to 36 bytes per tile. Each valid tile piggy backs a structure of 16 bytes per concurrent path we can be calculating. I'm doing this because it makes it easy to find the data associated with a tile for a given path and avoids allocation or pooling of this path information. The number of concurrent paths is configurable so where little or no pathing will occur on a map, 1 (or even 0) path data structures are allocated. a 256x256 map will eat nearly 9 megs if I allow 4 concurrent paths to be calculated. 256x256 I would consider a HUGE map. Naturally on a desktop 9 megs is nothing...

I imagine about 50% of tiles are in-use typically nulls would halve the memory footprint.

It was actually more like 44, but I've shrunk it back to 36 and 12 bytes per concurrent path. I can shrink it to 20 bytes + 12 per concurrent path I think but that's about as right as it'll go but it's still probably worth doing. Those numbers would be 4.5$$anonymous$$B @256x256 fully populated not counting the array itself, and 2.25$$anonymous$$B for part population and around 600$$anonymous$$ for 128x128 which is a big map anyway.

3 Replies

· Add your reply
  • Sort: 
avatar image
1
Best Answer

Answer by Bunny83 · Sep 27, 2011 at 10:52 PM

Well, it would help to see your actual definition of your array / Tile-class. Your problem might be that you use a public 1-dimensional native array and your Tile class is serializable. In this case Unity will automatically create the members of the array. You can also resize the array in the inspector. The only way to prevent this is to disable the serialization of Unity (by making it private / protected or nonserialized).

The serialization in Unity is a bit annoying. It also doesn't support inheritance. So if you have an array of a base type and populate it with derived classes they are "converted" into base-class-instances after deserialization (which happens quite often: enter playmode, enter editmode, loadlevel, ...)

There is a little exception that works: MonoBehaviour! Because it's a Component, Unity will just hold the reference to the real component. The disadvantage is that all your instances have to be attached to a GameObject (can be the same GO for all). An array that holds a base-class that is derived from MonoBehaviour works very well. Unity will also "remember" the true type of the instances since they are serialized seperately as Components.

It's not a nice setup, but it's the only way to use the built-in serialization.

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bovine · Sep 28, 2011 at 06:54 AM 0
Share

I see, so it seems the 1d solution is giving me serialisation but that the serialisation isn't great. What can I do to override what gets serialized? I tried to add the ISerializable interface to the BV$$anonymous$$ap2D class but it didn't seem to call any of my serialization methods :o/

The map itself is a $$anonymous$$onoBehaviour subclass - I could do the same for tiles, but that's a lot of overhead on a give GO. I had thought of making it generic but adding it in the inspector wouldn't work unless I derived a concrete type (which I could have done) but feeling this might not work I just exposed a Data property which any object can be shoved into. A Generic type would be cleaner perhaps but maybe less runtime performant.

avatar image
1

Answer by Eric5h5 · Sep 27, 2011 at 10:39 PM

If you're using a struct, then you can't have null items. If you're using a class, then all entries are null until initialized. You'd probably make things easier on yourself if you used a 2D array.

Comment
Add comment · Show 6 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image msknapp · Sep 27, 2011 at 10:55 PM 0
Share

2D arrays suffer resizing issues, they also lack a ton of useful functionality that the collections have built into them. I would not use them unless you know the initial size before hand and that it will never change. Use a list or map collection or some other collection implementation. Personally I think Dictionary is best, it will give you the best runtime performance because of its indexing.

avatar image Bunny83 · Sep 27, 2011 at 11:34 PM 1
Share

@msknapp: The OP is using a 1D array so it has the same advantages / disadvantages. Sure i love the container classes too but a level-map doesn't change it's size, so a native array is the best choice. A 2D array has the "advantage" over a 1D array that it isn't serialized by Unity, but i'm not sure if that is intended. It would solve the problem by disabling the automatic initialization of the items by Unity.

avatar image Eric5h5 · Sep 28, 2011 at 02:57 AM 1
Share

It's highly unlikely the game world is going to change size.

avatar image Bovine · Sep 28, 2011 at 06:49 AM 0
Share

@Eric5h5 Sure it was a 2d array of classes but Unity was happily throwing that away, hence the 1d array faked up to use as a 2d array. The game world will not change size and if it needed to I could fake that up by having additional maps that are linked or editing edge information on tiles to expand the accessible area - which is how doors currently work.

In the mono debugger I can see my array contains nulls in the right places but as mentioned by someone else serialization seems to be happening and moments later - i.e. when running the game - those nulls are replaced with partially constructed BV$$anonymous$$apTiles.

@msknapp Given the world does not change size the most efficient thing is to have a 2d array (serialization allowing). There's no useful functionality I am missing and in this instance an array feels like the most appropriate collection. I just don't want to have classes hanging about for tiles that don't need the detail - a null tile means it can be discarded from my A* pathing calculations and considered blocking for player movement.

avatar image Eric5h5 · Sep 28, 2011 at 08:11 AM 0
Share

@Bovine: "throwing it away" doesn't really make any sense. If you can use a 1D array, you can use a 2D array.

Show more comments
avatar image
0

Answer by msknapp · Sep 27, 2011 at 10:30 PM

each collection class is programmed differently, some allow nulls, some don't. When you try setting some value, the class could do all kinds of things behind the scenes you don't know about.

I'm assuming that you are using the javascript "Array" class to hold your tiles right? My guess is that it refuses to take null values, or it interprets them as not a true index of the array. Unfortunately the documentation on "Array" does not say. Quite typical of Unity, leaving us uncertain, forced into trial and error. Some ideas:

  1. You could easily switch to use the .net class List. I'm pretty sure that it will accept null values.
    http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

  2. You could add a special tile class to the array as a place marker. Something that is not literally null, but represents a null value.

Comment
Add comment · Show 6 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Eric5h5 · Sep 27, 2011 at 10:37 PM 2
Share

Clearly he's not using the JS Array class when the question specifically mentions C#.

avatar image Bovine · Sep 28, 2011 at 06:48 AM 0
Share

Yep it is a C# array but thanks for the effort. I've edited the title to make it clear.

I'm actually having to infer a tile is null because the tiles that are null have particular properties, rather like your suggestion 2.

avatar image syclamoth · Sep 28, 2011 at 07:01 AM 0
Share

As I said above, you may as well make it explicit- just have a bool value which you use like you would use null status before.

avatar image Bovine · Sep 28, 2011 at 07:41 AM 0
Share

@syclamoth Given that I already have other data that infers the same thing, I am not going to bloat the structure with an additional bool.

avatar image syclamoth · Sep 28, 2011 at 07:49 AM 0
Share

It seems that you are trading between cpu time and memory usage- both things that are at a premium on iOS devices! I really can't claim to know much about iPhone optimisation, I'm afraid.

Show more comments

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

7 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

C#: Serialize a class as Field 1 Answer

Reorder array with nulls at the end 1 Answer

C#-XMLSerialize a Struct with Vector3 Array in it 2 Answers

Does XML Serialization Support "Class Object" Arrays? 1 Answer

Object says it's null, but on inspecting it seems to have the correct value 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges