[SFML Tutorial] 텍스트와 폰트 (번역)

https://www.sfml-dev.org/tutorials/2.5/graphics-text.php
손번역입니다.



폰트 로딩

텍스트를 그리기 위해서는 무조건 사용가능한 폰트가 필요합니다. 폰트는 sf::Font 클래스에 캡슐화되어있는데요. 주요 기능은 폰트의 로딩과 glyphs(시각적인 문자)의 획득, 속성(attributes)을 읽어오는 겁니다.
일반적인 프로그램에서는 거의 첫번째 기능인 폰트 로딩만을 사용합니다. 그래서 일단 폰트 로딩에 대해서만 중점을 두고 다루도록 하겠습니다.

일반적으로 폰트는 loadFromFile 메서드를 사용해서 디스크에서 로드해 사용합니다. 

sf::Font font; 
if (!font.loadFromFile("arial.ttf")) 
// error... }

sfml은 자동으로 시스템에서 폰트를 로드해주지 않습니다.
그러니까 font.loadFromFile("Courier New")같은 코드는 작동하지 않습니다.
첫번째로, sfml은 폰트명이 아니라 파일명을 필요로 하거든요.
두번째로, sfml은 시스템 폰트 폴더에 마법처럼 접근을 할수는 없기 때문입니다.
그래서 폰트를 로드하려면 이미지나 사운드 같은 다른 리소스처럼 앱에 폰트 파일을 포함시켜야만 합니다.
loadFromFile 메서드도 가끔 불명확한 이유로 실패할 수 있는데요. 그럼 먼저 sfml이 콘솔에 출력하는 에러 메시지를 체크하세요.
에러 메시지가 파일을 열수 없다고 말한다면, 작업 디렉터리를 확인하세요. 파일 경로는 상대적으로 해석될 수 있음을 유의해주세요.
예를 들어, 데스크탑 환경에서 앱을 실행할때는 작업 디렉터리가 실행 폴더가 됩니다.
하지만 IDE에서 프로그램을 실행할 때는 작업경로가 프로젝트 디렉터리로 설정될 수 있는데요. 이건 보통 프로젝트 설정에서 바꿀수 있습니다.

이외에도 loadFromMemory 메서드로 메모리에서 폰트 파일을 로드할 수도 있고, loadFromStream 메서드를 쓰면 커스텀 입력스트림에서 로드할 수도 있습니다.

sfml은 대부분의 폰트 포맷을 지원합니다.
자세한 건 API 문서에서 볼 수 있어요.

자 이제 필요한 것들은 전부 준비됐습니다.
이제 폰트가 로드됐으니, 텍스트를 그려보죠.


텍스트 그리기

텍스트를 그리기 위해서는 sf::Text 클래스를써야합니다.
사용법은 별거없어요! 쉽습니다.

sf::Text text; 

// 폰트 설정
text.setFont(font); 
// font는 sf::Font의 객체입니다.

// 보여줄 문자열 세팅
text.setString("Hello world"); 

// 문자 사이즈 세팅
text.setCharacterSize(24); 
// 포인트과 아니라 픽셀 단위입니다!

// 색 설정
text.setFillColor(sf::Color::Red); 

// 텍스트 스타일 설정 text.setStyle(sf::Text::Bold | sf::Text::Underlined); 

... 
// 메인 루프 내부

window.clear() and window.display() window.draw(text);


텍스트는 변환될 수도 있습니다.
위치와 방향(orientation), 스케일도 가지거든요.
포함된 메서드는 sf::Sprite 클래스나 다른 sfml 엔티티들과 같습니다.
자세한 설명은 엔티티 변환 튜토리얼에서 보세요.

비-아스키(non-ASCII) 문자에 대한 문제는 어떻게 할까요?

비-아스키 문자를 정확하게 처리하는건 꽤 까다롭습니다.
일단 텍스트를 해석하고 그리는 과정에 관련된 다양한 인코딩에 대해서 이해를 해야 합니다.
인코딩이라는 골칫거리에서 벗어나고 싶다면, 와이드(wide) 문자열 리터럴을 쓰는게 제일 간편합니다.
이렇게요.

text.setString(L"יטאח"); 

문자열 앞에 L 접두어를 붙이는건 컴파일러에게 와이드 문자열로 처리해달라고 말해주는겁니다.
그런데 와이드 문자열은 C++에서는 이상한 짐승 같습니다. 표준에서 사이즈나 인코딩 방식을 명시하지 않거든요. 그래서 컴파일러마다 제각각으로 구현을 합니다. gcc는 uft-32를 써서 4바이트씩이고, msvc는 uft-16를 써서 2바이트입니다.
하지만 우리는 대부분의 플랫폼에서 유니코드 문자열을 처리해준다는걸 압니다. 그리고 sfml도 유니코드를 올바르게 처리할 줄 압니다.

C++11 표준에서는 새 문자 타입과 uft-8/16/32 문자열 리터럴을 위한 접두어들을 제공합니다.
하지만 sfml은 아직 지원하지 않아요.

게다가 폰트가 그리려는 문자를 전부 포함하는지도 확인을 해야합니다.
사실, 폰트는 모든 문자들을 포함하지 않거든요. 예를 들어 Arabic 폰트는 일본어는 띄워줄수 없어요. 
유니코드에는 무려 10만여개의 문자가 있답니다...  현실적인 문제죠.


나만의 텍스트 클래스 만들기

sf::Text는 꽤 제한이 있죠. 미리 정의된 타입 말고 다른걸 쓰고싶을 수도 있습니다.
sf :: Text가 너무 제한적인 것 같거나 미리 렌더링 된 glyphs으로 뭔가 다른걸 하고 싶나요?
sf :: Font가 당신에게 필요한 모든 것을 제공합니다.

텍스쳐에 포함될 모든 미리정의된 glyphs들의 사이즈를 명시할 수도 있습니다.
이렇게요.

const sf::Texture& texture = font.getTexture(characterSize); 

glyph는 요청 시에 텍스쳐에 추가된다는 점이 중요합니다.
앞서 언급했듯이 폰트를 로드하더라도, 나타낼 수 없는 수많은 문자들이 존재합니다. 
대신에 getGlyph 메서드를 호출할 때 즉시 렌더링 됩니다.

폰트 텍스쳐로 의미있는 무언가를 해보고 싶다면, 거기에 포함된 glyph의 텍스쳐 좌표를 가져와야만 합니다.

sf::Glyph glyph = font.getGlyph(character, characterSize, bold); 

character는 가져오려는 glyph 문자의 UTF-32 코드입니다. 그리고 문자의 사이즈나, glyph의 진한 글씨(bold) 여부를 특정할 필요가 있습니다. 

sf::Glyph 구조체는 3개의 멤버를 가지는데요.
->그 중 textureRect는 텍스쳐 이내 glyph의 텍스쳐 좌표를 포함합니다.
->bounds는 glyph의 bounding 사각을 포함하는데요. 이건 텍스트의 기준선에 상대적으로 위치를 잡는걸 도와줍니다.
->advance는 텍스트에서 다음 glyph의 시작위치를 가져올 때 사용할 수평 오프셋입니다.

폰트의 다른 metrics의 일부를 가져올 수도 있습니다. 가령 두 문자 사이의 틈이나 줄 간격 같은거요. 

int lineSpacing = font.getLineSpacing(characterSize); 
int kerning = font.getKerning(character1, character2, characterSize);

설정

트랙백

댓글

[SFML Tutorial] Sprite와 Texture (번역)

https://www.sfml-dev.org/tutorials/2.5/graphics-sprite.php
손번역입니다.



용어

웬만하면 이 두가지의 객체를 친숙하게 느낄 겁니다. 그것들을 간단하게 정의해보죠.

texture는 이미지입니다. 그런데도 texture라 부르는건 2D 엔티티에 매핑된다는 구체적인 역할이 있기 때문입니다.

sprite는 그냥 질감이 있는(textured) 직사각형이에요.



그래도 sprite와 texture가 뭔지 이해가 안되시면 위키백과에 더 훌륭한 설명이 있습니다.


texture 로딩하기

sprite를 생성하기 전에 먼저 유효한 texture가 필요합니다.
texture를 캡슐화한 클래스는 놀랍게도 sf::Texture입니다.(--이게 놀라운 건가?--)
texture의 유일한 역할은 로딩해서 그래픽 엔티티에 매핑되는겁니다. 그래서 여기에 들어있는 거의 모든 메서드는 로딩과 업데이트에 대한 것들뿐이에요.
texture를 로딩하는 가장 일반적인 방법은 디스크에서 이미지 파일을 가져오는거죠. 이건 loadFromFile 메서드를 쓰면 됩니다.

sf::Texture texture; 
if (!texture.loadFromFile("image.png")) 

    // error... 
}

loadFromFile 메서드는 가끔 이상한 이유로 실패할 수가 있는데요.
그럼 먼저 sfml이 콘솔에 출력하는 에러 메시지를 체크하세요.
->에러 메시지가 파일을 열수 없다고 말한다면, 작업 디렉터리를 확인하세요. 파일 경로는 상대적으로 해석될 수 있음을 유의해주세요.
->If the message is unable to open file, make sure that the working directory (which is the directory that any file path will be interpreted relative to) is what you think it is: 
예를 들어, 데스크탑 환경에서 앱을 실행할때는 작업 디렉터리가 실행 폴더가 됩니다.
하지만 IDE에서 프로그램을 실행할 때는 작업경로가 프로젝트 디렉터리로 설정될 수 있는데요. 이건 보통 프로젝트 설정에서 바꿀수 있습니다.

