C# woes

A place for your artistic side. Discuss techniques and tools for pixel art on the NES, GBC, or similar platforms.

Moderator: Moderators

zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

C# woes

Post by zkip »

Hello all!

I'm trying to ultimately program a NES metatile editor with C# for this game I've been working on.. the only thing thus far I've been able to complete was drawing an 8x8 tile to a picturebox.. The whole C# class/stuct stuff really confuses me but I have a basic understanding of it. My question is what steps would I go through to program a routine to draw an entire CHR page rather than a single 8x8 tile.. It's probably simple and I'm over thinking it, but, I dunno. Than, I would like to know how to arrange those into an array of 8x8 tiles where I could draw a 16x16 tile by saying upper left for 16x16 tile 0x02 is 8x8 tile 0x1C and so on..

Thanks.
snarfblam
Posts: 143
Joined: Fri May 13, 2011 7:36 pm

Re: C# woes

Post by snarfblam »

zkip wrote:What steps would I go through to program a routine to draw an entire CHR page rather than a single 8x8 tile
Are you trying to draw a whole tile table, or a whole screen out of tiles?

Personally, I tend to use very simple metatile structs.

Code: Select all

struct MetaTile 
{
    public byte topLeft;
    public byte topRight;
    public byte bottomLeft;
    public byte bottomRight;
}
Some utility functions to extract metatiles from a byte array (and/or stream) and write them back are usually handy and simple to write.

As far as drawing screens made out of tiles, I've written plenty of NES level editors, and in my experience, GDI+ simply isn't fast enough with tiled images to make anything more than a bare-bones quick-and-dirty editor. If that's what you're after then you're all set. For my needs I ended up writing my own drawing code that supports drawing tile-based images with palettes. If you're interested, I'd be glad to share the code.

As far as actually drawing things out, I tend to use a set of classes along these lines:
  • GameScreen - Represents the data that defines a screen. The actual data would vary depending on whether you're using an object-based layout, a metatile-based layout, or something else.
  • NameTable - Represents an NES name table (32x30 tiles), tile and attribute data. The GameScreen would "draw" itself to the NameTable by telling it which tiles go where.
  • ScreenRenderer - Takes a NameTable and a tile source and renders a screen image to a bitmap.
Again, if you're doing something Q&D, you might not want to spend a ton of time architecturing. You could just declare a couple of arrays to represent your nametable and write a single function that loops over the nametable and draws each tile.

If it sounds like I'm severely over-thinking things (yeah... I tend to do that on occasion), it basically breaks down to this:

Code: Select all

class Example {
    byte[,] ntTiles = new byte[32,30];
    byte[,] ntPalettes = new byte[32,30]; // If applicable

    Bitmap screenImage = new Bitmap(256, 240, 32bppwhatever);
    Graphics gScreenImage; // Initialize with screenImage (if you're using GDI)
 
    void DrawScreen() {
        // lay out your nametable here
    }

    void RenderScreen() {
        for(tileY as int = 0; tileY < 30; tileY++) {
            for(tileX as int = 0; tileX < 32; tileX++) {
                DrawTile(tileX * 8, tileY * 8, ntTiles[tileX, tileY], ntPalettes[tileX, tileY]);
            }
        }
    }
    
    void DrawTile(blah blah) {
        // It sounds like you've got this figured out
    }
}
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: C# woes

Post by zkip »

Thanks a lot for writing such a detailed response.. but honestly I'm still sort of lost and so confused... :x
Yes, eventually my plan is a level editor but at the moment, I just need this metatile editor.

Also,

Code: Select all

    void DrawScreen() {
        // lay out your nametable here
    }
What exactly goes here??

Here is my 8x8 tile drawing routine:

Code: Select all

        private Bitmap _8x8(byte[] tileDat, Bitmap canvas, Color[] palDat)
        {
            byte plane0;
            byte plane1;
            byte final;

            for (int y = 0; y < canvas.Height; y++)
            {
                plane0 = 0;
                plane1 = 0;
                for (int x = 0; x < canvas.Width; x++)
                {
                    plane0 = 0;
                    plane1 = 0;
                    plane0 = Convert.ToByte(((tileDat[y]) & (0x80 >> x)) >> (7 - x));
                    plane1 = Convert.ToByte(((tileDat[y + 8]) & (0x80 >> x)) >> (7 - x));
                    final = Convert.ToByte((plane1 << 1) + plane0);
                    canvas.SetPixel(x, y, palDat[final]);

                }
            }
            return canvas;
        }
