SFML 튜토리얼 목차

SFML 오피셜 튜토리얼

[시작]

비주얼 스튜디오
-> http://insalat.tistory.com/14

코드블럭
-> 안함

리눅스
-> 안함

x코드
-> 안함

CMake
-> 안함


[시스템 모듈]

시간 다루기
-> http://insalat.tistory.com/28

스레드
->

사용자 데이터 스트림
->


[윈도우 모듈]

윈도우 열고 관리하기
-> http://insalat.tistory.com/15

이벤트
-> http://insalat.tistory.com/16

키보드와 마우스, 조이스틱
-> http://insalat.tistory.com/17


[그래픽 모듈]

2D 사물 그리기 
-> http://insalat.tistory.com/18

스프라이트와 텍스쳐
-> http://insalat.tistory.com/19

텍스트와 폰트
-> http://insalat.tistory.com/20

도형
-> http://insalat.tistory.com/21

vertex 배열
-> http://insalat.tistory.com/22

엔티티 변환
-> http://insalat.tistory.com/23

셰이더로 효과 넣기
-> http://insalat.tistory.com/24

View로 2D 카메라 제어하기
-> http://insalat.tistory.com/25


[오디오 모듈]

사운드 재생
-> http://insalat.tistory.com/26

오디오 기록
-> http://insalat.tistory.com/27

커스텀 오디오 스트림
->

3D에서 어쩌고
->


[네트워크 모듈]

소켓을 사용한 통신
->

패킷의 사용과 확장
->

HTTP로 웹 리퀘스트
->

FTP?
->



설정

트랙백

댓글

[SFML Tutorial] 시간 다루기 (번역)

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



SFML의 시간(Time)

다른 수많은 라이브러리들이 시간을 uint32짜리 밀리초나 부동소수점 초로 처리를 하는 것과
다르게, sfml은 시간값의 단위나 타입을 특정해놓진 않았습니다.
sfml의 모든 클래스와 메서드들은 sf::Time 클래스를 써서 시간값을 다룹니다.
sf::Time은 시간 주기(time period)로 표현을 합니다. 두 이벤트 사이에서 경과한 시간을 재는거죠.
그래서 타임스탬프로 날짜-시간(date time)을 다루진 않습니다. 그냥 시간의 명확한 한계만을 표현하기 때문에 사용된 문맥(context)에 의존적이죠.


시간의 변환(Converting)

sf::Time의 값은 초나, 밀리초, 마이크로초 등의 다양한 단위로 생성할 수 있습니다.
그래서 이런 변환 함수들이 있어요.

sf::Time t1 = sf::microseconds(10000); 
sf::Time t2 = sf::milliseconds(10); 
sf::Time t3 = sf::seconds(0.01f); 

이 세 시간은 전부 동등합니다.
마찬가지로 sf::Time도 초나 밀리초, 마이크로초로 변환이 가능하죠.

sf::Time time = ...; 
sf::Int64 usec = time.asMicroseconds(); 
sf::Int32 msec = time.asMilliseconds(); 
float sec = time.asSeconds(); 


시간값으로 재생하기

sf::Time은 그냥 시간의 총합이라 덧셈이나 뺄셈, 비교같은 산술연산도 지원합니다.
그리고 시간은 음수값이 될수도 있습니다.

sf::Time t1 = ...; 
sf::Time t2 = t1 * 2; 
sf::Time t3 = t1 + t2; 
sf::Time t4 = -t3; 
bool b1 = (t1 == t2); 
bool b2 = (t3 > t4); 


시간 측정

시간값을 다루는 법을 살펴봤으니. 이제 경과시간을 측정하는 법에 대해 알아보죠.
sfml에는 sf::Clock이라는 뛰어난 시간측정용 클래스가 있읍니다.
이건 딱 두가지의 메서드만 갖고 있는데요. getElapsedTime으로 clock이 시작되었을 때부터 경과한 시간을 가져오고 restart로 재시작합니다.

sf::Clock clock; 

// clock 시작
... 
sf::Time elapsed1 = clock.getElapsedTime();

std::cout << elapsed1.asSeconds() << std::endl; 

clock.restart(); 
... 
sf::Time elapsed2 = clock.getElapsedTime(); 

std::cout << elapsed2.asSeconds() << std::endl; 

참고로 restart도 경과시간을 반환해줍니다. 그래서 restart 전에 명시적으로 getElapsedTime을 호출한다면 약간의 갭을 피할 수 있습니다.

여기 이에 대한 예제가 있습니다.
게임 로직을 업데이트하려고 게임 루프가 반복될때마다 계속 경과 시간을 보내주는거죠.

sf::Clock clock; 

while (window.isOpen()) 

    sf::Time elapsed = clock.restart(); 
    updateGame(elapsed); 
    ... 
}


설정

트랙백

댓글

[SFML Tutorial] 오디오 기록하기 (번역)

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



사운드 버퍼에 기록하기

캡쳐된 오디오 데이터의 일반적인 사용법은 사운드 버퍼에 저장해서 재생하거나 파일에 저장하는겁니다.
이건 sf::SoundBufferRecorder 클래스를 써서 간단하게 해결할 수 있어요.

// 해당 시스템에서 오디오 입력 디바이스를 사용가능한지부터 먼저 체크
if (!sf::SoundBufferRecorder::isAvailable()) 

ㅤ// error: 이 시스템에서든 오디오 캡쳐가 불가
    ... 


// recorder 생성
sf::SoundBufferRecorder recorder; 

// capture 시작
recorder.start(); 

// 대기중

// capture 중단
recorder.stop(); 

// 캡쳐된 오디오 데이터를 포함하는 버퍼 가져옴 
const sf::SoundBuffer& buffer = recorder.getBuffer(); 

SoundBufferRecorder::isAvailable 정적 메서드는 오디오 레코딩이 해당 시스템에 지원되는지를 체크해줍니다.
가령, false가 반환된다면 sf::SoundBufferRecorder를 쓸 수 없는 것이죠.

start와 stop 메서드는 설명이 필요없겠죠?

캡쳐는 해당 스레드에서 수행이 되는데요. start와 stop 사이에서 원하는건 뭐든 할수 있다는 뜻이기도 합니다.

캡쳐가 끝난 후에, 기록된 오디오 데이터는 getBuffer 메서드로 가져온 사운드 버퍼에서 사용이 가능합니다.
기록된 데이터는 이렇게 쓸수 있어요.

파일로 저장
buffer.saveToFile("my_record.ogg"); 

Play it directlysf::Sound sound(buffer); sound.play(); 

생(raw) 오디오 데이터에 접근해서 분석하거나 변환하려면 이렇게 하세요.

const sf::Int16* samples = buffer.getSamples();
std::size_t count = buffer.getSampleCount(); 
doSomething(samples, count); 

주의
레코더가 파괴되거나 재시작한 뒤에도 캡쳐된 오디오 데이터를 사용하고 싶다면 버퍼를 복사하는걸 잊지 마세요.


입력 디바이스 선택

컴퓨터에 여러개의 사운드 입력 디바이스가 연결되어있다면 레코딩에 사용할 디바이스를 명시할 수 있읍니다.
사운드 입력 디바이스는 각자의 이름으로 구별 합니다.

std::vector<std::string>로 저장된 연결된 디바이스의 이름들은 SoundBufferRecorder::getAvailableDevices() 정적메서드를 통해 사용할 수 있습니다. setDevice() 메서드에 선택한 디바이스 이름을 넣어줘서 기록할 목록에서 디바이스를 선택할 수 있습니다.
게다가 기록하는 동안에도 그때그때 디바이스를 변경하는게 가능합니다.
현재 사용되는 디바이스의 이름은 getDevice()를 호출하면 가져올 수 있어요.
디바이스를 직접 선택하지 않는다면 디폴트 디바이스가 사용될겁니다.
디폴트 디바이스의 이름은 SoundBufferRecorder::getDefaultDevice() 정적메서드로 가져올 수 있어요.

여기에 입력 디바이스의 세팅 방법에 대한 간단한 예제가 있읍니다.

// 사용 가능한 입력 디바이스의 이름 획득
std::vector<std::string> availableDevices = sf::SoundRecorder::getAvailableDevices(); 

// device 선택
std::string inputDevice = availableDevices[0]; 

// recorder 생성
sf::SoundBufferRecorder recorder; 

// device 세팅
if (!recorder.setDevice(inputDevice)) 

    // error: 디바이스 선택 실패
    ... 

// 평소처럼 레코더 사용


커스텀 레코딩

사운드 버퍼에 캡쳐된 데이터를 저장하는걸 원하지 않는다면, 자신만의 리코더를 만들수도 있습니다.
이렇게 하면 캡쳐가 되는 동안, 레코딩 디바이스에서 (거의) 바로 오디오 데이터를 처리할 수도 있어요.
이 방법은 네트워크를 통해 캡쳐된 오디오를 스트리밍하면서 실시간으로 분석을 하는 등의 일도 가능합니다.
자신만의 레코더를 만들려면 sf::SoundRecorder 추상 클래스를 상속받아야 합니다.
sf::SoundBufferRecorder도 이 클래스의 특수화(specialization)에요.
onProcessSamples라는 가상메서드 하나만 오버라이딩햐면 됩니다. 이건 오디오샘플의 새 청크가 캡쳐될때마다 호출되니까 여기에서 해당되는 내용을 넣어주면 돼요.

