Search code examples
c#winformsgridtilesprite-sheet

Basic tile map in winforms


I am making a program where you bassicly move from tile to tile in windows forms. So in order to do that, I wanted to use panels each panel has a tag. To detect collision.

So I have an image of my map. and I divided into multiple tiles. However now I have to drag 900 tiles onto panels.

This isn't very effective in 2 ways. First loading 900 textures isn't really a smart idea. Also it would take ages. So i wanted to use a spritesheet or tilemap. But how would I do that in winforms. I believe I have seen some people use a grid view or whatever. However im not sure how to do what I want to do.

What would be the best solution?

Thanks in advance!


Solution

  • For any serious gaming project WinForms is not the best platform. Either WPF or XNA or Unity are able to deliver high performance use of DirectX.

    But since you want to do it in Winforms here is a way to do it. It creates a whopping number of 900 PictureBoxes and loads each with a fraction of an source image:

    private void Form1_Load(object sender, EventArgs e)
    {
        int tileWidth = 30;
        int tileHeight = 30;
        int tileRows = 30;
        int tileCols = 30;
    
        using (Bitmap sourceBmp = new Bitmap("D:\\900x900.jpg"))
        {
            Size s = new Size(tileWidth, tileHeight);
            Rectangle destRect = new Rectangle(Point.Empty, s);
            for (int row = 0; row < tileRows; row++)
                for (int col = 0; col < tileCols; col++)
                {
                    PictureBox p = new PictureBox();
                    p.Size = s;
                    Point loc = new Point(tileWidth * col, tileHeight * row);
                    Rectangle srcRect = new Rectangle(loc, s);
                    Bitmap tile = new Bitmap(tileWidth, tileHeight);
                    Graphics G = Graphics.FromImage(tile);
                    G.DrawImage(sourceBmp, destRect, srcRect, GraphicsUnit.Pixel);
                    p.Image = tile;
                    p.Location = loc;
                    p.Tag = loc;
                    p.Name = String.Format("Col={0:00}-Row={1:00}", col, row);
                    // p.MouseDown += p_MouseDown;
                    // p.MouseUp += p_MouseUp;
                    // p.MouseMove += p_MouseMove;
                    this.Controls.Add(p);
                }
        }
    
    }
    

    When I tried it I was a bit worried about perfomance, but..

    • This takes under 1 second to load on my machine.
    • Starting the programm adds 10MB to VS memory usage. That is like nothing.

    For a fun project this will do; for best performance one might use Panels but these will have to be filled and refilled in the Paint event. This solution saves you the hassle and since you don't change the tile picture all the time this works well enough.

    Pleae note: I have added a Name and a Tag to each PictureBox, so you can later refer to it. These both contain info about the original position of the Picturebox. The Name looks like this: Col=23-Row=02 and the Tag is the original Location object.

    Also: Dynamically added controls take a little extra to script since you can't create their method bodies in the designer. Instead you add them like above. In doing so Intellisense and the Tab key are your best friends..

    I have added three event handlers for a few mouse events. When you uncomment them you will have to add the methods like e.g. this:

    void p_MouseMove(object sender, MouseEventArgs e)
    {
        throw new NotImplementedException();
    }
    

    But maybe you want to use other events to play like Drag&Drop or keyboard events..

    There are two ways to refer to these tiles. Maybe you want to try and/or use both of them: You can loop over the form's controls with a

        foreach (Control ctl in this.Controls)
        { if (ctl is PictureBox ) this.Text = ((PictureBox)ctl).Name ; }
    

    It tests for the right type and then casts to PictureBox. As an example it displays the name of the tile in the window title.

    Or you can have a variable and set it in the MouseDown event:

    PictureBox currentTile;
    void p_MouseDown(object sender, MouseEventArgs e)
    {
        currentTile = (PictureBox ) sender;
    }