• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

[SFML] Obsługa różnych rozdzielczości

Object Storage Arubacloud
0 głosów
413 wizyt
pytanie zadane 25 sierpnia 2018 w C i C++ przez niezalogowany

Piszę warcaby. Chciałbym aby mój program działał dobrze na różnych rozdzielczościach. 

1. Poszperałem trochę w googlach i znalazłem funkcję

sf::View getLetterboxView(sf::View view, int windowWidth, int windowHeight)
{
	// Compares the aspect ratio of the window to the aspect ratio of the view,
	// and sets the view's viewport accordingly in order to archieve a letterbox effect.
	// A new view (with a new viewport set) is returned.

	float windowRatio = windowWidth / (float)windowHeight;
	float viewRatio = view.getSize().x / (float)view.getSize().y;
	float sizeX = 1;
	float sizeY = 1;
	float posX = 0;
	float posY = 0;

	bool horizontalSpacing = true;
	if (windowRatio < viewRatio)
		horizontalSpacing = false;

	// If horizontalSpacing is true, the black bars will appear on the left and right side.
	// Otherwise, the black bars will appear on the top and bottom.

	if (horizontalSpacing)
	{
		sizeX = viewRatio / windowRatio;
		posX = (1 - sizeX) / 2.f;
	}
	else
	{
		sizeY = windowRatio / viewRatio;
		posY = (1 - sizeY) / 2.f;
	}

	view.setViewport(sf::FloatRect(posX, posY, sizeX, sizeY));

	return view;
}

Dzięki niej po zmianie rozmiaru okna, w przypadku gdy stosunek szerokości do wysokości jest inny niż na moim obiekcie Sprite, u góry i na dole lub po bokach pojawiają się czarne paski.

2. Domyślam się też, że jeżeli aplikacja ma działać na różnych rozdzielczościach, to powinienem użyć dużych grafik pasujących do największej docelowej rozdzielczości. 
Jeśli ktoś będzie miał mniejszy ekran to ewentualnie pomniejszy się grafiki. 
Gdybym natomiast próbował je rozciągnąć to by się wszystko rozmazało.

3. Używam też funkcji

bool isSpriteClicked(sf::Sprite object, sf::Mouse::Button button, sf::RenderWindow& window)
{
	if (sf::Mouse::isButtonPressed(button))
	{
		sf::IntRect playButtonRect(object.getPosition().x, object.getPosition().y,
			object.getGlobalBounds().width, object.getGlobalBounds().height);

		sf::Vector2i pixelPos = sf::Mouse::getPosition(window);
		sf::Vector2f worldPos = window.mapPixelToCoords(pixelPos);
		sf::Vector2i fPos(worldPos.x, worldPos.y);

		if (playButtonRect.contains(fPos))
		{
			return true;
		}
	}

	return false;
}

aby sprawdzać czy kliknięto grafikę, a wewnątrz używam 

mapPixelToCoords(sf::Vector2i);

dzięki czemu nawet po zmianie rozmiaru okna kliknięcia są poprawnie wykrywane.

 

Jednak nie mam pojęcia w jaki sposób wykrywać, który wiersz i kolumnę kliknięto.

Tutaj fragment kodu z tutoriala dotyczącego tworzenia TicTacToe w SFML

// pozycja myszki wzgledem okna aplikacji
		sf::Vector2i touchPoint = this->m_data->input.getMousePosition(this->m_data->window);
		// prostokąt planszy
		sf::FloatRect gridSize = m_gridSprite.getGlobalBounds();
		// wolna przestrzeń po lewej i prawej stronie planszy
		sf::Vector2f gapOutsideOfGrid = sf::Vector2f((SCREEN_WIDTH - gridSize.width) / 2, (SCREEN_HEIGHT - gridSize.height) / 2);
		// pozycja myszki względem planszy
		sf::Vector2f gridLocalTouchPos = sf::Vector2f(touchPoint.x - gapOutsideOfGrid.x, touchPoint.y - gapOutsideOfGrid.y);
		// pojedynczy kwadrat planszy
		sf::Vector2f gridSectionSize = sf::Vector2f(gridSize.width / 3, gridSize.height / 3);

		int column, row;

		// sprawdzenie, na którą kolumnę kliknął gracz
		if (gridLocalTouchPos.x < gridSectionSize.x) // pierwsza kolumna
		{
			column = 1;
		}
		else if (gridLocalTouchPos.x < gridSectionSize.x * 2) // druga kolumna
		{
			column = 2;
		}
		else if (gridLocalTouchPos.x < gridSize.width) // trzecia kolumna
		{
			column = 3;
		}

		// sprawdzenie, na który wiersz kliknął gracz
		if (gridLocalTouchPos.y < gridSectionSize.y) // pierwszy wiersz
		{
			row = 1;
		}
		else if (gridLocalTouchPos.y < gridSectionSize.y * 2) // drugi wiersz
		{
			row = 2;
		}
		else if (gridLocalTouchPos.y < gridSize.height) // trzeci wiersz
		{
			row = 3;
		}