기본적으로 오디오 샘플은 100ms마다 onProcessSamples에게 제공이 됩니다. 이건 setProcessingInterval 메서드를 쓰면 간격을 바꿀수도 있어요.
예를 들어, 실시간으로 기록된 데이터를 처리하려면 더 작은 간격이 필요할 겁니다.
이건 그냥 힌트고 실제 필요한 수치는 그때그때 다르니까 특정 타이밍에 의존하진 마세요.

그리고 선택적으로 오버라이딩할 수 있는 두개의 가상 메서드로 onStart와 onStop가 있는데요.
이건 각각 캡쳐가 시작되거나 중단될때 호출됩니다.
이건 작업(task)을 초기화하거나 정리할 때 유용합니다.

여기 제대로 된 파생 클래스의 뼈대가 있어요.

class MyRecorder : public sf::SoundRecorder 

    virtual bool onStart() // 선택사항 
    { 
        // 캡쳐가 시작되기 전에 할것들 초기화
        ... 
        // 캡쳐를 시작하려고 true 반환. 아니라 취소하려면 false 반환
        return true; 
    } 

    virtual bool onProcessSamples(const sf::Int16* samples, std::size_t sampleCount) 
    { 
        // do something useful with the new chunk of samples ... 

        // return true to continue the capture, or false to stop it 

        return true; 
    } 

    virtual void onStop() // 선택사항 
    { 
        // 캡쳐가 끝난 뒤에 정리 
        ... 
    } 
};

isAvailable/start/stop functions 메서드는 sf::SoundRecorder 클래스에 정의돼서, 모든 파생클래스가 가져갑니다. 어떤 리코더 클래스를 쓰든간에 위에서 sf::SoundBufferRecorder를 쓴것처럼 사용하면 된다는 말이죠.

if (!MyRecorder::isAvailable()) 
// error... 
MyRecorder recorder; 
recorder.start(); 
... 
recorder.stop(); 


스레딩 문제

레코딩은 별개의 스레드에서 수행되기 때문에 무엇이 발생하는지, 어디에 있는지를 알아야합니다.
onStart는 start 메서드가 바로 호출해줍니다. 그리고 onProcessSample과 onStop은 sfml이 생성하는 내부 레코딩 스레드에서 항상 호출이 됩니다. 
리코더가 호출자 스레드와 레코딩 스레드, 이 2개의 스레드에서 동시에 접근할 수도 있는 데이터를 사용한다면, mutex같은걸로 동시에 접근이 되질 않도록 조치를 취해줘야 합니다. 아니면 데이터의 손상이나 충돌 같은 정의되지 않은 행동이 발생할 수도 있어요.

스레딩에 익숙하지 않다면 해당 튜토리얼을 참조하세요.



설정

트랙백

댓글

[SFML Tutorial] 사운드와 음악 재생하기 (번역)

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



사운드? 뮤직?

SFML은 오디오 클래스로 sf::Sound와 sf::Music를 제공합니다.
이 두 클래스는 뭐 대략 비슷한 기능을 가지는데요. 동작방식이 조금 다릅니다.

sf::Sound는 sf::SoundBuffer에서 로딩된 오디오를 재생하는 가벼운 객체입니다.
이건 메모리에 딱 맞는 작은 사운드를 사용해서 재생할 때 렉이 걸리지 않습니다. 예를 들어 총소리나 발소리같은 것들이 여기에 포함이 되죠.

sf::Music은 모든 오디오 데이터를 메모리에 로딩하지 않습니다. 대신 소스에서 곧장 스트리밍을 하죠.
이건 보통 몇분짜리 음악을 재생할 때 쓰입니다. 그래서 음악의 크기가 너무 크면 메모리에 로드하고 재생하는 데에 엄청난 시간이 소요될 수 있어요.


사운드의 로딩과 재생

위에서 언급했듯이, 사운드 데이터는 sf::Sound에 바로 저장되지 않고 일단 sf::SoundBuffer에 들어갑니다.
이 클래스도 오디오 데이터를 캡슐화한건데요. 기본적으로 2바이트 정수의 배열입니다. "오디오 샘플"이라고 부르기도 해요. 
샘플은 딱 한 시점의 사운드 신호의 진폭(amplitude)이고, 따라서 샘플의 배열은 전체 사운드를 나타냅니다.

주의
사실 sf::Sound와 sf::SoundBuffer의 관계는 그래픽 모듈에서의 sf::Sprite와 sf::Texture의 관계와 유사합니다.
그래서 sprite와 텍스쳐가 어떻게 동작하는지 이해한다면, 그걸 사운드와 사운드버퍼에도 적용할 수 있어요.

그리고 loadFromFile 메서드를 쓰면 파일을 버퍼로 로딩할 수 있습니다.

#include <SFML/Audio.hpp

int main() 

    sf::SoundBuffer buffer; 

    if (!buffer.loadFromFile("sound.wav")) 
        return -1; 
    ... 

    return 0; 


다른 모든 것들과 마찬가지로, 메모리에서 로드하려면 loadFromMemory을 쓰면 되고, 커스텀 입력 스트림에서 로드하려면 loadFromStream을 쓰면 됩니다.
sfml은 wav나 ogg, vorbis, flac 등의 오디오 포맷을 지원합니다. mp3는 저작권 때문에 아쉽게도 지원이 안됩니다.
다른 소스에서 나온 샘플의 배열에서 바로 사운드 버퍼로 로딩을 할 수도 있습니다.
->You can also load a sound buffer directly from an array of samples, in the case they originate from another source:

std::vector<sf::Int16> samples = ...; 

buffer.loadFromSamples(&samples[0], samples.size(), 2, 44100); 

loadFromSamples은 오디오 파일 대신 샘플의 배열을 로드하기 때문에, 사운드를 제대로 표현하려면 추가 인자가 필요합니다.  

그 중 첫번째 인자. 그러니까 세번째 인자죠. 이 세번째 인자는 채널(channels)의 개수입니다.
하나의 채널은 모노 사운드를 뜻하고, 두개의 채널은 스테레오 사운드를 뜻합니다.

그리고 두번째 인자는 샘플의 비율(rate)입니다. 이건 오리지널 사운드를 재구성하려면 초마다 얼마나 많은 샘플을 재생해야 하는지를 표현합니다.

이제 오디오 데이터가 로드됐으니, sf::Sound의 객체로 재생을 해봅시다!

sf::SoundBuffer buffer; 

// 무언가를 사운드 버퍼로 로드...
sf::Sound sound; 
sound.setBuffer(buffer); 
sound.play(); 

정말 멋진건 같은 사운드 버퍼를 원하는 여러개의 사운드에 할당할 수도 있다는 겁니다. 게다가 이렇게 해도 아무 문제 없이 재생이 됩니다.

주의
사운드와 뮤직은 별개의 스레드에서 재생이 됩니다. 
이건 play()를 호출한 뒤에는 뭘 해도 된다는겁니다. 사운드는 그게 종료되거나 정지될 때까지 재생을 유지합니다.
물론 사운드 데이터에 손상을 주는 걸 빼고요!


뮤직 재생

사운드와 다르게 sf::Music은 오디오 데이터를 미리 로딩하지 않습니다. 대신 소스에서 바로 데이터를 스트리밍하죠.
그래서 뮤직의 초기화는 더욱 직접적입니다.
이렇게요.

sf::Music music; 

if (!music.openFromFile("music.ogg")) 
    return -1; // error 

music.play(); 

여기에서 중요한건, 다른 sfml 리소스들과 다르게 로딩 메서드의 이름이 loadFromFile이 아니라 openFromFile이라는 겁니다.
이건 진짜로 로드를 해놓는게 아니라 그냥 열기만 해서 그렇습니다. 그래서 뮤직의 데이터는 오직 재생된 후에만 로딩이 됩니다. 
그러니까 오디오 파일은 재생된 만큼만 유지가 된다는걸 명심하세요.
sf::Music의 다른 로딩 메서드들도 이런 이름규칙을 따릅니다. 그래서 openFromMemory와 openFromStream로 되어있죠.


다음은 뭐에요?

이제 사운드와 뮤직을 로딩하고 재생할 수 있습니다! 그럼 이걸로 뭘 할수 있는지 살펴볼까요?

재생 상태(playback)을 제어하려면 아래 메서드들을 쓰면 됩니다. 

play -> 시작과 재시작

pause -> 정지(pause)

stop -> 중단(stop)과 되감기(rewind)

setPlayingOffset -> 현재 재생 위치 변경

Example:
// 시작(start) 
sound.play(); 

// 2초 앞으로 sound.setPlayingOffset(sf::seconds(2)); 

// 정지(pause) 
sound.pause(); 

// 다시 시작(resume) 
sound.play(); 

// 중단(stop)과 되감기(rewind)
sound.stop(); 

getStatus 메서드는 사운드나 뮤직의 현재 상태를 반환하는데요. 이걸 쓰면 재생중인지 아닌지를 확인할 수가 있읍니다.
사운드와 뮤직의 재생 상태(playback)는 언제나 변경될 수 있는 몇몇 속성에 의해 제어됩니다.

그 중 하나인 pitch(정점)는 사운드의 감지 주파수를 변경하는 요소입니다.
1보다 크면 높은 pitch로 소리가 나고, 1보다 작으면 낮은 pitch로 소리가 나고, 1이면 그냥 두는 식이죠.
그리고 pitch를 변경하는건 재생 속도에 부수효과(side effect)를 발생시킵니다.

sound.setPitch(1.2); 

volume은 말 그대로 볼륨입니다. 볼륨의 범위는 0~100입니다. 당연히 0은 음소거고 100은 최대 볼륨입니다.
기본값은 100입니다. 초기화된 볼륨보다 더 큰 사운드는 못 맨든다는 말이죠.

