Auto-Tiling - Edycja bitów Kafelka

Auto-Tiling - Edycja bitów Kafelka
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

Cześć Wszystkim!
Próbuję napisać auto-tiling. I nie wiem jak edytować dane bity. Normalnie do kafelka odnoszę się w ten sposób:

Kopiuj

class Terrain : public sf::Drawable, public sf::Transformable {
public:
	short width, height;		// normal is a 16x16
	sf::Vector2i coords;		// multiply by 16x16
	sf::VertexArray vertexes;	// vertexes of tiles
	SingleTexture* tileset;			// main texture
	std::vector < short > tiles;// tile values
    // etc..
};

void Terrain::edit(sf::Vector2f worldMousePosition, short value) {

    // edycja kafelka
	short coord_x = (worldMousePosition.x - coords.x * 16) / 16;
	short coord_y = (worldMousePosition.y - coords.y * 16) / 16;

	if (coord_x < 0 || coord_x >= width || coord_y < 0 || coord_y >= height)
		return;

      	tiles[coord_y * width + coord_x] = short(value); // chciałbym przypisywać bity

	sf::Vertex* quad = &vertexes[(coord_y * width + coord_x) * 4];

    // edycja vertex-array
	short tu = (short(abs(coord_x) * tileSide) % 64) + (value * 64);
	short tv = (short(abs(coord_y) * tileSide) % 64);

	//cout << "tu: " << tu << ", tv: " << tv << "\n";


	quad[0].texCoords = sf::Vector2f(tu + 1, tv + 1);
	quad[1].texCoords = sf::Vector2f(tu + tileSide - 1, tv + 1);
	quad[2].texCoords = sf::Vector2f(tu + tileSide - 1, tv + tileSide - 1);
	quad[3].texCoords = sf::Vector2f(tu + 1, tv + tileSide - 1);


}

Chciałbym przerobić tak klasę Terrain by zamiast std::vector < short > tiles miała std::vector < char > tile_bits

Kopiuj
class Terrain : public sf::Drawable, public sf::Transformable {
public:
	short width, height;		// normal is a 16x16
	sf::Vector2i coords;		// multiply by 16x16
	std::vector < char > tile_bits;	// np. 0000 - 0 - czerwony, 0001 - 1 - lewy-górny szary, 0010 - 2 prawy-górny szary, 1111 - 15 - szary 
	sf::VertexArray vertexes;	// vertexes of tiles
	SingleTexture* tileset;			// main texture

// ... etc.
};

I taki tile_bits zawierał by dane binarne o kafelku. Tak jak na załączonym screenie. Jak odnieść się do takiego bitu ?

00_info.png
U mnie kafelki bazowe mają rozmiar 64x64 stąd:

Kopiuj
short tu = (short(abs(coord_x) * tileSide) % 64) + (value * 64);
short tv = (short(abs(coord_y) * tileSide) % 64);

00_info1.png


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 6x, ostatnio: tBane
Spine
  • Rejestracja:prawie 22 lata
  • Ostatnio:minuta
  • Postów:6628
0
  • Dzielisz kafelek na 4 ćwiartki, czyli po warunku if (coord_x < 0 || coord_x >= width || coord_y < 0 || coord_y >= height) sprawdzasz, w której ćwiartce znajduje się kursor,
  • W zależności od ćwiartki pod kursorem ustalasz maskę bitową (jeden bit zapalony),
  • Zapalasz bit: tiles[coord_y * width + coord_x] |= quarterMask;
  • Gasisz bit: tiles[coord_y * width + coord_x] &= ~quarterMask;

🕹️⌨️🖥️🖱️🎮
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
1

który bit jest zaznaczony

Kopiuj
short bit_x = (worldMousePosition.x - coords.x*16) % 2;
short bit_y = (worldMousePosition.y - coords.y*16) % 2;

edycja bitu

Kopiuj
value = tiles[coord_y * width + coord_x];
bool bit0 =(( value >> 0 ) & 1 ) > 0;
bool bit1 =(( value >> 1 ) & 1 ) > 0;
bool bit2 =(( value >> 2 ) & 1 ) > 0;
bool bit3 =(( value >> 3 ) & 1 ) > 0;

W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 1x, ostatnio: tBane
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

Mam jeszcze jedno pytanie.

