Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Tile Map engine - tearing problem  (Read 4160 times)

0 Members and 1 Guest are viewing this topic.

lrx

  • Newbie
  • *
  • Posts: 29
    • View Profile
Tile Map engine - tearing problem
« on: July 29, 2012, 08:06:54 pm »
Hello there!

I've been writing simple tile map engine for fun and learning purposes. I's been going well till I decided to walk off path pointed by tutorial I've been using and went and made diamond shaped map instead of staggered one. Some google'ing gave me decent code to base on. It's all working well aside from fact that during scrolling(up and right mostly) there are spaces between tiles. They however are never there when scrolling stops.
Problem persists regardless of draw() method being ran in different thread or at the end of "game loop".
Same deal with Vsynch on and off.
I feel there is no need to post whole code as it would be just too much so I'll include only drawing and event handling(game) methods.
I've tried to clean up code a bit and add comments every now and then, there is a lot of temporary code, flagged with //temp comment or just empty one //

All help and tips and constructive critique will be appreciated. If it turns out whole code is needed let me know  and I will include it.

Since its missing from included code and is not obviously named, "test" is sf::RectangleShape and is used to imitate window bounds to see how many tiles are drawn out of it.

Language: c++
SFML: 2.0 RC

void Game::Draw()
{
        //temp fps
        int counter=0;
        int fps3 = 0;
        sf::Text fpsText;
        sf::Clock clk;
        //
        window.setActive(true);
        window.setVerticalSyncEnabled(false);
       
        //first tiles to draw
        int startX, startY;
        //futherest tiles to draw
        int maxX, maxY;
        //minimum X index to draw
        int minX;
        //check if futherest and min tiles were drawn
        bool mGotXmax;
        bool nGotXmin;

        //increse before reaching min/max, decrese after(bump)
        int nBuff, mBuff;
        //delay
        int n,m;


        while(window.isOpen())
        {
                window.clear();
                //initial values for every run
                if(tempDrawChange==1)
                {
                        //Fullscreen Calc here
                        minX = IndxFromIso(Cam->CameraPos.x, Cam->CameraPos.y).x-1;
                        maxX = IndxFromIso(Cam->CameraPos.x+window.getSize().x, Cam->CameraPos.y+window.getSize().y).x + 2;

                        maxY = IndxFromIso(Cam->CameraPos.x, Cam->CameraPos.y+window.getSize().y).y + 2;

                        startX = IndxFromIso(Cam->CameraPos.x+window.getSize().x, Cam->CameraPos.y).x+1;
                        startY = IndxFromIso(Cam->CameraPos.x+window.getSize().x, Cam->CameraPos.y).y;
                }
                else
                {
                        //rect calc here
                        minX = IndxFromIso(Cam->CameraPos.x+test.getPosition().x, Cam->CameraPos.y+test.getPosition().y).x-1;
                        maxX = IndxFromIso(Cam->CameraPos.x+test.getPosition().x+test.getSize().x, Cam->CameraPos.y+test.getPosition().y+test.getSize().y).x+2;

                        maxY = IndxFromIso(Cam->CameraPos.x+test.getPosition().x, Cam->CameraPos.y+test.getPosition().y+test.getSize().y).y+2;

                        startX = IndxFromIso(Cam->CameraPos.x+test.getPosition().x+test.getSize().x, Cam->CameraPos.y+test.getPosition().y).x+1;
                        startY = IndxFromIso(Cam->CameraPos.x+test.getPosition().x+test.getSize().x, Cam->CameraPos.y+test.getPosition().y).y;
                }
               
                nBuff = 1;
                mBuff = 1;

                mGotXmax = false;
                nGotXmin = false;

                n=0;
                m=3;

                //drawing loops
                for(int y=startY; y<maxY; y++)
                {
                        for(int x=startX-nBuff; x<startX+mBuff; x++)
                        {
                                //Drawing, multiple layers
                                if(x>0 && x< Map->MapW-1 && y>0 && y< Map->MapH-1)
                                {
                                        for(int i=0; i<Map->MapGrid[x][y].TileID.size(); i++)
                                        {
                                                sf::Vector2i position = IndxToIso(x, y);

                                                TileSprite.setTextureRect(TileSet.GetSourceRectangle(Map->MapGrid[x][y].TileID[i]));
                                                TileSprite.setPosition(position.x-Cam->CameraPos.x, position.y-Cam->CameraPos.y);
                                        }
                                }

                                window.draw(TileSprite);
                        }
                        //bump mechanics
                        if(startX+mBuff==maxX)
                                mGotXmax = true;
                        if(startX-nBuff==minX)
                                nGotXmin = true;

                        if(mGotXmax)
                        {
                                if(m>0)
                                        m--;
                                else
                                        mBuff--;
                        }
                        else
                        {
                                mBuff++;
                        }

                        if(nGotXmin)
                        {
                                if(n>0)
                                        n--;
                                else
                                        nBuff--;
                        }
                        else
                        {
                                nBuff++;
                        }
                }
                //fps temp
                counter++;
                if(clk.getElapsedTime().asSeconds()>=1)
                {
                        fps3 = counter/clk.getElapsedTime().asSeconds();
                        counter=0;
                        clk.restart();
                        std::string sss;
                        char tempB[20];
                        sprintf(tempB, "%d", fps3);
                        sss = tempB;
                        fpsText.setString(sss);
                }
                //

                window.draw(fpsText);
                window.draw(test);
                window.display();
        }
};