sound.setVolume(50); 

loop 속성은 사운드나 뮤직을 자동으로 반복할지 말지 제어합니다. 반복을 한다면, 그게 종료됐을 때 처음부터 다시 재생을 해서 명시적으로 stop을 호출할때까지 반복할겁니다.
그리고 반복을 설정하지 않았다면, 끝날때 바로 stop이 될겁니다.

sound.setLoop(true); 

사용가능한 더 많은 속성들이 있지만 다른것들은 공간화(spatialization)와 관련한 것들이라, 해당 튜토리얼에서 설명합니다.
 

흔한 실수들

소멸된 사운드 버퍼

가장 흔한 실수는 사운드가 사용되고 있는데 사운드 버퍼가 스코프를 벗어나서 파괴된겁니다.

sf::Sound loadSound(std::string filename) 

    sf::SoundBuffer buffer; 
    // 이 버퍼는 함수에 지역적이라서 머지않아 파괴됩니다. 

    buffer.loadFromFile(filename); 

    return sf::Sound(buffer); 

// ... 여기에서요

sf::Sound sound = loadSound("s.wav"); 

sound.play(); 

// ERROR: 사운드의 버퍼가 더이상 존재하지 않습니다. 정의되지 않은(undefined) 행동입니다.

사운드는 지정한 사운드의 포인터를 저장해두기만 하지 복사하는 게 아니란걸 기억하세요.
그래서 사운드버퍼의 생명주기를 정확하게 관리해야합니다. 사운드가 사용되는 동안은 반드시 살아있도록요.


너무 많은 사운드

또다른 에러는 너무 많은 사운드를 생성하려 할때 발생합니다.
sfml은 내부적으로 크기의 한계를 가지는데요. 이건 해당 OS에 의존적입니다. 그래도 256은 절대 넘으면 안돼요.
SFML internally has a limit; it can vary depending on the OS, but you should never exceed 256. 
이 한계는 sf::Sound와 sf::Music의 객체가 전부 같습니다.
This limit is the number of sf::Sound and sf::Music instances that can exist simultaneously. 
한계를 벗어나지 않는 좋은 방법은 필요없는 사운드를 그때그때 제거해주는 겁니다. 
물론 이건 엄청 많은 수의 사운드와 뮤직을 써야할때만 적용되는 사항입니다.


재생되는 동안에 뮤직 리소스를 파괴

뮤직이 재생되는 동안은 소스가 살아있어야 한다는걸 꼭 기억하세요.
디스크에 있는 뮤직 파일은 프로그램이 재생을    하고 있는 동안에는 삭제되거나 이동되어서는 안 됩니다. 
게다가 메모리나 커스텀 인풋 스트림에서 뮤직 파일을 재생할 때는 더 복잡해집니다.

// 메모리에서 뮤직 파일을 start합니다.(zip archive에서 파일을 추출했다고 생각해보세요) 
std::vector<char> fileData = ...; 

// 재생!
sf::Music music; music.openFromMemory(&fileData[0], fileData.size()); 
music.play(); 

// "좋아, 이제 소스파일이 필요없어보이네." 
fileData.clear(); 
// ERROR: 이 뮤직은 여전히 fileData의 항목을 스트리밍중임!! 정의되지 않은 행동. 


sf::Music은 복사할수 없읍니다

마지막으로 꼽는 실수는 이겁니다.
sf::Music 클래스는 복사할 수 없어요!!
그래서 이런건 허용되지 않습니다.

sf::Music music; 
sf::Music anotherMusic = music; 
// ERROR 

void doSomething(sf::Music music) 
{ ... } 

sf::Music music; 

doSomething(music); 
// ERROR : 이 함수는 값이 아니라 레퍼런스로 인자를 받아야합니다.

설정

트랙백

댓글

[SFML Tutorial] View로 2D 카메라 제어하기 (번역)

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




view가 뭔가요?

게임에서, level(실제 내용)들이 윈도우보다 큰건 드문 일이 아니죠. 이럴때 사용자는 그 내용의 작은 일부만을 볼수 있습니다.
통상적으로 RPG나 플랫폼 게임, 등 수많은 장르의 게임들이 이에 해당하죠.

개발자들이 가끔 잊곤 하는 것이, 엔티티를 투영해야 할 곳은 2D 세상이란 것입니다. 윈도우가 아니라요.
윈도우는 그냥 view입니다. 드넓은 세상의 한 단면만을 보여주는거죠.
같은 세상의 여러 view들을 병행적으로(parallel) 그려 내거나 세상을 윈도우보다는 텍스처로 그리는 편이 완벽합니다.
세상이 바뀌는 것이 아니라, 보이는 방식이 달라져야죠.

윈도우에서 보이는 것들은 2D 세상의 일부일 뿐이죠. 따라서, 세상의 일부를 특정해서 윈도우에 띄울 방법을 강구해야 합니다.
게다가 윈도우 내에서 표시될 장소나 방법도 정해야 할겁니다.
이것들이 바로 sfml view의 2가지 주요기능입니다.

요약하자면, view는 세상을 스크롤하거나, 회전하거나, 줌을 조정할때 필요합니다.
게다가 이것들은 분할된 화면과 미니맵을 만드는 데도 좋아요.


view가 보여줄 것들을 정의하기

view를 캡슐화한 클래스는 sf::View입니다.
이건 view 영역의 정의를 바로 생성해줍니다.

// 보여줄 2D 세상의 사각 영역으로 view 생성
sf::View view1(sf::FloatRect(200, 200, 300, 200)); 

// 중간점과 크기를 명시해서 view 생성 
sf::View view2(sf::Vector2f(350, 300), sf::Vector2f(300, 200)); 

이 두가지 정의는 서로 동일한데요.
전부 2D 세상의 같은 영역, (350, 300) 좌표를 기준으로 300x200 사각을 보여줍니다.




만약 저렇게 하지 않고, 생성 후에 따로 설정을 하고 싶다면 아래와 같이 setter메서드들을 쓰면 됩니다.

sf::View view1; 
view1.reset(sf::FloatRect(200, 200, 300, 200)); 
sf::View view2; 
view2.setCenter(sf::Vector2f(350, 300)); 
view2.setSize(sf::Vector2f(200, 200)); 

그리고 view가 정의될 때부터, 비로소 2D 세상에서 보이는 방식들을 변환할 수가 있습니다. 회전이나 크기 조절 같은거요.


view 움직이기(스크롤하기)

다른 drawable 엔티티들과 다르게, view는 항상 중간으로 기본 위치가 잡힙니다. sprite나 shape 같은건 기본적으로 왼쪽 상단에 찍히게 되어있었죠?
뭐 이건 그냥 편의성을 위한건데요.
어쨌든 그래서 view의 위치를 바꾸는 함수는 setPosition이 아니라 setCenter입니다.

// view를 (200, 200) 지점으로 이동
view.setCenter(200, 200); 

// view를 (100, 100) 지점으로 이동 
// 최종 위치는 (300, 300)
view.move(100, 100);






view 회전

view를 회전하려면 setRotationfunction을 쓰면 됩니다.

// 20도로 회전 
view.setRotation(20); 

// 현재 방향을 기준으로 5도 회전
// 결국은 25도 회전
view.rotate(5);




view 줌 조정하기

사이즈의 변경을 통해 view의 줌을 조절하려면 setSize 메서드를 쓰면 됩니다.

// 보여줄 view의 사이즈를 1200x800으로 변경
// 더 큰 영역을 보기 때문에, 오히려 축소(zoom out)됩니다. 
view.setSize(1200, 800); 

// 현재 사이즈에 상대적으로 줌 조절
// 최종 사이즈는 (600x400) 
view.zoom(0.5f);





view가 어떻게 보일지 정의하기

창에 2D 세상의 어느 부분을 띄울지도 정했으니, 이번엔 어디에서 보일지도 정해봅시다.
기본적으로 view는 윈도우를 꽉 채우게 되어있는데요.
view가 윈도우와 크기가 같다면 모든 것이 1:1로 렌더링될겁니다.
그리고 view가 윈도우보다 작거나 크다면 모든 것이 창에 맞게 조절이 됩니다.

이 기본 동작은 대부분의 상황에는 적절하지만, 가끔은 조정이 필요할 수도 있습니다.
예를 들어 멀티플레이어 게임에서 화면을 분할해, 각각 화면의 반씩을 나눠서 쓰게하는 것이 있죠.

또 미니맵을 구현할 수도 있습니다. 윈도우 구석에 작은 영역을 두고, 거기다 세상 전체를 그려넣으면 되죠.
view의 내용이 보여주는 영역을 viewport라 부릅니다. 그리고 view의 viewport를 설정하려면 setViewport 메서드를 쓰면 됩니다.

// 윈도우 절반의 크기로 중간에 viewport 정의 
view.setViewport(sf::FloatRect(0.25f, 0.25, 0.5f, 0.5f));



또 중요한 게, viewport는 픽셀로 정의되는 것이 아니라 윈도우 사이즈의 비율로 결정하는 것이라는 겁니다.
이게 더 편한 부분이 있어요. 덕분에 윈도우의 크기가 바뀔 때마다 viewport의 크기를 업데이트하기 위해 resize 이벤트를 추적할 필요가 없습니다.
게다가 직관적이기도 하죠. 고정된 크기의 사각을 쓰는게 아니라, 전체 윈도우 영역에 상대적으로 표현하는 것이니까요

어쨌든 viewport를 사용하면 간단하게 스크린을 분할해서 멀티플레이어 게임을 구현할 수 있읍니다.
이렇게요.

