Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

2D Diamond (isometric) map editor - Textures extended infinitely?

I'm currently developing a 2D isometric map editor. I display entity(cube, player) which contains points and textures. Each cubes are composed by 12 points.(12 points, but handled as 3 sides of 4 points when displayed by sfml(sf::VertexArray).

(I know I include some '.cpp' times to times, I have a problem with my IDE(visual studio) which I'm trying to resolve, please do not care about it.)

main.cpp

#pragma once
#include "globalfunctions.h" //global functions + main headers + class headers

int main() {
    int mapSize = 0;
    int cubeSize = 0;

    cout << "Map size: "; cin >> mapSize; cout << endl;
    cout << "Cube size: "; cin >> cubeSize; cout << endl;

    int windowWidth = (mapSize * cubeSize) - (cubeSize * 2);
    int windowHeight = ((mapSize * cubeSize) - (cubeSize * 2)) / 2;

    renderWindow window(windowWidth, windowHeight, mapSize, cubeSize);
        int nbMaxTextures = 9;
        for (int t = 0; t < nbMaxTextures; t++) {
            window.loadTexture("test", t);
        }

    window.run();

    return EXIT_SUCCESS;
}

globalfunctions.h

#pragma once
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <iostream>
#include <math.h>
//#include <sstream>
#include <vector>

using namespace std;

sf::Vector2u isometricToCartesian(int i, int j, int cubeSize) {
    sf::Vector2u carth;
        carth.x = (j - i) * (cubeSize / 2);
        carth.y = (j + i) * (cubeSize / 4);

    return carth;
}

sf::Vector2u cartesianToIsometric(int x, int y, int cubeSize) {//TODO
    sf::Vector2u iso;
        iso.x = 0;
        iso.y = 0;

    return iso;
}

#include "entity.h"
#include "renderWindow.h"

renderWindow.h

#pragma once

class renderWindow {
    public:
        renderWindow(float WIDTH, float HEIGHT, int MAPSIZE, int CUBESIZE);
        void run();
        void loadTexture(sf::String folder, int numTexture);

        //SETTERS
        //...

        //GETTERS
        //...

    private:
        int mCurrentLayerID;
        int mMapSize;
        int mCubeSize;
        int mSelectedTexture;

        vector<entity> mMap;

        sf::RenderWindow mWindow;
        vector<sf::Texture> mTextures;
            sf::Texture mMemoryTexture;

        void processEvent();
        void update(sf::Time deltaTime);
        void render();

//CUBE ACTION-------------------------------------------
        void addCube(int layerID, float x, float y);
        entity& getCube(int ID);
        entity& getCubeAt(float x, float y);
        vector<sf::VertexArray> loadCube(int cubeID);//UPDATE DATA LIKE COORDINATES -> create/chnge the vertex
        void drawCube(int cubeID);//draw the vertex

        //VARIABLES
        vector<sf::VertexArray> verticesSide1;
        vector<sf::VertexArray> verticesSide2;
        vector<sf::VertexArray> verticesSide3;
//CUBE ACTION-------------------------------------------
};

#include "renderWindow.cpp"

renderWindow.cpp

#pragma once

renderWindow::renderWindow(float WIDTH, float HEIGHT, int MAPSIZE, int CUBESIZE) : mWindow(sf::VideoMode(WIDTH, HEIGHT), "") {
    mMapSize = MAPSIZE;
    mCubeSize = CUBESIZE;

    mSelectedTexture = 6;

    mCurrentLayerID = -1;

    int x = 0;
    int y = 0;

    //default layer
    for (int j = 0; j < mMapSize; j++) {
        for (int i = 0; i < mMapSize; i++) {
            x = isometricToCartesian(i, j, mCubeSize).x;
            y = isometricToCartesian(i, j, mCubeSize).y;
            addCube(0, x, y);
        }
    }

    for (int c = 0; c < mMap.size(); c++) {
        verticesSide1.push_back(loadCube(c)[0]);
        verticesSide2.push_back(loadCube(c)[1]);
        verticesSide3.push_back(loadCube(c)[2]);

        //then only do that when something the cube's coordinate changed
    }
}

void renderWindow::run() {
    sf::Clock clock;
    sf::Time timeSinceLastUpdate = sf::Time::Zero;
    sf::Time TimePerFrame = sf::seconds(1.f / 60.f);

    while (mWindow.isOpen()) {
        processEvent();

        timeSinceLastUpdate += clock.restart();

        while (timeSinceLastUpdate > TimePerFrame) {
            timeSinceLastUpdate -= TimePerFrame;

            processEvent();
            update(TimePerFrame);
        }

        render();
    }
}

void renderWindow::loadTexture(sf::String folder, int numTexture) {
    if (mMemoryTexture.loadFromFile("textures/" + folder + "/" + to_string(numTexture) + ".jpg"))
        mTextures.push_back(mMemoryTexture);
    else
        cout << "Texture n°" << numTexture << " as failed to load." << endl;
}


//SETTERS
//...

//GETTERS
//...

//PRIVATE METHODE
void renderWindow::processEvent() {
    sf::Event event;

    while (mWindow.pollEvent(event)) {
        switch (event.type) {
        case sf::Event::Closed:
            mWindow.close();
            break;

        case sf::Event::KeyPressed:
            if (event.key.code == sf::Keyboard::Escape)
                mWindow.close();
            break;

        case sf::Event::MouseButtonPressed:
            if (event.MouseButtonPressed == sf::Mouse::Left)
                getCubeAt(event.mouseButton.x, event.mouseButton.y).setTexture(0, mSelectedTexture);//TEST
                getCubeAt(event.mouseButton.x, event.mouseButton.y).setTexture(1, mSelectedTexture + 1);//TEST
                getCubeAt(event.mouseButton.x, event.mouseButton.y).setTexture(2, mSelectedTexture + 2);//TEST
            break;

            /*case sf::Event::MouseMoved:
                cout << "(" << event.mouseMove.x << ", " << event.mouseMove.y << ")" << endl;
                break;*/
        }
    }
}

void renderWindow::update(sf::Time deltaTime) {
    //REMEMBER: distance = speed * time
    //MOVEMENT, ANIMATIONS ETC. ...
}

void renderWindow::render() {
    mWindow.clear();

    for (int c = 0; c < mMap.size(); c++) {
        drawCube(c);
    }

    mWindow.display();
}

//CUBE ACTION-------------------------------------------
void renderWindow::addCube(int layerID, float x, float y) {
    //Thoses make the code more readable:
        int half_cubeSize = mCubeSize / 2;
        int oneQuarter_cubeSize = mCubeSize / 4;
        int twoQuarter_cubeSize = oneQuarter_cubeSize * 2;
        int treeQuarter_cubeSize = oneQuarter_cubeSize * 3;

    mCurrentLayerID = layerID;

    entity dummy(mMap.size(), 0, layerID);
        dummy.addPoint(12);
        dummy.addTexture(6);
        dummy.addTexture(7);
        dummy.addTexture(8);
    //SIDE 1------------------------------------------------
        dummy.setPoint(0, x, y + oneQuarter_cubeSize);
        dummy.setPoint(1, x + half_cubeSize, y + twoQuarter_cubeSize);
        dummy.setPoint(2, x + half_cubeSize, y + mCubeSize);
        dummy.setPoint(3, x, y + treeQuarter_cubeSize);
    //SIDE 2------------------------------------------------
        dummy.setPoint(4, x + half_cubeSize, y + twoQuarter_cubeSize);
        dummy.setPoint(5, x + mCubeSize, y + oneQuarter_cubeSize);
        dummy.setPoint(6, x + mCubeSize, y + treeQuarter_cubeSize);
        dummy.setPoint(7, x + half_cubeSize, y + mCubeSize);
    //SIDE 3------------------------------------------------
        dummy.setPoint(8, x, y + oneQuarter_cubeSize);
        dummy.setPoint(9, x + half_cubeSize, y);
        dummy.setPoint(10, x + mCubeSize, y + oneQuarter_cubeSize);
        dummy.setPoint(11, x + half_cubeSize, y + twoQuarter_cubeSize);

    mMap.push_back(dummy);
}

entity& renderWindow::getCube(int ID) {
    for (int c = 0; c < mMap.size(); c++) {
        if (mMap[c].getID() == ID)
            return mMap[c];
    }
}

entity& renderWindow::getCubeAt(float x, float y) {//TO DO
    return entity(-1, 0, 0);
}

vector<sf::VertexArray> renderWindow::loadCube(int cubeID) {
    vector<sf::VertexArray> vertices;
    vertices.push_back(sf::VertexArray());
    vertices.push_back(sf::VertexArray());
    vertices.push_back(sf::VertexArray());

    vertices[0].setPrimitiveType(sf::Quads);
    vertices[0].resize(4);

    vertices[1].setPrimitiveType(sf::Quads);
    vertices[1].resize(4);

    vertices[2].setPrimitiveType(sf::Quads);
    vertices[2].resize(4);

    sf::Vector2f tv0 = sf::Vector2f(0, 0);
    sf::Vector2f tv1 = sf::Vector2f(mCubeSize, 0);
    sf::Vector2f tv2 = sf::Vector2f(mCubeSize, mCubeSize);
    sf::Vector2f tv3 = sf::Vector2f(0, mCubeSize);

    sf::Vector2f v0 = sf::Vector2f(getCube(cubeID).getPoint(0, 0), getCube(cubeID).getPoint(0, 1));
    sf::Vector2f v1 = sf::Vector2f(getCube(cubeID).getPoint(1, 0), getCube(cubeID).getPoint(1, 1));
    sf::Vector2f v2 = sf::Vector2f(getCube(cubeID).getPoint(2, 0), getCube(cubeID).getPoint(2, 1));
    sf::Vector2f v3 = sf::Vector2f(getCube(cubeID).getPoint(3, 0), getCube(cubeID).getPoint(3, 1));

    sf::Vector2f v4 = sf::Vector2f(getCube(cubeID).getPoint(4, 0), getCube(cubeID).getPoint(4, 1));
    sf::Vector2f v5 = sf::Vector2f(getCube(cubeID).getPoint(5, 0), getCube(cubeID).getPoint(5, 1));
    sf::Vector2f v6 = sf::Vector2f(getCube(cubeID).getPoint(6, 0), getCube(cubeID).getPoint(6, 1));
    sf::Vector2f v7 = sf::Vector2f(getCube(cubeID).getPoint(7, 0), getCube(cubeID).getPoint(7, 1));

    sf::Vector2f v8 = sf::Vector2f(getCube(cubeID).getPoint(8, 0), getCube(cubeID).getPoint(8, 1));
    sf::Vector2f v9 = sf::Vector2f(getCube(cubeID).getPoint(9, 0), getCube(cubeID).getPoint(9, 1));
    sf::Vector2f v10 = sf::Vector2f(getCube(cubeID).getPoint(10, 0), getCube(cubeID).getPoint(10, 1));
    sf::Vector2f v11 = sf::Vector2f(getCube(cubeID).getPoint(11, 0), getCube(cubeID).getPoint(11, 1));

    vertices[0][0] = sf::Vertex(v0, tv0);
    vertices[0][1] = sf::Vertex(v1, tv1);
    vertices[0][2] = sf::Vertex(v2, tv2);
    vertices[0][3] = sf::Vertex(v3, tv3);

    vertices[1][0] = sf::Vertex(v4, tv0);
    vertices[1][1] = sf::Vertex(v5, tv1);
    vertices[1][2] = sf::Vertex(v6, tv2);
    vertices[1][3] = sf::Vertex(v7, tv3);

    vertices[2][0] = sf::Vertex(v8, tv0);
    vertices[2][1] = sf::Vertex(v9, tv1);
    vertices[2][2] = sf::Vertex(v10, tv2);
    vertices[2][3] = sf::Vertex(v11, tv3);

    return vertices;
}

void renderWindow::drawCube(int cubeID) {
    mWindow.draw(verticesSide1[cubeID], &mTextures[getCube(cubeID).getTexture(0)]);
    mWindow.draw(verticesSide2[cubeID], &mTextures[getCube(cubeID).getTexture(1)]);
    mWindow.draw(verticesSide3[cubeID], &mTextures[getCube(cubeID).getTexture(2)]);
}

//CUBE ACTION-------------------------------------------

entity.h

    #pragma once

    class entity {
    public:
        entity();
        entity(int id, int type, int numlayer);
        void addPoint(int nbPoints);
        void addTexture(int numTexture);

        //SETTERS
        void setPoint(int numPoint, float x, float y);
        void setTexture(int textureID, int numTexture);

        //GETTERS
        int getID();
        float getPoint(int numPoint, int numIndex);//if numIndex = 0 -> x || if numIndex = 1 -> y
        int getType();
        int getNumLayer();
        int getTexture(int numTexture);

    private:
        int mID;
        int mType;
        int mNumLayer;
        vector<sf::Vector2u> mPoints;
        vector<int> mTextures;
    };

    #include "entity.cpp"

entity.cpp

#pragma once

entity::entity() {
    mID = 0;
    mType = -1;
    mNumLayer = 0;
}

entity::entity(int id, int type, int numlayer) {
    mID = id;
    mType = type;
    mNumLayer = numlayer;
}

void entity::addPoint(int nbPoints) {
    mPoints.clear();

    int newSize = 0;
    for (int p = 0; p < nbPoints; p++) {
        newSize++;
    }

    mPoints = vector<sf::Vector2u>(newSize);
}

void entity::addTexture(int numTexture) {
    mTextures.push_back(numTexture);
}

//SETTERS
void entity::setPoint(int numPoint, float x, float y) {
    mPoints[numPoint].x = x;
    mPoints[numPoint].y = y;
}

void entity::setTexture(int textureID, int numTexture) {
    mTextures[textureID] = numTexture;
}

//GETTERS
int entity::getID() {
    return mID;
}

float entity::getPoint(int numPoint, int numIndex) {
    if (numIndex == 0)
        return mPoints[numPoint].x;
    else
        return mPoints[numPoint].y;
}

int entity::getType() {
    return mType;
}

int entity::getNumLayer() {
    return mNumLayer;
}

int entity::getTexture(int numTexture) {
    return mTextures[numTexture];
}

I've done a lot of test, too much, so I won't post them right now, but if you have any question, feel free to ask.

Here is the problem described in the title :

enter image description here

And here, screens with only one face displayed(in the same order in the code):

Side 1 only displayed

Side 2 only displayed

Side 3 only displayed

The only thing I don't understand is that a cube displayed alone work perfectly fine if you enter the coordinates manually. Even the extended ones. But the coordinates formula is ok... (I noticed that the cube n°50 for a 15x15 map with 64x64 cube display a rectangle 'infinite' in width) If the texture is extended(maybe to the infinite), it suggest that the coordinates are continuously increasing somewhere ? Then, why the cubes are still well placed ?

Here are the assets(64*64 png) : enter image description hereenter image description hereenter image description here Directories : textures/test/

like image 518
Madz Avatar asked Oct 19 '22 12:10

Madz


1 Answers

Not really an answer (as the code will be rewritten anyway) so few hints for the new code instead (some of them are already mentioned in the comments).

  1. Tileset

    In the final isometric engine use sprites. They are faster and support pixel art. For my purposes I use compilation of these two free to use tilesets (64x64):

    • outside tileset
    • medieval building tileset

    Both are compatible. I compiled and edited them to suite the needs of my engine. So this is what I use (still work in progress):

    my tileset

    White color 0x00FFFFFF means transparent. The sprite is not enough. I added info about the height of tile and rotations.

    If you see first 4 tiles from upper left corner they all are the same thing rotated by 90 degrees. So all my tiles have index of 4 tiles (the 90 degree rotations) int rot[4]. This way I can rotate the whole map or just view. I compile the set so the rotations are next to each other. There are 3 options:

    • tile[ix].rot[]={ ix,ix,ix,ix }; where ix is tile without rotation (ground)
    • tile[ix].rot[]={ ix,ix+1,ix,ix+1 }; where ix is tile with 2 rotations (those 2 tiles with chunk of chopped tree in the middle right)
    • tile[ix].rot[]={ ix,ix+1,ix+2,ix+3 }; where ix is tile with 4 rotations (like the first tile)

    The indexes are valid of coarse only for the first tile only, the others have the whole rot[] array rotated by 1 value from neighbor. Some rotations are invisible (see the wide trees) but the tile is still present to allow rotations.

    The tile height is important for placing tiles while editing and also for automatic map generations.

    tileset info

    I plan to add also A* map for each tile so I can use path finding or compute watter flows and more.

  2. Map editor

    I prefer 3D maps. with bigger resolution you need to properly select the viewed area for viewing to maximize performance. Also a good idea is to create hollow underground so the rendering is much faster (this can be also done virtually during rendering process without the need of updating map).

    I recommend to code these features:

    • make ground hollow
    • make ground solid
    • random terrain (diamond & square)
    • filter out small holes and smooth edges (add the slope tiles to cubic ones)
  3. tile editor

    Apart from the obvious paint editor you should add also another features like:

    1. floor <-> ceiling
    2. left <-> right
    3. front <-> back
    4. divide large sprite into regular tiles
    5. copy/merge/paste
    6. adjust lighting after left <-> right mirror operation

    They are really handy while compiling/editing tileset resources. As you can see my tileset has many of tiles not present in the source tilesets. They were created by these functions + some minor paint editing... The colored masks on the bottom of the tileset are used to mask out and properly combine parts of tiles to create the missing ones ... (you can take one side form one tile and other from other ...)

    tile operations

[Notes]

For more info/ideas have a look at some related Q/As:

  • Improving performance of click detection on a staggered column isometric grid
  • How to procedurally generate isometric map

    And here my Standalone no install Win32 Demo:

  • demo v1.000

  • demo v1.034

like image 177
Spektre Avatar answered Oct 21 '22 05:10

Spektre