loadFromMemory 메서드는 메모리에서 이미지파일을 로드할 수 있고, loadFromStream는 커스텀 입력스트림에서 이미지를 로드할 수 있습니다.
그리고 loadFromImage는 이미 로드된 이미지를 로드할 수 있습니다.

후자는 이미지 데이터의 저장과 조작을 도와주는 유틸리티 클래스, sf::Image에서 텍스처를 로드합니다. 픽셀 수정이나, 투명도 채널(transparency channel) 생성 같은걸 할수 있어요.

시스템 메모리에 있는 sf::Imagestay의 픽셀은 비디오 메모리에 머무르는 텍스쳐의 픽셀에 비하면, 최대한 빠른 동작을 보장하는데요. 검색이나 업데이트는 느리지만 그리는 건 빠릅니다.

sfml은 대부분의 일반적인 이미지 포맷을 거의 다 지원합니다.
자세한건 API 문서에서 볼수 있어요.

이 모든 로딩 메서드들은 선택적인 인자를 가지는데요. 이건 이미지의 더 작은 부분을 로드하고자 할 때 사용됩니다.

// 10,10 위치에다 32x32 직사각형으로 로드 
if (!texture.loadFromFile("image.png", sf::IntRect(10, 10, 32, 32))) 
// error... } 

sf::IntRect 클래스는 사각형을 표현하는 간단한 유틸리티 타입입니다.
이 클래스의 생성자는 왼쪽-위 모퉁이의 좌표와 사각형의 사이즈를 인자로 받습니다.

이미지로 텍스쳐를 로드하지 않고, 대신에 픽셀 배열로 바로 업데이트를 하고 싶다면, 일단 빈 객체로 생성부터 해놓고 나중에 업데이트를 하면 됩니다.

// 빈 200x200 텍스쳐 생성
if (!texture.create(200, 200)) 
// error... } 

이 시점에 텍스쳐의 내용물은 정의되지 않았습니다.

텍스쳐의 픽셀을 업데이트하기 위해서는 update 메서드를 써야만 하는데요. 이 메서드는 데이터 종류만큼의 오버로드 버전을 가집니다.

// 픽셀의 배열로 텍스쳐 업데이트
sf::Uint8* pixels = new sf::Uint8[width * height * 4]; 
// *4는 픽셀이 4개의 컴포넌트를 가지기 때문 (RGBA)

...
texture.update(pixels); 

// sf::Image 로 텍스쳐 업데이트
sf::Image image; 
... 
texture.update(image); 

// 윈도우의 현재 컨텐츠들로 텍스쳐 업데이트
sf::RenderWindow window; 
... 
texture.update(window); 

이 예제들은 모두 소스가 텍스쳐와 크기가 같다고 가정합니다.
이런 경우가 아니면서 텍스쳐의 한 부분만을 업데이트하려고 한다면, 그 부분에 해당하는 서브-사각형(sub-rectangle) 부분의 좌표를 특정할 수 있습니다.
더 자세한걸 원한다면 문서를 참조하세요.

추가로, 텍스쳐는 렌더링 방식을 변경하는 2개의 속성을 가집니다.

첫번째 속성은 텍스쳐를 부드럽게(smooth) 해주는데요. 픽셀의 경계를 덜 보이게 하는 방식으로 작동하기 때문에 이미지가 좀 흐려질 수 있는 부작용이 있습니다. 그래서 크기가 어느정도 큰 경우에 바람직한 방법입니다.

texture.setSmooth(true);

주의

그 텍스쳐의 인접한 픽셀을 부드럽게 만들기 때문에, 선택한 텍스쳐 영역 밖에서 원치 않은 사이드 이펙트가 발생할 수도 있습니다.
이건 sprite가 정수가 아닌 좌표에 있을 때 발생할 수 있습니다.

두번째 속성을 사용하면 단일 sprite 내에서 반복해서 나열할 수 있습니다.

texture.setRepeated(true);




이건 sprite가 텍스쳐보다 큰 사각형으로 설정되어있을 때만 작동합니다. 아니면 아무런 효과도 안 나와요

그럼 이제 sprite를 만들어봅시다!

sf::Sprite sprite; 
sprite.setTexture(texture); 

이러고 나서 그리면 됩니다.

// inside the main loop, between window.clear() and window.display() window.draw(sprite); 

sprite에 전체 텍스쳐에 사용하지 않으려면, 사각 텍스쳐에 세팅하세요.

sprite.setTextureRect(sf::IntRect(10, 10, 32, 32)); 

sprite의 색도 바꿀수 있는데요. 설정한 색은 sprite의 텍스처로 변경됩니다.
이건 sprite의 전역 투명도(transparency[alpha])를 바꾸는 것에도 사용할 수 있습니다.
This can also be used to change the global transparency (alpha) of the sprite.

sprite.setColor(sf::Color(0, 255, 0)); 
// 초록색 
sprite.setColor(sf::Color(255, 255, 255, 128)); 
// 반투명

아래 sprite들은 전부 같은 텍스쳐를 사용했지만, 다른 색으로 나타납니다.



sprite들도 이렇게 변환이 될 수 있어요. 각자 위치와 방향(orientation), 스케일(scale)을 가지고 있기 때문이죠.


// 위치(position)
sprite.setPosition(sf::Vector2f(10, 50)); 

// 절대 위치(absolute position)
sprite.move(sf::Vector2f(5, 10)); 

// 현재 각도(angle)에 상대적으로 offset
// 로테이션(rotation)
sprite.setRotation(90); 

// 절대 각도(absolute angle)
sprite.rotate(15); 

// 현재 각도(angle)에 상대적으로 offset
// 스케일(scale)
sprite.setScale(sf::Vector2f(0.5f, 2.f)); 

// absolute scale factor sprite.scale(sf::Vector2f(1.5f, 3.f)); 
// factor relative to the current scale 

기본적으로, 이 3가지 변환의 원점은 왼쪽-위 모퉁이입니다.

다른 지점을 원점으로 설정하고 싶다면, setOrigin 메서드를 쓸 수 있습니다.

sprite.setOrigin(sf::Vector2f(25, 25)); 

변환 함수는 모든 sfml 엔티티에 공통적인데요. 자세한건 "엔티티 변환" 튜토리얼에서 봅시다.
https://www.sfml-dev.org/tutorials/2.5/graphics-transform.php


white square 문제

성공적으로 텍스쳐를 로드해서! sprite도 제대로 구성됐는데! 
눈앞에 보이는건 하이얀 바탕 뿐입니다..

왜 이런걸까요?

사람들이 많이 저지르는 실수인데요.
sprite의 텍스쳐가 세팅되었을 때, 내부적으로 텍스쳐 객체의 포인터를 저장합니다.
때문에, 텍스쳐가 파괴되거나 이동된다면 sprite는 유효하지 않은 텍스쳐 포인터를 갖게 됩니다.

이 문제는 아래와 같은 종류의 함수를 작성할 때 발생합니다.

sf::Sprite loadSprite(std::string filename) 

    sf::Texture texture;
    texture.loadFromFile(filename); 
    return sf::Sprite(texture); 

// error: 텍스쳐가 여기에서 파괴됩니다.

그래서 텍스쳐의 생명주기(lifetime)을 확실하게 관리해서 sprite가 사용되는 동안은 유지되게 해야합니다.

  
텍스쳐의 사용은 최소한으로

텍스쳐는 가능한 한 적게 쓰는게 좋습니다. 이유는 간단합니다. 현재 텍스쳐를 변경하는건 비용이 많이 드는 작업이거든요. 특히 그래픽 카드에요.

때문에, 동일 텍스쳐로 여러개의 sprite를 그리면 성능에 도움이 됩니다.

추가로 단일 텍스쳐를 사용하면 정적 geometry를 단일 엔티티로 그룹화할 수 있습니다. (draw 호출마다 하나의 텍스쳐만 사용할 수 있습니다.) 이렇게 하면 여러 엔티티의 집합을 쓰는것보다 꽤 빨라지거든요.
정적 geometry를 처리하는건 다른 클래스들을 포함하기 때문에, 이 튜토리얼의 영역을 벗어납니다.
더 자세한걸 원하신다면 vertex array 튜토리얼을 살펴보세요.

애니메이션 sheet이나 tileset을 생성할 때마다 이걸 기억하세요.
-> 텍스쳐의 사용은 최대한 적게!


OpenGL 코드와 함께 sf::Texture 사용하기

만약 sfml 그래픽 엔티티 대신에 OpenGL을 쓴다면 sf::Texture를 OpenGL 텍스쳐 객체의 래퍼로 써서 나머지 OpenGL 코드와 함께 사용할 수 있습니다.

드로잉(보통 glBindTexture)을 위해 sf::Texture를 바인딩한다면 bind 정적 메서드를 호출하면 됩니다.

sf::Texture texture; 
... 

// 텍스쳐 바인딩
sf::Texture::bind(&texture); 

// 여기에서 텍스쳐된(textured) OpenGL 엔티티를 그림