Mam 10 bazowych płytek i 16 wariantów kafelków granicznych co daje łącznie 10x10x16 możliwych kombinacji kafelków. Zważywszy, że pojedynczy kafelek ma wymiary 64x64 to daje nam teksturę o rozmiarze 64x102400... Nie zmieści się to w pamięci GPU chyba. Jak więc w sfml przechowywać teksturę kafelków?

Kopiuj
class Terrain : public sf::Drawable, public sf::Transformable {
public:
	short width, height;		// normal is a 16x16
	sf::Vector2i coords;		// multiply by 16x16
	sf::VertexArray vertexes;	// vertexes of tiles
	SingleTexture* tileset;			// main texture
	std::vector < char > tiles_bits;// tiles bits

	/// ...

private:

	virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
	{
		// draw tiles - terrain
		states.transform *= getTransform();
		states.texture = &*tileset->texture;	// odniesienie się do tekstury
		target.draw(vertexes, states);
	}
};

W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12164
0

@tBane: zamiast ręcznie manipulować bitami, możesz skorzystać z unii, jeśli potrzebujesz wygodniejszego sposobu dostępu do bitów i manipulowania nimi. Przy czym jeśli zakres wartości liczbowych kafli ogranicza się do czterech bitów, to taka unia powinna mieć rozmiar jednego bajtu, tak aby wektor tych unii zajmował minimalną ilość pamięci (łatwiej i chętniej będzie cache'owana).

Co do Twojego problemu — trudno mi wywnioskować co w ogóle chcesz zrobić (zakrawa to o problem XY). Napisz może jaki efekt Cię interesuje, a za pomocą metodyki top-down zejdziemy do implementacji. Na razie widzę, że nie wyzbyłeś się nawyku zapychania pamięci duplikatami, więc od tego będziemy musieli zacząć. 😉


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 4x, ostatnio: flowCRANE
LukeJL
nawet ja bym poszedł dalej i wydzielił metody do odpytania/aktualizacji danej rzeczy w kafelku, natomiast reprezentację bitową pozostawił jako szczegół implementacyjny sposobu, w jaki dane są trzymane w pamięci. Czyli nawet jak pod spodem jest maska bitowa, to fajnie mieć jednak funkcje, które będą oferować jakąś większą abstrakcję. Ale nie wczytywałem się w ten cały wątek, więc nie wiem, co dokładnie OP chce zrobić.
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

@flowCRANE Chcę renderować mapę kafelkową, tak by każdy kafelek składał się z czterech części - tak żeby przejścia między kafelkami ładnie wyglądały :-)
No i znalazłem rozwiązanie tzn. generować klasę dziedziczącą po class Terrain : public sf::Drawable, public sf::Transformable (link)

Niestety ta klasa ma wbudowaną funkcję:

Kopiuj
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
    {
        // apply the transform
        states.transform *= getTransform();

        // apply the tileset texture
        states.texture = &m_tileset;

        // draw the vertex array
        target.draw(m_vertices, states);
    }

i próbuję teraz jakoś wkleić teksturę tak by można było renderować tilesy

ps. Twój link @flowCRANE w opisie do mikroblogu nie działa :-) na moim mikroblogu


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 4x, ostatnio: tBane
flowCRANE
Który link nie działa? Ten w opisie pod moim postem? Działa, kieruje na mojego bloga.
tBane
już działa. Przed chwilą nie działał...
Spine
  • Rejestracja:prawie 22 lata
  • Ostatnio:minuta
  • Postów:6628
0
tBane napisał(a):

Zważywszy, że pojedynczy kafelek ma wymiary 64x64 to daje nam teksturę o rozmiarze 64x102400... Nie zmieści się to w pamięci GPU chyba.

64*102400 = 6553600

Pierwiastek z 6553600 to 2560.
Czyli prawie jakbyś miał teksturę o wymiarach 2048x2048.
To nie powinno być zbyt wiele dla współczesnych GPU.

Ale na przyszłość dobrze by było zaimplementować obsługę wielu tekstur.
W ten sposób mógłbyś też kontrolować warstwy terenu - kolejność renderowania.

Próbowałeś zaznajomić się z jakimiś tutorialami na ten temat?
Czy Twój "grid" jest całkiem nowatorski?


🕹️⌨️🖥️🖱️🎮
edytowany 2x, ostatnio: Spine
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