// player 1 (스크린 왼쪽) 
player1View.setViewport(sf::FloatRect(0, 0, 0.5f, 1)); 

// player 2 (스크린 오른쪽) 
player2View.setViewport(sf::FloatRect(0.5f, 0, 0.5f, 1));





그리고 미니맵은 이렇게 할수 있어요.

// 게임 뷰 (윈도우 전체) 
gameView.setViewport(sf::FloatRect(0, 0, 1, 1)); 

// 미니맵 (오른쪽-위 모퉁이) 
minimapView.setViewport(sf::FloatRect(0.75f, 0, 0.25f, 0.25f));







view 사용하기

view를 써서 뭔가를 그려내려면 그릴 대상의 (sf::RenderWindow 또는 sf::RenderTexture) setView 메서드를 호출한 후에 그려야 합니다.

// view 정의
sf::View view(sf::FloatRect(0, 0, 1000, 600)); 

// 활성화 
window.setView(view); 

// 그 view를 써서 아무거나 그림 
window.draw(some_sprite); 

// 가시성(visibility)을 확인하고 싶어요? 
// view를 가져오세요. 
sf::View currentView = window.getView(); 
... 

이 세팅된 view는 다시 다른걸 세팅할때까지 유지됩니다. 그러므로 대상이 나타나는 방식과 그려질 위치를 정의할 view는 언제나 존재합니다.

그래서 명시적으로 view를 세팅하지 않으면 사이즈가 1:1로 매칭되는 디폴트 view가 적용이 됩니다. 그리고 렌더링 대상의 디폴트 view는 getDefaultView 메서드로 가져올 수 있어요.
이건 자신만의 view를 정의하거나, gui와 같은 고정된 엔티티를 화면에 그리기 위해서 디폴트 뷰를 복원할 때 유용합니다.

// 디폴트 view의 절반 사이즈로 view 생성 
sf::View view = window.getDefaultView(); 
view.zoom(0.5f); 
window.setView(view); 

// 디폴트 view 복원 
window.setView(window.getDefaultView()); 

주의
setView를 호출할 때, 렌더링 대상은 포인터를 저장하는게 아니라 view의 복사본을 만들어버립니다.
이건 view를 업데이트할때마다, setView를 호출해서 그 변경사항을 적용해야한다는 말이 되죠.
그래도 view를 복사하거나 그걸 허공에 생성하는걸 꺼리진 마세요. view는 값이 비싼 객체는 아닙니다. view는 내부적으로 몇개의 실수만 가지거든요.


윈도우의 크기가 달라질때마다 적절히 표시하기 

디폴트 뷰는 윈도우가 생성된 뒤에는 절대 바뀌지 않습니다. 그래서 보이는 내용이 항상 같은거에요.
그래서 윈도우의 크기가 변경될때마다 새 크기를 따라서 늘었다 줄었다 합니다.

이 기본동작 대신에 윈도우의 새 크기에 의존적으로 더 크거나 작게 나타내고 싶다면, 윈도우의 크기에 맞춰 뷰의 크기를 업데이트해야만 합니다.

// 이벤트 루프
sf::Event event; 
while (window.pollEvent(event)) 

    ... 

    // resize 이벤트 감지 
    if (event.type == sf::Event::Resized) 
    { 
        // 윈도우의 새 크기로 view를 업데이트
        sf::FloatRect visibleArea(0, 0, event.size.widthevent.size.height); 
        window.setView(sf::View(visibleArea)); 
    } 



좌표 변환

커스텀 뷰를 사용하거나 위의 방법을 쓰지 않고 윈도우의 크기를 변경한다면, 대상에 표시될 픽셀들은 더이상 2D 세상의 구성 단위와 일치하지 않을 겁니다.
예를 들어, (10, 50)의 픽셀을 클릭해도, 실제 세상에서는 (26.5, -84) 지점에 작용할 겁니다.
For example, clicking on pixel (10, 50) may hit the point (26.5, -84) of your world. 
이러면 결국 변환 함수 mapPixelToCoords를 써서 픽셀 좌표를 세상의 좌표에 매핑해야합니다.

// 윈도우에서의 현재 마우스 위치 획득 
sf::Vector2i pixelPos = sf::Mouse::getPosition(window); 

// 세상의 좌표로 변환
sf::Vector2f worldPos = window.mapPixelToCoords(pixelPos); 

기본적으로 mapPixelToCoords는 현재 view를 사용하는데요. 활성되지 않은 view를 써서 좌표를 변환하고 싶다면 메서드의 추가 인자에 전달해주면 됩니다.

반대로, 세상의 좌표를 픽셀 좌표로 변환하는 것도 mapCoordsToPixel 메서드로 가능합니다.



설정

트랙백

댓글

[SFML Tutorial] Shader로 특수효과 넣기 (번역)

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



들어가는 말

셰이더(shader)는 그래픽카드에서 실행되는 작은 프로그램입니다.
이건 프로그래머가 그냥 OpenGL을 사용하는 것보다 더 쉽고 간단한 방법으로 그리기 과정을 제어하게 도와주죠.
그래서 셰이더는 매우 복잡한 효과를 만들어주는 것에 사용되는데요. 가능하다면 보통 OpenGL 함수로 표현을 하곤 합니다. 예를 들면 Per-pixel lighting나 그림자 같은 거요.
요즘 쓰는 그래픽카드나 새 버전의 OpenGL은 이미 완전하게 셰이더를 베이스로 씁니다. 그리고 state와 함수들의 고정된 집합(고정된 파이프 라인-"fixed pipeline"이라고 불림)은 이미 deprecated되었고, 머지않아 제거될겁니다.

셰이더는 C언어와 매우 비슷한 GLSL(OpenGL Shading Language)라는 언어로 작성되어있습니다.
그리고 셰이더에는 vertex 셰이더와 fragment(또는 pixel) 셰이더, 2종류가 있는데요.
vertex 셰이더는 각각의 vertex에서 수행이 되고, fragment 셰이더는 생성된 모든 fragment(pixel)에서 수행이 됩니다.
사용자가 원하는 효과의 종류에 따라, vertex 셰이더나 fragment 셰이더, 혹은 둘다 제공할 수도 있습니다.

셰이더가 뭘 하고 그게 어떤 방식으로 작동하는지 아셨다면 렌더링 파이프라인의 기본에 대해서도 이해하셔야 합니다.
그리고 나서는 GLSL로 프로그램을 짜는 법을 배우고, 시작하기 좋은 튜토리얼과 예제들을 찾으면 되죠.
다운받았던 sfml sdk와 함께 들어있는 "Shader" 예제를 살펴봐도 됩니다.

이 튜토리얼은 sfml의 특별한 기능에만 초점을 맞춥니다. 그래서 셰이더의 로딩이나 적용에 대해서만 다루고, 셰이더의 작성은 다루지 않습니다.


셰이더 로딩

sfml에서 셰이더는 sf::Shader 클래스로 표현을 합니다.
이건 vertex 셰이더와 fragment 셰이더 둘 다 다룹니다. 
때문에 sf::Shader 객체는 이 두가지의 조합일 수 있습니다. 경우에 따라서는 하나일 수도 있지만요.

셰이더가 보편화되긴 했지만, 아직도 그걸 지원하지 않는 구식 그래픽카드가 많습니다.
그래서 프로그램에서 가장 먼저 해야할건 system에서 셰이더의 사용이 가능한지 체크하는겁니다.

if (!sf::Shader::isAvailable()) 

    // shaders 사용불가


sf::Shader::isAvailable()가 false를 반환했다면, sf::Shader 클래스는 사용이 불가합니다.

셰이더를 disk 안의 파일에서 로딩할 때는, 보통 loadFromFile 메서드를 씁니다.

sf::Shader shader; 

// vertex shader만 로딩 
if (!shader.loadFromFile("vertex_shader.vert", sf::Shader::Vertex)) 
// error... 

// fragment shader만 로딩 
if (!shader.loadFromFile("fragment_shader.frag", sf::Shader::Fragment)) 
// error... } 

// shader 둘다 로딩 
if (!shader.loadFromFile("vertex_shader.vert", "fragment_shader.frag")) 
// error... 

셰이더의 소스는 c++의 코드처럼 간단한 텍스트 파일에 포함되어있는데요. ".vert"와 ".frag" 같은 확장자를 쓰기도 하지만, 확장자는 별로 중요하지 않고, 심지어 생략할 수도 있습니다.

주의
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 메서드로 문자열에서 바로 로딩할 수도 있습니다.
이 메서드는 셰이더 소스를 바로 프로그램에 내장할 경우에 유용하죠.

const std::string vertexShader = 
\ "void main()"
\ "{" 
    \ " ..." 
\ "}"; 

const std::string fragmentShader = 
\ "void main()" 
\ "{" 
    \ " ..." 
\ "}"; 

// vertex 셰이더만 로딩
if (!shader.loadFromMemory(vertexShader, sf::Shader::Vertex)) 
// error... } 