// 아무 텍스쳐도 바인딩하지 않음
sf::Texture::bind(NULL);

설정

트랙백

댓글

[SFML Tutorial] 2D 사물 그리기 (번역)

https://www.sfml-dev.org/tutorials/2.5/graphics-draw.php
손번역입니다.



들어가는 말

이전 튜토리얼에서 언급했듯이 sfml의 윈도우 모듈은 OpenGL 윈도우를 열고 이벤트를 핸들링하는 쉬운 방법을 제공하는 거지. 무언가를 그리게 해주는 건 아닙니다.

남아있는 옵션은 더 강력하고 복잡한 저수준 OpenGL API를 쓰는거겠죠.

다행히도 sfml은 OpenGL보다 간단한 graphics module을 제공합니다.


윈도우 그리기

graphics module이 제공하는 엔티티를 그리기 위해서는 sf::RenderWindow 같은 특수화된 윈도우를 써야만 합니다.

sf::Window를 상속한 클래스들은 그 메서드들을 전부 포함합니다.(당연한 소리)
sf::Window에 대해 익혔던 생성, 이벤트핸들링,    프레임 제어, OpenGL와의 혼합 등 모든 건 RenderWindow에도 똑같이 적용됩니다.

그 외에도 sf::RenderWindow에는 드로잉을 도와주는 고수준 메서드가 있습니다.
이 튜토리얼에서는 clear와 draw라는 두 메서드에 초점을 맞출 거에요.
이름에서 보이는것처럼 clear는 선택된 색으로 윈도우를 지워버리고, draw는 무슨 객체든간에 그려놓습니다.
They are as simple as their name implies: clear clears the whole window with the chosen color, and draw draws whatever object you pass to it.

다음은 render윈도우로 돌리는 메인 루프가 대강 어떻게 생겼는지 보여줍니다.

#include <SFML/Graphics.hpp
int main() 

    // 윈도우 생성
    sf::RenderWindow window(sf::VideoMode(800, 600), "My window"); 

   // 윈도우가 열려있는만큼 프로그램이 돕니다.
    while (window.isOpen()) 
    { 
        // 루프 중에 발생하는 모든 이벤트 체크 
        sf::Event event; 

        while (window.pollEvent(event)) 
        { 
            // "close 요청" 이벤트. 이제 윈도우 닫음.
            if (event.type == sf::Event::Closed) 
                window.close(); 
        } 

        // 윈도우를 검은색으로 색칠
        window.clear(sf::Color::Black); 

        // 뭐든 여기에서 그리세요.
        // window.draw(...); 
        // 현재 프레임을 끝냅니다.
        window.display(); 
    } 
    return 0; 
}

뭔가를 그리기 전에는 의무적으로 clear를 호출해야 합니다. 안그러면 이전 프레임의 내용물들이 뒤에 계속 깔려있을거에요.
유일한 예외사항은 그려낸 것들이 윈도우를 전부 덮어버려서 새로 그려지지 않은 픽셀이 없을 때 뿐입니다. 
이 경우에는 clear를 호출하지 않아도 돼요. 뭐 clear를 쓰든 안쓰든 눈에 띌만한 성능차이는 없습니다.

display도 의무적으로 호출해야 합니다. 이건 가장 마지막 호출까지 그려진걸 가져와서 윈도우에 표시해줍니다.
사실 윈도우에 바로 그리는 게 아니라 숨겨진 버퍼에 넣었다가 그려넣는 거에요.
이 버퍼는 display를 호출할 때 윈도우로 복사됩니다. 이걸 이중 버퍼링(double-buffering)이라고 해요

주의
clear/draw/display 사이클이 가장 좋은 드로잉 방법입니다. 
다른 구조를 사용하려고 들지 마세요. 가령 이전 프레임의 픽셀을 유지하거나, 픽셀을 "지우거나(erasing)", 한번 그리고 display를 여러번 호출하는 짓들이요.
그럼 이중버퍼링 때문에 이상한 결과물이 나올수 있거든요.
Modern--한 graphics 하드웨어와 API들은 main 루프가 반복될 때마다 모든것들이 완벽하게 refresh되는 clear/draw/display 사이클을 염두에 두고 만들어졌습니다.
1000개의 sprite들을 초마다 60번씩 그려넣는걸 겁내지 마세요. 요즘 컴퓨터는 동시에 수백만개의 삼각형들도 처리할 수 있습니다. 이에 비하면 저런건 아무것도 아니죠.


뭘 그릴 수 있나요?

그리기를 준비할 main 루프가 준비됐네요. 그럼 이제 무엇으로, 어떻게 해서 실제로 그려넣을 수 있는지 살펴봅시다.

sfml은 그릴 수 있는 4종류의 엔티티를 제공합니다. 그 중의 3개인 sprites와 text, shapes는 바로 써먹을 수 있죠.
마지막 하나는 사용자 정의 엔티티 생성을 도와주는 vertex arrays라는 이름의 building block입니다.

이것들이 다 일부 속성들을 공유하긴 하지만 엔티티마다 각자의 뉘앙스가 있어서요.
자세한건 아래 튜토리얼들에서 설명해놨습니다. 

Sprite 튜토리얼
https://www.sfml-dev.org/tutorials/2.5/graphics-sprite.php
Text 튜토리얼
https://www.sfml-dev.org/tutorials/2.5/graphics-text.php
Shape 튜토리얼
https://www.sfml-dev.org/tutorials/2.5/graphics-shape.php
Vertex array 튜토리얼
https://www.sfml-dev.org/tutorials/2.5/graphics-vertex-array.php


Off-screen drawing

SFML은 윈도우에 직접 접근하지 않고 텍스쳐를 그리는 방법을 제공합니다.
근데 그렇게 하려면 sf::RenderWindow 대신에 sf::RenderTexture를 써야합니다.
이건 같은 드로잉 메서드를 갖고 있어요. 같은 슈퍼클래스 sf::RenderTarget에게서 상속을 받거든요.

// 500x500 render-텍스쳐 생성
sf::RenderTexture renderTexture; 

if (!renderTexture.create(500, 500)) 

    // error... 


// 같은 메서드를 써서 그립니다.
renderTexture.clear(); renderTexture.draw(sprite); 
// 그릴거 있으면 더 그리세요.

renderTexture.display(); 

// 타겟 텍스쳐 획득 (stuff가 그려질 곳) 
const sf::Texture& texture = renderTexture.getTexture(); 

// window에 sf::Sprite를 그림 
sprite(texture); 
window.draw(sprite); 

getTexture 메서드는 읽기전용 텍스쳐를 반환합니다. 쓸 수만 있고 수정할 수는 없단거죠.
쓰기 전에 수정할 필요가 있다면 sf::Texture 객체에다 복사해서 그걸 대신 수정하면 됩니다.

게다가 sf::RenderTexture는 sf::RenderWindow에서 쓰던 것과 동일한 메서드로 view와 OpenGL을 처리할 수 있습니다.
자세한건 해당 튜토리얼에서 보세요.
OpenGL을 써서 render-texture를 그릴 경우, create 메서드의 세번째 인자를 써서 깊이(depth) 버퍼의 생성을 요청할수 있습니다.

renderTexture.create(500, 500, true); 
// depth 버퍼 가능


스레드로 그리기

sfml은 멀티스레드 드로잉을 지원합니다. 뭐 다른 특별한 작업을 하지 않아도 돼요.
딱 하나 기억해야 할건 다른 스레드에서 뭘 그리기 전에 윈도우를 정지시켜야 한단 겁니다.
윈도우는, 더 정확하게는 OpenGL 컨텍스트는, 동시에 하나 의상의 스레드에서 수행될수 없기 때문이죠.

void renderingThread(sf::RenderWindow* window) 

    // 렌더링 루프
    while (window->isOpen()) 
    { 
        // draw... 
        // 현재 프레임 끝냄
        window->display(); 
    } 


int main() 

    // 윈도우를 생성합니다. 
    // OS의 한계가 있으니 웬만하면 메인 스레드에서 생성하는게 좋다는걸 명심하세요. 
    sf::RenderWindow window(sf::VideoMode(800, 600), "OpenGL"); 

    // 윈도우의 OpenGL context를 정지합니다.
    window.setActive(false); 

    // 렌더링 스레드를 launch합니다.
    sf::Thread thread(&renderingThread, &window); 
    thread.launch(); 

    // 이벤트나 로직 등등을 처리하는 루프
    while (window.isOpen()) 
    { ... } 

    return 0; 


보다시피, 렌더링 스레드에서는 윈도우의 활성에 대해서 신경 안써도 됩니다.
필요할 때는 언제나 자동으로 수행을 해주거든요

최대한의 이식성을 위해서라도 항상 메인 스레드에서 윈도우를 생성하고 이벤트를 핸들링해야한단걸 잊지 마세요.
이건 윈도우 튜토리얼에서도 설명합니다.



설정

트랙백

댓글

[SFML Tutorial] 키보드와 마우스, 조이스틱 (번역)

https://www.sfml-dev.org/tutorials/2.5/window-inputs.php
손번역입니다.



들어가는 말