I'm not sure how to incorporate this into what you're talking about. I'm terribly sorry for the bother, but I'd really like to fully understand this.
snarfblam
Posts: 143
Joined: Fri May 13, 2011 7:36 pm

Re: C# woes

Post by snarfblam »

zkip wrote: Yes, eventually my plan is a level editor but at the moment, I just need this metatile editor.
Well then you can safely ignore most of what I said for now. One thing I'll re-emphasize is that GDI+ is a little slow, but sometimes workable. On the other hand, using SetPixel makes GDI+ look like greased lightning. If you really want to avoid the much-more-complicated but oh-so-much-faster approach of directly modifying raw image data, then you might be able to get away using SetPixel to load the tiles. You'll have to try and see how fast it is.

I would recommend drawing all your tiles to an image that is 8 pixels wide and 2048 (8 px * 256 tiles) pixels tall. Basically, you want to stack all your tiles together vertically. That way the location for tile x would be (0, 8 * x).

It looks like you're already almost there. All you need to do is accept a tile number as a parameter and adjust where you draw your pixels based on the tile number. If you use a vertical arrangement like I described:

Code: Select all

canvas.SetPixel(x, y + tileNumber * 8, palDat[final]);
I personally load the whole ROM into a single byte array, so if I snarf-ified your code a little it would look like this:

Code: Select all

Bitmap Load_8x8(int tileNum, byte[] rom, int tileOffset, Bitmap canvas, Color[] palDat)
        {
            byte plane0;
            byte plane1;
            byte final;

            for (int y = 0; y < 8; y++)
            {
                for (int x = 0; x < 8; x++)
                {
                    plane0 = Convert.ToByte(((rom[tileOffset + y]) & (0x80 >> x)) >> (7 - x));
                    plane1 = Convert.ToByte(((rom[tileOffset + y + 8]) & (0x80 >> x)) >> (7 - x));
                    final = Convert.ToByte((plane1 << 1) + plane0);
                    canvas.SetPixel(x, y + tileNum * 8, palDat[final]);

                }
            }
            return canvas;
        }
Then you can load a whole page of contiguous CHR like so:

Code: Select all

void LoadWholeChrPage(int chrOffset) {
    Bitmap chr = new Bitmap(8, 2048, PixelFormat.32bitgoodness);

    for(i as integer = 0; i < 256; i++) {
        int tileOffset = chrOffset + i * 16;
        Load_8x8(i, _theRom, tileOffset, chr, _thePalette);
    }
}
Hope that's a little more helpful.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: C# woes

Post by thefox »

Unless you absolutely want to write your own editor, you could try MapEd Pro, it has a metatile editor.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: C# woes

Post by zkip »

Ugh, I'm terribly sorry.. I just can't grasp anything :oops: .. I finally understand this, however, now I can't figure out how to define each 8x8 as separate tiles to use to construct a 16x16 tile in a separate picture box. Would this be the metatile structs you referred to earlier?

Thanks again, and sorry for so much bother.
snarfblam
Posts: 143
Joined: Fri May 13, 2011 7:36 pm

Re: C# woes

Post by snarfblam »

It sounds like you're pretty new to C#. I'm not sure whether you're getting stuck on the C# side of things or the underlying concepts. I don't mind in the least trying to help, but right now I'm just basically guessing which blanks need to be filled in.

I know you probably already understand this, but just to walk through the logic... A complete CHR table contains 256 tiles. Each tile is identified by its index. So if you load your tiles into a vertical strip, the top tile would be tile 0, the next one down would be 1, and so on.

The metatile structure simply stores the four tile numbers that make up a metatile. So to store all your actual metatile data you could declare an array of MetaTile or a List<MetaTile>, depending on which one suits your needs better. An array might be simpler if you want a fixed number of metatiles. A List<MetaTile> would be easier if you want to be able to add/remove/insert MetaTile entries.

Code: Select all

List<MetaTile> metatiles = new List<MetaTile> ()

void Example() {
    // Create and initialize a metatile and add it to the list

    MetaTile mt = new MetaTile();
    mt.topLeft = 0x40;
    mt.topRight = 0x41;
    mt.bottomLeft = 0x50;
    mt.bottomRight = 0x51;

    metatiles.Add(mt);
}
The trickier part is presenting the data in the UI and letting the user manipulate it. If you want to draw a metatile, you'll probably want to start with a function that draws an individual tile by index.

