DEV Community

Noah11012
Noah11012

Posted on • Updated on

Using SDL2: Viewports

When rendering a scene or world not everything will be inside the boundaries of the window. Only a partial region can be rendered and displayed to the user at a time. In SDL (and generally in GC) this region is also known as the viewport. Anything within the viewport is rendered and everything outside is ignored. Many games have a character that can move and perform operations within in a world. Depending on where the character moves, the viewport must move with it to keep the character in focus.

As an example to demonstrate viewports in SDL, we will have a program that can zoom and move around in a world that is basically an image of a house and tree.

In previous posts, we have the Appplication class that handles window events, updating entities and lastly rendering. However, unlike in the previous installments, we will have an extra method named handle_zoom(). This method handles the user zoom when the event SDL_KEYDOWN is detected.

void Application::loop()
{
    bool keep_window_open = true;
    while(keep_window_open)
    {
        while(SDL_PollEvent(&m_window_event) > 0)
        {
            switch(m_window_event.type)
            {
                case SDL_QUIT:
                    keep_window_open = false;
                    break;

                case SDL_KEYDOWN:
                    handle_zoom();
            }
        }

        update(1.0/60.0);
        draw();
    }
}

Because I decided on using transparency for the image's background, I had to use the SDL2 image extension library for opening PNG images. In the constructor, the image library is initialized and the image we will use is loaded into memory.

Application::Application()
{
    // ...

    m_render_viewport = { 0, 0, 680, 480 };

    IMG_Init(IMG_INIT_PNG);

    SDL_Surface *image_surface = IMG_Load("world.png");
    m_image = SDL_CreateTextureFromSurface(m_window_renderer, image_surface);
    SDL_FreeSurface(image_surface);
}

In past articles when using an image, we loaded the image's pixel data into an SDL_Surface *, converted it into an SDL_Texture, and finally, queried for the texture's width and height. We do the first two here, but not the last. m_render_viewport takes care of what part of the image gets rendered onto the screen.

Now finally, I can show the method that makes all the magic happens:

void Application::handle_zoom()
{
    unsigned char const *keys = SDL_GetKeyboardState(nullptr);
    if(keys[SDL_SCANCODE_UP])
    {
        m_render_viewport.w += 5;
        m_render_viewport.h += 5;
    } else if(keys[SDL_SCANCODE_DOWN])
    {
        m_render_viewport.w -= 5;
        m_render_viewport.h -= 5;
    }
    else if(keys[SDL_SCANCODE_W])
    {
        m_render_viewport.y += 20;
        if(m_render_viewport.y > 0)
            m_render_viewport.y = 0;
    } else if(keys[SDL_SCANCODE_S])
    {
        m_render_viewport.y -= 20;
        if(m_render_viewport.y < -720)
            m_render_viewport.y = -720;
    } else if(keys[SDL_SCANCODE_A])
    {
        m_render_viewport.x += 20;
    } else if(keys[SDL_SCANCODE_D])
    {
        m_render_viewport.x -= 20;
        if(m_render_viewport.x < -1280)
            m_render_viewport.x = -1280;
    }
}

This merely manipulates the viewports location and dimensions. In the Application::draw() method, we set the viewport and render.

void Application::draw()
{
    SDL_SetRenderDrawColor(m_window_renderer, 255, 255, 255, 255);
    SDL_RenderClear(m_window_renderer);
    SDL_RenderCopy(m_window_renderer, m_image, nullptr, nullptr);
    SDL_RenderSetViewport(m_window_renderer, &m_render_viewport);
    SDL_RenderPresent(m_window_renderer);
}

Compile and run.

Of course, you can move about and zoom in and out.

What's next

Nothing is explicitly scheduled for the next post, but you can think it as a surprise.

Github repository: https://github.com/Noah11012/sdl2-tutorial-code

Top comments (0)