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

Author Topic: Help with Tile Map tutorial  (Read 11109 times)

0 Members and 1 Guest are viewing this topic.

gop_t3r

  • Newbie
  • *
  • Posts: 35
    • View Profile
Help with Tile Map tutorial
« on: January 23, 2014, 10:26:16 pm »
Hi, I've been following the tile map tutorial on the SFML website and so far my tiles load very well, but one improvement I've been trying to make is having the map be loaded from a *.txt file. Essentially instead of having

I want to be able to contain those values in a *.txt file and load them so I don't have to keep recompiling. The problem is that when I create a raw array the amount of characters is fixed and I do not want that. I want to be able to store the characters from the text file into a vector array. I tried doing this but to not avail. I'd like to know how I can go about this and what I need to change either in the load() function to accommodate this.

From what I understand, a vertex array is based on a vector array, so by analogy my raw array would be treated as a vector array thereby dynamically allocating memory as required. Is this correct?

http://www.sfml-dev.org/tutorials/2.1/graphics-vertex-array.php

I've been following the tutorial mentioned in the foresaid link.

tl;dr:

1. I want to load a map from a .txt file while still being able to load tilesets in conjunction thereof. How can I implement this?
2. I want to use a vector array, rather than a raw array. (If necessary)
« Last Edit: January 26, 2014, 09:10:05 pm by gop_t3r »

Lignum

  • Newbie
  • *
  • Posts: 20
    • View Profile
Re: Help with Tile Map tutorial
« Reply #1 on: January 23, 2014, 10:51:33 pm »
The Vector equivalent of your array would be this:
std::vector< std::vector<int> > level;
(Space on the right is mandatory because the compiler likes to confuse it with the right shift bitwise operator: ">>")

You then would use the fstream header to read a .txt file and load it into the vector.