Wszystko fajnie, zrozumiale i w ogóle.

Tylko jak teraz to przerobić żeby po resizie okna wszystko działało ok? I to w dodatku jak moja plansza ma obwódkę?

Próbowałem używać

sf::FloatRect gridSize = sprite.getGlobalBounds();

oraz

sf::FloatRect gridSize = sprite.getLocalBounds();

ale zawsze zwraca mi rozmiar startowy mojego sprite, a nie jego faktyczny rozmiar po resizie. Czy jest na to w ogóle jakiś sposób?

Będę bardzo wdzięczny za każdą poradę.

1 odpowiedź

0 głosów
odpowiedź 26 sierpnia 2018 przez jankustosz1 Nałogowiec (35,880 p.)
https://www.sfml-dev.org/tutorials/2.5/graphics-view.php

https://www.youtube.com/watch?v=CpVbMeYryKo

Ustaw klasie View jakąś duża rozdzielczość typu 1920x1080 i Viewport na całe okno aby się skalowało niezależnie od jego wielkości.
komentarz 26 sierpnia 2018 przez niezalogowany
Ok. Tylko, że mi zależy na tym żeby określić współrzędne kliknięcia względem sprite-a po resizie okna i rozmiar samego sprite-a. Funkcje getGlobalBounds() i get LocalBounds() zwracają ciągle to samo czyli rozmiar sprite-a na początku, a nie o to mi chodzi.
komentarz 26 sierpnia 2018 przez jankustosz1 Nałogowiec (35,880 p.)
Jeżeli zrobisz to co powiedziałem, to niezależnie od tego jaki rozmiar ma okno program i tak będzie działał tak samo i tylko skalował obraz do rozmiaru okna przy wyświetlaniu.

Jeżeli chcesz przechwytywać jakieś eventy myszki na oknie to prawdopodobnie, będą one posiadały informacje o pozycji myszki względem okna na oknie, a nie o pozycji myszki względem okna na widoku. Dlatego będziesz musiał odpowiednio wymnożyć pozycje myszki, aby dostać pozycje na jaką pokazuje w widoku. Mam nadzieję, że rozumiesz o co chodzi?
komentarz 26 sierpnia 2018 przez jankustosz1 Nałogowiec (35,880 p.)
komentarz 26 sierpnia 2018 przez niezalogowany

Czyli zgodnie z Twoją poradą powinienem całkowicie odpuścić sobie ten cały widok Letterbox z paskami?

To mój cały kod

#include <SFML/Graphics.hpp>
#include "SFMLStarter/assetmanager.h"
#include <iostream>

bool isSpriteClicked(sf::Sprite object, sf::Mouse::Button button, sf::RenderWindow& window);
sf::View getLetterboxView(sf::View view, int windowWidth, int windowHeight);