@Spine czytałem jakieś poradniki ale zazwyczaj nie traktowały o tym czego potrzebuję, albo po prostu nie potrafię wyszukiwać informacji :P Jedyne źródło z którego skorzystałem to graphics-vertex-array, więc tak - wszystko pisane i wymyślane samemu.

Skoro tekstura będzie mieć wymiary
10*10*16*64*64 = 6,553,600
sqrt(6,553,600) = 2560

2560x2560 to da się to zrobić


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 6x, ostatnio: tBane
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
1

Algorytm do generowania wszystkich kombinacji 10 bazowych kafelków z 16 maskami w teksturze o rozmiarach 2816x2816 zamieszczam poniżej. :-)
tileset.png

Kopiuj
void generateTileSet() {
	sf::Image masks[16];

	for (int i = 0; i < 16; i++) {
		masks[i].create(64, 64);
		if (!masks[i].loadFromFile("assets/tiles/basic2/" + std::to_string(i) + ".png"))
			std::cout << "błąd wczytywania maski: " << i << "\n";
	}

	std::cout << "Wczytano maski\n";

	std::vector<SingleTexture*> tiles = getSingleTextures("tiles/tile_");
	std::cout << "Tiles: " << tiles.size() << "\n";

	// Finalna tekstura 2816x2816
	unsigned int newWidth = 2816;
	unsigned int newHeight = 2816;
	unsigned int tilesPerRow = newWidth / 64; // 44 kafelki na wiersz
	sf::Image tile_set_image;
	tile_set_image.create(newWidth, newHeight, sf::Color::Transparent);

	sf::Image first_tile, second_tile, mask, result_tile;
	first_tile.create(64, 64);
	second_tile.create(64, 64);
	mask.create(64, 64);
	result_tile.create(64, 64);

	unsigned int tileIndex = 0; // Licznik kafelków

	for (short tile_1_id = 0; tile_1_id < 11; tile_1_id++) {
		first_tile = tiles[tile_1_id]->texture->copyToImage();

		for (short tile_2_id = 0; tile_2_id < 11; tile_2_id++) {
			second_tile = tiles[tile_2_id]->texture->copyToImage();

			for (short mask_id = 0; mask_id < 16; mask_id++) {
				mask = masks[mask_id];

				// Generowanie kafelka
				for (unsigned int y = 0; y < 64; y++) {
					for (unsigned int x = 0; x < 64; x++) {
						sf::Color pixel = mask.getPixel(x, y);

						if (pixel.r == 237 && pixel.g == 28 && pixel.b == 36) {
							result_tile.setPixel(x, y, first_tile.getPixel(x, y));
						}
						else if (pixel.r == 127 && pixel.g == 127 && pixel.b == 127) {
							result_tile.setPixel(x, y, second_tile.getPixel(x, y));
						}
						else {
							result_tile.setPixel(x, y, pixel);
						}
					}
				}

				// Obliczanie pozycji w finalnej teksturze
				unsigned int newX = (tileIndex % tilesPerRow) * 64;
				unsigned int newY = (tileIndex / tilesPerRow) * 64;

				if (newY >= newHeight) {
					std::cerr << "Za dużo kafelków! Przekracza rozmiar 2816x2816." << std::endl;
					return;
				}

				tile_set_image.copy(result_tile, newX, newY);
				tileIndex++;
			}
		}
	}

	tile_set_image.saveToFile("assets/tiles/tile_set_final.png");}

W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 2x, ostatnio: tBane
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

Wiele kafelków się powtarza. Spróbuję wyeliminować powtórzenia.


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12164
0

Lepiej by było, gdybyś te ”przejściowe” kafelki namalował samodzielnie (tak samo jak te podstawowe) i dał możliwość ich wybrania z zasobnika, w trakcie edytowania mapy. Z reguły tak się to robi, że wszystkie typy kafli są w danym atlasie i nie ma duplikatów. Mało tego, nie tylko nie ma duplikatów, ale każdy kafel istnieje jako jeden, natomiast renderer zajmuje się jego rotacją i flipem w trakcie renderowania mapy.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 1x, ostatnio: flowCRANE
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

Tak mam obecnie. Ale chce uniknąć ręcznego wstawiania kafelków przejściowych


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
Spine
  • Rejestracja:prawie 22 lata
  • Ostatnio:minuta
  • Postów:6628