Code: Select all

void DrawTile(Graphics target, byte tileIndex, int x, int y) {
    // Determine where we will grab the tile from, assuming tiles are stored in a vertical strip
    var sourceRect = new Rectangle(0, tileIndex * 8, 8, 8);

    // Determine where it will be drawn
    var destRect = new Rectangle(x, y, 8, 8);

    // Draw it
    target.DrawImage(tileSourceImage, destRect, sourceRect);
}
Then you can simply call this function four times to draw a metatile.

Code: Select all

void DrawMetatile(MetaTile mt, int x, int y) {
    DrawTile(mt.TopLeft, x, y);
    DrawTile(mt.TopRight, someX, someY);
    // ... and two more times for the bottom tiles
}

void Usage() {
    // Draw the first metatile (number 0) at the location 0,0
    DrawMetatile(metatiles[0], 0, 0);
}
Hopefully that clears things up a bit.
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: C# woes

Post by zkip »

Thank you so much snarfblam. :D I owe you man. I do have a few more questions though; :?

This is working like I wanted it to, however, any suggestions as to how to get everything all cleaned up and how to arrange it to where I have a page of metatiles displayed? Such as rows? I.E Row 0 contains metatiles 0-A etc.

Edit: Erm.. I see what you mean now about the slowness.. It takes ~20 secs. to draw 49 metatiles.. :? I'd like for you to explain the faster method if possible. Thanks again.
Jsolo
Posts: 27
Joined: Mon Jun 27, 2011 4:14 am
Location: Lurker Cave

Re: C# woes

Post by Jsolo »

How are you drawing the tiles? In my custom tool I can draw a whole screen of 240 16x16 metatiles many times per second without any slowdown at all, so it shouldn't take long to draw 50 metatiles. Have you tried profiling your code to find out where it's taking so long?
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: C# woes

Post by Dwedit »

In C#, if you still want to use the awful System.Drawing way of drawing images, at least use LockBits as your way to draw stuff.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: C# woes

Post by zkip »

Basically, I'm doing nothing more than what is already shared here in this thread... the 8x8 function to render the 8x8 and the DrawTile, and DrawMetatile functions from snarfblam to construct the full metatile.

Anyways, here is the whole shabang complete with trimmings.
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: C# woes

Post by Dwedit »

Don't use SetPixel, it's too slow. Instead use LockBits and Marshal.Copy along with an int array.
Something like this:

Code: Select all

            int[] pixels = new int[128 * 128];
            //your code here, set the pixels.  A value is like R + (G << 8) + (B << 16).  The index in the array = X + Y * width
            Bitmap bmp = new Bitmap(128,128,  System.Drawing.Imaging.PixelFormat.Format32bppRgb);
            var bits = bmp.LockBits(new Rectangle(0, 0, 128, 128), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
            Marshal.Copy(pixels, 0, bits.Scan0, pixels.Length);
            bmp.UnlockBits(bits);
            //assign bmp somewhere like to a picture box or panel
If you already have a bitmap to store the new image into, there's no need to create a new one.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: C# woes

Post by zkip »

Erm.. could it really be ONLY the SetPixel doing this?? I just tried a class from here. And it seems like it's even slower... :x

By doing this

Code: Select all

                    LockBitmap lockBitmap = new LockBitmap(canvas);
                    lockBitmap.LockBits();
                    lockBitmap.SetPixel(x, y + tileNum * 8, palDat[final]);
                    lockBitmap.UnlockBits();
in the Load_8x8 function and using that class it still hasn't loaded yet so it's slower apparently.....
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: C# woes

Post by Dwedit »

Basically, you need to make ALL your drawing happen on the int array, then at the very very end, you copy that to a bitmap. So you won't be making a 8x2048 image, and doing rectangle copies or anything like that.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: C# woes

Post by zkip »

Dwedit wrote:Basically, you need to make ALL your drawing happen on the int array, then at the very very end, you copy that to a bitmap. So you won't be making a 8x2048 image, and doing rectangle copies or anything like that.
Argh... I'm so confused.. I'm sorry, but then what exactly do I need to do. Get rid of the .DrawImage parts?? I'm so lost.. :?

I'm sorry for so much bother on everyone and thanks.
Post Reply