int main()
{
	// Create a window
	int resX = 1366;
	int resY = 768;
	sf::RenderWindow window(sf::VideoMode(resX, resY), "Letterbox", sf::Style::Resize | sf::Style::Close);

	// Create a view. This can be of any size, but in this example it will be the same size as the window.
	// After creating it, it applies the letterbox effect to it; this is not neccesary if the view
	// is the same size as the window initially, but let's do this just in case.

	sf::View view;
	view.setSize(resX, resY);
	view.setViewport(sf::FloatRect(0.f, 0.f, 1.f, 1.f));
	view.setCenter(view.getSize().x / 2, view.getSize().y / 2);
	view = getLetterboxView(view, resX, resY);

	// Create a sprite. This sprite represents the whole map of a game in this example.
	sf::Texture texture;
	sf::Texture texture2;
	texture.loadFromFile("gamedata/img/big.png");
	texture2.loadFromFile("gamedata/img/grid.png");

	sf::Sprite sprite(texture);
	sf::Sprite sprite2(texture2);

	//sprite.setPosition(30, 20);
	sprite2.setPosition((window.getSize().x - sprite2.getGlobalBounds().width) / 2, (window.getSize().y - sprite2.getGlobalBounds().height) / 2);

	// Game loop.
	while (window.isOpen())
	{
		// Poll events.
		sf::Event event;
		while (window.pollEvent(event))
		{
			if (event.type == sf::Event::Closed)
				window.close();

			if (event.type == sf::Event::Resized)
			{
				view = getLetterboxView(view, event.size.width, event.size.height);
			}

			if (isSpriteClicked(sprite2, sf::Mouse::Right, window))
			{
				std::cout << "Sprite clicked" << std::endl;
				// window.setSize(sf::Vector2u(750, 600));

				// pozycja myszki wzgledem okna aplikacji
				sf::Vector2i /*pixelPos*/ touchPoint = sf::Mouse::getPosition(window);
				//sf::Vector2f worldPos = window.mapPixelToCoords(pixelPos);
				//sf::Vector2i touchPoint(worldPos.x, worldPos.y);

				// prostokąt planszy
				sf::FloatRect gridSize = sprite2.getGlobalBounds();

				// wolna przestrzeń po lewej i prawej stronie planszy
				sf::Vector2f gapOutsideOfGrid = sf::Vector2f((resX - gridSize.width) / 2, (resY - gridSize.height) / 2);

				// pozycja myszki względem planszy
				sf::Vector2f gridLocalTouchPos = sf::Vector2f(touchPoint.x - gapOutsideOfGrid.x, touchPoint.y - gapOutsideOfGrid.y);
				
				// pojedynczy kwadrat planszy
				sf::Vector2f gridSectionSize = sf::Vector2f(gridSize.width / 3, gridSize.height / 3);

				int column, row;

				// sprawdzenie, na którą kolumnę kliknął gracz
				if (gridLocalTouchPos.x < gridSectionSize.x) // pierwsza kolumna
				{
					column = 1;
				}
				else if (gridLocalTouchPos.x < gridSectionSize.x * 2) // druga kolumna
				{
					column = 2;
				}
				else if (gridLocalTouchPos.x < gridSize.width) // trzecia kolumna
				{
					column = 3;
				}

				// sprawdzenie, na który wiersz kliknął gracz
				if (gridLocalTouchPos.y < gridSectionSize.y) // pierwszy wiersz
				{
					row = 1;
				}
				else if (gridLocalTouchPos.y < gridSectionSize.y * 2) // drugi wiersz
				{
					row = 2;
				}
				else if (gridLocalTouchPos.y < gridSize.height) // trzeci wiersz
				{
					row = 3;
				}

				std::cout << "row = " << row << std::endl;
				std::cout << "column = " << column << std::endl;
			}
		}

		window.clear();
		//window.clear(sf::Color::Blue);

		window.setView(view);

		//window.draw(sprite);
		window.draw(sprite2);

		window.display();
	}

	return 0;
}

bool isSpriteClicked(sf::Sprite object, sf::Mouse::Button button, sf::RenderWindow& window)
{
	if (sf::Mouse::isButtonPressed(button))
	{
		sf::IntRect playButtonRect(object.getPosition().x, object.getPosition().y,
			object.getGlobalBounds().width, object.getGlobalBounds().height);

		sf::Vector2i pixelPos = sf::Mouse::getPosition(window);
		//sf::Vector2f worldPos = window.mapPixelToCoords(pixelPos);
		//sf::Vector2i fPos(worldPos.x, worldPos.y);

		if (playButtonRect.contains(pixelPos))
		//if (playButtonRect.contains(fPos))
		{
			return true;
		}
	}

	return false;
}

sf::View getLetterboxView(sf::View view, int windowWidth, int windowHeight)
{
	// Compares the aspect ratio of the window to the aspect ratio of the view,
	// and sets the view's viewport accordingly in order to archieve a letterbox effect.
	// A new view (with a new viewport set) is returned.

	float windowRatio = windowWidth / (float)windowHeight;
	float viewRatio = view.getSize().x / (float)view.getSize().y;
	float sizeX = 1;
	float sizeY = 1;
	float posX = 0;
	float posY = 0;

	bool horizontalSpacing = true;
	if (windowRatio < viewRatio)
		horizontalSpacing = false;

	// If horizontalSpacing is true, the black bars will appear on the left and right side.
	// Otherwise, the black bars will appear on the top and bottom.

	if (horizontalSpacing)
	{
		sizeX = viewRatio / windowRatio;
		posX = (1 - sizeX) / 2.f;
	}
	else
	{
		sizeY = windowRatio / viewRatio;
		posY = (1 - sizeY) / 2.f;
	}

	view.setViewport(sf::FloatRect(posX, posY, sizeX, sizeY));

	return view;
}

 