This is pseudocode for loading the map from the .txt file (written in browser, haven't tested):
std::vector< std::vector<int> > LoadLevel(const std::string& filename)
{
      std::vector< std::vector<int> > level;
      std::ifstream in(filename); // Open an input stream for the file 'filename'.

      if (!in || in.fail())
      {
           std::cerr << "Couldn't load level " << filename << "!\n";
      } else
      {
           std::string height;
           getline(in, height);

           int mapHeight = atoi(height.c_str());
     
           for (int y = 0; y < mapHeight; ++y)
           {
               std::vector<int> row;
               std::string line;
               getline(in, line); // Get the next line in the stream.

               // Iterate over every character in the read line and put its numeric
               // value in our row.
               for (int x = 0; x < line.length(); ++x)
               {
                  row.push_back(atoi(line[x].c_str())); // Unless you know nobody's going to fool around,
                                                                 // you should check whether the character is actually
                                                                 // a number you should do some error checking.
               }

               level.push_back(row); // Put the parsed row into our level.
          }

          in.close(); // Free resources. This is important! We don't want any memory leaks.
      }

      return level;
}

Usage:
std::vector< std::vector<int> > level = LoadLevel("level.txt");
if (level.is_empty())
{
    // Level didn't load.
}

// Getting a tile at the coordinates (3, 2).
// Note that the coordinates are swapped.
int tileAt3_2 = level[2][3];

Example .txt file (6x6 level):
6    <-- This is the level's height.
333333
303003
303003
333003
300003
333333
« Last Edit: January 23, 2014, 10:58:31 pm by Lignum »

G.

  • Hero Member
  • *****
  • Posts: 1593
    • View Profile
Re: Help with Tile Map tutorial
« Reply #2 on: January 23, 2014, 10:52:38 pm »
What have you tried and what was wrong with it?

In your txt file you need the width, height and content of your map.

Open your file.
Read the width, read the height.
Then instead of using const int level[] use a std::vector<int> level. Read the content of your map in your txt one by on, and append each int to your level vector.

Pass the width, height, and level to the load function.
The load and draw functions do not change except that the load function takes a std::vector<int> instead of a int*

gop_t3r

  • Newbie
  • *
  • Posts: 35
    • View Profile
Re: Help with Tile Map tutorial
« Reply #3 on: January 23, 2014, 11:05:44 pm »
I don't think you're following me. I am trying to get this function on the SFML tutorial:

to load maps from a *.txt file. I want to pass a vector array to
const int* tiles
instead of a raw array. Not only that, but instead of loading the map from within the code, I want it loaded from a *.txt file. I am familiar with vectors and file input/output, but on the first sign of declaring something like:

Since I can't do this I might have to explore other options available, hence I need help.
« Last Edit: January 26, 2014, 09:10:36 pm by gop_t3r »

Lignum

  • Newbie
  • *
  • Posts: 20
    • View Profile
Re: Help with Tile Map tutorial
« Reply #4 on: January 23, 2014, 11:21:11 pm »
Make the 3rd parameter of the load function a std::vector<int> instead of a const int*. Then use my code to load the map from a .txt file. You need to modify it a little bit to return a std::vector<int> rather than a std::vector< std::vector<int> > but other than that, that's it.

gop_t3r

  • Newbie
  • *
  • Posts: 35
    • View Profile
Re: Help with Tile Map tutorial
« Reply #5 on: January 24, 2014, 01:00:37 am »
Meh, am back in square one again. :-P No pun intended.

The code you wrote for reading values in a file was a bit complex and it didn't work out with my code. I would've written. Essentially your function returns a vector, but the one I've shown returns a boolean.

Kojay

  • Full Member
  • ***
  • Posts: 104
    • View Profile
Re: Help with Tile Map tutorial
« Reply #6 on: January 24, 2014, 04:52:06 pm »
          in.close(); // Free resources. This is important! We don't want any memory leaks.
 

Do I need to manually close a ifstream?

Given a text file such as this:

Code: [Select]
0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1
0 1 1 1 1 1 1 0 0 0 0 2 0 0 0 0
1 1 0 0 0 0 0 0 3 3 3 3 3 3 3 3
0 1 0 0 2 0 3 3 3 0 1 1 1 0 0 0
0 1 1 0 3 3 3 0 0 0 1 1 1 2 0 0
0 0 1 0 3 0 2 2 0 0 1 1 1 1 2 0
2 0 1 0 3 0 2 2 2 0 1 1 1 1 1 1
0 0 1 0 3 2 2 2 0 0 0 0 1 1 1 1

The following will read from it (it is the same as the tutorial example, except that it reads from a file instead of an array):

#include <SFML/Graphics.hpp>
#include <fstream>
#include <iterator>
#include <sstream>

class TileMap : public sf::Drawable, public sf::Transformable
{
public:

    bool load(const std::string& tileset, sf::Vector2u tileSize)
    {
        // load the tileset texture
        if (!m_tileset.loadFromFile(tileset))
            return false;

        std::vector<std::vector<int> > tiles;
        std::ifstream map_stream("map.txt");
        for (std::string line; std::getline(map_stream, line);){
            tiles.push_back(std::vector<int>());
            std::stringstream line_stream(line);
            std::copy(std::istream_iterator<int>(line_stream), std::istream_iterator<int>(), std::back_inserter(tiles.back()));
        }

        unsigned int height = tiles.size();

        m_vertices.setPrimitiveType(sf::Quads);


        // populate the vertex array, with one quad per tile
        for (unsigned int j = 0; j < height; ++j){
            unsigned int width = tiles[j].size();
            for (unsigned int i = 0; i < width; ++i)
            {
                // get the current tile number
                int tileNumber = tiles[j][i];

                // find its position in the tileset texture
                int tu = tileNumber % (m_tileset.getSize().x / tileSize.x);
                int tv = tileNumber / (m_tileset.getSize().x / tileSize.x);

                // get a pointer to the current tile's quad
                sf::Vertex quad[4];

                // define its 4 corners
                quad[0].position = sf::Vector2f(i * tileSize.x, j * tileSize.y);
                quad[1].position = sf::Vector2f((i + 1) * tileSize.x, j * tileSize.y);
                quad[2].position = sf::Vector2f((i + 1) * tileSize.x, (j + 1) * tileSize.y);
                quad[3].position = sf::Vector2f(i * tileSize.x, (j + 1) * tileSize.y);

                // define its 4 texture coordinates
                quad[0].texCoords = sf::Vector2f(tu * tileSize.x, tv * tileSize.y);
                quad[1].texCoords = sf::Vector2f((tu + 1) * tileSize.x, tv * tileSize.y);
                quad[2].texCoords = sf::Vector2f((tu + 1) * tileSize.x, (tv + 1) * tileSize.y);
                quad[3].texCoords = sf::Vector2f(tu * tileSize.x, (tv + 1) * tileSize.y);

                for (int k=0; k<4; ++k)
                    m_vertices.append(quad[k]);
            }
        }
        return true;
    }

private:

    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
    {
        // apply the transform
        states.transform *= getTransform();

        // apply the tileset texture
        states.texture = &m_tileset;

        // draw the vertex array
        target.draw(m_vertices, states);
    }

    sf::VertexArray m_vertices;
    sf::Texture m_tileset;
};

int main()
{
    // create the window
    sf::RenderWindow window(sf::VideoMode(512, 256), "Tilemap");

    // create the tilemap from the level definition
    TileMap map;
    if (!map.load("tileset.png", sf::Vector2u(32, 32)))
        return -1;

    // run the main loop
    while (window.isOpen())
    {
        // handle events
        sf::Event event;
        while (window.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
                window.close();
        }

        // draw the map
        window.clear();
        window.draw(map);
        window.display();
    }

    return 0;
}
 

No information about the map's dimensions is hardcoded. In fact, it is possible for some lines to have fewer elements and it will still work.

gop_t3r

  • Newbie
  • *
  • Posts: 35
    • View Profile
Re: Help with Tile Map tutorial
« Reply #7 on: January 25, 2014, 01:13:50 am »
Your code is nice, but I'm afraid it's not what I'm after because you are over-complicating what I really want. I firstly don't even understand half of what your code does so I can't use it; it would've been helpful to assume that I am not an advanced C++/SFML programmer like you else I wouldn't be making this thread! :-P Secondly, I prefer the function to contain the other parameters and be more more modular than that rather than hardcoding the map's location and other very important information.

So far I've modified my current function to instead use vectors of ints, rather than a const int*. The only last thing I ought to do is move all the values from the array into a file, and then load the values and continue from there. There is a dilemma though; it's possible that I have to replace a parameter in the load() function to also accept another fileLocation wherein the map's location is stored. I just need to find a way to store the values in a text file into a vector array (in this case vector<int>) and just continue on from there. If I wanted to load values within code I could always overload it; but for now I just want something like this:

I just don't feel comfortable using the code you've written because it's too advanced for me; no hard feelings!
« Last Edit: January 26, 2014, 09:10:53 pm by gop_t3r »

Kojay

  • Full Member
  • ***
  • Posts: 104
    • View Profile
Re: Help with Tile Map tutorial
« Reply #8 on: January 25, 2014, 09:57:22 pm »
I just need to find a way to store the values in a text file into a vector array (in this case vector<int>)

This does the trick:

std::vector<int> tiles;
std::ifstream map_stream("map.txt");
std::copy(std::istream_iterator<int>(map_stream), std::istream_iterator<int>(), std::back_inserter(tiles));
 

However, it means the number of columns will still be hardcoded.

std::copy is an algorithm copying the given range (defined by the first two parameters, which are two iterators) to the one supplied by the third parameter.
The istream_iterators specify a range covering the input stream of the text file
std::back_inserter(tiles) will append elements at the end of tiles - effectively the same as invoking push_back in a hand-written loop.

Yes it's fancy; you still have to help yourself.

i) if Lignum's function return a vector and you want one returning a boolean, write both and have the latter call the former
ii) The map's location doesn't got anything to do with the other parameters. If you wish to pass it, pass it.

This isn't a diary to blabber on about wants and needs nor is it a place to lay out requirements, unless you mean to hire us.

gop_t3r

  • Newbie
  • *
  • Posts: 35
    • View Profile
Re: Help with Tile Map tutorial
« Reply #9 on: January 26, 2014, 09:09:46 pm »
Sorry for the delayed response; I've made changes to my code as required and my tile loader now functions how I want it.

Thank you for your help.
« Last Edit: February 20, 2014, 11:05:03 pm by gop_t3r »

Kojay

  • Full Member
  • ***
  • Posts: 104
    • View Profile
Re: Help with Tile Map tutorial
« Reply #10 on: January 26, 2014, 10:08:23 pm »
Quote
Using algorithms was not required

Besides the point; all STL algorithms can be hand-written.

Quote
KISS
Yes, using the STL makes for far simpler and more robust code than the obscurities individual programmers come up with.