SFML

김성진·2024년 2월 1일

2월 1일

오늘부터 SFML을 사용해 기초적인 게임들을 모작해보는 수업을 했다.

SFML

Simple & Fast Multimedia Library의 준말로, 멀티미디어 API, 서드파티 라이브러리, GUI 라이브러리 등 다양하게 불린다. 쉽게 말하면 기존 비쥬얼 스튜디오로는 만들기 힘든 기능들을 구현해주는 외부 라이브러리 같은것이다.

SFML Template

SFML를 다운받았으면, 앞으로 자주 사용하게 될테니 그때그때 사용할 수 있도록 기본적인 셋팅을 해놓자.
1. SFML Template 이라는 이름을 가진 빈 프로젝트를 만들고, 프로젝트 폴더 안에 다운받은 SMFL 폴더를 넣는다.
2. 프로젝트를 우클릭해서 속성 - C/C++ - 추가 포함 디렉토리 에서 .\SFML-2.6.1\include 를 입력한다.
현재 폴더에서 SFML 폴더를 찾아 들어가는 명령어이다.
3. 소스코드에 #include "SFML/Graphics.hpp" 입력해서 작동되나 확인한다.
4. 프로젝트를 우클릭해서 속성 - 링커 - 일반 - 추가 라이브러리 디렉터리 여기서 .\SFML-2.6.1\lib 를 입력한다.
5. 프로젝트를 우클릭해서 속성 - 링커 - 입력 - 추가 종속성
여기서 sfml-graphics-d.lib;sfml-window-d.lib;sfml-system-d.lib;sfml-network-d.lib;sfml-audio-d.lib 를 입력한다.
6. SFML Template 폴더를 만든곳 옆에, SFML Template Bin 이라는 빈 폴더를 만든다. 이후 SFML 폴더 안에 있는 Bin 폴더 안에 있는 dll 파일들 전부 복사해서 SFML Template Bin 안에 넣는다.
7. 프로젝트를 우클릭해서 속성 - 일반 - 출력 디렉토리 에서
..$(ProjectName) Bin\ 를 적는다.
8. 프로젝트를 우클릭해서 속성 - 디버깅 - 작업 디렉토리에서
..$(ProjectName) Bin\ 를 적는다.
9. 이제 압축해서 사용하면 된다.

참조 사이트

https://www.packtpub.com/product/beginning-c-game-programming-second-edition/9781838648572
모방해볼 게임들

https://github.com/PacktPublishing/Beginning-Cpp-Game-Programming-Second-Edition
해당 게임들의 코드와 리소스

Timber

맨 처음 만들어볼 게임은 Timber 라는 나무를 베어내는 간단한 게임이다.
SFML Template 파일의 압축을 풀어서 폴더와 바이너리 폴더의 이름을 전부 Timber로 바꿔준다. 이후 비쥬얼 스튜디오 안에서 솔루션, 프로젝트, 소스파일등의 이름도 전부 바꿔주고 솔루션 파일을 메모장 혹은 코드 편집기로 열어서 내용에 있는 이름도 전부 바꾸어준다.

간단한 원 그리고 코드 이해하기

원을 그리는 코드

#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;
}

SFML을 사용한 간단한 코드이다. 이를 실행해보면 윈도우 창에 녹색 원이 그려지는데 코드를 해석해보자면, sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!"); 은 RenderWindow 클래스에서 window라는 객체를 만들고, 창 크기가 가로세로200인 윈도우 창을 띄워 제목을 SFML works! 로 하겠다는 말이다.
sf::CircleShape shape(100.f); 원의 반경을 100으로 하고,
shape.setFillColor(sf::Color::Green); 색을 초록색으로 하겠다는 이야기다.
Green에 커서를 올리고 F12를 눌러보면 여러가지 색이 상수화 되어서 미리 지정되어 있다는것을 알 수 있다.

while (window.isOpen()) 은 메인 루프이다. 기본적으로 게임은 이렇게 종료버튼을 누르지 않으면 계속해서 반복된다.
isOpen은 윈도우가 켜져있는 동안 bool 형의 1값을 리턴하는 멤버 함수이다.
sf::Event event;는 이벤트들을 모아놓은 함수이다. 이벤트란, 마우스 커서가 올라가거나, 클릭 혹은 버튼 입력, 최소화 최대화 등 사건들을 이벤트라고 한다.
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
따라서 이 식을 통해 종료되면 , 윈도우 창을 닫고 메인 루프도 끝나게 된다.
window.clear();
window.draw(shape);
window.display();
clear는 메모리에 올라간 내용을 비우고, draw는 메모리상에 입력받은 그림을 그리고,
display는 화면에 그림을 출력한다.
기본적으로 게임이란건 이렇게 1초에도 수백번씩 비우고 지우고를 반복한다. 이것을 초당 몇번씩 보여주냐가 프레임으로 결정되는 것이다.

게임 만들어보기

우선 요즘 모니터에 맞는 해상도를 가진 창을 만들어보자
sf::VideoMode vm(1920, 1080);
sf::RenderWindow window(vm, "SFML works!" , sf::Style::Default);

