SFML 2

김성진·2024년 2월 2일

2월 2일 수업 정리

Framework

Frame 이란 뼈대 라는 뜻이다. 이번 수업을 하면서 기본적인 게임의 틀을 알게되면, 각자가 자신에게 맞는 기본 틀을 만들고 저장해뒀다가 새 프로젝트를 시작할때마다 사용하게 될건데 그것은 프레임워크라고 부른다. 이 프레임워크는 개인의 편리함을 위해서도 잘 만들어야 하지만 추후 작성할 포트폴리오의 첫 번째 주제이기도 하다. 무척이나 신경써서 다듬어야겠다.

어제 만들던 나무꾼 게임 Timber 을 이어서 만들어보자.

1초마다 방향이 바뀌는 벌

방향을 바꾸는 것, 시간을 재는 것. 둘다 배웠었다.
내부적으로 1번 호출될때마다 초단위로 시간을 재던 assecond 함수를 통해 deltaTime 변수를 만들었었다. 이것을 누적시켜 1초가 된다면 벌의 방향이 바뀌고 누적된 시간을 초기화시켜주면 될것이다.
이것이 내가 구현한 방법이고, 교수님은 다른 방법도 소개시켜주었다. 내부적인 시간을 계속 체크하고, 벌이 움직이는 시간을 1초마다 설정해두는 것이다. 벌의 움직여야 되는 시간보다 현재 시간이 많으면 벌을 움직임과 동시에 벌이 움직여야 되는 시간을 1초 더해준다.

//1초마다 방향이 바뀌는 벌 , 다른 방법
        time += deltaTime;   //시간을 항상 잼

    float time = 0;   시간 초기화
    float beeDirChangeDuration = 1.f;   //벌 이동주기
    float beeChangeTime = beeDirChangeDuration;   //벌이 움직여야 될 시간

        if (time > beeChangeTime)
        {
            beeDirection = rotation * beeDirection;
            rotation = rotation.rotate((float)(rand()) / RAND_MAX * 360);

            beeChangeTime = time + beeDirChangeDuration;
        }
//움직이면  1초 더 추가하고 그 시간이 지나면 또 움직이고 반복

벌이 보는 방향 변경

2D 게임들은 좌 우를 보고 있을때의 텍스처가 따로 없다. 때문에 외팔이 캐릭터의 팔이 왼쪽 오른쪽을 바꿔가며 나온다. 이것은 보는 방향을 바꾸는게 그저 텍스처를 뒤집을 뿐이기 때문이다.

피봇 점 찍기

캐릭터를 뒤집으려면 피봇 점을 정해야 하는데, 보통 지면과 맞닿기 위해 중앙 하단을 정한다고 한다.
피봇 점은 상중하,좌중우 총 9군데 중 하나를 찍는것이 보통이기에, 이 9개의 점을 지정하는 함수를 만들어 프레임워크로 저장하기로 한다.
defines 라는 헤더파일을 만들어서, Origins 라는 열거형을 만들어 9개의 점을 상수화 시켜놓자.

enum class Origins
{
	// T M B 탑미드바텀
	// L C R 좌 센터 우

	TL = 0, TC, TR,
	ML, MC, MR,
	BL, BC, BR,
};

이제 0 1 2 // 3 4 5 // 6 7 8 이 어딜 가리키는지, 나는 평생 외우고 살아야 한다.

이제 피봇 점을 찍어주던 함수 setOrigin을 오버로딩 해서, 내가 지정한 9개의 피봇점을 자동으로 찍어주는 함수를 만든다.

void Utills::SetOrigin(sf::Transformable& obj, Origins originPreset, const sf::FloatRect& rect)
{

	sf::Vector2f newOrigin(rect.width, rect.height);
	// Rect height, width
	newOrigin.x *= ((int)originPreset % 3) * 0.5f;  
    /*  00 01 02 10 11 12  
    이렇게 결과가 나올떄 2로 나눠서 
    00 00.5 01 이렇게 배율맞추는거임*/
	newOrigin.y *= ((int)originPreset / 3) * 0.5f;
	obj.setOrigin(newOrigin);
}

obj는 해당 오브젝트, originpreset은 피봇 점 9개의 상수,
rect는 오브젝트의 가로세로 크기를 뜻한다.
따라서 코드를 해석해보면, 오브젝트의 가로세로 값을 받아서 newOrigin에 저장한다. 그 후 피봇점 위치가 9개중 무엇인지 판단하는데, 알고리즘은 다음과 같다.
좌 중 우 가운데 무엇인지 알려면, 3으로 나눈 나머지를 판단한다.
0 1 2
3 4 5
6 7 8 인데, 나머지만 보면

0 1 2
0 1 2
0 1 2 니까.

상 중 하 가운데 무엇인지 알려면 3으로 나눈 몫을 본다.
0 0 0
1 1 1
2 2 2 일 테니까.