void Game::Run()
{
        //temp pressed time
        bool a = true;
        sf::Clock clk;
        //

        window.setActive(false);

        //new thread for draw function
        sf::Thread RenderThread(&Game::Draw, this);
        RenderThread.launch();
       
        while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
                        if(event.type == sf::Event::KeyPressed)
                        {
                                controls.KeyPressed[event.key.code] = true;

                                //temp pressed time
                                if(a)
                                {
                                        a = false;
                                        clk.restart();
                                }
                        }
                        if(event.type == sf::Event::KeyReleased)
                        {
                                controls.KeyPressed[event.key.code] = false;
                               
                                //temp pressed time
                                std::cout<<"Pressed for "<<clk.getElapsedTime().asSeconds()<<std::endl;
                                a = true;

                                //temp draw mode
                                if(event.key.code == sf::Keyboard::F1)
                                        tempDrawChange*=-1;
                        }
                        if(event.type == sf::Event::MouseButtonPressed)
                        {
                                //temp for debug
                                sf::Vector2i index = IndxFromIso(event.mouseButton.x+Cam->CameraPos.x, event.mouseButton.y+Cam->CameraPos.y);
                                std::cout<<"Index: "<<index.x<<" "<<index.y<<std::endl;
                                std::cout<<"Mouse: "<<event.mouseButton.x<<" | "<<event.mouseButton.y<<std::endl;
                                std::cout<<"Camera: "<<Cam->CameraPos.x<<" "<<Cam->CameraPos.y<<std::endl;
                                std::cout<<"M-C: "<<event.mouseButton.x+Cam->CameraPos.x<<" "<<event.mouseButton.y+Cam->CameraPos.y<<std::endl<<std::endl;
                        }
        }

                if(controls.isPressed(sf::Keyboard::Escape))
                        window.close();

                if(controls.isPressed(sf::Keyboard::Right))
                {
                        Cam->CameraPos.x++;
                }
                if(controls.isPressed(sf::Keyboard::Left))
                {
                        Cam->CameraPos.x--;
                }
                if(controls.isPressed(sf::Keyboard::Down))
                {
                        Cam->CameraPos.y++;
                }
                if(controls.isPressed(sf::Keyboard::Up))
                {
                        Cam->CameraPos.y--;
                }
    }
};
 

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11037
    • View Profile
    • development blog
    • Email
Re: Tile Map engine - tearing problem
« Reply #1 on: July 29, 2012, 09:02:52 pm »
It's all working well aside from fact that during scrolling(up and right mostly) there are spaces between tiles. They however are never there when scrolling stops.
Can you ellaborate more on that or show some screenshots or a video? Because spaces between tiles doesn't have to be the same as tearing...

As for your code, it's actualy quite a bit code to dig through and trying to understand it etc. You should provid a fully working but minimal example of the problem.
Additionally I'd advice you to use sf::VertexArray for your tile map. It will boost your application speed and you'd only have to call window.draw() once (next to other benefits).
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

lrx

  • Newbie
  • *
  • Posts: 29
    • View Profile
Re: Tile Map engine - tearing problem
« Reply #2 on: July 29, 2012, 11:58:45 pm »
Ok I will look into Vertexes :) However I'm afraid problem doesn't lay there and it might not work. Whole idea is not to draw whole map but only visible part :) Regardless I'll look into it.
As for screen shots and minimal sample that, That's all I can make :( any less and it wouldn't replicate the problem.

Screens

To run this code you will need This tile set image
#include <SFML/Graphics.hpp>

sf::RenderWindow window;
sf::Sprite tile;
sf::Texture tileTexture;

int CamX = -400;
int CamY = -100;

sf::Vector2i IndxToIso(int x, int y)
{
        sf::Vector2i temp;
        //hardcoded valuse, TileSep X and Y, increse to create spacing between tiles
        temp.x = (x-y) * 32;
        temp.y = (x+y) * 16;

        return temp;
};

void Draw();