0

Wesnoth ma bardzo ładnie wyglądające przejścia między hexami.
Można sobie przejrzeć pliki, np. Steam\steamapps\common\wesnoth\data\core\images\terrain\flat:

screenshot-20250321005850.png

Przykładowa mapka:

screenshot-20250321010107.jpg

Wygląda jakby każdy kafel miał zdefiniowane przejścia do innych kafli.
U siebie mógłbyś w analogiczny sposób zdefiniować mapę przejść - każdy z każdym.


🕹️⌨️🖥️🖱️🎮
edytowany 3x, ostatnio: Spine
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
1

@Spine no właśnie zrobiłem już coś takiego tylko, że dla kafelków 64x64, gdzie w edytorze są 16x16.
00_info1.png
Następnie wygenerowałem wszytkie możliwe przejścia. Teraz spróbuję pozbyć się duplikatów.

tileset.png


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 1x, ostatnio: tBane
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12164
0

screenshot-20250321173103.png

Każda grafika z powyższych ma mnóstwo powtórzeń, których też musisz się pozbyć.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

@flowCRANE no właśnie nie. To są maski dla 64x64 tekstur ale pola mapy maja rozmiar 16x16. Dlatego tekstura jest tak duża, żeby wyeliminować "powtarzalność" pól mapy

tile_0_water.png
tile_1_sands.png
tile_2_grass.png
tile_3_gravel.png
tile_4_steps.png
tile_5_highlands.png


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 1x, ostatnio: tBane
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12164
0

Jak tej maski używasz do renderowania kafli 16x16?


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

@flowCRANE No więc tak. Pola w mapie mają rozmiar 16x16 ale tekstury mają rozmiar 64x64 po to by pola się za bardzo nie powtarzały. Dzięki temu co czwarte pole wygląda tak samo.
Używam maski 64x64 bo tekstury mają 64x64.

Kopiuj
short tu = (short(abs(coord_x) * 16) % 64) + (value * 64);
short tv = (short(abs(coord_y) * 16) % 64);

W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 2x, ostatnio: tBane
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12164
0

Co nieco już ogarniam. Czyli potrzebujesz renderować kafle w rozmiarze 16x16, których zawartość znajduje się w teksturach w rozmiarze 64x64. Maska ma taki rozmiar jak tekstura, a nie kafel. Przez to, że maska jest w rozmiarze tekstury a nie kafla, maska zawiera duplikaty co 16 pikseli (16 duplikatów w formie macierz 4x4 po 16x16 pikseli).

Niepotrzebnie używasz masek 64x64, skoro docelowy obszar (kafla) ma 16x16. Maska powinna być w rozmiarze zgodnym z rozmiarem kafla, natomiast kafel w mapie powinien zawierać dane identyfikujące źródłową teksturę z grafiką terenu (tę w rozmiarze 64x64) oraz współrzędne określające offset w teksturze terenu. W trakcie renderowania, odczytujesz ID tekstury terenu (np. tekstura 64x64 z trawą) oraz offset, który wyznacza współrzędne kwadratu 16x16 w teksturze trawy.

Tak w pseudokodzie:

Kopiuj
struct Tile
{
  int texture_id; // ID tekstury
  int offset_x;   // offset X kafla w tekturze 64x64 z terenem
  int offset_y;   // offset Y kafla —||—
}

Aby wyrendeorwać kafel 16x16, wyznaczasz obszar w teksturze terenu w ten sposób:

Kopiuj
int x = tile.offset_x; // 0, 16, 32 lub 48
int y = tile.offset_y; // 0, 16, 32 lub 48
int w = tile_size;     // 16
int h = tile_size;     // 16

Czyli w sumie to x i y to nic innego jak uv.

To o czym piszę wyżej to standardowa technika renderowania tekstur z atlasu. Masz atlas np. z glifami znaków, więc jeśli chcesz wyrenderować jeden znak z takiej tekstury atlasu to po prostu oblicza się jego współrzędne w atlasie i taki pod-obszar się renderuje. To samo sam możesz zrobić, tylko zamiast renderowania glifu znaku, renderujesz fragment tekstury terenu. I w ten sposób możesz każdą ze swoich masek zredukować do formy 16x16, a pod-obszar w teksturze gruntu obliczyć.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 3x, ostatnio: flowCRANE
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