이것을 조합해보면 각 피봇점은 다음과 같은 xy값을 가진다.
00 01 02
10 11 12
20 21 22 이것을 판단하는 알고리즘이다.

또한 0.5로 나눠주는 이유는, 셋오리진 함수로 해당 오브젝트의 중앙을 찍으려면 가로 세로 총 길이의 0.5 배 지점이고, 끝을 찍으려면 가로세로 값 그 자체(1배) 인 식이니까 2를 1로, 1을 0.5로 치환해주는 것이다.

이후 메인함수에

    const sf::FloatRect& boundsBee = spriteBee.getLocalBounds(); 
    //로컬바운스는좌상점을 00으로 하는 사각형을 리턴 즉 가로세로값
    Utills::SetOrigin(spriteBee, Origins::MC, boundsBee);

를 하면 벌 스프라이트의 중앙점을 오리진으로 지정할 수 있다.
getLocalBounds 함수는 해당 오브젝트의 가로세로 값을 리턴해준다. 매번 이렇게 쓸 수 있지만 사실 반복작업이니 함수 오버로딩에 추가해서 오브젝트 이름과 피봇점만 지정해도 결과값을 출력하게 하자.

void Utills::SetOrigin(sf::Sprite& obj, Origins originPreset)
{
	SetOrigin(obj, originPreset, obj.getLocalBounds());
}

이렇게 해두면 앞으로 어떤 스프라이트를 찍어내도 간단히 피봇점을 지정할 수 있다. 참고로, Sprite 에만 해당되기 때문에 Text 같은 다른 오브젝트들은 오버로딩을 또 해줘야 한다.

보는 방향 변경하기

이제 피봇점을 기준으로 뒤집어주면 된다. 좌우로 뒤집는건 단순히 벡터의 x값을 -1 로 하는 것이다.
따라서 spriteBee.setScale(-1.f , 1.f); 가 된다.
또한 랜덤하게 벌의 움직임을 지정했었다, 벌이 랜덤으로 움직일 때마다 이동방향으로 보는 방향을 돌리게 해주자.

       if (time > beeChangeTime)
        {
            beeDirection = rotation * beeDirection;
            rotation = rotation.rotate((float)(rand()) / RAND_MAX * 360);

            beeChangeTime = time + beeDirChangeDuration;

            if (beeDirection.x > 0.f)
            {
                spriteBee.setScale(-1.f, 1.f);
            }
            else if (beeDirection.x < 0.f)
            {
                spriteBee.setScale(1.f, 1.f);

            }
        }

이동 벡터에 맞춰 스케일을 뒤집어 주었다.

일시정지

일시정지 기능은 정말로 시스템이 멈추는 것이 아니다.
내부적으로 계속해서 읽고 쓰고 그리는 작업은 반복되며, 단순히 움직이는 모든 오브젝트들이 멈춘 것 뿐이다. 때문에 일시정지 기능은 보통 시간을 건드리는 방법으로 만들어지며, 시간과 무관하게 만들어진 기능들은 따로 일시정지 코드를 추가해줘야 한다.
예를들어 벌과 구름은 deltaTime에 따라 움직였다. 이 deltaTime에 0을 곱해주면 정지, 2를 곱해주면 2배속, 즉 배속 시스템인 것이다.


   //float beeTimeCount = 0;
    float time = 0;
    float beeDirChangeDuration = 1.f;
    float beeChangeTime = beeDirChangeDuration;
    float deltaTime = 0.f;
    float timeScale = 1.0f;
    //벌은 델타 타임에 비례해서 움직이고 있다.

        realTime += deltaTime; // 리얼타임 표시해주는용도

        deltaTime *= timeScale; // 배속

        time += deltaTime;

주의할 점은 deltaTime *= timeScale;
이 반드시 time += deltaTime; 보다 먼저 적혀야 한다.
반대로 썼더니 time이 계속 올라갔었다.

이제 키 입력에 따라 배속을 입력받는 코드를 작성해보자.

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Num0))
        {
            timeScale = 0;
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Num1))
        {
            timeScale = 1;
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Num2))
        {
            timeScale = 2;
        }

텍스트 표시

텍스트는 텍스처와는 약간 다르다. 폰트를 기반으로 한다.
작성 전에, setOrigin 함수 오버로딩에 텍스트로 된 것을 만들어두면 편하다.

    sf::Font font;
    font.loadFromFile("fonts/KOMIKAP.ttf");    // 폰트 텍스처처럼 받기

    sf::Text textMessage;
    textMessage.setFont(font);
    textMessage.setString("Press Enter to Start!");
    textMessage.setCharacterSize(75);
    textMessage.setFillColor(sf::Color::White);    // 텍스트 설정

    Utills::SetOrigin(textMessage, Origins::MC); // 
    textMessage.setPosition(1920 / 2, 1080 / 2);  // 텍스트 오리진과 위치 설정

            window.draw(textMessage);  // 텍스트 그리기.

