0

I'm currently creating a 2d tile game and I'm wondering if the tiles has to move or the character.

I ask this question because I already have created the "2d tile map" but it is running too slow, and I can't fix it. I tried everything now and the result is that I get 30 fps.

The reason that it is running too slow is because every 1ms with a timer, the tiles are being redrawn. But I can't figure out how to fix this problem.

This is how I make the map :

        public void makeBoard()
    {
        for (int i = 0; i < tileArray.GetLength(0); i++)
        {
            for (int j = 0; j < tileArray.GetLength(1); j++)
            {
                tileArray[i, j] = new Tile() { xPos = j * 50, yPos = i * 50 };
            }
        }
    }

Here I redraw each 1ms or higher the tiles and sprites :

        private void Wereld_Paint_1(object sender, PaintEventArgs e)
    {
        //label1.Text = k++.ToString();
        using (Graphics grap = Graphics.FromImage(bmp))
        {
            for (int i = 0; i < tileArray.GetLength(0); i++)
            {
                for (int j = 0; j < tileArray.GetLength(1); j++)
                {
                    grap.DrawImage(tileArray[i, j].tileImage, j * 50, i * 50, 50, 50);
                }
            }
            grap.DrawImage(player.movingObjectImage, player.xPos, player.yPos, 50, 50);
            grap.DrawImage(enemyGoblin.movingObjectImage, enemyGoblin.xPos, enemyGoblin.yPos, 50, 50);

            groundPictureBox.Image = bmp;
           // grap.Dispose();

        }
    }

This is the Timer with a specific interval :

        private void UpdateTimer_Tick(object sender, EventArgs e)
    {
        if(player.Update()==true) // true keydown event is fired
        {
            this.Invalidate();
        }
        label1.Text = lastFrameRate.ToString(); // for fps rate show
        CalculateFrameRate(); // for fps rate show
    }
halfer
  • 19,824
  • 17
  • 99
  • 186
