2D 钻石(等距)地图编辑器 - 无限扩展的纹理?

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

我目前正在开发 2D 等距地图编辑器。 我显示包含点和纹理的实体(立方体,播放器)。 每个立方体由12个点组成。(12个点,但在sfml(sf::VertexArray)显示时被处理为4个点的3个边。

(我知道我时常包含一些“.cpp”,我的 IDE(visual studio) 有问题,我正在尝试解决,请不要关心它。)

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];
}

我已经做了很多测试,太多了,所以我现在不会 post 它们,但如果您有任何问题,请随时提问。

这里是标题描述的问题:

这里,只显示一张脸的屏幕(代码中的顺序相同):

我唯一不明白的是,如果您手动输入坐标,单独显示的立方体工作得很好。即使是扩展的。但是坐标公式没问题...(我注意到 15x15 地图的立方体 n°50 和 64x64 立方体显示一个宽度为 'infinite' 的矩形) 如果纹理被扩展(可能无限大),这表明坐标在某处不断增加?那么,为什么立方体仍然放置得很好?

以下是资源(64*64 png): 目录:textures/test/

在 OpenGL 中,当您手动创建 OpenGL 纹理时,您可以指定 4 种类型:

GL_REPEAT, GL_CLAMP_TO_EDGE, GL_CLAMP  and GL_CLAMP_TO_BORDER

如果您想从 openGL 纹理差异中了解更多信息,请查看 here。基本上,它将图像中的最后一个像素扩展到保留内存的其余部分。

为了解决你的问题,尝试加载贴图,修改参数。不知道sfml是否允许用Texture.hpp header,在reference出现setRepeated,试试设置true看能不能解决问题。其他方式,例如 loadfromfile 大小为 sf::IntRect(0, 0, 32, 32)。

此代码未经测试,但理论上,使用 OpenGL 可以工作:

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;

    // Generate OpenGL Texture manually
    GLuint texture_handle;
    glGenTextures(1, &texture_handle);

    // Attach the texture
    glBindTexture(GL_TEXTURE_2D, texture_handle);

    // Upload to Graphic card
    glTexImage2D(
        GL_TEXTURE_2D, 0, GL_RGBA,
        mMemoryTexture.GetWidth(), mMemoryTexture.GetHeight(),
        0,
        GL_RGBA, GL_UNSIGNED_BYTE, mMemoryTexture.GetPixelsPtr()
    );

    // Set the values
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

也许这可以帮助您解决问题。

感谢 Whosebug 的成员,我找到了更好的方法来完成这段代码。对于通过寻找类似问题的解决方案到达那里的人,我邀请您查看评论以获得一些有用的链接和评论。

不是真正的答案(因为代码无论如何都会被重写)所以对新代码的提示很少(其中一些已经在评论中提到)。

  1. 方块集

    在最终的等距引擎中使用精灵。它们速度更快并且支持像素艺术。出于我的目的,我使用这两个免费使用的 tilesets (64x64) 的编译:

    两者兼容。我编译和编辑它们以适应我的引擎的需要。这就是我使用的(仍在进行中):

    白色0x00FFFFFF表示透明。精灵是不够的。我添加了有关平铺高度和旋转的信息。

    如果您从左上角看到前 4 个图块,它们都是旋转 90 度后的相同内容。所以我所有的瓷砖都有 4 个瓷砖的索引(90 度旋转)int rot[4]。这样我就可以旋转整个地图或只是查看。我编译了集合,因此旋转彼此相邻。有 3 个选项:

    • tile[ix].rot[]={ ix,ix,ix,ix }; 其中 ix 是没有旋转的平铺(地面)
    • tile[ix].rot[]={ ix,ix+1,ix,ix+1 }; 其中 ix2 旋转的方块(那 2 个方块在右中间有一块砍伐的树)
    • tile[ix].rot[]={ ix,ix+1,ix+2,ix+3 }; 其中 ix4 旋转的方块(与第一个方块一样)

    索引仅对第一个图块有效,其他图块使整个 rot[] 数组从邻居旋转 1 个值。有些旋转是不可见的(见宽阔的树),但瓷砖仍然存在以允许旋转。

    图块高度对于在编辑时放置图块以及自动生成地图很重要。

    我还计划为每个图块添加 A* 地图,这样我就可以使用路径查找或计算水流等等。

  2. 地图编辑器

    我更喜欢 3D 地图。对于更大的分辨率,您需要适当地 select 查看区域以最大化性能。另外一个好主意是创建空心地下,这样渲染速度要快得多(这也可以在渲染过程中虚拟完成,无需更新地图)。

    我建议对这些功能进行编码:

    • 让地面空心
    • 使地面坚固
    • 随机地形(菱形和方形)
    • 过滤掉小孔和光滑的边缘(将坡度瓦片添加到立方体瓦片中)
  3. 磁贴编辑器

    除了显而易见的绘图编辑器之外,您还应该添加其他功能,例如:

    1. 地板<->天花板
    2. <->
    3. 正面<->背面
    4. 将大精灵分成规则的图块
    5. copy/merge/paste
    6. <->右镜操作后调整灯光

    当 compiling/editing tileset 资源时,它们真的很方便。如您所见,我的瓦片集中有许多瓦片在源瓦片集中不存在。它们是由这些功能+一些小的绘画编辑创建的...图块集底部的彩色遮罩用于遮盖并正确组合部分图块以创建缺失的部分...(您可以采用一侧形式瓷砖和其他来自其他...)

[备注]

更多info/ideas看看一些相关的Q/As:

  • Improving performance of click detection on a staggered column isometric grid
  • 这里是我的独立版未安装 Win32 演示:

  • demo v1.000

  • demo v1.034