이 튜토리얼에서는 키보드와 마우스, 조이스틱 등의 전역 입력 디바이스에 액세스하는 방법을 알려줍니다.
이벤트와 헷갈리지 마세요.
실시간 입력은 언제나 키보드나 마우스,조이스틱의 전역-상태(global state)에 이런식으로 query를 날릴 수 있습니다. "버튼 눌려있냐?", "마우스 지금 어딨냐?"
그러면 무언가가 발생할때마다 이렇게 이벤트가 날라올겁니다. "버튼 눌렸다", "마우스 움직임"


키보드

키보드 상태에 액세스할 수 있는 기능을 제공하는 클래스는 sf::Keyboard입니다. 
이건 isKeyPressed라는 메서드 하나만을 갖고 있는데요. key가 눌렸는지 떼였는지를 체크해줍니다. 그리고 이건 정적 메서드라서 객체 생성 안해도 돼요.

이 메서드는 윈도우의 포커스는 무시하고 키보드의 상태를 바로 읽어들입니다.
이건 윈도우가 비활성중이라더라도 true를 반환할 수 있다는거죠.

if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) 

    // left key가 눌렸네요. character가 움직입니다.
    character.move(1, 0); 


Key 코드들은 sf::Keyboard::Key enum(열거체)에 정의되어있습니다.

주의
os와 키보드 레이아웃에 의존적인 일부 키 코드는  미스가 발생하거나 부정확하게 작동할 수 있습니다.
이건 아마 나중에 고쳐질 수 있을거에요.


마우스

마우스 상태에 접근하게 해주는 클래스는 sf::Mouse입니다.
친구인 sf::Keyboard처럼, sf::Mouse도 정적 메서드만을 가져서 객체 생성은 안해도 됩니다.
sfml은 동시에 하나의 마우스만을 핸들링하기 때문이죠.

이렇게 버튼이 눌렸는지 체크할 수 있읍니다.

if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) 

    // 마우스 왼쪽 버튼 눌림. shoot!
    gun.fire(); 


마우스 버튼 코드들은 마찬가지로 sf::Mouse::Button enum에 정의되어있습니다.

SFML은 왼쪽/오른쪽버튼, 가운데 휠과 두개의 여분 버튼까지 해서 총 5개의 버튼을 지원합니다.

데스크탑이나 윈도우에 상대적인 마우스의 현재 위치를 획득/설정할 수 있습니다. 이렇게요.

// 데스크탑에 상대적인 전역 마우스 위치 획득
sf::Vector2i globalPosition = sf::Mouse::getPosition(); 

// 윈도우에 상대적인 로컬 마우스 위치 획득
sf::Vector2i localPosition = sf::Mouse::getPosition(window); 
// window는 sf::Window입니다.

// 데스크탑에 상대적인 전역적 마우스 위치 세팅 sf::Mouse::setPosition(sf::Vector2i(10, 50));

// 윈도우에 상대적인 로컬 마우스 위치 세팅 sf::Mouse::setPosition(sf::Vector2i(10, 50), window); 

마우스 휠의 현재 상태를 읽을 수 있는 함수는 없어요.
휠은 상대적으로만 움직이고 질의(query)할 수 있는 절대 상태(absolute state)를 가질 수 없기 때문이죠

마우스의 커서를 보고서 스크린 상의 위치를 알아낼 수도 있습니다.
하지만 마우스 휠을 봐서는 "tick"을 알수는 없어요.
그냥 움직일 때만 MouseWheelScrolled 이벤트로 알려주는거죠.


조이스틱

조이스틱에 접근할 수 있게 해주는 놈은 sf::Joystick입니다.
앞에서 언급한 다른 클래스처럼 이것도 정적메서드만 갖습니다.

조이스틱은 인덱스로 식별할 수 있어요.
sfml은 8개의 조이스틱을 지원하니까 0에서 7까지죠.
그러므로, sf::Joystick의 모든 메서드의 첫번째 인자는 쿼리를 날릴 조이스틱의 인덱스가 들어갑니다.

조이스틱이 연결됐는지 여부도 체크할 수 있습니다.

if (sf::Joystick::isConnected(0)) 

    // 0번째 조이스틱이 연결됨
    ... 


그럼 연결된 조이스틱의 기능들을 가져올 수 있습니다.

// 0번 조이스틱의 버튼 수를 체크합니다.
unsigned int buttonCount = sf::Joystick::getButtonCount(0); 

// 0번 조이스틱에 Z축이 있는지 체크합니다.
bool hasZ = sf::Joystick::hasAxis(0, sf::Joystick::Z); 

조이스틱 축은 sf::Joystick::Axis enum에 정의되어었습니다.  
버튼에는 뭐 별다른 의미가 있는건 아니라서 그냥 간단하게 0-31로만 숫자가 매겨졌습니다.

마지막으로, 조이스틱 축들과 버튼의 상태에 쿼리를 날릴 수 있습니다.

// 0번째 조이스틱의 button 1이 눌렸냐?
if (sf::Joystick::isButtonPressed(0, 1)) 

    // 맞음. shoot! 
    gun.fire(); 


// X와 Y의 현재 위치가 뭐냐
float x = sf::Joystick::getAxisPosition(0, sf::Joystick::X); 
float y = sf::Joystick::getAxisPosition(0, sf::Joystick::Y); 
character.move(x, y); 

주의
조이스틱의 상태는 이벤트가 체크될때마다 자동으로 업뎃됩니다.
game loop를 시작하기 전에, 이벤트를 체크하지 않거나 조이스틱 상태에 쿼리(연결여부 체크 등)를 날리지 않을 거라면, 
직접 sf::Joystick::update() 메서드를 호출해서 업데이트 상태를 확인해야합니다.

설정

트랙백

댓글

[SFML Tutorial] 이벤트 (번역)

https://www.sfml-dev.org/tutorials/2.5/window-events.php

손번역입니다.



들어가는 말

이 튜토리얼은 윈도우 이벤트에 대해 설명합니다.
그리고 그걸 어떻게 쓰고 쓰지 말아야 하는지 보여주겠습니다.


sf::Event 타입

이벤트를 처리하기 전에, sf::Event 타입이 무엇인지, 그것 어떻게 써야하는지 이해하는 것이 가장 중요하죠. 

sf::Event는 union(공용체)인데요, 동시에 그 멤버들 중 오직 하나만 유효하다는 말입니다.
유효한 멤버는 그 이벤트 타입에 해당되는 멤버입니다. 예를들어 KeyPressed 이벤트는 event.key에 해당하죠.

다른 멤버를 읽으려고 시도하는건 정의되지 않은 행동(이하 UB)을 일으킵니다.(대부분 랜덤값이나 유효하지 않은 값이에요)
타입에 해당되지 않는 이벤트 멤버를 사용하지 않고자 노력하는 것이 중요합니다.

sf::Event 객체들은 sf::Window의 메서드 pollEvent나 waitEvent로 의해 채워집니다. 
오직 이 두 메서드만 유효한 이벤트를 제공해요.
뭐든간에 pollEvent나 waitEvent를 쓰지 않고 얻은 sf::Event는 위에 언급된 UB를 일으킵니다.

일반적인 이벤트 루프는 이렇습니다.

sf::Event event; 

// 이벤트를 기다립니다.
while (window.pollEvent(event)) 

    // 이벤트의 타입을 체크합니다.
    switch (event.type
    { 
       // 윈도우가 닫힘
        case sf::Event::Closed: window.close(); break; 

       // key가 눌림
        case sf::Event::KeyPressed: ... break; 

        // 다른 이벤트 타입들은 무시함
        default: break; 
    } 
}

주의
위 단락을 한번 더 읽어서 확실히 이해하도록 합시다.
미숙한 프로그래머들은 sf::Event union에서 많은 문제들을 일으키곤 합니다.

좋아요. 그럼 이제 sfml이 지원하는 이벤트가 뭔지, 그것들이 무얼 의미하고 어떻게 써야하는지 알아봅시다.


Closed 이벤트

sf::Event::Closed 이벤트는 윈도우 관리자가 제공하는 방법으로, 가령 닫기 버튼이나 키보드 단축키 등으로 사용자가 윈도우를 닫으려고 할때 트리거가 발생합니다.
이 이벤트는 오직 close 요청만을 표현하는데요. 그럼 그 윈도우는 그 이벤트가 전달되기 전에는 닫히지 않습니다.

일반적인 코드는 이 이벤트를 받을 때 그냥 window.close()를 호출해서 실제로 윈도우를 닫습니다.
하지만 현재 앱 상태를 저장하거나 사용자가 원하는 것을 물어보는 것 등의 다른 작업을 먼저 수행할 수도 있습니다.
아무것도 하지 않으면 윈도우는 그냥 열려있습니다.

sf::Event union에는 이 이벤트와 관련된 멤버가 없습니다.

if (event.type == sf::Event::Closed)
    window.close();


Resized 이벤트

sf::Event::Resized 이벤트는 윈도우의 사이즈가 재조정되거나, 유저의 행동이나 프로그램의 동작으로 window.setSize를 호출할 때 트리거가 발생합니다.

렌더링 세팅을 조정하기 위해서 이 이벤트를 쓸 수 있습니다.
OpenGL을 바로 쓰면 viewport고,
sfml-graphics를 쓰면 current view입니다.

이 이벤트와 관련된 멤버는 event.size인데요. 이건 윈도우의 새 사이즈를 포함합니다.

