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

Author Topic: So, I want to save a tile map.  (Read 15493 times)

0 Members and 1 Guest are viewing this topic.

ineed.help

  • Jr. Member
  • **
  • Posts: 56
    • View Profile
So, I want to save a tile map.
« on: April 25, 2014, 07:49:45 pm »
So, I just watched a video which explained how to write a tile map in a .txt file and then load it, using C++ code and the SFML API.
I'm trying to make a level editor, however, so I also want to know how to export it to another .txt file, not only to import it.

Here is the code I'm using.

#include <SFML/Graphics.hpp>
#include <fstream>
#include <cctype>
#include <string>
#include <vector>
#include <sstream>

using namespace sf;

void LoadMap(std::string mapFileName, RenderWindow& window)
{
        std::ifstream LOADMAP(mapFileName);

        Texture tileTexture;
        Sprite tiles;

        std::vector<std::vector<Vector2i>> map;
        std::vector<Vector2i> tempMap;

        if(LOADMAP.is_open())
        {
                std::string tileLocation;

                LOADMAP >> tileLocation;

                tileTexture.loadFromFile(tileLocation);

                tiles.setTexture(tileTexture);

                while(!LOADMAP.eof())
                {
                        std::string str, value;

                        std::getline(LOADMAP, str);

                        std::stringstream stream(str);

                        while (std::getline(stream, value, '|'))
                        {
                                if (value.length() > 0)
                                {
                                        std::string xx = value.substr(0, value.find('/')).c_str();
                                        std::string yy = value.substr(value.find('/') + 1).c_str();

                                        int i, j, x, y;

                                        for (i = 0; i < xx.length(); i++)
                                        {
                                                if(!isdigit(xx[i])) { break; }
                                        }

                                        for (j = 0; j < yy.length(); j++)
                                        {
                                                if (!isdigit(yy[j])) { break; }
                                        }

                                        if (i == xx.length()) { x = atoi(xx.c_str()); }
                                        else { x = -1; }

                                        if (j == yy.length()) { y = atoi(yy.c_str()); }
                                        else { y = -1; }

                                        tempMap.push_back(Vector2i(x, y));
                                }
                        }

                        map.push_back(tempMap);
                        tempMap.clear();
                }
        }

        while (window.isOpen())
        {
                Event event;

                while (window.pollEvent(event))
                {
                        if (event.type == Event::Closed) { window.close(); }
                }

                window.clear();

                for (int i = 0; i < map.size(); i++)
                {
                        for (int j = 0; j < map[i].size(); j++)
                        {
                                if (map[i][j].x != -1 && map[i][j].y != -1)
                                {
                                        tiles.setPosition(j * 32, i * 32);

                                        tiles.setTextureRect(IntRect(map[i][j].x * 32, map[i][j].y * 32, 32, 32));

                                        window.draw(tiles);
                                }
                        }
                }

                window.display();
        }
}

And here is my tile map.

tiles.png
-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1
-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1
-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1
-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1
-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1
-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1
-1/-1|-1/-1|-1/-1|-1/-1|00/01|01/00|01/01|-1/-1|-1/-1|-1/-1
-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1|-1/-1
00/00|00/00|00/00|00/00|00/00|00/00|00/00|00/00|00/00|00/00

"tiles.png" is the name of the tile set, and everything loads correctly. The tiles are 32x32 pixels. As for the formatting, "|" separates each tile and "-1" is a tile without a texture. As for 00/01 and such, it's for a tileset I'm using (2x2 tiles currently), so 00/01 is the second tile (01) of the first row (00).

So, is there anyone here who'd be willing to help me with this? I understand most of this code, but I can't wrap my mind around how to "reverse" the process.

I'd appreciate any help I can get.

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: So, I want to save a tile map.
« Reply #1 on: April 25, 2014, 08:11:47 pm »
If seems like simply using an ofstream and << instead of an ifstream and >> would be enough to reverse that process.  You clearly understand the file format so it should be easy to insert the right things into the ofstream in the right order.