텍스처, 스프라이트 만드는것과 상당히 비슷하다.
텍스처 -> 폰트, 스프라이트 -> 텍스트 로 변한것 뿐이다.

일시정지 시 텍스트 표시

이제 배운것들의 응용이다. 일시 정지를 했을 때 시간을 멈추고 텍스트를 출력해보자.

    float timeScale = 0.0f;
//첫 시작을 멈춘 상태로

    bool isPause = true; // 정지 상태 명령어
    timeScale = isPause ? 0.f : 1.f;

정지 상태를 관장하는 bool형 명령어 isPause를 만든다.
이후 삼항연산자로 isPause가 키고 꺼질때마다 timeScale을 조정해준다.

이제 이벤트 루프문 안에

        sf::Event event;
        while (window.pollEvent(event))
        {
            switch (event.type)
            {
            case sf::Event::Closed:
                window.close();
                break;
            case sf::Event::KeyReleased:
                if(event.key.code == sf::Keyboard::Space)
                isPause = !isPause;
                timeScale = isPause ? 0.f : 1.f;
                break;
            }
        }

KeyReleased: 를 통해 스페이스바가 눌리면 isPause를 작동시켜주면 된다. 또한 텍스트도 출력해주면

        if (isPause)
        {
            window.draw(textMessage);
        }

이렇게 된다.

점수 표시하기

게임이라면 어떠한 이벤트가 일어나야만 점수가 오르겠지만, 아직 배우지 않았기에 특정 키를 입력하면 점수가 오르는것으로 구현해보자.
점수를 저장하는 변수 scoreNumber 를 만들고, 이 변수를 포함하는 점수 문자열을 저장하는 변수 scoreString을 만들고, 이들을 출력할 텍스트 객체 scoreMessage 를 만들어서 구현해보았다.

   int scoreNumber = 0;
    std::string scoreString = "SCORE = " + std::to_string(scoreNumber); // 점수 저장

   
    sf::Text scoreMessage;
    scoreMessage.setFont(font);
    scoreMessage.setString(scoreString);
    scoreMessage.setCharacterSize(90);
    scoreMessage.setFillColor(sf::Color::White); // 점수 띄우기

                if (event.key.code == sf::Keyboard::Enter)
                {
                    scoreNumber += 10;
                    scoreString = "SCORE = " + std::to_string(scoreNumber);
                    scoreMessage.setString(scoreString);
                    break;

scoreString에 인트형을 + 로 사용해서 더해주려면 to_string 함수가 필요하다.

교수님의 방법

우리가 배운 방법은 이게 한계라서, 좀더 세련된 방법을 알려주셨다.

#include <sstream>

    std::stringstream ss;
    int score = 0;
    ss << "Score : " << score;

stream 클래스에서 << 연산자 재정의를 해주어 쉽게 해준다. 간단하구만

타임바 게이지

시간 제한도 있어야 하니, 타임바 라는 게이지를 만들어 시간이 지나면 줄어들게 만들어보자. 단순히 네모난 사각형을 그리고 크기가 줄어들게 하면 되는 문제다.

 sf::Vector2f timeBarSize(400.f, 80.f);
    sf::Vector2f timeBarCurrentSize = timeBarSize;
    sf::RectangleShape timeBar;

    timeBar.setSize(timeBarCurrentSize);
    timeBar.setFillColor(sf::Color::Red);
    Utills::SetOrigin(timeBar, Origins::MC);
    timeBar.setPosition(1920 / 2, 1080 - 100);


    float timeBarDuration = 3.f; //  n초동안 타임바가 다 줄어든다는 뜻 . 400/3이 속도
    float timeBarSpeed = -timeBarSize.x / timeBarDuration; //줄어들어야 하니 음수

맨 처음 CircleShape 처럼, 사각형을 그리려면RectangleShape을 쓴다. timebarsize 를 3초로 나눠서 음수로 하고 그것을 스피드로 저장하면, 3초간 timebarspeed를 호출마다 더해주기만 해도 3초만에 타임바의 크기가 0이 될 것이다.

    timeBarCurrentSize.x += timeBarSpeed * deltaTime;
    timeBar.setSize(timeBarCurrentSize);

이제 메인 루프에 타임바를 그리고, 이걸 적어주면 된다.

오늘 배운것을 응용해서, 특정 키를 누르면 타임바가 일정량 차오르게(최대치는 넘지않게) , 그리고 0이 되면 게임이 정지하면서 게임오버 메시지가 출력되게 만들고 수업을 마쳤다!
단순한 반복과 응용이라 따로 코드를 적어놓진 않겠다.

profile
듀얼리스트

0개의 댓글