if (event.type == sf::Event::Resized) 

    std::cout << "new width: " 
                    << event.size.width 
                    << std::endl; 
    std::cout << "new height: " 
                     << event.size.height 
                     << std::endl;
}


LostFocus와 GainedFocus 이벤트

sf::Event::LostFocus와 sf::Event::GainedFocus 이벤트는 윈도우가 focus를 잃거나 획득했을때 트리거가 발생하는데요.
다시말해 사용자가 현재 활성된 윈도우를 전환할 때 발생하는겁니다.
윈도우가 포커스를 벗어나면 키보드이벤트를 받을 수가 없죠.

이 이벤트는 가령 윈도우가 비활성될 때 게임을 정지시키고자 할 경우에 쓰이곤 합니다.

sf::Event union에는 이 이벤트와 관련된 멤버가 없습니다.

if (event.type == sf::Event::LostFocus)
    myGame.pause(); 
if (event.type == sf::Event::GainedFocus)
    myGame.resume();


TextEntered 이벤트 

sf::Event::TextEntered 이벤트는 문자가 타이핑될때 트리거가 발생합니다.
이건 KeyPressed 이벤트와 혼동하면 안 돼요.
TextEntered는 사용자의 입력을 번역해서
적절히 출력할 수 있는 문자로 제공합니다.
예를 들어 프랑스 키보드에서 '^'를 누르고 'e'를 누르면 두개의 KeyPressed 이벤트가 발생할겁니다.
하지만 이때 발생한 하나의 TextEntered는 문자 'ê'를 포함합니다.

이건 모든 os가 제공하는 모든 방법, 심지어 가장 구체적이거나 복잡한 방식에서도 작동합니다.

이 이벤트는 일반적으로 텍스트필드에서 사용자입력을 잡아낼때 사용됩니다.

이 이벤트와 관련된 멤버는 event.text입니다. 이건 입력된 문자의 유니코드값을 포함해요

그럼 sf::String에 바로 그걸 집어넣거나, ASCII 범위(0 - 127)로 확신할 수 있다면 char로 캐스팅할 수도 있습니다.

if (event.type == sf::Event::TextEntered) 

    if (event.text.unicode < 128) 
        std::cout << "ASCII character typed: " 
                         << static_cast<char>(event.text.unicode
                         << std::endl; 
}

주목할 만한 점으로, 저것들이 유니코드 표준의 일부라면, 백스페이스 같은 출력할 수 없는 문자도 이 이벤트로 생성될 수 있다는 겁니다. 
대부분의 경우엔 그걸 걸러낼 필요가 있을거에요.

주의
많은 프로그래머들이 사용자 입력을 얻기 위해 KeyPressed 이벤트를 사용하는데요. 이러면 정확한 문자들 제공하기 위해서 가능한 모든 키 조합을 번역하는 정신나간 알고리즘을 구현해야합니다.
그러지 마세요!


KeyPressed와 KeyReleased 이벤트

sf::Event::KeyPressed와 sf::Event::KeyReleased 이벤트는 키보드키가 눌리거나 떼어졌을 때 트리거가 발생합니다.

만약 key가 걸린다면 여러개의 KeyPressed 이벤트가 생성될 겁니다. 디폴트 os 딜레이로요.
(텍스트 에디터에서 문자가 걸렸을 때 적용되는 딜레이와 같습니다.)
반복되는 KeyPressed 이벤트에 장애를 두고 싶다면, window.setKeyRepeatEnabled(false)를 호출할 수 있습니다. 
뒤집어보면 KeyReleased 이벤트 또한 확실히 반복이 되지 않습니다.

이 이벤트는 키가 눌리거나 떼어졌을 때 트리거를 발생시키고자 한다면 사용됩니다.
문자를 만들며 스페이스나 다른 이스케이프를 건너뛰는것처럼요.
이 이벤트는 키를 눌렀거나 떼어졌을 때 스페이스 바를 사용하여 문자를 점프하거나 이스케이프를 사용하여 종료할 때 동작을 정확히 한 번 트리거하려는 경우에 사용됩니다.

주의
종종 사람들은 KeyPressed 이벤트에 반응해서 바로 스무스한 움직임을 구현하려고 하는데요.
이건 기대하는 효과를 보여주지 않습니다. 키가 걸렸을 때는 몇개의 이벤트만을 얻기때문이죠.(반복 딜레이를 기억하세요)
이벤트로 스무스한 움직임을 짜놓고자 한다면, KeyPressed를 세팅하고 KeyReleased가 clear하는 boolean을 써야만 합니다.
그러면 boolean이 설정된만큼 길게, 이벤트와 독립적으로 움직일 수 있습니다.
you can then move (independently of events) as long as the boolean is set.
스무스한 움직임을 제공하는 다른, 더 쉬운 해결책으로는 sf::Keyboard으로 실시간 키보드 입력을 사용하는 게 있습니다.
(해당하는 튜토리얼을 보세요)

이 이벤트와 관련된 멤버는 event.key입니다. 그리고 이건 눌린 키와 떼는 키의 코드뿐만 아니라 modifier 키의 현재 상태에 대한 코드도 포함합니다.(알트,컨트롤,쉬프트)

if (event.type == sf::Event::KeyPressed) 

    if (event.key.code == sf::Keyboard::Escape) 
    { 
        std::cout << "the escape key was pressed" << std::endl; 
        std::cout << "control:" << event.key.control << std::endl; 
        std::cout << "alt:" << event.key.alt << std::endl; 
        std::cout << "shift:" << event.key.shift << std::endl; 
        std::cout << "system:" << event.key.system << std::endl; 
    } 
}

중요한 점은 일부 키들이 os에 특정적인 의미를 가져서 기대하지 않은 행동이 나올수 있단겁니다.
예를 들어 Windows에서의 F10 키는 포커스를 "훔치거나", F12키는 VS를 사용할때 디버거를 시작토록 합니다.
이 문제는 아마 나중에 해결될 수 있을거에요.
This will probably be solved in a future version of SFML.


MouseWheelMoved 이벤트 

sf::Event::MouseWheelMoved 이벤트는 SFML 2.3부터 deprecated되었습니다. 대신 MouseWheelScrolled을 쓰세요.


MouseWheelScrolled 이벤트

sf::Event::MouseWheelScrolled 이벤트는
마우스 휠이 올라가거나 내려갈 때, 아니면 마우스가 곧장 그걸 지원할 때도 트리거가 걸립니다.

이 이벤트와 관련된 멤버는 event.mouseWheelScroll인데요.이건 움직인 휠의 tick 수와, 휠이 움직인 방향, 마우스 커서의 현재 위치를 포함합입니다.

if (event.type == sf::Event::MouseWheelScrolled) 

    if (event.mouseWheelScroll.wheel == sf::Mouse::VerticalWheel) 
        std::cout << "wheel type: vertical" << std::endl; 
    else if (event.mouseWheelScroll.wheel == sf::Mouse::HorizontalWheel) 
        std::cout << "wheel type: horizontal" << std::endl; 
    else 
        std::cout << "wheel type: unknown" << std::endl; 

    std::cout << "wheel movement: " << event.mouseWheelScroll.delta << std::endl; 
    std::cout << "mouse x: " << event.mouseWheelScroll.x << std::endl; 
    std::cout << "mouse y: " << event.mouseWheelScroll.y << std::endl; 
}


MouseButtonPressed와 MouseButtonReleased 이벤트

sf::Event::MouseButtonPressed와 sf::Event::MouseButtonReleased 이벤트는 마우스 버튼이 눌리거나 떼어질 때 트리거가 걸립니다. 

SFML은 왼쪽, 오른쪽 중간버튼과, 추가로 2개의 사이드 버튼을 지원합니다.

이 이벤트들과 관련된 멤버는 event.mouseButton인데요. 이건 버튼이 눌렸는지/떼였는지와, 마우스 커서의 현재 위치를 포함합니다. 

if (event.type == sf::Event::MouseButtonPressed) 

    if (event.mouseButton.button == sf::Mouse::Right) 
    { 
        std::cout << "the right button was pressed" << std::endl; 
        std::cout << "mouse x: " << event.mouseButton.x << std::endl; 
        std::cout << "mouse y: " << event.mouseButton.y << std::endl; 
    } 
}


MouseMoved 이벤트

sf::Event::MouseMoved 이벤트는 윈도우 내부에서 마우스가 움직일때 트리거가 걸립니다.

이 이벤트는 윈도우가 포커스를 가지지 않았을 때도 트리거됩니다.
하지만 윈도우 내부에서 마우스가 움직일 때만 트리거가 걸리는거지, 타이틀바나 테두리에서 움직일 때는 안 걸립니다.

이 이벤트와 관련된 멤버는 event.mouseMove입니다. 이건 윈도우에 상대적인(relative) 마우스 커서의 현재 위치를 포함합니다.

if (event.type == sf::Event::MouseMoved) 

    std::cout << "new mouse x: " << event.mouseMove.x << std::endl; 
    std::cout << "new mouse y: " << event.mouseMove.y << std::endl; 
}


MouseEntered와 MouseLeft 이벤트

sf::Event::MouseEntered와 sf::Event::MouseLeft 이벤트는 마우스가 윈도우로 들어오거나 나갈때 트리거됩니다.