Jesper Juhl

  • Hero Member
  • *****
  • Posts: 1405
    • View Profile
    • Email
Re: So, I want to save a tile map.
« Reply #2 on: April 25, 2014, 08:26:07 pm »
Why don't you just use something like "Tiled" ( http://www.mapeditor.org/ ) instead of reinventing the wheel and create your own map editor / format?

Personally I use Tiled to create my levels, export them as JSON and then use picojson ( https://github.com/kazuho/picojson ) to load them into my game (there are also loaders for some of the other formats that you can find by searching this forum).

ineed.help

  • Jr. Member
  • **
  • Posts: 56
    • View Profile
Re: So, I want to save a tile map.
« Reply #3 on: April 25, 2014, 10:04:31 pm »
Why don't you just use something like "Tiled" ( http://www.mapeditor.org/ ) instead of reinventing the wheel and create your own map editor / format?

Personally I use Tiled to create my levels, export them as JSON and then use picojson ( https://github.com/kazuho/picojson ) to load them into my game (there are also loaders for some of the other formats that you can find by searching this forum).

Yeah, but I always value the experience of doing it "all on my own" (excluding the fact that I am using a programming language, API and PC, et cetera, that someone else created).

If seems like simply using an ofstream and << instead of an ifstream and >> would be enough to reverse that process.  You clearly understand the file format so it should be easy to insert the right things into the ofstream in the right order.

It doesn't quite work. An error I can see right away is the "std::getline" problem, which only works for input file streams, not output ones.

This part right here:

while (std::getline(stream, value, '|'))
                        {
                                if (value.length() > 0)
                                {
                                        std::string xx = value.substr(0, value.find('/')).c_str();
                                        std::string yy = value.substr(value.find('/') + 1).c_str();

                                        int i, j, x, y;

                                        for (i = 0; i < xx.length(); i++)
                                        {
                                                if(!isdigit(xx[i])) { break; }
                                        }

                                        for (j = 0; j < yy.length(); j++)
                                        {
                                                if (!isdigit(yy[j])) { break; }
                                        }

                                        if (i == xx.length()) { x = atoi(xx.c_str()); }
                                        else { x = -1; }

                                        if (j == yy.length()) { y = atoi(yy.c_str()); }
                                        else { y = -1; }

                                        tempMap.push_back(Vector2i(x, y));
                                }
                        }
... is something I'm having some problems with. I want to be able to find the '|':s and '/':s.

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: So, I want to save a tile map.
« Reply #4 on: April 25, 2014, 10:15:44 pm »
Quote
It doesn't quite work. An error I can see right away is the "std::getline" problem, which only works for input file streams, not output ones.

Just insert '\n's at the end of each line.  And there's no '|'s or '/'s to find; you're going to insert those too.

This is fairly basic C++ I/O stuff, so you might want to focus on learning C++ for a while and hold off on libraries like SFML until you're more familiar with the language they're written in.
« Last Edit: April 25, 2014, 10:20:54 pm by Ixrec »

ineed.help

  • Jr. Member
  • **
  • Posts: 56
    • View Profile
Re: So, I want to save a tile map.
« Reply #5 on: April 25, 2014, 10:39:26 pm »
Quote
It doesn't quite work. An error I can see right away is the "std::getline" problem, which only works for input file streams, not output ones.

Just insert '\n's at the end of each line.  And there's no '|'s or '/'s to find; you're going to insert those too.

This is fairly basic C++ I/O stuff, so you might want to focus on learning C++ for a while and hold off on libraries like SFML until you're more familiar with the language they're written in.

It seems I was unclear - what I mean is that I don't quite understand how I'm supposed to add those '|':s and '/':s to the exported file. (I didn't think of using '\n', though, so thanks for that!)

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: So, I want to save a tile map.
« Reply #6 on: April 25, 2014, 10:52:57 pm »
fileStream << '|';
fileStream << '/';

Seriously, go read a book or internet tutorial on how C++ iostreams work.  This is need-to-know stuff for anyone using the language.

ineed.help

  • Jr. Member
  • **
  • Posts: 56
    • View Profile
Re: So, I want to save a tile map.
« Reply #7 on: April 26, 2014, 08:49:31 am »
fileStream << '|';
fileStream << '/';

Seriously, go read a book or internet tutorial on how C++ iostreams work.  This is need-to-know stuff for anyone using the language.

I'll do that.

ineed.help

  • Jr. Member
  • **
  • Posts: 56
    • View Profile
Re: So, I want to save a tile map.
« Reply #8 on: April 26, 2014, 02:52:11 pm »
(Sorry for the double post.)

I changed my methods a little, and I am now using the following code:

"Game.hpp"
#ifndef GAME_HPP
#define GAME_HPP

#include <SFML/Graphics.hpp>
#include <iostream>
#include <fstream>
#include <cctype>
#include <string>
#include <vector>
#include <sstream>

using namespace sf;

std::string tileFileName;

std::string xString[] = { "0" };
std::string yString[] = { "0" };

std::string peekNextLine[] = { "0" };

#endif

"LoadMap.cpp"
#include "Game.hpp"

void LoadMap(std::string mapFileName, RenderWindow& window)
{
    std::ifstream OPENFILE(mapFileName);
 
    Texture tileTexture;
    Sprite tileSprite;
 
    std::vector<std::vector<Vector2i>> tileMap;
    std::vector<Vector2i> tempTileMap;
 
    if(OPENFILE.is_open())
    {
        OPENFILE >> tileFileName;
        tileTexture.loadFromFile(tileFileName);
                tileSprite.setTexture(tileTexture);

        while(!OPENFILE.eof())
        {
                        int tileAmount = 0;

                        std::string tileString;
            OPENFILE >> tileString;
                        std::stringstream STRINGSTREAM;
                        STRINGSTREAM << tileString[0] << tileString[1];
                        STRINGSTREAM >> xString[tileAmount];
                        STRINGSTREAM = std::stringstream();
                        STRINGSTREAM << tileString[3] << tileString[4];
                        STRINGSTREAM >> yString[tileAmount];

                        int xPosition = stoi(xString[tileAmount]);
                        int yPosition = stoi(yString[tileAmount]);

                        tempTileMap.push_back(Vector2i(xPosition, yPosition));

            if(OPENFILE.peek() == '\n')
            {
                tileMap.push_back(tempTileMap);
                tempTileMap.clear();
                                peekNextLine[tileAmount] = "\n";
            }
        }
        tileMap.push_back(tempTileMap);
    }
 
    while(window.isOpen())
    {
        Event event;
        while(window.pollEvent(event))
        {
            switch (event.type)
            {
            case Event::Closed:
                window.close();
                break;
            }
        }
 
        window.clear();
 
        for(unsigned int i = 0; i < tileMap.size(); i++)
        {
            for(unsigned int j = 0; j < tileMap[i].size(); j++)
            {
                                if(tileMap[i][j].x != -1 && tileMap[i][j].y != -1)
                                {
                                        tileSprite.setPosition(j * 32, i * 32);
                    tileSprite.setTextureRect(IntRect(tileMap[i][j].x * 32, tileMap[i][j].y * 32, 32, 32));
                    window.draw(tileSprite);
                                }
            }
        }
 
        window.display();
    }
}

"SaveMap.cpp"

#include "Game.hpp"

void SaveMap(std::string mapFileName)
{
    std::ofstream SAVEFILE(mapFileName);
       
        if(SAVEFILE.is_open())
    {
        SAVEFILE << tileFileName;
                SAVEFILE << "\n";
        }

        while(!SAVEFILE.eof())
    {
                int tileAmount = 0;
        SAVEFILE << xString[tileAmount][0];
                SAVEFILE << ",";
                SAVEFILE << xString[tileAmount][1];
                SAVEFILE << ",";
                SAVEFILE << yString[tileAmount][0];
                SAVEFILE << ",";
                SAVEFILE << yString[tileAmount][1];

                if (peekNextLine[tileAmount] == "\n")
                {
                        SAVEFILE << "\n";
                }
        }
}

"Main.cpp"

#include <SFML/Graphics.hpp>
#include <vector>

void LoadMap(std::string mapFileName, sf::RenderWindow& window);
void SaveMap(std::string mapFileName);

int main()
{
        sf::RenderWindow window(sf::VideoMode(640, 480, 32), "TileEngine");

        LoadMap("Map1.txt", window);
}

There seems to be something wrong with both "LoadMap" and "SaveMap", but I can't see it (the error messages point to the .obj files, and I don't understand them).

I'd really appreciate any help.

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: So, I want to save a tile map.
« Reply #9 on: April 26, 2014, 03:47:43 pm »
Show us the actual error messages.  Just saying they mention "obj files" doesn't help much.

With streams, it's best to just use "while(OPENSTREAM)", because the standard streams have built-in conversions to boolean which account for not only whether it's open but also whether an error has occurred or an EOF has been reached.

Using getline() is also simpler than explicitly peeking for the \n character.

As a stylistic point, most people will expect all caps to be used only for macro names and constant names, so using it for ordinary variables like streams is very strange and potentially confusing.

I don't see why you're using stringstreams at all in the load function.  There are standard string methods like substr() which seem to do what you want much faster and more clearly.

It makes no sense to check for EOF in a file you are writing to.  Don't do that.

What is the purpose of peekNextLine?  It looks like an incredibly roundabout way of storing how many lines your tilemap has (which you could just do with an int), but there's no reason to do that because on read you just keep reading lines until you hit EOF, and on write you just write until you run out of data.

Don't declare your texture inside the loading function.  It'll just get destroyed when the function ends.  See the "white square problem" section of the sprites and textures tutorial.

Also, I assume this is only because it's a test program, but having your main game loop inside of LoadMap makes no sense.  That loop should be in main(), or LoadMap() should be renamed to reflect what it actually does.


Some of this stuff is still basic C++ I/O material, so you definitely need to go read an introductory C++ book or something.
« Last Edit: April 26, 2014, 03:52:36 pm by Ixrec »

ineed.help

  • Jr. Member
  • **
  • Posts: 56
    • View Profile
Re: So, I want to save a tile map.
« Reply #10 on: April 26, 2014, 04:02:05 pm »
Using getline() is also simpler than explicitly peeking for the \n character.
All right, I guess I'll try again with the old code. I'll come back if there are any problems. (I do know basic C++ I/O, I just don't know how to utilize it when making a map editor.)

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: So, I want to save a tile map.
« Reply #11 on: April 26, 2014, 04:09:29 pm »
I do know basic C++ I/O, I just don't know how to utilize it when making a map editor.

C++ I/O works exactly the same way with map files as with any other kind of file (or cin/cout).  You happen to be inserting/extracting data that represents a map, but the iostreams don't know or care about that.  Any iostream function you knew about before will work here just fine.

ineed.help

  • Jr. Member
  • **
  • Posts: 56
    • View Profile
Re: So, I want to save a tile map.
« Reply #12 on: April 26, 2014, 04:26:58 pm »
C++ I/O works exactly the same way with map files as with any other kind of file (or cin/cout).  You happen to be inserting/extracting data that represents a map, but the iostreams don't know or care about that.  Any iostream function you knew about before will work here just fine.

So, here's a "problem": how can I find the end of a row in the tile map? I know how to do it when reading from a file, but how do I do it when I save to a file? (I don't mean to use "\n".)

EDIT: How would I access the final "x" value of the 2D vector, as well as the "y" value of the 2D vector? As I see it, I need these to know when to write a new line in the exported tile map file.
« Last Edit: April 26, 2014, 04:44:36 pm by ineed.help »

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: So, I want to save a tile map.
« Reply #13 on: April 26, 2014, 05:09:39 pm »
You're storing your tile map in a std::vector<std::vector<Vector2i>>, so...just use standard vector methods like size() and back().  For instance, tileMap[0].size() gets you the length of a row.

The interface of std::vector and other STL containers is another need-to-know thing for any C++ user, so that's also worth reading some tutorials on.


P.S. A vector of vectors is not a great way to store a two-dimensional array, but that gets into some non-trivial issues you may not want to worry about while you're busy figuring out how to get basic information like the size of your vectors.

ineed.help

  • Jr. Member
  • **
  • Posts: 56
    • View Profile
Re: So, I want to save a tile map.
« Reply #14 on: April 26, 2014, 05:27:01 pm »
You're storing your tile map in a std::vector<std::vector<Vector2i>>, so...just use standard vector methods like size() and back().  For instance, tileMap[0].size() gets you the length of a row.

The interface of std::vector and other STL containers is another need-to-know thing for any C++ user, so that's also worth reading some tutorials on.


P.S. A vector of vectors is not a great way to store a two-dimensional array, but that gets into some non-trivial issues you may not want to worry about while you're busy figuring out how to get basic information like the size of your vectors.

(What would be a better way to store it, then?)

Just when I finally thought I'd got it working, the file doesn't output anything but the tileset name and a new line.

#include <SFML/Graphics.hpp>
#include <iostream>
#include <fstream>
#include <cctype>
#include <string>
#include <vector>
#include <sstream>

using namespace sf;
 
void LoadMap(std::string mapFileName, RenderWindow& window);
void SaveMap(std::string mapFileName);

std::vector<std::vector<Vector2i>> map;

std::string tileLocation;

int main()
{
        RenderWindow window(VideoMode(640, 480, 32), "TileEngine");
        LoadMap("Map1.txt", window);
        SaveMap("Map1Copy.txt");
}

void LoadMap(std::string mapFileName, RenderWindow& window)
{
    std::ifstream openfile(mapFileName);
 
    Texture tileTexture;
    Sprite tiles;

    std::vector<Vector2i> tempMap;
 
    if(openfile.is_open())
    {
        openfile >> tileLocation;
        tileTexture.loadFromFile(tileLocation);
        tiles.setTexture(tileTexture);

        while(!openfile.eof())
        {
            std::string str, value;
            std::getline(openfile, str);
            std::stringstream stream(str);
 
            while(std::getline(stream, value, '|'))
            {
                if(value.length() > 0)
                {
                    std::string xx = value.substr(0, value.find('/')).c_str();
                    std::string yy = value.substr(value.find('/') + 1).c_str();

                                        int x = atoi(xx.c_str());
                                        int y = atoi(yy.c_str());

                                        tempMap.push_back(Vector2i(x, y));
                }
            }

            map.push_back(tempMap);
            tempMap.clear();
        }
        }
 
    while(window.isOpen())
    {
        Event event;
        while(window.pollEvent(event))
        {
            switch (event.type)
            {
            case Event::Closed:
                window.close();
                break;
            }
        }
 
        window.clear();
 
        for(int i = 0; i < map.size(); i++)
        {
            for(int j = 0; j < map[i].size(); j++)
            {
                if(map[i][j].x != -1 && map[i][j].y != -1)
                {
                    tiles.setPosition(j * 32, i * 32);
                    tiles.setTextureRect(IntRect(map[i][j].x * 32, map[i][j].y * 32, 32, 32));
                    window.draw(tiles);
                }
            }
        }
 
        window.display();
    }
}

void SaveMap(std::string mapFileName)
{
    std::ofstream savefile(mapFileName);
       
        savefile << tileLocation;
        savefile << "\n";

        int amountOfTiles = 0;
        int xCount = 0;
        int yCount = 0;

        while (amountOfTiles << map.size())
        {
                savefile << map[xCount][yCount].x;
                savefile << "/";
                savefile << map[xCount][yCount].y;

                if(yCount == map[0].size())
                {
                        savefile << "\n";
                        xCount++;
                        yCount = 0;
                }

                else
                {
                        savefile << "|";
                        yCount++;
                }

                amountOfTiles++;
        }
}

What am I doing wrong here?