1
\$\begingroup\$

I'm developing a 2D tile based game and I have a few questions regarding it.

First I would like to know if this is the correct way to structure my Tile class:

namespace TileGame.Engine {
    public enum TileType {
        Air,
        Stone
    }

    class Tile {
        TileType type;
        bool collidable;

        static Tile air = new Tile(TileType.Air);
        static Tile stone = new Tile(TileType.Stone);

        public Tile(TileType type) {
            this.type = type;

            collidable = true;
        }
    }
}

With this method I just say world[y, x] = Tile.Stone and this seems right to me but I'm not a very experienced coder and would like assistance.

Now the reason I doubt this so much is because I like everything to be as optimized as possible and there is a major flaw in this that I need help overcoming. It has to do with saving and loading... well more on loading actually. The way it's done relies on the principle of casting an enumeration into a byte which gives you the corresponding number where its declared in the enumeration. Each TileType is cast as a byte and written out to a file. So TileType.Air would appear as 0 and TileType.Stone would appear as 1 in the file (well in byte form obviously). Loading in the file is alot different though because I can't just loop through all the bytes in the file cast them as a TileType and assign it:

for(int x = 0; x < size.X; x++) {
     for(int y = 0; y < size.Y; y+) {
          world[y, x].Type = (TileType)byteReader.ReadByte();
     }
}

This just wont work presumably because I have to actually say world[y, x] = Tile.Stone as apposed to world[y, x].Type = TileType.Stone. In order to be able to say that I need a gigantic switch case statement (I only have 2 tiles but you could imagine what it would look like with hundreds):

Tile tile;
for(int x = 0; x < size.X; x++) {
     for(int y = 0; y < size.Y; y+) {
          switch(byteReader.ReadByte()){
               case 0:
                    tile = Tile.Air;
                    break;
               case 1:
                    tile = Tile.Stone;
                    break;
          }
          world[y, x] = tile;
     }
}

Now you can see how unoptimized this is and I don't know what to do. I would really just like to cast the byte as a TileType and use that but as said before I have to say world[y, x] = Tile.whatever and TileType can't be used this way.

So what should I do? I would imagine I need to restructure my Tile class to fit the requirements but I don't know how I would do that. Please help!

Thanks.

\$\endgroup\$
3
  • \$\begingroup\$ Nevermind the switch statement, you're already going to have a crazy amount of static field variables. Why do you want to do it that way? Why not just make the TileType a normal field variable and load it from binary as you said? Also, I don't see how this works at all: world[y, x] = Tile.Stone \$\endgroup\$ Commented Jun 9, 2012 at 4:34
  • \$\begingroup\$ I should have made this more clear but the world is a 2D array of Tile. \$\endgroup\$ Commented Jun 9, 2012 at 15:28
  • \$\begingroup\$ @MrPlosion1243 I assume you also intended to show that you'd defined a few static objects: Air and Stone. \$\endgroup\$
    – House
    Commented Jun 9, 2012 at 15:49

2 Answers 2

5
\$\begingroup\$

It's been a while since I used C#, but I think you can structure your class something like this:

public static class Tile {
    public static enum TileType {
        Air,
        Stone
    }
    public static bool IsSolid(TileType tile) {
        switch(tile) {
            case Air:
                return false;
            case Stone:
                return true;
        }
    }
}

Then just fill your world[y, x] with the type TileType and fill it up like so:

for(int x = 0; x < size.X; x++) {
     for(int y = 0; y < size.Y; y+) {
          world[y, x] = (Tile.TileType)byteReader.ReadByte();
     }
}

Then when you want to test collision you can just do this:

if(Tile.IsSolid(world[y,x])) {
   //collide with tile
}

Finally I don't think you should worry so much about optimizing right now. You can optimize forever. You'll never finish your game like that! You can worry about making it neat and clean, but not necessarily optimal. If something is really bugging you, just add a little comment for later defining how important you think the optimization is:

//TODO optimize this! Level 3

Then when you find things going slow, check your to-dos, tackling the highest level ones first.

\$\endgroup\$
3
  • 5
    \$\begingroup\$ "You can optimize forever. You'll never finish your game like that!" so very true! \$\endgroup\$
    – Kromster
    Commented Jun 9, 2012 at 5:29
  • \$\begingroup\$ Ok I see. Quick question though should I do the same thing and have a switch case statement for something like if the tile does damage or the amount of damage it does or anything else where each tile needs specific data? \$\endgroup\$ Commented Jun 9, 2012 at 15:25
  • \$\begingroup\$ Yep, as long as that information is general to the tile type and not to the specific tile. If you want information to be specific to the tile you'll either have to create a second array to hold that or you'll need to use something other than TileType in your world array. But everything that is common among all tiles of a specific type should be handled the same way as the solid attribute. \$\endgroup\$
    – House
    Commented Jun 9, 2012 at 15:45
0
\$\begingroup\$

There is no right way to optimize loading code other than simply compressing your files. We've benchmarked and shown that loading uncompressed TGA files (textures that can be directly loaded into the GPU with no CPU transformation necessary) is waaaay slower than loading compressed PNGs (which need several CPU-side passes to decompress before loading into the GPU).

The simple fact is that your CPU is several orders of magnitude faster than your storage device (even SSDs), and any loading/saving code is going to be I/O bound, not CPU bound.

Instead of worrying about byte conversions, worry about making your I/O code streamable. Meaning, process data as it is read in, rather than needing to wait for it all to be loaded and then processing it. The processing time will then end up being completely hidden by the disk I/O.

I'm not a C# expert so I don't know the proper APIs off the top of my head. I do know that the APIs are available, and Googling ".net streaming io" should find them for you.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .