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

5
我目前正在开发一个二维等角地图编辑器。 我显示包含点和纹理的实体(立方体、玩家)。 每个立方体由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];
}

我已经进行了很多测试,太多了,所以现在不会立即发布它们,但如果您有任何问题,请随时问。

以下是标题中描述的问题:

enter image description here

这里展示的是只有一个面的屏幕(按代码中的顺序):

Side 1 only displayed

Side 2 only displayed

Side 3 only displayed

唯一让我不理解的是,如果您手动输入坐标,则独立显示的方块可以正常工作,即使是扩展的坐标也是如此。 但是坐标公式没问题......(我注意到,对于一个15x15地图上的第50个方块,64x64的显示会显示一个“无限宽度”的矩形) 如果纹理被扩展(可能是无限的),那么坐标应该在某处不断增加?那么,为什么这些方块仍然放置得很好呢?

这里是资源(64*64 png) : enter image description hereenter image description hereenter image description here 目录 : textures/test/


1
你是否意识到,在isometricToCartesian中的整数除法会导致无法被4整除的立方体尺寸失去精度? - Andreas
@mPoints mPoints = vector<sf::Vector2u>(newSize); 这是我的点向量的大小。 - Madz
@Madz 我只是想指出你不需要使用循环,你可以直接赋值 newSize = nbPoints,它会做同样的事情。 - Chara
@Chara 哦,是的,谢谢! - Madz
@Madz 实际上你可以这样做 mPoints = vector<sf::Vector2u>(nbPoints) :D - Chara
显示剩余17条评论
3个回答

3

这并不是一个答案(因为代码将被重写),所以我提供一些新代码的提示(其中一些已经在评论中提到)。

  1. 图块集

    在最终的等角引擎中使用精灵。它们速度更快,支持像素艺术。为了我的目的,我使用这两个免费使用的图块集合并而成(64x64):

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

    我的图块集

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

    如果您看到左上角的前4个图块,它们都是同一物体旋转90度得到的。因此,我所有的图块都具有4个图块(90度旋转)的索引 int rot[4]。这样我就可以旋转整个地图或只是视图。我编译了这个集合,使得旋转相邻。有三种选项:

    • tile[ix].rot[]={ ix,ix,ix,ix }; 其中 ix 是没有旋转(地面)的图块
    • tile[ix].rot[]={ ix,ix+1,ix,ix+1 }; 其中 ix 是有 2 个旋转的图块(右侧中间有一块砍掉的树木的那两个图块)
    • tile[ix].rot[]={ ix,ix+1,ix+2,ix+3 }; 其中 ix 是有 4 个旋转的图块(像第一个图块)

    索引当然只针对第一个图块有效,其他的都将整个 rot[] 数组从相邻的值旋转1次。一些旋转是不可见的(请参见宽树),但仍存在图块以允许旋转。

    图块高度对于编辑和自动地图生成非常重要。

    图块集信息

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

  2. 地图编辑器

    我更喜欢 3D 地图。对于更大的分辨率,您需要正确选择视图区域以最大化性能。还有一个好主意是创建空心地下室,这样渲染速度会更快(这也可以在渲染过程中虚拟地完成而无需更新地图)。

    我建议编写以下功能:

    • 使地面为空心
    • 使地面实心
    • 随机地形(钻石和正方形)
    • 过滤掉小孔并平滑边缘(将斜坡图块添加到立方体图块中)
  3. 图块编辑器

    除了显而易见的

    [注]

    想了解更多信息/想法,请查看一些相关的问答:


非常感谢所有这些资源和信息(我现在还没有完全理解它们,但我会花时间去理解它们)。非常感谢这个大回答,我认为你将帮助很多人。 - Madz
@Madz 在我的回答末尾添加了我的演示链接(请阅读文本)。钻石布局中的旋转非常酷 :) - Spektre
厉害了!你用什么制作了钻石界面的GUI? - Madz
我可以问你一个问题吗?你是怎么做到让那个大地图(钻石地图)完全不卡顿的?我的意思是,我正在拼命尝试解决CPU问题,以便能够显示非常非常大的地图。 - Madz
@Madz 将屏幕定位于中间位置 (xs/2,ys/2),然后将其转换为地图上的单元格位置 scr2cell(),并仅渲染该位置周围的瓷砖。所覆盖的区域大小取决于瓷砖大小和屏幕大小... 这种方式不论地图有多大,渲染速度只取决于屏幕大小和可见表面的复杂性。顺便说一下,我的源代码中设置了菱形布局 #define isometric_layout_1。附注中的数字是渲染时间和上一帧渲染/处理的单元格数量。 - Spektre
显示剩余14条评论

0
在OpenGL中,当你手动创建一个OpenGL纹理时,你可以分配4种类型:
GL_REPEAT, GL_CLAMP_TO_EDGE, GL_CLAMP  and GL_CLAMP_TO_BORDER

如果你想要了解更多关于openGL纹理差异的知识,请看这里。基本上,它会将图像中的最后一个像素扩展到其余保留内存。

为了解决你的问题,尝试加载纹理并修改参数。我不知道sfml是否允许使用Texture.hpp头文件进行操作,在参考文献中出现了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);
}

或许这可以帮助你解决问题。


谢谢您的回答。没有OpenGL就没有解决方案吗? - Madz
我的意思是,我相信这是我的代码逻辑的问题。以前使用SFML显示那种等距地图没有问题。自从我改变了代码结构后,它就无法工作了。 - Madz
下次尝试使用Git或某个代码库来管理不同的版本。如果你把代码放在Github或Bitbucket上,我可以尝试调试它,但如果没有这些,要定位问题就非常复杂了。 - vgonisanz
为什么这很复杂(这里有整个代码)?我们有一个Git,但是是私有的。 - Madz
好的,我需要对代码进行一些更改,创建一个cmake文件进行编译...但问题是我没有测试资源来模拟您的问题,使用一些图块文件进行测试,我得到了:http://imgur.com/KXlKUOB - vgonisanz
显示剩余4条评论

0
我最终找到了更好的方法来编写这段代码,感谢 Stack Overflow 社区成员们的帮助。如果有遇到类似问题需要解决的朋友们,我邀请你们查看评论中的一些有用的链接和评论。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接