// fragment 셰이더만 로딩
if (!shader.loadFromMemory(fragmentShader, sf::Shader::Fragment)) 
{ // error... 

// 둘다 로딩 
if (!shader.loadFromMemory(vertexShader, fragmentShader)) 
{ // error... 

마지막으로, 다른 sfml 리소스들처럼 셰이더도 커스텀 입력 스트림에서 로딩을 할 수 있습니다. loadFromStreamfunction 메서드로 말이죠

만약 로딩에 실패했다면, 콘솔에 찍한 메세지를 확인하는걸 잊지 마세요. GLSL 컴파일러가 자세하게 진단을 해줄겁니다.


셰이더의 사용

셰이더의 사용법은 간단합니다. 그냥 그걸 draw 메서드의 인자로 넣어주면 돼요.

window.draw(whatever, &shader); 


셰이더에 변수 넣기

다른것들처럼 셰이더도 인자를 받아먹고 드로잉될때 뭔가를 더 할 수가 있는데요.
이때 이 인자들은 uniform이라는 전역 변수로 선언됩니다.

uniform float myvar; 
void main() 

    // myvar 사용... 


유니폼들은 sf::Shader::setUniform 메서드의 다양한 오버로드를 활용해서 설정할 수 있습니다. 

shader.setUniform("myvar", 5.f); 

setUniform의 오버로드 버전들은 sfml에서 제공하는 모든 타입을 지원합니다.

float (GLSL type float)
2 floats, sf::Vector2f (GLSL type vec2)
3 floats, sf::Vector3f (GLSL type vec3)
4 floats (GLSL type vec4)
sf::Color (GLSL type vec4)
sf::Transform (GLSL type mat4)
sf::Texture (GLSL type sampler2D)

주의
GLSL의 컴파일러는 사용되지 않은 변수들을 최적화해서 빼버립니다.
<-참고로 '사용되지 않았다'는 것은 최종 vertex/픽셀 연산에 포함되지 않았다는 걸 말합니다.
뭐 그러니까 테스트용으로 setUniform을 호출할 때, [셰이더에서 "xxx"라는 변수를 찾는 것에 실패했습니다.] 같은 에러 메세지가 뜨더라도 놀라진 마세요.


Minimal 셰이더

여기에서 GLSL 셰이더의 사용법을 배우지는 않을 겁니다.
하지만, sfml이 셰이더에 대해 무엇을 제공하고, 이에 대해 무엇을 기대할 수 있는지는 알아야합니다.


Vertex 셰이더

sfml은 sf::Vertex 구조체로 표현되는 고정된 vertex 포맷을 가집니다.
그리고 sfml의 vertex는 2D 위치와, 색상, 2D 텍스쳐 좌표를 포함하는데요.
이것은 vertex 셰이더에서 가져오고, 내장된 gl_Vertex, gl_Color 및 gl_MultiTexCoord0 변수에 저장되는 명확한 입력입니다. 참고로 저것들은 선언할 필요 없어요.

void main() 

    // vertex 위치 변환 
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 

    // 텍스쳐 좌표 변환
    gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; 

    // vertex 색깔 전달(forward)
    gl_FrontColor = gl_Color; 


위치는 보통 현재 view와 결합된 엔티티 변환을 포함하는 model-view와 projection matrices로 변환이 되어야합니다.

그리고 텍스쳐 좌표는 텍스쳐 행렬로 변환이 되어야 합니다. 근데 뭐 이건 sfml 구현 사항이라서 사용자에겐 별 의미 없습니다.

마지막으로 색상은 전달이 되어야(forwarded) 합니다.

물론 원하지 않는다면 텍스쳐 좌표나 색상이나 그냥 무시하고 써도 되죠.
어쨌든 이 모든 변수들은 그래픽 카드에 의해 기본요소들로 끼어들어가고서, fragment 셰이더로 전달됩니다.
이 모든 변수들은 


Fragment 셰이더

fragment 셰이더도 비슷한 메서드들을 가집니다.
텍스쳐 좌표와 생성된 fragment의 색상을 인자로 받죠.
위치는 따로 없습니다. 이 시점에서 이미 그래픽 카드가 fragment의 최종 raster 위치를 계산을 해놓거든요.

하지만 텍스쳐가 부여된 엔티티를 다룬다면 현재 텍스쳐도 필요합니다.

uniform sampler2D texture; 
auto main()->int

    // texture에서 픽셀 룩업 
    vec4 pixel = texture2D(texture, gl_TexCoord[0].xy); 

   // 색으로 곱하기 
    gl_FragColor = gl_Color * pixel; 


현재 텍스쳐는 자동으로 가져올순 없습니다. 그래서 다른 입력변수들처럼 수동으로 처리해야합니다.

각각의 엔티티는 서로 다른 텍스처를 가질 수 있기 때문에 셰이더에 그걸 가져다가 넣어줄 방법이 없을 수도 있는데요.
setUniform 메서드의 특별한 오버로드 버전이 이걸 도와줄 수 있습니다.

shader.setUniform("texture", sf::Shader::CurrentTexture); 

이 특별한 파라미터는 주어진 이름으로 셰이더 변수에 그려질 엔티티의 텍스쳐를 자동으로 설정해줍니다.
그럼 새로운 엔티티를 그릴때마다, 셰이더 텍스쳐 변수를 적절히 업데이트해줄거에요.

셰이더에 대한 더 멋진 예제들을 보고싶다면 다운로드한 sfml 팩에 들어있는 셰이더 예제들을 보세요.


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

sfml 그래픽 엔티티 말고 OpenGL을 쓰고싶다면, sf::Shader을 OpenGL 프로그램 객체에 대한 래퍼처럼 써서 OpenGL 코드 안에 집어넣을 수도 있습니다.

그리고 드로잉을 위해 sf::Shader를 활성화시키려면 bind 라는 정적메서드를 호출해야만 합니다.

sf::Shader shader; 
... 
// shader 바인딩 
sf::Shader::bind(&shader); 

// 여기에서 OpenGL 엔티티 그림

// 아무 셰이더도 바인딩하지 않음
sf::Shader::bind(NULL);

설정

트랙백

댓글

[SFML Tutorial] 엔티티 변환 (번역)

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



SFML 엔티티 변환

모든 sfml의 클래스들은 sf::Transformable이란 변환용 인터페이스를 상속받는데요.
이건 엔티티의 이동, 회전, scale에 대한 간단한 API를 제공해줍니다.
안타깝게도 최대한의 유연성은 제공해주지 못합니다만, 대신에 이해하기 쉽고 사용하기에도 쉬운데다, 거의 99%의 유즈케이스를 커버합니다.
나머지 1%는 마지막 챕터에서 찾아보세요!

sf::Transformable은 위치(position), 회전(rotation), 스케일(scale)과 origin이라는 4개의 속성을 가지는데요. 
이것들은 각각 게터와 세터를 갖고 있습니다.
그리고 이 변환 컴포넌트들은 각자 독립적으로 작동을 하는데요.
예를 들어, 엔티티의 방향(orientation)을 바꾸고싶다면 회전(rotation) 속성을 설정해주면 됩니다. 현재 위치나 스케일같은건 신경 안써도 돼요.


위치(Position)

position은.. 2D세상에서의 엔티티 위치입니다.
더 설명할 필요는 없겠죠 :)

// '엔티티'는 sf::Sprite나 sf::Text, sf::Shape, 또는 다른 어떤 transformable 클래스들도 될 수 있습니다.
// 엔티티의 절대 위치 설정 
entity.setPosition(10, 50); 

// 현재 위치를 기준으로 이동
entity.move(5, 5); 

// 엔티티의 절대위치 획득
sf::Vector2f position = entity.getPosition(); 
// = (15, 55)



기본적으로 엔티티는 왼쪽-상단 구석을 기준으로 위치를 잡는데요.
좀 있다가 'origin'이라는 속성으로 변경하는 법도 알아볼겁니다. 


회전(Rotation)

2D 세상에서 회전은 엔티티의 방향입니다.
The rotation is the orientation of the entity in the 2D world. 
이건 시계방향의 각도로 정의가 되어있습니다. 그 Y축이 아래를 가리키기 때문이죠.

// ''엔티티'는 sf::Sprite나 sf::Text, sf::Shape, 또는 다른 어떤 transformable 클래스들도 될 수 있습니다.
// 엔티티의 절대 rotation 설정 
entity.setRotation(45); 

// 현재 방향을 기준으로 회전
entity.rotate(10); 

// 엔티티의 절대 rotation 획득
float rotation = entity.getRotation(); 
// = 55


sfml에서는 getRotation을 호출할때 항상 (0,360) 범위 내의 각도를 반환해줍니다.
위치와 마찬가지로, 회전도 기본적으로 왼쪽 위 모퉁이를 기준으로 돌아가는데요. 따로 원점을 설정하면 기준을 변경할 수 있습니다.


스케일(Scale)

스케일은 엔티티의 사이즈를 변경할 수 있게 해줍니다.
스케일의 기본값은 1인데요.
The default scale is 1. 
1보다 작은 값을 두면 엔티티가 작아지고, 1보다 큰 값을 두면 커집니다.
음수 값도 넣을 수 있는데요. 그러면 엔티티가 뒤집힐(mirror) 수 있습니다.

// '엔티티'는 sf::Sprite나 sf::Text, sf::Shape, 또는 다른 어떤 transformable 클래스들도 될 수 있습니다.
// 엔티티의 절대 스케일 설정
entity.setScale(4.0f, 1.6f); 

// 현재 스케일을 기준으로 변경
entity.scale(0.5f, 0.5f); 

// 엔티티의 절대 스케일 획득
sf::Vector2f scale = entity.getScale(); 
// = (2, 0.8)




Origin

origin은 다른 3개의 변환요소들의 중심점인데요.
엔티티의 위치는 그 origin의 위치이고, rotation은 origin을 기준으로 돌아가며, 스케일 또한 origin을 기준으로 적용됩니다. 
기본적으로 이건 엔티티의 왼쪽-상단 모퉁이 (point (0, 0))인데요. 원한다면 엔티티의 중간이나 다른 모퉁이 부분으로 바꿀 수도 있습니다.

그리고 구성을 단순하게 만들기 위해, 3개의 변환요소들에 대해 단 하나의 origin만을 배치해둡니다.
예를 들어, origin을 중간에 놓고 회전하는 동안에는 좌-상단으로 origin을 두고 위치를 조정할 수는 없다는겁니다. 
혹시라도 이런걸 해야한다면, 다음 챕터를 봐주세요.

// ' '엔티티'는 sf::Sprite나 sf::Text, sf::Shape, 또는 다른 어떤 transformable 클래스들도 될 수 있습니다.
// 엔티티의 origin 설정
entity.setOrigin(10, 20); 

// 엔티티의 origin 가져옴
sf::Vector2f origin = entity.getOrigin(); 
// = (10, 20) 

뭐 그래서 origin가 변경되면, 위치 속성이 바뀌지 않더라도 엔티티가 그려질 위치가 바뀔수 있습니다.
왜 그런지 이해가 안되신다면 다시 한번 천천히 읽어보세요!


커스텀 클래스의 변환

sf::Transformable은 sfml의 기존 클래스 전용이 아니라, 직접 만든 커스텀 클래스의 베이스가 될 수도 있습니다.

class MyGraphicalEntity : public sf::Transformable 

    // ... 
}; 
MyGraphicalEntity entity; entity.setPosition(10, 30); 
entity.setRotation(110); 
entity.setScale(0.5f, 0.2f); 

