В настоящее время я занимаюсь разработкой редактора 2D-изометрических карт.
Я отображаю сущность (куб, игрок), которая содержит точки и текстуры.
Каждый куб состоит из 12 точек (12 точек, но обрабатывается как 3 стороны по 4 точки при отображении в sfml (sf :: VertexArray).
(Я знаю, что время от времени я включаю некоторые файлы .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];
}
Я сделал много тестов, слишком много, поэтому я не буду публиковать их прямо сейчас, но если у вас есть какие-либо вопросы, не стесняйтесь спрашивать.
Вот проблема, описанная в заголовке:
И здесь, экраны с одним лицом отображаются (в том же порядке в коде):
Единственное, чего я не понимаю, так это того, что отображаемый кубик прекрасно работает, если вы вводите координаты вручную. Даже расширенные. Но с формулой координат все в порядке … (Я заметил, что куб n ° 50 для карты 15×15 с кубом 64×64 отображает прямоугольник ‘бесконечный’ по ширине)
Если текстура расширена (возможно, до бесконечности), это говорит о том, что координаты постоянно где-то увеличиваются? Тогда почему кубики все еще хорошо расположены?
На самом деле это не ответ (так как код все равно будет переписан), поэтому несколько подсказок для нового кода (некоторые из них уже упомянуты в комментариях).
Tileset
В финальном изометрическом двигателе используют спрайты. Они быстрее и поддерживают пиксель арт. Для моих целей я использую компиляцию этих двух бесплатных наборов плиток (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 };
где ix
это плитка с 2
повороты (эти две плитки с кусочком рубленого дерева в центре справа)tile[ix].rot[]={ ix,ix+1,ix+2,ix+3 };
где ix
это плитка с 4
повороты (как первая плитка)Индексы действительны только для грубой только для первого тайла, остальные имеют целое rot[]
массив повернут на 1 значение от соседа. Некоторые повороты невидимы (см. Широкие деревья), но плитка все еще присутствует, что позволяет вращаться.
Высота плитки важна для размещения плиток во время редактирования, а также для автоматической генерации карты.
Планирую также добавить A*
карта для каждой плитки, так что я могу использовать поиск пути или вычислять потоки воды и многое другое.
Редактор карт
я предпочитаю 3D карты. с большим разрешением вам нужно правильно выбрать просматриваемую область для просмотра, чтобы максимизировать производительность. Также хорошей идеей является создание углубления под землей, чтобы рендеринг был намного быстрее (это также можно сделать практически во время процесса рендеринга без необходимости обновления карты).
Я рекомендую кодировать эти функции:
редактор плиток
Помимо очевидного редактора рисования, вы должны добавить еще одну функцию, такую как:
<->
потолок<->
право<->
назад<->
правое зеркалоОни действительно удобны при компиляции / редактировании ресурсов набора тайлов. Как вы можете видеть, мой набор плиток содержит много плиток, которых нет в исходных наборах плиток. Они были созданы этими функциями + небольшое редактирование краски … Цветные маски в нижней части набора плиток используются для маскировки и правильного комбинирования частей плиток для создания недостающих … (одну сторону можно выбрать из плитка и прочее от других …)
[Заметки]
Для получения дополнительной информации / идеи взглянуть на некоторые связанные Q /s:
Как процедурно сгенерировать изометрическую карту
И вот мой автономный не установить Win32 Demo:
В OpenGL, когда вы создаете текстуру OpenGL вручную, вы можете назначить 4 типа:
GL_REPEAT, GL_CLAMP_TO_EDGE, GL_CLAMP and GL_CLAMP_TO_BORDER
Если вы хотите узнать больше о различиях в текстурах openGL, посмотрите Вот. По сути, он расширяет последний пиксель изображения до остальной части зарезервированной памяти.
Чтобы решить вашу проблему, попробуйте загрузить текстуру, изменив параметры. Я не знаю, позволяют ли sfml сделать это с заголовком Texture.hpp, в ссылка появляются setRepeated, попробуйте установить true, чтобы увидеть, если решить проблему. Другой способ — загрузить файл с размером 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);
}
Может быть, это поможет вам решить вашу проблему.
Я заканчиваю, находя лучший способ сделать этот код, благодаря членам stackoverflow. Для людей, которые пришли туда в поисках решения аналогичной проблемы, я приглашаю вас посмотреть комментарии для некоторых полезных ссылок и комментариев.