M Yil
  • 877
  • 1
  • 14
  • 35
  • Only draw the tiles that are supposed to appear on screen, and use a camera to help you draw stuff: http://stackoverflow.com/questions/9997006/slick2d-and-jbox2d-how-to-draw?answertab=votes#tab-top – user3814613 Jul 16 '16 at 00:12
  • @user3814613 First of all, thanks for your comment! I dont really understand what you are saying. I have to redraw all of the tiles because i want the tiles to move so it will look like the player is walking around the "world". Also, i have never used the camera function and i wouldnt know where to start with it. – M Yil Jul 16 '16 at 00:16
  • Did you read the post that was linked in that comment? If you had, you wouldn't have to say *i wouldnt know where to start with it*. People provide links to other questions to help; it's not very nice to ignore them after they've done the work to find it for you. – Ken White Jul 16 '16 at 00:35
  • @Ken White I did read it and it was about Java. C# Doesn't have a camera control as far as I know. That is why I asked how I should implement the "camera controle" or redrawing every tile with good performance. Maybe you can help me ? : ) – M Yil Jul 16 '16 at 00:58
  • Ignore the language the question is about, and read the **concepts** explained in the linked answer. It contains a lot of useful information about how to handle drawing. Think of *camera* as *the point of view of the user* - it's what's *in front of the lens* if you were filming a movie. Read to understand the ideas; it's not always about the coding language being used. – Ken White Jul 16 '16 at 01:03
  • @Ken White I get what he is trying to say but I really don't know how to implement it. I know how to place the sprite on the center of the screen and moving all the tiles and i also know that this should be the correct way building the game. But the thing is, it is running very slow and i really don't know how i can fix it. The concept i want to use is like this. When i press the left keybutton, the map will go to the right and i will place a new collum on the left side of the map and delete the right side of it. So it will look like an never ending map. – M Yil Jul 16 '16 at 01:21
  • @Ken White Or is it just not possible with only the materials i use (vosiul studio / wndows form application)? I really hoped someone could help me with this one, i have been trying this for days now and i keep hanging at 30 fps and i can't imagine how the frames will drop if i add objects to the map. Do you have any more tips for me? – M Yil Jul 16 '16 at 01:24
  • You do know they write entire books about game programming in C#, right? It might be a good idea to invest in one. :-) There are also specific versions of Visual Studio for game programming. I highly doubt you'll find that any games use WinForms as a base; they all use DirectX instead, because it's built specifically for the purpose of providing high performance graphics on Windows platforms. – Ken White Jul 16 '16 at 01:28
  • @Ken White You are right. I will get a book tomorrow to learn from it : ) But i can't just stop my project, I have to make it work or else i have done all for nothing and it will demotivate me : / – M Yil Jul 16 '16 at 01:35
  • I didn't say to stop your project; whether you do that or not is up to you. I'm simply saying that your outcome is most likely not going to be what you want, because you started off on the wrong path from the beginning. For future reference, a quick search for *game development visual studio* turned up [this page at MS](https://www.visualstudio.com/en-us/features/game-development-vs.aspx) as the very first result, and it has links for many other resources (including samples and tutorials). – Ken White Jul 16 '16 at 01:39
  • I do not use your language/IDE so just few hints to check: **1. check if `grap.DrawImage` is not scaling** try to use copy rectangle or non scalable Draw method (scaling is usually slow even if size ratio is 1:1) **2. `player.Update()` is method not variable and you are not setting it to false after redraw** and lastly **3. instead of `this.Invalidate();` you should call rendering directly to your backbuffer.** And inside repaint of your paintbox event just copy actual backbuffer to it. – Spektre Jul 17 '16 at 07:26
  • There are also few minor performance problems (but not that significant) 1. you got single pointer per tile instead of one 2D array of tiles which is usually faster. 2. paintbox sometimes have tendency slows down the output by its own backbufer and or flickers if not updated/invalidated properly anyway accessing Canvas of the Form directly is usually much faster. I would measure how much it take to process the world repaint for loop (use performance counter). I do not see how are you computing fps it is possible you are measuring something different then you think. – Spektre Jul 17 '16 at 07:31
  • @Spektre Thank you again for all your help. I got a code for the fps but even without that, if i look with my own eyes i see it not moving fluent. But when i don't draw the whole map every 1ms then i see it moving slower and flikkering. I will do a little research about backbuffer and the scaling part. – M Yil Jul 17 '16 at 15:01

2 Answers2

1

As mentioned in the comments your concept is wrong. So here's just a simple summary of how to do this task:

  1. Tile map is static

From functional point of view does not matter if player moves or the map but from performance point of view the number of tiles is hugely bigger then number of players so moving player is faster.

To achieve player centered or follow views you have to move the camera too.

  1. rendering

Repainting every 1ms is insane and most likely impossible on nowadays computers if you got medium complexity of the scene. Human vision can't detect it anyway so there is no point in repainting more that 25-40 fps. The only reason for higher fps needs is to be synchronized with your monitor refreshing to avoid scan line artifacts (even LCD use scan lines refreshing). To have more fps then the refresh rate of your monitor is pointless (many fps players would oppose but our perception is what it is no matter what they say).

Anyway if your rendering took more then 1ms (which is more then likely) then your timer is screwed because it should be firing several times before the first handler even stops. That usually causes massive slowdowns due to synchronisation problems so the resulting fps is usually even smaller then the rendering engine could provide. So how to remedy that?

  1. set timer interval to 20ms or more
  2. add bool _redraw=false And use it to redraw only when you need to repaint screen. So on any action like player movement, camera movement or turn, animation change set it to true
  3. inside timer event handler call your repaint only if _redraw==true and set it to false afterwards.

This will boost performance a lot. even if your repaint will take more than the timer interval still this will be much much faster then your current approach.

To avoid flickering use Back buffering.

  1. camera and clipping

Your map is most likely much bigger then the screen so there is no point to repaint all the tiles. You can look at the camera as a means to select the right part of your map. If your game does not use rotations then you need just position and may be zoom/scale. If you want rotations then 2D 3x3 homogeneous matrices are the way.

Let assume you got only position (no zoom or rotating) then you can use this transformations:

    screen_x=world_x-camera_x
    screen_y=world_y-camera_y

    world_x=screen_x+camera_x
    world_y=screen_y+camera_y

So camera is your camera view position, world is you tile position in map grid and screen is the position on screen. If you got indexes of your tile in map then just multiply them by tile size in pixels to obtain the world coordinates.

To select only visible tiles you need to obtain the corner positions of your screen, convert them into world coordinates, then into indexes in map and finally render only tiles inside rectangle that these points form in your map + some margin of error (for example render 1 tile enlarged rectangle in all directions). This way the rendering will be independent on your map size. This process is called clipping.

I strongly recommend to look at these related QAs:

The demo in the linked QAs use only GDI and direct pixel access to bitmaps in win32 form app so you can compare performance with your code (they should be similar) and tweak your code until it behaves as should.

halfer
  • 19,824
  • 17
  • 99
  • 186
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • First off all, thank you for all of your help : ) I have done step 2 completely but i don't need to to step 3 now. I got an idea how to do that. But first of all, i wanted to get rid of de 30 fps p/s. Do you have tips to achieve around 50/60 fps with drawing the complete map at every 20ms? I'm only drawing the map as big as the screen so no tiles come outside it. Later ofcourse i will implement the rest... Thanks for your time ! – M Yil Jul 16 '16 at 17:58
  • @MYil in the first linked QA is also my source code for the demo which is fast enough. Without seeing how are you rendering is hard to tell where the bottleneck is... how are you rendering sprites? or do you got just tiles (no transparency or masks)? anyway wrongly coded gfx rendering can be painfully slow ... – Spektre Jul 16 '16 at 20:43
  • Thanks for your answer! Oke, ill try to be as clear as possible : ) I have a 2d array filled with tiles. Those Tiles are classes and in those classes there is a image. That image is around 1kb. I repaint with a invalidate every 1ms(or 20ms, gives the same performance) the images to a bitmap. and paint that on a big picturebox. The bitmap size is as big as the picturebox size. The redrawing is done each time when i press a keydown event. Then the timer will open the movement method in the sprite class and will make it move. Let me place a porition of my code, hang on :) – M Yil Jul 16 '16 at 21:04