int main()
{
        //initialize window and all that junk
        tileTexture.loadFromFile("part4_tileset.png");
        tile.setTexture(tileTexture);

        window.create(sf::VideoMode(800, 600),"");
        window.setActive(false);

        //thread for draw function
        sf::Thread RenderThread(&Draw);
        RenderThread.launch();

        //siplificed Controllset
        bool KeyPressed[4] = {false, false, false, false};

        //GameLoop
        while(window.isOpen())
        {
                //Event Handling
                sf::Event event;
                while(window.pollEvent(event))
                {
                        if(event.type == sf::Event::KeyPressed)
                        {
                                if(event.key.code == sf::Keyboard::Left)
                                        KeyPressed[0] = true;
                                if(event.key.code == sf::Keyboard::Right)
                                        KeyPressed[1] = true;
                                if(event.key.code == sf::Keyboard::Up)
                                        KeyPressed[2] = true;
                                if(event.key.code == sf::Keyboard::Down)
                                        KeyPressed[3] = true;
                        }
                        if(event.type == sf::Event::KeyReleased)
                        {
                                if(event.key.code == sf::Keyboard::Left)
                                        KeyPressed[0] = false;
                                if(event.key.code == sf::Keyboard::Right)
                                        KeyPressed[1] = false;
                                if(event.key.code == sf::Keyboard::Up)
                                        KeyPressed[2] = false;
                                if(event.key.code == sf::Keyboard::Down)
                                        KeyPressed[3] = false;
                        }
                }

                //Game logic Update!
                if(KeyPressed[0])
                        CamX--;
                if(KeyPressed[1])
                        CamX++;
                if(KeyPressed[2])
                        CamY--;
                if(KeyPressed[3])
                        CamY++;
               
        }
}

void Draw()
{
        window.setActive(true);
        window.setVerticalSyncEnabled(false);
       
        while(window.isOpen())
        {
                window.clear();

                //drawing loops
                for(int y=0; y<20; y++)
                {
                        for(int x=0; x<20; x++)
                        {
                                //position where tile will be drawn based on index
                                sf::Vector2i position = IndxToIso(x, y);
                                //set rect on TileSet img to draw certain tile (size of single tile img)
                                tile.setTextureRect(sf::Rect<int>(0,0,64,64));
                                //set position of tile
                                tile.setPosition(position.x-CamX, position.y-CamY);

                                window.draw(tile);
                        }
                }
                window.display();
        }
};
 

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11037
    • View Profile
    • development blog
    • Email
Re: Tile Map engine - tearing problem
« Reply #3 on: July 30, 2012, 01:11:58 am »
From a supporter perspective I have to give you a big thanks! ;D
Most of the time when I/we request a full minimal example the troubled user will find all sorts of excuses not to provide such a minimal example, but you did without asking why/how/what/etc. Thanks! :)

Okay now for your problem. I had to add a few extra lines to even get that effect, after that it was pretty clear where the problem lays: Your updates and drawings happen in diffrent threads.

Now this isn't a multithreading problem but the problem is that your drawing and updating are not synced (= sequential = single threaded) and thus before you've moved all of your tiles to the new position you're already drawing them again, i.e. some tiles are already moved and some are still at the old position.

As I've implied above you shouldn't split the drawing and the updating part into two diffrent threads, because updating should always come before drawing and should get completly finished and not partial.

If you allow me, I'll make a few comments on the provided code:
  • The problem I had to fix, was that your movement isn't depending on the framerate, thus, since my PC is probably faster than yours, one key stroke moved the whole map off-screen. Always make your movements depend on the framerate.
  • Against many false beliefs from programmer (often with background in other programming languages) in C++ increment, decrement and also boolean operations are not guaranteed to be atomic operations, thus they also need multithread protections like mutexes. Which means you have a race condition for CamX and CamY.
  • The key checking you're making is complelty obsolten if you'd just use sf::Keyboard::isKeyPressed() instead. (Except if you really want to rely on key events, which you shouldn't.)
  • SFML provides a class sf::View that is potentially a 2D camera which lets you move around your 2D scene, without having to alter the sprites position. With that class you won't need to implement any movements for your tiles but just the movement for the view. (See the docs and my tutorial.)
« Last Edit: July 30, 2012, 01:15:30 am by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

lrx

  • Newbie
  • *
  • Posts: 29
    • View Profile
Re: Tile Map engine - tearing problem
« Reply #4 on: July 30, 2012, 02:08:11 am »
From a supporter perspective I have to give you a big thanks! ;D
Most of the time when I/we request a full minimal example the troubled user will find all sorts of excuses not to provide such a minimal example, but you did without asking why/how/what/etc. Thanks! :)
Well it's only logical that if I want to get helped I have to play by your rules :)

As to rest of your post :
  • "Framerate movement" -  Yes I am aware of that, just didn't bother just for sake of sample, even more since it still is not implemented in general project :)
  • That's news to me, I could have sworn they were atomic, but since I'm beginner I will have to believe you
  • I see, It seemed like good idea at time, since that's what I had been doing in my first small project in SDL. Will implement
  • That seems to be useful feature, will try to use that instead of my wanna be camera

Points taken thanks a lot.

And indeed putting sf::Keyboard::isKeyPressed() for camera movement in the same thread as drawing eliminated problem. Should have thought of that myself :-[

So I guess I should run all input that directly changes drawing place in same thread as Draw(), and all logic as what is happening over time/animations/"Computer Player Logic" in different thread?

I will toy with vertexes, Views and such tomorrow since it's kinda late already :)
Thanks again for help
« Last Edit: July 30, 2012, 02:18:57 am by lrx »