엔티티의 최종 변환값(보통 그릴때 필요함)을 얻어내려면, getTransform 메서드를 쓰면 됩니다.
이 메서드는 sf::Transform 객체를 반환합니다.
sfml 엔티티의 변환에 대한 설명과 사용법은 아래에서 확인하세요.

만약 sf::Transformable 인터페이스에서 제공하는 완성된 메서드 집합이 필요하지 않다면,
간단하게 그걸 멤버로 쓰고 자신만의 기능을 추가하는걸 망설이지 마세요.
근데 이건 추상화 클래스는 아니라서, 오직 기본클래스로만 사용할 수 있는 대신에, 자체로 생성을 할 수도 있습니다.


커스텀 변환

sf::Transformable 클래스는 쓰기 쉽긴 하지만, 한계가 있습니다.
그래서 일부 사용자들은 더 높은 유연성이 필요할 수도 있는데요.
아마 이런 분들은 아마 최종 변환값을 각각의 변환 요소에 대한 맞춤 조합으로 처리하고 싶을겁니다.
이런 분들을 위해서 저수준 클래스인 sf::Transform이 존재합니다!
이건 그냥 3x3의 행렬인데요. 그래서 이건 2D 공간에서는 어떤 변환이든 표현할 수 있답니다.

sf::Transform은 다양한 방법으로 생성할 수 있는데요.
1->가장 일반적인 변환 요소들(translation, rotation, scale)에 대한, 미리 정의된 메서드들을 써도 되고
2->2개의 transform을 결합해도 되고
3->9개의 요소를 바로 들입다 박아버려도 됩니다.

여기에 몇개의 예제가 있어요.

// transform 확인(identity)(아무것도 안함) 
sf::Transform t1 = sf::Transform::Identity; 

// 회전 transform 
sf::Transform t2; 
t2.rotate(45); 

// 커스텀 matrix 
sf::Transform t3(2, 0, 20, 0, 1, 50, 0, 0, 1); 

// 조합된 transform 
sf::Transform t4 = t1 * t2 * t3; 

게다가 동일한 변환을 할 경우에도 여러가지의 미리 정의된 변환을 적용할 수가 있습니다.
이것들은 전부 순차적으로 결합될거에요.

sf::Transform t; 
t.translate(10, 100); 
t.rotate(90); 
t.translate(-10, 50); 
t.scale(0.5f, 0.75f); 

다시 요점으로 돌아가서, 어떻게 하면 커스텀 transform이 그래픽 엔티티에 적용이 될 수 있을까요?
간단합니다. draw 메서드에 바로 박아주면 돼요.

window.draw(entity, transform); 

... 이건 사실 아래를 줄인겁니다.

sf::RenderStates states; 
states.transform = transform; 
window.draw(entity, states); 

엔티티가 자체 내부 변환을 포함하는 sf::Transformable인 경우, 내부 변환과 전달된 변환이 결합되어 최종 변환을 생성합니다.


경계 상자(Bounding boxes)

엔티티를 변환하고 나서 그걸 그린다면, 엔티티를 사용해서 몇몇개의 계산을 수행할 수 있습니다. 이를테면 충돌(collision) 검사같은거요

sfml 엔티티들은 각자의 경계 상자를 제공해주는데요.
이건 엔티티에 속한 모든 point들을 포함하는 최소한의 사각형입니다. 그리고 변은 x와 y축으로 정렬됩니다.




경계 상자는 충돌 감지 기능을 구현할때 매우 유용합니다.

point나 다른 축으로 정렬된 사각형에 대한 체크는 매우 빠르게 수행될 수 있습니다. 그리고 그 영역은 실제 엔티티의 영역에 충분히 근접한 훌륭한 근사치를 제공합니다.

// 엔티티의 경계 상자 가져옴
sf::FloatRect boundingBox = entity.getGlobalBounds(); 

// point로 충돌 체크 
sf::Vector2f point = ...; 

if (boundingBox.contains(point)) 

    // 충돌! 
}

// 다른 박스와의 충돌 체크
sf::FloatRect otherBox = ...; 
if (boundingBox.intersects(otherBox)) 

    // 충돌! 


이 메서드는 getGlobalBounds라고 이름이 붙었는데요. 그 이유는 모든 변환이 적용된 뒤에, 글로벌 좌표 시스템에서 그 엔티티의 경계상자를 반환하기 때문입니다.

로컬 좌표 시스템에서 변환 적용 전에, 엔티티의 경계상자를 반환해주는 또다른 메서드는 getLocalBounds입니다.
이 메서드는 엔티티의 초기 크기를 얻거나 더 구체적인 계산을 수행할때 쓰이곤 합니다.


객체 계층 (scene graph)

이전에 보았던 커스텀 변환을 쓴다면, 자식이 부모와 관련해서 변환되는 객체의 계층을 쉽게 구현할 수 있습니다.
이제 해야할건 그릴때마다 조합된 변환을 최종 drawable 엔티티들에 도달할 때까지 부모에서 자식까지 전달해주는겁니다. 모든 방법을 동원해서요.

// 추상 기본클래스 
class Node 

public: 
    // ... node를 변환할 메서드들 
    // ... node children을 관리할 메서드들
    void draw(sf::RenderTarget& target, const sf::Transform& parentTransform) const 
    { 
        // 부모의 변환을 노드의 변환과 결합
        sf::Transform combinedTransform = parentTransform * m_transform; 

        // node의 draw 메서드로 그리게 함
ㅤㅤonDraw(target, combinedTransform);

        // children 그리기 
        for (std::size_t i = 0; i < m_children.size(); ++i) 
            m_children[i]->draw(target, combinedTransform); 
    } 

private: 
    virtual void onDraw(sf::RenderTarget& target, const sf::Transform& transform) const = 0; 

    sf::Transform m_transform;
    std::vector<Node*> m_children; 
}; 

// sprite를 그리는 간단한 서브클래스
class SpriteNode : public Node 

public: 
    // .. sprite를 정의할 메서드들
private: 
    virtual void onDraw(sf::RenderTarget& target, const sf::Transform& transform) const 
    { 
        target.draw(m_sprite, transform); 
    } 
    sf::Sprite m_sprite; 
};


설정

트랙백

댓글

[SFML Tutorial] Vertex Array로 자신만의 엔티티 만들기 (번역)

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


들어가는 말

sfml은 대부분의 2D 엔티티들을 표현할 수 있는 간단한 클래스들을 제공합니다.
더 복잡한 엔티티들도 이 빌딩 블럭들로 쉽게 생성할 수 있긴 한데요. 항상 좋은 해결법은 아닙니다.
예를 들어, 너무 많은 sprite를 그리면 순식간에 그래픽카드의 한계에 도달할 겁니다.
왜 그럴까요? draw 메서드를 얼마나 호출했느냐에 따라서 성능이 크게 갈리기 때문입니다.
실제로 각 호출에는 OpenGL 상태 설정, 행렬 재설정, 텍스처 변경 등이 포함되는데요. 이것들은 전부 간단하게 두개의 삼각형을 그릴 때도 요구됩니다.
이건 그래픽 카드에 썩 좋은건 아니에요.
오늘날의 GPU는 최대 수천에서 수백만개의 삼각형을 일괄 처리할 수 있게끔 설계되어있거든요.

이 갭을 메우려면, Vertex array라는 저레벨 드로잉 메커니즘을 활용해야 합니다.

실제로, vertex array는 다른 모든 sfml 클래스들에서도 내부적으로 사용됩니다. 
이걸 쓰면 필요한만큼의 수많은 삼각형들을 포함하는 2D 엔티티들보다 훨씬 유연하게 정의를 할수 있습니다. 게다가 point나 선도 그릴 수 있어요.

근데 도대체 vertex는 뭐고 왜 배열(array)에 있는걸까요?

vertex는 다룰 수 있는 가장 작은 단위의 그래픽 엔티티입니다.
요컨대, 그래픽의 point라 할 수 있죠.
물론, 이건 2D 위치뿐만 아니라 색상과 텍스쳐 좌표쌍도 가집니다.
이 속성들의 역할에 대해서는 나중에 살펴볼게요.