1

Are you writing the tile implementation yourself? Probably the issue is that at every frame you're drawing all tiles.

2D engines with scrolling tiles should draw tiles on a larger sprite than the screen, then draw that sprite around which is a fast operation (you'd need to specify the language you're using so I can provide some hint on how to actually make that fast - basically an in video memory accelerated blit, but every language has it's way to make it happen)

enter image description here

when the border of this super-sprite is closer to the screen border than a threshold (usually half tile), the larger sprite is redrawn around the current position - but there is no need to draw all the tiles on this! start copying the supersprite on this recentered sprite and you only need to draw the tiles missing from the previous supersprite because of the offset.

enter image description here

Lorenzo Boccaccia
  • 6,041
  • 2
  • 19
  • 29
  • 1
    forgot to mention this in my answer at first. so +1. btw this is called back buffering or double buffering instead of super sprite ... My understanding is that super sprite is used to combine layers of sprites to improve performance of animations on per tile basis and not as shadow screen ... but the idea is correct – Spektre Jul 16 '16 at 09:09
  • back/double buffering has a precise meaning and it's having a back buffer being drawn every frame and swapped for the front buffer - here you build a prenrendered sheet, which you can blit on the back or front buffer afterward - so it's orthogonal - but the idea is similar, have the graphic card push the most pixel instead of the cpu – Lorenzo Boccaccia Jul 16 '16 at 09:16
  • Hmm I think you're right the super scroll shadow screen does not fit the bill exactly ... – Spektre Jul 16 '16 at 09:18
  • @Spektre Hi there. Thanks for your help :) I wasn't home currently but now I am. I will study this page and will notice you if i need more help. U were asking me about what language i use : c#. And i only draw tiles when it fits in the screen so not more than that. – M Yil Jul 16 '16 at 17:40
  • also have a look here, related https://docs.google.com/document/d/1iNSQIyNpVGHeak6isbP6AHdHD50gs8MNXF1GCf08efg/pub – Lorenzo Boccaccia Jul 27 '16 at 13:24