komentarz 26 sierpnia 2018 przez niezalogowany

@jankustosz1,

No właśnie korzystam z funkcji mapPixelToCoords(pixelPos) w moim kodzie po to aby po resizie nadal poprawnie wykrywało kliknięcia na sprite-ach. I działa, nawet nie potrzebne do tego są widoki. Ale mi zależy na tym żeby się dowiedzieć o rozmiar sprite-a po resizie okno po to bym mógł podzielić go na 3 i sprawdzić, na który wiersz i kolumnę na moim spricie kliknięto. Na razie to działa mi tak, że klikam na 2,2 a wyświetla mi 1,2 albo 3,1.

Chodzi mi o to

if (isSpriteClicked(sprite2, sf::Mouse::Right, window))
            {
                std::cout << "Sprite clicked" << std::endl;
                // window.setSize(sf::Vector2u(750, 600));

                // pozycja myszki wzgledem okna aplikacji
                sf::Vector2i /*pixelPos*/ touchPoint = sf::Mouse::getPosition(window);
                //sf::Vector2f worldPos = window.mapPixelToCoords(pixelPos);
                //sf::Vector2i touchPoint(worldPos.x, worldPos.y);

                // prostokąt planszy
                sf::FloatRect gridSize = sprite2.getGlobalBounds();

                // wolna przestrzeń po lewej i prawej stronie planszy
                sf::Vector2f gapOutsideOfGrid = sf::Vector2f((resX - gridSize.width) / 2, (resY - gridSize.height) / 2);

                // pozycja myszki względem planszy
                sf::Vector2f gridLocalTouchPos = sf::Vector2f(touchPoint.x - gapOutsideOfGrid.x, touchPoint.y - gapOutsideOfGrid.y);
                
                // pojedynczy kwadrat planszy
                sf::Vector2f gridSectionSize = sf::Vector2f(gridSize.width / 3, gridSize.height / 3);

                int column, row;

                // sprawdzenie, na którą kolumnę kliknął gracz
                if (gridLocalTouchPos.x < gridSectionSize.x) // pierwsza kolumna
                {
                    column = 1;
                }
                else if (gridLocalTouchPos.x < gridSectionSize.x * 2) // druga kolumna
                {
                    column = 2;
                }
                else if (gridLocalTouchPos.x < gridSize.width) // trzecia kolumna
                {
                    column = 3;
                }

                // sprawdzenie, na który wiersz kliknął gracz
                if (gridLocalTouchPos.y < gridSectionSize.y) // pierwszy wiersz
                {
                    row = 1;
                }
                else if (gridLocalTouchPos.y < gridSectionSize.y * 2) // drugi wiersz
                {
                    row = 2;
                }
                else if (gridLocalTouchPos.y < gridSize.height) // trzeci wiersz
                {
                    row = 3;
                }

                std::cout << "row = " << row << std::endl;
                std::cout << "column = " << column << std::endl;
            }
komentarz 26 sierpnia 2018 przez jankustosz1 Nałogowiec (35,880 p.)
Obstawiam że mieszasz gdzieś pozycje na oknie z pozycjami na widoku. Próbowałeś zakomentować robienie tych czarnych pasków?

Debugowałeś to? Zastaw pułapkę i sprawdź czy każda z tych zmiennych które zrobiłeś ma prawidłową wartość. To najlepszy sposób na tego typu błędy.

gridLocalTouchPos wygląda moim zdaniem podejrzanie bo mieszasz pozycję myszki NA OKNIE z szerokością paska NA VIEW.

Podobne pytania

0 głosów
1 odpowiedź 665 wizyt
pytanie zadane 31 maja 2016 w C i C++ przez Grzyboo Nałogowiec (28,860 p.)
0 głosów
1 odpowiedź 221 wizyt
pytanie zadane 22 października 2020 w C i C++ przez Xarcane Początkujący (440 p.)
0 głosów
0 odpowiedzi 106 wizyt
pytanie zadane 10 września 2021 w Java przez Mateusz85 Początkujący (370 p.)

92,573 zapytań

141,423 odpowiedzi

319,648 komentarzy

61,959 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...