이 이벤트는 sf::Event union에 관련된 멤버가 없읍니다.

if (event.type == sf::Event::MouseEntered) 
    std::cout << "the mouse cursor has entered the window" << std::endl; 

if (event.type == sf::Event::MouseLeft) 
    std::cout << "the mouse cursor has left the window" << std::endl;


JoystickButtonPressed와 JoystickButtonReleased 이벤트 

sf::Event::JoystickButtonPressed와 sf::Event::JoystickButtonReleased 이벤트는 조이스틱 버튼이 눌리거나 떼였을때 트리거됩니다. 


SFML은 8개의 조이스틱과 32개의 버튼을 지원합니다.

이 이벤트와 관련된 멤버는 event.joystickButton인데요. 이건 조이스틱의 식별자와 눌리거나 떼인 버튼의 인덱스를 포함합니다.

if (event.type == sf::Event::JoystickButtonPressed) 

    std::cout << "조이스틱 버튼 눌림!" << std::endl; 
    std::cout << "조이스틱 id: " << event.joystickButton.joystickId << std::endl; 
    std::cout << "버튼: " << event.joystickButton.button << std::endl; 
}


JoystickMoved 이벤트

The sf::Event::JoystickMoved 이벤트는 조이스틱이 움직일때 트리거됩니다.  

조이스틱은 일반적으로 매우 예민한데요. 이 때문에 sfml은 감지의 한계를 둬서 이벤트 루프에 쓸데없는 이벤트를 던지지 않게 해줍니다.

더 많거나 적은 움직임을 받길 원한다면. 이 한계점은 Window::setJoystickThreshold 메서드로 바꿀 수 있습니다. 


SFML은 supports 8개의 조이스틱 축. X, Y, Z, R, U, V, POV X와 POV Y를 지원합니다.
조이스틱이 어떻게 매핑되어있을지는 드라이버에 달려있습니다.

이 이벤트와 관련된 멤버는 event.joystickMove인데요. 이건 조이스틱의 식별자와 축의 이름, [-100, 100] 범위 내의 현재 위치를 포함합니다.

if (event.type == sf::Event::JoystickMoved) 

    if (event.joystickMove.axis == sf::Joystick::X) 
    { 
        std::cout << "X axis moved!" << std::endl; 
        std::cout << "joystick id: " << event.joystickMove.joystickId << std::endl; 
        std::cout << "new position: " << event.joystickMove.position << std::endl; 
    } 
}


JoystickConnected와 JoystickDisconnected 이벤트

sf::Event::JoystickConnected와 sf::Event::JoystickDisconnected 이벤트는 조이스틱이 연결되거나 해제될때 트리거됩니다. 

이 이벤트와 관련된 멤버는 event.joystickConnect입니다. 이건 연결되거나 해제된 조이스틱의 식별자를 포함합니다.

if (event.type == sf::Event::JoystickConnected) 
    std::cout << "joystick connected: " << event.joystickConnect.joystickId << std::endl; 

if (event.type == sf::Event::JoystickDisconnected) 
    std::cout << "joystick disconnected: " << event.joystickConnect.joystickId << std::endl;



설정

트랙백

댓글

[SFML Tutorial] 윈도우 열고 관리하기 (번역)

https://www.sfml-dev.org/tutorials/2.5/window-window.php


손번역입니다.


들어가는 말

이 튜토기얼은 윈도우를 어떻게 열고 관리하는지만 설명합니다.
무언가를 그리는 건 sfml-window 모듈의 영역을 벗어납니다. 이런건 sfml-graphics 모듈에서 다루죠.
하지만, 윈도우의 관리는 어떤 경우든 간에 동일하기 때문에 이 튜토리얼은 매우 중요합니다.


윈도우 열기 

SFML에서 윈도우들은 sf::Window 클래스에 정의되어있습니다.
윈도우는 생성되자마자 열릴 수 있어요. 

#include <SFML/Window.hpp
int main() 

    sf::Window window(sf::VideoMode(800, 600), "My window"); 

    ... 
    return 0; 
}

첫번째 인자는 video mode입니다. 윈도우의 사이즈를 정의하죠. 여기서의 사이즈란 타이틀바나 borders를 제외한 내부 사이즈입니다.
어쨌거나 여기에서, 우리는 800x600 픽셀의 사이즈로 윈도우를 생성했습니다.
sf::VideoMode 클래스는 데스크탑 resolution이나 풀스크린 모드 등의 유효한 비디오모드들의 리스트를 가져다주는 정적 메서드를 갖고 있어요.
문서를 펼쳐보는 것을 망설이지 마세요!

두번째 인자는 그냥 윈도우의 타이틀입니다. 간단하죠?

이 생성자는 선택적인 세번째 인자를 제공하는데요.
바로 스타일입니다. 갖가지 장식이나 기능들을 선택할 수 있어요.
막 섞어쓸 수도 있습니다.
이게 그 목록입니다.

sf::Style::None
데코가 아예 없습니다.(useful for splash screens, for example)
이 스타일은 다른 것들이랑 섞어쓸 수 없어요.

sf::Style::Titlebar
타이틀바를 가집니다.

sf::Style::Resize
사이즈를 조절할 수 있고, 최대화버튼이 있습니다.

sf::Style::Close
종료버튼이 있습니다.

sf::Style::Fullscreen
풀스크린모드로 보입니다. 이 스타일은 다른 것들과 섞어쓸 수 없습니다. 그리고 유효한 video mode를 필요로 해요.

sf::Style::Default
디폴트 스타일입니다. Titlebar | Resize | Close의 축약이에요.


이게 다가 아닙니다. 4번째 인자도 있어요!
OpenGL의 옵션을 정의하는 거죠.
이건 OpenGL 튜토리얼에서 봅시다.

윈도우 객체 생성 후에 윈도우를 만들고싶거나, 윈도우를 재설정하고싶다면 create 메서드를 쓰면 됩니다.
생성자와 인자가 완전히 같습니다.


#include <SFML/Window.hpp
int main() 

    sf::Window window;
    window.create(sf::VideoMode(800, 600), "My window"); 

    ... 
    return 0; 
}


윈도우에 생명을 불어넣기

만약 저 코드에서 ...의 자리에 아무것도 넣지 않고 실행한다면 뭔가를 보기가 엄청 어려울 겁니다. 

그 이유들을 꼽아보자면.

첫째, 프로그램이 바로 종료됩니다.

둘째, event handling이 없습니다. 그럼 이 코드는 끝없는 무한루프가 되죠. 그럼 그 윈도우는 움직일 수도 없고, 크기를 조절할 수도 없고, 닫을 수도 없는 멍청이가 뵙니다.

이제 코드를 추가해서 이 프로그램을 조금이라도 더 재밌게 만들어봅시다.

#include <SFML/Window.hpp
int main() 

    sf::Window window(sf::VideoMode(800, 600), "My window"); 

    // 윈도우가 열려있는 동안은 프로그램을 계속 실행합니다.
    while (window.isOpen()) 
    { 
        // 루프가 반복되는 동안 트리거가 걸린 윈도우의 이벤트를 전부 체크합니다. 
        sf::Event event; 
        while (window.pollEvent(event)) 
        { 
            //"close 요청" 이벤트. 윈도우를 종료.
            if (event.type == sf::Event::Closed)
                window.close(); 
        } 
    } 
    return 0; 
}

위의 코드는 윈도우를 열고, 사용자가 닫을때 종료됩니다.
어떻게 작동되는건지 자세히 살펴봅시다.

먼저, 루프를 하나 추가했습니다. 이건 윈도우가 닫힐때까지 어플리케이션이 refreshed되고, 업데이트되게끔 해주죠 
'대부분'의 SFML 프로그램들은 이런 종류의 루프를 가지고, 종종 main loop나 game loop를 호출합니다.

그리고나서 우리 game loop에서 우리가 수행하려 하는 첫번째 행동은 모든 이벤트의 발생을 체크하는겁니다.
그리고 while 루프를 사용해서 이벤트가 여럿일 경우에도 모두 처리될 수 있도록 합니다.
pollEvent 메서드는 이벤트가 대기중일 경우에 true를 반환하고, 아니면 false를 반환합니다.

그럼 이제 우리는 언제 이벤트를 획득하더라도 그게 뭔지 확인할 수 있습니다. 
(윈도우가 닫혔는가? key가 눌렸는가? mouse가 움직였는가?? joystick이 연결되었나? ...)
그리고 그 이벤트에 관심이 있다면 그에 따라 행동하면 되죠.
이 경우에, 우리는 오직 Event::Closed 이벤트에만 신경을 쓰고 있죠. 이건 사용자가 윈도우를 닫으려고 할때 트리거가 발동됩니다.
이 부분에서, 저 윈도우는 여전히 열려있고,
명시적으로 close 함수를 사용해서 닫아야 합니다.
윈도우가 닫히기 전에 뭔가를 더 할수도 있죠. 예를 들면 앱의 현재 상태를 저장할 수도 있고, 메세지를 띄워줄 수도 있습니다.