vertex들만으로는 뭐 많은걸 하진 않습니다.
기본적인 것들로 그룹화해서 쓰거든요.
예를 들어, point는 1개의 vertex이고, 선은 2개, 삼각형은 3개, 사각형은 4개입니다.
그리고 이 기본요소들을 결합해서 엔티티의 최종 형태를 생성할 수 있죠.

그럼 이제 왜 vertex array에 대해서 얘기를 하고 vertex들에 대해서는 다루지 않는지, 이해하실겁니다. 


간단한 Vertex Array

이제 sf::Vertex 클래스에 대해서 살펴봅시다. 
이건 3개의 public 멤버만을 갖고, 생성자 외의 다른 메서드가 하나도 없는 간단한 컨테이너입니다.
그리고 생성자를 쓰면 원하는 속성의 설정들로 vertex를 구성할 수도 있습니다. 뭐 꼭 그렇게 할 필요는 없고요.

// 새 vertex 생성
sf::Vertex vertex; 

// 좌표 설정
vertex.position = sf::Vector2f(10, 50); 

// 색 설정
vertex.color = sf::Color::Red; 

// 텍스쳐 좌표 설정 
vertex.texCoords = sf::Vector2f(100, 100); 

... 또는 생성자를 써도 됩니다.

sf::Vertex vertex(sf::Vector2f(10, 50), 
sf::Color::Red, sf::Vector2f(100, 100)); 

자 그럼 기본 요소를 정의해봅시다.
기억하세요. 기본요소들은 여러개의 vertex로 구성됩니다. 따라서 vertex의 array가 필요해요.
그래서 sfml은 sf::VertexArray라는 간단한 래퍼를 제공합니다.
이건 std::vector와 유사한 구조를 가지고, vertex로 정의된 기본 타입을 저장합니다.

// 삼각형을 만들기 위해 3 vertex의 배열을 생성
sf::VertexArray triangle(sf::Triangles, 3); 

// 삼각형의 point 좌표들을 정의
triangle[0].position = sf::Vector2f(10, 10); 
triangle[1].position = sf::Vector2f(100, 10); 
triangle[2].position = sf::Vector2f(100, 100); 

// 삼각형의 point에 색상을 정의 
triangle[0].color = sf::Color::Red; 
triangle[1].color = sf::Color::Blue; 
triangle[2].color = sf::Color::Green; 

// 텍스쳐 좌표는 없음. 이건 나중에 

이제 삼각형을 그릴 준비가 됐습니다.
vertex array를 그릴 때는, 다른 엔티티를 쓸때처럼 하면 됩니다.
이렇게 draw 메서드를 써서요.

window.draw(triangle);



vertex의 색이 기본요소들을 채우기 위해 막 끼어들어간걸 볼수 있죠.
이 방법은 그라데이션을 만들기 아주 좋습니다.

근데 뭐 sf::VertexArray를 꼭 써야만 하는건 아닙니다. 이건 그냥 편의상 정의된거라서, sf::PrimitiveType으로 std::vector<sf::Vertex>을 쓰는거랑 별반 차이가 없습니다.
더 큰 유연성을 원하거나, 정적배열을 쓰고싶다면 자신만의 저장소를 사용할 수도 있습니다.

그럼 vertex의 포인터와 vertex의 수, 그리고 기본 타입들을 받는 draw의 오버로드 버전을 사용해야합니다.

std::vector<sf::Vertex> vertices; vertices.push_back(sf::Vertex(...)); 
... 
window.draw(&vertices[0], vertices.size(), sf::Triangles); 
sf::Vertex vertices[2] = { sf::Vertex(...), sf::Vertex(...) }; 
window.draw(vertices, 2, sf::Lines); 


기본타입

잠시 멈추고, 어떤 종류의 기본요소들을 만들수 있을지 봐봅시다.
위에서 설명한것처럼, point, 선, 삼각/사각형 등 대부분의 2D 요소들을 정의할수 있습니다.
참고로, 사각형은 그냥 편의를 위한 것이고, 사실 내부적으로는 그래픽카드가 2개의 삼각형으로 나눠 처리합니다.
게다가 두 개의 연속적인 기본 요소들 간에 vertex를 공유할 수 있도록하는 이러한 기본 타입의 "체인화 된(chained)" 변형이라는 것이 있습니다.
이건 연속적인 기본요소들이 종종 어떤 방법으로든 연결되어있기 때문에, 꽤나 쓸만합니다.

그에 대한 전체 목록을 봐봅시다.

sf::Points 
연결되지 않은 point의 집합입니다.
이 point들은 두께를 갖지 않습니다.
그리고 현재의 transform이나 view에 상관없이 언제나 하나의 픽셀을 차지합니다.

예)




sf::Lines
연결되지 않은 선의 집합입니다.
이것도 두께가 없어요. 그리고 현재의 transform이나 view에 관계없이 항상 하나의 픽셀 폭만을 가집니다.

예)




sf::LineStrip
연결된 선의 집합입니다.
한 선의 마지막 vertex는 다음 vertex의 시작과 같습니다.

예)



sf::Triangles
연결되지 않은 삼각형의 집합입니다.

예)



sf::TriangleStrip
연결된 삼각형의 집합입니다.
각각의 삼각형들은 마지막 2개의 vertex를 다음 vertex와 공유합니다.

예)




sf::TriangleFan
중간점(central point)으로 연결된 삼각형들의 집합입니다.
첫번째 vertex는 중심입니다. 그 이외의 새로운 vertex들은 각각 중심과 이전의 vertex를 사용해서 삼각형을 정의하는거에요.

예)




sf::Quads
연결되지 않은 사각형의 집합입니다.
각 사각형의 4 point는 시계방향이나 반시계방향의 순서를 지켜서 정의해야 합니다.

예)




텍스쳐 부여(Texturing)

다른 엔티티들처럼, vertex array도 텍스쳐를 줄수 있습니다.
그렇게 하려면 우선 vertex의 texCoords 속성을 조절할 필요가 있는데요.
이 속성은 그 vertex에 매핑될 텍스쳐의 픽셀을 정의합니다.

// 사각형 생성
sf::VertexArray quad(sf::Quads, 4); 

// (10, 10) 위치에 100x100 사이즈로 직사각형 생성
quad[0].position = sf::Vector2f(10, 10); 
quad[1].position = sf::Vector2f(110, 10); 
quad[2].position = sf::Vector2f(110, 110); 
quad[3].position = sf::Vector2f(10, 110); 

// (0, 0) 지점에 25x50크기로 텍스쳐 영역 정의
quad[0].texCoords = sf::Vector2f(0, 0); 
quad[1].texCoords = sf::Vector2f(25, 0); 
quad[2].texCoords = sf::Vector2f(25, 50); 
quad[3].texCoords = sf::Vector2f(0, 50); 

주의
텍스쳐 좌표는 픽셀로 정의됩니다. sprite나 shape의 textureRect처럼요.
이건 OpenGL에 익숙한 사람들이 예상할 수 있듯이, 표준화되지는 않았습니다. 보통 0과 1 사이에요.

vertex array는 저수준의 엔티티라서 오직 geometry만을 다루고, 텍스쳐처럼 추가속성을 저장하지는 않습니다.
vertex array를 텍스쳐와 함께 그리려면 draw 메서드에 바로 넘겨줘야 합니다.
이렇게요.

sf::VertexArray vertices; 
sf::Texture texture; 
... 
window.draw(vertices, &texture); 

이건 짧은 버전인데, blend 모드나 transform 같은 render 상태를 전달해야한다면 sf::RenderStates를 사용할 수 있습니다.

sf::VertexArray vertices; 
sf::Texture texture; 
... 
sf::RenderStates states; 
states.texture = &texture; window.draw(vertices, states); 


vertex array 변환(Transforming)

변환도 텍스쳐를 부여하는 것과 비슷합니다.
변환은 vertex array에 저장되지는 않습니다. 그래서 draw 메서드로 넘겨줘야해요.

sf::VertexArray vertices; 
sf::Transform transform; 
... 
window.draw(vertices, transform); 

또는, 다른 render 상태를 넣어줘야 한다면 이렇게 하면 됩니다.

sf::VertexArray vertices; 
sf::Transform transform; 
... 
sf::RenderStates states; 
states.transform = transform; 
window.draw(vertices, states); 

변환과 sf::Transform에 대해서 더 알고싶다면, transforming 엔티티에 관한 튜토리얼을 참고하세요.


Creating an SFML-like entity

이제 자신만의 텍스쳐나 색상, 변환이 된 엔티티를 만드는 법에 대해서 충분히 아셨을 겁니다. 그럼 sfml 스타일의 클래스로 래핑을 해야하지 않겠어요?
다행스럽게도 sfml은 이에 대한 손쉬운 해결책으로 sf::Drawable와 sf::Transformable이라는 베이스 클래스를 제공합니다.
이 두 클래스는 sfml 엔티티들의 기본이 됩니다.
sf::Drawable은 인터페이스인데요.
이건 하나의 순수가상메서드만을 가지고, 다른 멤버나 메서드를 갖지 않습니다.
그리고 이 sf::Drawable를 상속하면 다른 sfml 클래스들처럼 여러분의 클래스를 바로 그릴 수 있어요! 

class MyEntity : public sf::Drawable 

private: 
    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const; 
}; 
MyEntity entity; 
window.draw(entity); 
// 내부적으로 entity.draw 호출

근데 이게 꼭 이렇게 해야하는건 아니고, 간단하게
entity.draw(window)를 호출해도 됩니다.
->Note that doing this is not mandatory, you could also just have a similar draw function in your class and simply call it with entity.draw(window). 
다른 방법으론, sf::Drawable를 베이스클래스로 사용하는 방법이 있는데요. 이게 더 일관적이고 좋습니다. 이걸 drawable 객체의 배열로 저장한다면, 기존 sfml의 drawable 객체들과 사실상 같아지기 때문에 뭐 추가적으로 신경쓸 게 없어집니다.