Tekstura 64x64 nie składa się z 4 takich samych tekstur 16x16. Jesy unikalna - po to by pola 16x16 nie powtarzały się za często, dzięki takiej teksturze pola powtarzają się co 4


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 1x, ostatnio: tBane
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12164
0

Tak, ale maska 64x64 zawiera kafle 16x16 — 16 takich samych, w formie macierzy 4x4, a wystarczy jedna 16x16.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 3x, ostatnio: flowCRANE
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

Tak, ale ma to niewielki rozmiar a kod jest czytelniejszy :-)


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12164
0

No nie jest czytelniejszy, za to implementacja renderowania jest przekombinowana i wprowadza zamieszanie. A do tego wymusza alokowanie znacznie większej ilości pamięci, co nie jest dobrym pomysłem. CPU potrafi cholernie szybko wykonywać obliczenia, natomiast posysa w temacie dostępu do pamięci. Dlatego zamiast generować kupę danych i pakować je do (V)RAM-u, ogranicz alokacje do minimum, a wszystko czego potrzebujesz po prostu oblicz.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 1x, ostatnio: flowCRANE
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

jest czytelniejszy bo nie muszę dodatkowo dorzucać dwóch pętli przy przetwarzaniu maski

Kopiuj
// Generowanie kafelka z maską
for (unsigned int y = 0; y < 64; y++) {
	for (unsigned int x = 0; x < 64; x++) {
		sf::Color pixel = mask.getPixel(x, y); // here

		if (pixel.r == 237 && pixel.g == 28 && pixel.b == 36) {
			result_tile.setPixel(x, y, first_tile.getPixel(x, y));
		}
		else if (pixel.r == 127 && pixel.g == 127 && pixel.b == 127) {
			result_tile.setPixel(x, y, second_tile.getPixel(x, y));
		}
		else {
			result_tile.setPixel(x, y, pixel);
		}
	}
}

W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 2x, ostatnio: tBane
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12164
0

Czemu ręcznie maglujesz dane pikseli? SFML nie wspiera kluczowania koloru czy innych sztuczek?

Łatwiej by było znaleźć sensowne rozwiązanie, gdybyś pokazał obrazkowo trzy tekstury — tę z gruntem, tę maski oraz wynik. Wtedy moglibyśmy się zastanowić jak ten temat ugryźć.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 4x, ostatnio: flowCRANE
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

Z tego co sie orientuję to SFML 2.6.2 nie wspiera edycji koloru z użyciem maski. Dlatego ręcznie piksele ustawiam. I tak robie to wydajnie bo w RAM to wszystko obliczam a potem konwertuje cały tileset na teksturę


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12164
0

Możesz to zrobić znacznie szybciej, jeśli unikniesz notorycznego wywoływania getpixel i setpixel, a także jeśli skorzystasz z maski o bardziej skondensowanych danych. Zauważ, że piksel w masce może przyjmować jeden z trzech kolorów, a taki zakres (trzech) wartości możesz zapisać na dwóch bitach (cztery piksele maski w jednym bajcie). Dla ułatwienia, możesz użyć jednego bajta na jeden piksel maski. Im mniejszy blok danych, tym CPU chętniej go wrzuci do cache, a więc tym mniej cold readów — odczyt z cache to od jednego do kilkudziesięciu cykli CPU, a z RAM-u to 150-200 cykli.

Przy czym skoro piksele wyjściowe montujesz ręcznie, to maska w ogóle nie musi być teksturą — wystarczy macierz bajtowych indeksów określających to jak zmieszać kolor źródłowy z maską (taki własny kanał alpha, bo tym jest właśnie Twoja maska). Maska może być teksturą na dysku, ale w trakcie jej ładowania do pamięci, możesz raz przekonwerować ją na tablicę bajtów z danymi mieszania kolorów, a potem z niej korzystać w trakcie renderowania, przez całą sesję. W ten sposób drastycznie przyspieszysz renderowanie, na którego wydajność w końcu narzekałeś już. 😉


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 1x, ostatnio: flowCRANE
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

Nie rozumiem :-/

Mam algorytm do wyznaczania unikalnych kafli - rozmiar tekstury 1920x1920. Teraz muszę uporządkować te kafelki w palecie w Edytorze

tile_set_final.png