그리고 esc를 누르면 창이 종료되는 식을 입력한다.
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
{
window.close();
isKeyPressed 는 특정 키가 눌렸을 때를 뜻하며,, Escape는 esc키이다.
이러면 기초작업이 끝났다.

배경

Texture 란 게임에 표현되는 이미지들을 뜻한다.
이미지는 참조 사이트에서 Timber 게임의 리소스들을 다운받아 Bin 폴더안에 넣는다.
이제 배경을 가져와서 등록시킬 것이다.
sf::Texture texBackGround;
texBackGround.loadFromFile("graphics/background.png");
Texture는 이미지를 등록해두는 과정이다. 선언과 비슷하다고 보면 된다.

    sf::Sprite spriteBackground;
spriteBackground.setTexture(texBackGround);
spriteBackground.setPosition(0,0);

실제로 다룰수 있는 오브젝트로 만드는것은 Sprite 이다.
레퍼런스로 가져오기 때문에 텍스쳐로 한번 등록해둔 것은 몇번이고 다른 객체의 Sprite로 만들어 낼 수 있다.
포지션은 해당 이미지를 위치시킬 자리를 뜻하며, 윈도우 창의 좌상점에 이미지의 기준점을 놓는다.
spriteBackground.setOrigin(1920/2, 1080/2);
기준점은 오리진 함수로 정할 수 있으며, 중앙 또는 좌상점중 어디에 기준점을 두냐에 따라 이동과 회전시에 실행되는게 다르다.
window.draw(spriteBackground);
이제 실제로 그리는 명령을 입력하면 배경이 그려진다.

이제 구름과 벌들도 마저 추가해보자, 응용이기 때문에 코드를 자세히 적지는않겠다.

움직이기

이미지를 움직일땐 호출되는 횟수랑 상관없이 일정하게 움직여야 한다. 컴퓨터마다 호출속도가 다르기 때문에 상수번 호출될때마다 이동하게 된다면 컴퓨터 성능에 따라 객체가 움직이는 속도가 다를것이다.

따라서 시간을 사용한다. 시간을 사용하려면 sf::Clock clock; 를 입력한다.
sf::Time dt = clock.restart();
float deltaTime = dt.asSeconds();
또한 메인루프 안에 이렇게 입력해준다.
restart는 시계를 재시작 해주는것이며, asSeconds 는 시작하고 시간이 얼마나 지났는지, 초 단위로 알려주는 것이다. 여기에 출력 함수를 걸어주면 0.0003초 같은게 계속해서 올라가는것을 볼 수 있다.

이제 벌을 움직이게 해보자.
움직이는것은 벡터와 스칼라값을 포함해서 속력, 그리고 시간을 곱해서 거리가 나온다.
벡터를 사용해야 하기 때문에 Vector2 클래스를 사용하는데, 주의할 점으로는 벡터로도 쓰이고 위치를 뜻하는 포지션으로도 쓰인다.
spriteBee.setPosition(0, 800); 은 사실 setPosition (sf::Vector2(0,800)) 과 같은 것이다.

길이가 1인 단위 벡터를 방향으로 사용하고, 스칼라값을 곱해서 속력을 정하자.

float beeSpeed = 100.f;   // 스칼라값
sf::Vector2f beeDirection(1.f, 0.f); //벡터

    sf::Vector2f beePos = spriteBee.getPosition();
    beePos += beeDirection * beeSpeed * deltaTime; 
    // 속력에 1번 호출되는 시간만큼을 곱해서 1초에 스칼라값만큼 이동시키게 하는 코드
    spriteBee.setPosition(beePos);
    

벌이 이동한다!

입력을 받아 이동

자 이제 배운걸 응용해서 입력을 받아 벌을 이동시켜보자.
이동은 방금 배운걸 사용하면 되고 입력은 아까 esc 입력시 종료되는 코드를 쓰면 된다

       if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
        {
            window.close();
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
        {
            beeDirection = sf::Vector2f(-1.f, 0.f);
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
        {
            beeDirection = sf::Vector2f(1.f, 0.f);
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
        {
            beeSpeed += 10;
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
        {
            if(beeSpeed > 0)
            beeSpeed -= 10;
        }

벌의 속력과 방향을 정할 수 있게 되었다. 문제라면 키를 누르고 있는동안 중첩되어 적용되어서 속력 제어가 어렵다는 점이다.

랜덤 이동

이전 과정에 배웠던 난수를 응용해서 속력과 방향을 랜덤하게 해보자.
srand(time(nullptr));
float cloudSpeed = (rand() % 500);
float x = (float)(rand() - (RAND_MAX 0.5f)) / (RAND_MAX 0.5f);
float y = (float)(rand() - (RAND_MAX 0.5f)) / (RAND_MAX 0.5f);
sf::Vector2f cloudDirection(x,y);

(float)(rand() - (RAND_MAX * 0.5f)) / (RAND_MAX * 0.5f)는 -1~1 사이의 랜덤한 숫자를 표현한다. 이해하는데 좀 어려웠다. 0.5를 빼면 0~1이 리턴된다

이렇게 벡터값을 랜덤하게 지정할 수도 있고 다음 방법도 할 수 있다.

벡터의 변환

기억이 잘 나지 않아도 이전에 선형대수를 배울때 벡터의 변환 transpormation 을 배웠었다. 코딩에서도 똑같이 적용할 수 있다.
sf::Transform rotation;
rotation.rotate((float)(rand()) / RAND_MAX 360);
cloudDirection3 = rotation
cloudDirection3;

rotation은 각도를 정하는 함수이며, Transform은 벡터의 변환을 맡는다. 따라서 랜덤하게 벡터의 방향을 변화시키는 함수인 것이다.

profile
듀얼리스트

0개의 댓글