주의 
사람들이 종종 하는 실수가, 이벤트루프를 쓰는걸 잊어버리는 겁니다. 이벤트를 핸들링하는 것에 익숙치 않기 때문이죠.(대신 실시간 입력을 사용합니다.)
이벤트루프가 없다면, 윈도우는 응답을 받을 수가 없게 됩니다.
잘 알아두세요. 이벤트루프에는 중요한 역할이 둘 있습니다.
사용자에게 이벤트를 제공하는 것 외에도,
1.추가적으로 윈도우에게 그 내부 이벤트를 처리할 기회를 줘야 하고,
2.이동이나 사이즈 조절 등의 사용자 행동에 잘 반응해야 합니다.

윈도우가 닫히고 나면, main 루프가 끝나고 프로그램은 종료됩니다. 

이 시점에서, 여러분은 아마 윈도우에 뭔가를 그리는 법에 대해서는 얘기를 듣지 못했을 텐데요.

들어오는 말에서 언급했듯이, 이건 sfml-window 모듈에 대한 게 아닙니다. sprites나 text, shapes 같은 것들을 그리고자 한다면 sfml-graphics 튜토리얼로 건너가면 됩니다.

무언가를 그리기 위해서는 바로 OpenGL을 쓰고 sfml-graphics 모듈을 완전히 무시할 수도 있습니다. 
sf::Window는 내부적으로 OpenGL 컨텍스트를 생성하고 OpenGL 호출에 대한 준비를 합니다. 

이것에 대해서는 해당하 튜토리얼에서 더 많이 볼 수 있을거에요.

이 윈도우에서 더 흥미로운 무언가를 기대하진 마세요.
흰색이나 흑색의 균일한 색을 볼 순 있을거에요. 아니면 OpenGL이 사용된 이전 앱의 마지막 내용이나 뭐 다른걸 볼 순 있을겁니다.


윈도우 실행하기

당연히, SFML은 윈도우의 실행을 허용합니다.
사이즈나 위치, 타이틀이나 아이콘의 변경이나 같은 기본적인 윈도우의 동작들은 지원되지만, Qt나 wxWidgets 같은 Gui 특화 라이브러리들과는 달리 고급 기능을 제공하지 않습니다. 
SFML의 윈도우는 오직 OpenGL이나 SFML의 드로잉 환경만을 제공합니다.

// 윈도우 위치 변경(relatively to the desktop)
window.setPosition(sf::Vector2i(10, 50)); 
// 윈도우 사이즈 변경 window.setSize(sf::Vector2u(640, 480)); 
// 윈도우 타이틀 변경
window.setTitle("SFML window"); 
// 윈도우 사이즈 get
sf::Vector2u size = window.getSize(); 
unsigned int width = size.x; 
unsigned int height = size.y; ...

sf::Window 메서드들에 대한 전체 목록은 API 문서를 참조해주세요.

윈도우 고급 기능이 정말 필요하다면 다른 GUI 라이브러리를 사용하고 sfml을 거기에 내장하면 됩니다.
그렇게 하기 위해서는 sf::Window의 다른 생성자나 create 메서드를 써서 기존 윈도우의 OS종속적 핸들을 가져다 가져와야 합니다.
이 경우에 sfml은 주어진 창 내부에 드로잉 컨텍스트를 만들고, 부모 창 관리를 방해하지 않기 위해 모든 이벤트를 잡습니다.

sf::WindowHandle handle = /* 뭘 수행하고 무슨 라이브러리를 사용할지 정합니다. */; 
sf::Window window(handle);

원한다면, 더욱 구체적인 기능을 원한다면, 다른 방법이 있긴 합니다.
SFML 윈도우를 생성하고 OS 특정 핸들을 가져다가 sfml에서 지원하지 않는 것들을 직접 구현하는거죠.

sf::Window window(sf::VideoMode(800, 600), "SFML window"); 
sf::WindowHandle handle = window.getSystemHandle(); 
// OS 특정 함수의 핸들을 사용합니다.

sfml을 다른 라이브러리에 포함시키는 것은 다른 몇몇 작업을 필요로 하지만, 여기에서는 설명하지 않습니다.
하지만 전용 튜토리얼이나 예제, 포럼의 포스트들을 참조할 수는 있죠.


프레임 비율 제어

때때로 앱이 빠르게 수행될 때, tearing(찢어짐?)과 같은 시각적인 아티팩트가 나타날 수도 있습니다.
이건 앱의 refresh rate(화면 재생 비율)이 모니터의 vertical frequency(수직 주파수)와 동기화되지 않아서, 결과적으로 이전 프레임의 bottom이 다음 프레임의 top과 섞였기 때문입니다.
이 문제에 대한 해결책은 vertical 동기화를 수행하는 겁니다.
자동적으로 그래픽카드를 제어해서 setVerticalSyncEnabled 함수로 쉽게 on에서 off로 전환이 될 수 있죠.

window.setVerticalSyncEnabled(true); 
// 윈도우 생성 후에 호출하세요

이걸 호출하고나면, 앱은 모니터의 refresh 비율과 같은 frequency로 작동될 겁니다. 

주의
종종 setVerticalSyncEnabled이 효과가 없을 때가 있습니다.
이건 대부분 vertical 동기화가 그래픽 드라이버 세팅을 강제로 "off"해버렸기 때문이에요.
그럼 대신에 "앱에 의한 제어(controlled by application)"가 세팅되어야 합니다.

다른 상황에서, 앱이 모니터의 frequency 대신, 주어진 프레임 비율로 수행되길 원할 수도 있습니다.
이건 setFramerateLimit를 호출하면 해결됩니다.

window.setFramerateLimit(60); 
// 윈도우 생성 후에 호출하세요

setVerticalSyncEnabled와 달리, 이 기능은 SFML에서 구현되어있습니다. sf::Clock와 sf::sleep를 섞어서 쓰죠. 
중요한 것은 프레임비율이 너무 높을 경우엔 100% 신뢰할 수 없다는 겁니다.
sf::sleep의 해결책은 근본적으로 os와 하드웨어에 의존적인데요. 
10이나 15 밀리초만큼은 높을 수가 있습니다.
이 기능이 정확한 시간을 구현했다고 믿으면 안돼요.

주의
setVerticalSyncEnabledand와 setFramerateLimit를 동시에 사용하면 안됩니다!
이놈들이 섞이면 끔찍한 결과물이 나와요!


윈도우에 대해서 알아야할 것들

여기에서는 sfml의 윈도우로 무엇을 할수 있고 할 수 없는지 간단하게 알아볼 겁니다.

SFML은 여러개의 윈도우를 생성하는 것이 허용됩니다.
그리고 메인스레드에서 전부 처리하거나, 각각의 스레드에서 각자 처리할 수도 있죠.
(하지만... 아래를 보시죠)
이 경우에, 각각의 윈도우를 위한 이벤트 루프를 두는 것을 잊으면 안 됩니다.

여러개의 모니터는 아직 완전히 지원되지 않습니다.

SFML은 명시적으로 여러개의 모니터를 관리하지 못해요.
결과적으로 윈도우를 나타낼 모니터를 고를 수 없고, 하나 이상의 풀스크린도 생성할 수 없죠.
이건 나중에 개선될 수 있습니다.

이벤트들은 윈도우의 스레드에서 획득됩니다.(polled)
대부분의 os에서 이에 대한 중요한 한계점이 있습니다.
이벤트 루프는, 정확히는 pollEvent나 waitEventfunction은 윈도우가 생성된 스레드에서만 호출되어야 합니다.
이건 이벤트핸들링을 위해 전용 스레드를 생성한다면, 윈도우가 이 동일 스레드에서도 확실히 생성되게 보장해야함을 의미합니다.
만먁 정말로 스레드들 사이에서 무언가를 나누고자 한다면, 메인 스레드에서 이벤트핸들링을 유지하고, 이것저것들을(렌더링, physics, 로직...) 별개의 스레드로 옮기는게 더 편합니다.
이 구성은 아래에 설명한 다른 제약사항들과 호환됩니다.

macOS에서, 윈도우와 이벤트들은 메인 스레드에서 관리되어야만 합니다.

네 맞습니다.(?)
macOS는 메인스레드 이외의 스레드에서 윈도우를 생성하거나 이벤트를 핸들링하는 것을 허용하지 않습니다. 

Windows에서, window가 데스크탑보다 클 경우, 제대로 동작하지 못합니다.

몇가지 이유 때문에, Windows는 데스크탑보다 큰 윈도우를 좋아하지 않습니다.
이건 VideoMode::getDesktopMode()으로 생성된 윈도우들을 포함합니다.
테두리나 타이틀바 따위의 윈도우 데코레이션을 추가하면, 그냥 데스크탑보다 조금 큰 윈도우로 끝납니다.

설정

트랙백

댓글

[SFML Tutorial] 비주얼 스튜디오 (번역)

https://www.sfml-dev.org/tutorials/2.5/start-vc.php


직접 번역했습니다. 의역 많아요



들어가는 말

이 튜토리얼은 비주얼 스튜디오(이하 VS)로 sfml을 사용하는 법을 알려줍니다.
그럼 이제 sfml를 세팅하는 방법을 소개하겠습니다.



SFML 설치

먼저 sfml sdk를 설치합시다.
여기에서요.
https://www.sfml-dev.org/download.php


주의:
비주얼 c++의 버전에 해당하는 패키지를 받아야 합니다.
가령 VC++10으로 컴파일된 라이브러리는 VC++12에서는 돌아가지 않습니다.
만약 원하는 버전의 sfml 패키지가 없다면, sfml을 직접 빌드해야합니다.
방법은 여기에 있어요
https://www.sfml-dev.org/tutorials/2.5/compile-with-cmake.php