Kopiuj
void generateTileSet() {
	sf::Image masks[16];

	for (int i = 0; i < 16; i++) {
		masks[i].create(64, 64);
		if (!masks[i].loadFromFile("assets/tiles/basic2/" + std::to_string(i) + ".png")) {
			std::cout << "Błąd wczytywania maski: " << i << "\n";
			return;
		}
	}

	std::cout << "Wczytano maski\n";

	std::vector<SingleTexture*> tiles = getSingleTextures("tiles/tile_");
	std::cout << "Tiles: " << tiles.size() << "\n";

	unsigned int N = tiles.size();
	unsigned int M = 16;  // Liczba masek
	unsigned int totalTiles = (N * (N - 1) / 2) * M;

	// Obliczanie wymiarów wynikowej tekstury
	unsigned int tileSize = 64;
	unsigned int tilesPerRow = std::ceil(std::sqrt(totalTiles));  // Najbardziej kwadratowy układ
	unsigned int rows = (totalTiles + tilesPerRow - 1) / tilesPerRow;

	sf::Image tile_set_image;
	tile_set_image.create(tilesPerRow * tileSize, rows * tileSize, sf::Color::Transparent);

	sf::Image first_tile, second_tile, mask, result_tile;
	first_tile.create(tileSize, tileSize);
	second_tile.create(tileSize, tileSize);
	result_tile.create(tileSize, tileSize);

	// Na początku zapiszemy bazowe kafelki, by się nie powtarzały
	unsigned int tileIndex = 0;

	for (short tile_id = 0; tile_id < N; tile_id++) {
		unsigned int newX = (tileIndex % tilesPerRow) * tileSize;
		unsigned int newY = (tileIndex / tilesPerRow) * tileSize;

		tile_set_image.copy(tiles[tile_id]->texture->copyToImage(), newX, newY);
		tileIndex++;
	}

	// Teraz generujemy przejściowe kafelki
	for (short tile_1_id = 0; tile_1_id < N; tile_1_id++) {
		for (short tile_2_id = tile_1_id + 1; tile_2_id < N; tile_2_id++) {
			first_tile = tiles[tile_1_id]->texture->copyToImage();
			second_tile = tiles[tile_2_id]->texture->copyToImage();

			for (short mask_id = 1; mask_id < 15; mask_id++) { // Pomiń 0 i 15
				mask = masks[mask_id];

				for (unsigned int y = 0; y < tileSize; y++) {
					for (unsigned int x = 0; x < tileSize; x++) {
						sf::Color pixel = mask.getPixel(x, y);

						if (pixel.r == 237 && pixel.g == 28 && pixel.b == 36) {
							result_tile.setPixel(x, y, first_tile.getPixel(x, y));
						}
						else if (pixel.r == 127 && pixel.g == 127 && pixel.b == 127) {
							result_tile.setPixel(x, y, second_tile.getPixel(x, y));
						}
						else {
							result_tile.setPixel(x, y, pixel);
						}
					}
				}

				unsigned int newX = (tileIndex % tilesPerRow) * tileSize;
				unsigned int newY = (tileIndex / tilesPerRow) * tileSize;

				if (newY >= rows * tileSize) {
					std::cerr << "Za dużo kafelków! Przekracza rozmiar tekstury.\n";
					return;
				}

				tile_set_image.copy(result_tile, newX, newY);
				tileIndex++;
			}
		}
	}

	// Zapisujemy obraz do pliku
	tile_set_image.saveToFile("assets/tiles/tile_set_final.png");
	std::cout << "Zapisano tile_set_final.png\n";
}

W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 4x, ostatnio: tBane
tBane
Tester Beta
  • Rejestracja:ponad rok
  • Ostatnio:3 minuty
  • Lokalizacja:Poznań
  • Postów:288
0

Chyba są rozmieszczone regularnie te kafelki z wstępnych obliczeń. Jutro będę próbował umieśćić je w palecie i zobaczymy czy ten autotiling to dobre rozwiązanie.

left-top | top | right-top
11 13 12
25 27 26
39 41 40
53 55 54

11+0 11+2 11+1
11+14 11+16 11+15
11+28 11+30 11+29
11+42 11+44 11+43


W wolnych chwilach od codzienności programuję hobbystycznie Edytor gier RPG 2D.
Technologie, z których korzystam to C++ oraz SFML 2.X.
edytowany 2x, ostatnio: tBane
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)