다른 베이스 클래스인 sf::Transformable은 아무런 가상메서드도 갖지 않습니다.
그래서 이걸 상속받으면 자동으로 다른 sfml 클래스와 같은 변환(transformation) 메서드를 추가해줘요. setPosition이나 setRotation, move, scale 같은것들 말이죠.
더 자세한 정보를 원하신다면 해당 튜토리얼을 참고하세요.

이 두개의 베이스 클래스와 vertex array를 활용한다면 sfml의 일반적인 그래픽 클래스와 같은 형식을 갖게 됩니다. 
아래는 그에 대한 구현입니다. 텍스쳐도 추가했어요.

class MyEntity : public sf::Drawable, public sf::Transformable 

public: 
    // 엔티티의 geometry, 색, 텍스쳐를 다룰 메서드를 추가합니다.

private: 
    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const 
    { 
        // 엔티티의 transform을 적용합니다. 그리고 그걸 호출자가 전달한 것으로 결합하세요. 
        states.transform *= getTransform();
        // getTransform()은 sf::Transformable 에 정의되어있습니다.

        // 텍스쳐 적용
        states.texture = &m_texture; 

        // states.shader나 or states.blendMode 중 하나를 오버라이드하세요. 

        // vertex array 그리기
        target.draw(m_vertices, states); 
    } 

    sf::VertexArray m_vertices; 
    sf::Texture m_texture; 
}; 

이러고 나면 이제 이 클래스는 sfml의 기본클래스들처럼 쓸수 있습니다.

MyEntity entity; 
// 변환할 수 있습니다.
entity.setPosition(10, 50); 
entity.setRotation(45); 
// 이제 그립시다.
window.draw(entity);


예제 : tile map

위의 내용을 전부 이해했다면 tile map을 캡슐화해서 클래스를 만들수 있을겁니다.
모든 맵이 vertex array메 포함돼서 엄청 빠를거에요. 대신 이 방법은 오직 전체 tile 집합이 하나의 텍스쳐에 들어갈 수 있는 경우에만 사용할 수 있습니다.
그래서 이외의 경우에는 텍스쳐마다 최소한 하나 이상의 vertex array를 둬야만 합니다.

class TileMap : public sf::Drawable, public sf::Transformable 

public: 
    bool load(const std::string& tileset, sf::Vector2u tileSize, const int* tiles, unsigned int width, unsigned int height) 
    { 
        // tileset 텍스쳐 로딩
        if (!m_tileset.loadFromFile(tileset)) 
            return false; 

        // vertex array의 사이즈를 그 level의 사이즈에 맞게 조정
        m_vertices.setPrimitiveType(sf::Quads); 
        m_vertices.resize(width * height * 4); 

        // tile마다 하나의 quad로 vertex array 채우기
        for (unsigned int i = 0; i < width; ++i) 
            for (unsigned int j = 0; j < height; ++j) 
            { 
                // 현재 tile 수 가져오기
                int tileNumber = tiles[i + j * width]; 

                // tileset 텍스쳐에서 그 위치 탐색 
                int tu = tileNumber % (m_tileset.getSize().x / tileSize.x); 
                int tv = tileNumber / (m_tileset.getSize().x / tileSize.x); 

                // 현재 tile의 quad 포인터 획득 
                sf::Vertex* quad = &m_vertices[(i + j * width) * 4]; 

                // 4개의 코너 정의 
                quad[0].position = sf::Vector2f(i * tileSize.x, j * tileSize.y); 
                quad[1].position = sf::Vector2f((i + 1) * tileSize.x, j * tileSize.y); 
                quad[2].position = sf::Vector2f((i + 1) * tileSize.x, (j + 1) * tileSize.y); 
                quad[3].position = sf::Vector2f(i * tileSize.x, (j + 1) * tileSize.y); 

                // 텍스쳐 좌표 4개 정의 
                quad[0].texCoords = sf::Vector2f(tu * tileSize.x, tv * tileSize.y); 
                quad[1].texCoords = sf::Vector2f((tu + 1) * tileSize.x, tv * tileSize.y); 
                quad[2].texCoords = sf::Vector2f((tu + 1) * tileSize.x, (tv + 1) * tileSize.y); 
                quad[3].texCoords = sf::Vector2f(tu * tileSize.x, (tv + 1) * tileSize.y); 
            } 
            return true; 
        } 
private: 
    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const 
    { 
        // transform 적용
        states.transform *= getTransform(); 

        // tileset 텍스쳐 적용
        states.texture = &m_tileset; 

        // vertex array 그리기
        target.draw(m_vertices, states); 
    } 

    sf::VertexArray m_vertices; 
    sf::Texture m_tileset; 
}; 

자, 그럼 이제 이걸 응용프로그램에서 사용해봅시다.

int main() 

    // window 생성
    sf::RenderWindow window(sf::VideoMode(512, 256), "Tilemap"); 

    // tile 인덱스의 배열로 level을 정의 
    const int level[] = { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 1, 0, 0, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 3, 3, 3, 0, 0, 0, 1, 1, 1, 2, 0, 0, 0, 0, 1, 0, 3, 0, 2, 2, 0, 0, 1, 1, 1, 1, 2, 0, 2, 0, 1, 0, 3, 0, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 3, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, }; 
    
    // 그리고 그 level로 tilemap 생성
    TileMap map; 
    if (!map.load("tileset.png", sf::Vector2u(32, 32), level, 16, 8))
         return -1; 

    // main loop 실행 
    while (window.isOpen()) 
    { 
        // 이벤트 핸들링
        sf::Event event; 

        while (window.pollEvent(event)) 
        { 
            if(event.type == sf::Event::Closed) 
                window.close(); 
        } 

        // map 그리기
ㅤㅤwindow.clear(); 
        window.draw(map); 
        window.display(); 
    } 
    return 0; 
}






예제: particle system

이 두번째 예제에서는 particle system이라는 또다른 엔티티를 구현해볼겁니다.

이건 엄청 간단해요. 텍스쳐도 없고 아주 최소한의 파라미터만 사용합니다.
정확히는, 모든 프레임을 변화시키는 동적 vertex array로 sf::Points를 사용합니다.

class ParticleSystem : public sf::Drawable, public sf::Transformable 

public: 
    ParticleSystem(unsigned int count) : m_particles(count), m_vertices(sf::Points, count), m_lifetime(sf::seconds(3)), m_emitter(0, 0) 
    { } 

    void setEmitter(sf::Vector2f position) 
    { 
        m_emitter = position; 
    } 

    void update(sf::Time elapsed) 
    { 
        for (std::size_t i = 0; i < m_particles.size(); ++i) 
        { 
            // particle의 생명주기(lifetime) 갱신
            Particle& p = m_particles[i]; 
            p.lifetime -= elapsed; 

            // particle이 죽어있다면 되살림
            if (p.lifetime <= sf::Time::Zero) 
                resetParticle(i); 

            // 해당 vertex의 위치 갱신
            m_vertices[i].position += p.velocity * elapsed.asSeconds(); 

            // particle의 수명에 따라 alpha(투명도) 갱신
            float ratio = p.lifetime.asSeconds() / m_lifetime.asSeconds(); 

            m_vertices[i].color.a = static_cast<sf::Uint8>(ratio * 255); 
        } 
    } 

private: 
    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const 
    { 
        // transform 적용
        states.transform *= getTransform(); 

        // our particles don't use a texture 
        states.texture = NULL; 

        // vertex array 그리기
        target.draw(m_vertices, states); 
    } 
private: 
    struct Particle 
    { 
        sf::Vector2f velocity; 
        sf::Time lifetime; 
    }; 

    void resetParticle(std::size_t index) 
    { 
        // particle 에 랜덤 속도(velocity)와 생명주기를 부여
        float angle = (std::rand() % 360) * 3.14f / 180.f; 
        float speed = (std::rand() % 50) + 50.f; 
        m_particles[index].velocity = sf::Vector2f(std::cos(angle) * speed, std::sin(angle) * speed); 
        m_particles[index].lifetime = sf::milliseconds((std::rand() % 2000) + 1000); 

        // 해당 vertex의 위치 재설정
        m_vertices[index].position = m_emitter;
    } 
    std::vector<Particle> m_particles;
    sf::VertexArray m_vertices; 
    sf::Time m_lifetime; 
    sf::Vector2f m_emitter; 
}; 

그럼 이걸 간단하게 사용해보죠.

int main() 

// window 생성 
    sf::RenderWindow window(sf::VideoMode(512, 256), "Particles"); 

    // particle system 생성 
    ParticleSystem particles(1000); 

    // 경과 시간을 측정할 clock 생성
    sf::Clock clock; 

    // main loop 수행
    while (window.isOpen()) 
    { 
        // 이벤트 핸들링 
        sf::Event event; 
        while (window.pollEvent(event)) 
        { 
            if(event.type == sf::Event::Closed) 
                window.close(); 
        } 

        // particle system이 마우스를 따라 뿜어지게(emit) 함 
        sf::Vector2i mouse = sf::Mouse::getPosition(window);
    particles.setEmitter(window.mapPixelToCoords(mouse)); 

        // 업데이트
        sf::Time elapsed = clock.restart(); 
        particles.update(elapsed); 

        // 그림
        window.clear(); 
        window.draw(particles); 
        window.display(); 
    } 
    return 0; 
}






설정

트랙백

댓글