그리고 원하는 위치에다가 sfml 압축파일을 풀어주면 됩니다.
헤더와 라이브러리들을 복사해서 VS의 작업 위치에 옮겨놓는건 추천하지 않아요.
특히 여러 버전의 동일 라이브러리나 여러 종류의 컴파일러를 사용하는 경우에는, 분리된 폴더에 따로 두는게 나아요.



sfml 프로젝트를 생성하고 설정하기

가장 먼저 해야할 건 무슨 프로젝트를 생성할지 고르는겁니다. 일단 "Win32 어플리케이션"을 선택하세요.
그럼 마법사는 프로젝트를 커스터마이징하는 몇개의 옵션을 제공합니다. 콘솔이 필요하다면 "콘솔 어플리케이션"을, 아니면 "윈도우 어플리케이션"을 선택하세요.
그리고 뭐가 자동으로 생성되는 게 싫다면 "빈 프로젝트"를 체크하세요.

그리고 프로젝트에 main.cpp 파일을 생성해서 C++ 설정에 액세스할 수 있도록 합니다.
안 그러면 VS에서 이 프로젝트에서 무슨 언어를 쓰는지 모르거든요.
안에다 뭘 넣을지는 이따 얘기합시다.

이제 우리는 컴파일러에게 sfml 헤더(.hpp)와 라이브러리(.lib)가 어디있는지 알려줘야해요. 

1.일단 프로젝트 속성에 들어갑니다.

2.그리고 C/C++ » General(일반) » Additional Include Directories(추가 포함 디렉터리)에다가 sfml 헤더의 경로를 설정합니다.
(<sfml-install-path>/include)

3.Linker(링커) » General(일반) » Additional Library Directories(추가 라이브러리 디렉터리)에다가 sfml 라이브러리의 경로를설정합니다.
(<sfml-install-path>/lib)

이 경로들은 디버그 환경이나 릴리즈 환경에서나 동일합니다. 그래서 프로젝트에 대해 전역으로 설정할 수 있죠.
("All configurations:모든 구성").



4.해당 코드에서 써야 할 라이브러리 파일들을 링크해줘야합니다.
sfml은 5개의 모듈로 구성되어있죠. (system, window, graphics, network and audio)
Linker(링커) » Input(입력) » Additional Dependencies(추가 의존성)에 라이브러리들을 추가합니다. 
"sfml-graphics.lib", "sfml-window.lib", "sfml-system.lib" 같은 필수요소들을 전부 넣어주세요.



주의:
configuration(구성)과 일치하는 라이브러리를 링크해야 합니다.
"sfml-xxx-d.lib"같은건 디버그 전용이고,
"sfml-xxx.lib"같은건 릴리즈 전용입니다. 섞어쓰면 충돌할 수 있어요.

여기에 표시된 설정은 응용 프로그램이 DLL 파일이 필요한 SFML의 동적 버전에 링크되도록합니다. 
DLL을 없애고 SFML을 실행 파일에 직접 통합하려면 정적 버전에 연결해야 해요.
정적 SFML 라이브러리에는 디버그의 경우 "sfml-xxx-s-d.lib", 릴리즈의 경우 "sfml-xxx-s.lib"처럼 접미어가 붙습니다.
이 경우 프로젝트의 전처리기 옵션에서 SFML_STATIC 매크로를 정의해야합니다.



주의:
SFML 2.2부터 정적 링크를 할 경우, sfml의 모든 의존성을 링크해야만 합니다.
가령, sfml-window-s.lib이나 sfml-window-s-d.lib를 링크하려 한다면, opengl32.libwinmm.libgdi32.lib 를 전부 링크해야만 했다는 것이죠.
일부 의존적인 라이브러리는 이미 "상속된 값"에 들어있을수도 있지만, 명시적으로 추가해주는게 좋긴 합니다.

여기에 있는 각각의 모듈 의존성에 -d를 붙이면 디버그 버전이 됩니다.

모듈
sfml-graphics-s.lib
의존성
sfml-window-s.lib
sfml-system-s.lib
opengl32.lib
freetype.lib

모듈
sfml-window-s.lib
의존성
sfml-system-s.lib
opengl32.lib
winmm.lib
gdi32.lib

모듈
sfml-audio-s.lib
의존성
sfml-system-s.lib
openal32.lib
flac.lib
vorbisenc.lib
vorbisfile.lib
vorbis.lib
ogg.lib

모듈
sfml-network-s.lib
의존성
sfml-system-s.lib
ws2_32.lib

모듈
sfml-system-s.lib
의존성
winmm.lib

위 글을 보면 sfml 모듈끼리도 서로 의존성을 가질 수 있다는 것을 알 수 있습니다.
예를 들어 sfml-graphics-s.lib는 sfml-window-s.lib와 sfml-system-s.lib에 의존적이죠.
정적 링크를 하려 한다면 해당 라이브러리의 의존성들을 확실히 링크해야할뿐만 아니라, 의존성의 의존성까지 링크해줘야 합니다.
의존성 연결(chain)에 실수가 발생한다면 에러를 뱉을 수 있어요.

너무 헷갈린다고요? 걱정 마세요. 초보자라면 그건 당연한겁니다.
첫번째 시도에서 작동을 제대로 하지 않는다면, 위에서 말한 것들을 천천히 곱씹어보시면 됩니다.
그래도 정적 링크가 작동하지 않는다면, FAQ나 포럼에서 정적 링크에 대한 글들을 확인해보세요.

동적/정적 라이브러리의 차이점이나, 사용법을 모르겠다면, 구글링을 해보세요.
좋은 article이나, 블로그, 포스트들이 많습니다.

그럼 이제 준비가 완료됐습니다.
제대로 돌아가는 코드를 한번 짜보죠!
main.cpp에 아래 코드를 써넣으시면 됩니다.


#include <SFML/Graphics.hpp
int main() 
{
    sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!"); 
    sf::CircleShape shape(100.f);
    shape.setFillColor(sf::Color::Green); 
    while (window.isOpen()) 
    { 
        sf::Event event; 
        while (window.pollEvent(event)) 
        { 
            if (event.type == sf::Event::Closed) window.close(); 
        } 
        window.clear(); 
        window.draw(shape); 
        window.display(); 
    } 
    return 0; 
}

처음에 "Windows application" 프로젝트로 생성했다면, 코드의 시작점(entry point)은 "main" 대신에 "WinMain"으로 설정될 겁니다. 
윈도우즈로 특정됐다면 리눅스나 맥에서는 컴파일되지 않는데요., sfml은 표준 "main" 시작점을 유지할 수 있는 방법을 제공합니다.
sfml-main 모듈이 바로 그거죠.
sfml-graphics, sfml-window, sfml-system를 링크했듯이 하면 됩니다.

컴파일을 해서 sfml을 동적으로 링크했다면, 실행파일이 있는 디렉터리에 sfml dll들을 복사해다 붙여주는걸 잊으면 안됩니다.(<sfml-install-path/bin>)

그럼 이제 실행해서 다음과 같이 작동하면 성공입니다.




만약 sfml-audio 모듈을 사용한다면, 정적 링크든 동적 링크든, 추가적으로 OpenAL32.dll를 복사해줘야 합니다.

이 파일들도 <sfml-install-path/bin>에서 찾을 수 있습니다.



설정

트랙백

댓글

[SFML] 음악 재생하기

오디오를 재생시켜주는 타입으로는 Music이 있다. 

비슷하게 Sound라는 것도 있는데 이건 말 그대로 효과음같은 사운드만 깔아주는거다. 좀 다르다.


어쨌든 이런식으로 짜면 알아서 잘 재생된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <SFML/Audio.hpp>
#include <string>
#include <iostream>
 
int main(void)
{
    std::string filename = R"(C:\Users\sssan\Desktop\Ludovico Einaudi - I Giorni.ogg)";
 
    sf::Music music_player;
    
    if (music_player.openFromFile(filename)) //파일 등록하고 제대로 됐나 체크.
    {
        music_player.play(); //오디오 실행!
 
        //커맨드에서는 루프 안걸면 바로 꺼져버림.
        while (music_player.getStatus() != sf::Music::Stopped) //음악 끝날때까지 루프
        {
            system("cls");
            std::cout << "재생중" << std::endl;
        }
        std::cout << "플레이 종료" << std::endl;
    }
    else
        std::cout << "에러요" << std::endl;
}
cs

처음에는 예외도 안떴는데 왜 노래 안켜고 바로 꺼지지? 하면서 삽질을 좀 했는데, 

생각해보니 음악과 상관없이 커맨드가 꺼져서 그런거였다.

GUI로 띄울때는 아마 루프 지워도 될거다.


그리고 주의할점으로는 이게 범용적인 오디오 포맷들을 얼추 지원하기는 하는데, 정작 중요한 mp3를 지원하지 않는다는 것이다. 저작권 때문에...


찾아보니까 얼마전에 저작권 소멸했다는 것 같은데 빨리 업데이트가 되었으면 하는 바람이다.



어쨌든 실행은 잘 된다.


감미로운 선율이 흘러내린다.



설정

트랙백

댓글