사용자 도구

사이트 도구


raylib:flappybird:포인터를_이용하여_최적화하기
포인터를 이용하여 최적화하기

스타팅 파일

문제점

기존의 프로젝트를 실행해서 터미널에 쏟아지는 문구를 보면, pipe그림 파일을 끊임 없이 로딩하고, 다시 언로딩하는 작업이 이루어지는 것을 볼 수 있다.

파일 IO문제

이는 Obstacle 클래스를 따로 만든 다음에 그 안에서 이니셜라이징을 하고, 또 장애물이 화면 바깥으로 빠지면 파이프 그림 에셋을 그래픽램에서 제거하게 만들었기 때문이다.

그런데 이렇게 하면 당연히 속도가 느려질 수 밖에 없다. 쓸데 없이 리소스를 계속 그래픽램에 올렸다가 내렸다가 해야 하기 때문이다.

아무래도 보조저장장치인 하드디스크에서 다시 램에 파일을 올리려면은 그만큼 속도를 희생시킬 수 밖에 없다.

전에 스프라이트 이미지로 애니메이션을 구하는 아이디어에서도 볼 수 있듯이, 일단 리소스 그 자체는 램에 올려놓은 후 그 리소스를 계속 돌려쓰기 하는게 훨씬 속도가 빠르다.

우리가 사용하는 장애물 파이프는 모두 똑같은 이미지를 이용한다. 이 이미지의 특정 부분만 가져다 쓰는 것이다. 따라서 스프라이트 이미지에서 특정 부분을 가져오는 것과 기본 개념은 동일하다. 그렇다면 한개의 에셋을 가지고 여러번 돌려쓰려면 어떠게 해야 할까?

우리는 그 에셋의 주소를 가져 온 다음에 그 주소를 가리키면 될 것이다. 굳이 엣세의 '값'을 가져올 필요가 없는 것이다.

에셋을 로딩하기

1. game.h

Obstacle클래스에서 쓸 에셋을 미리 로딩해야 한다.

여러 방법이 있곘지만, 일단은 가장 직관적으로 생각할 수 있는 것은 게임 클래스에서 처음부터 에셋을 로딩하는 것이다.

       Texture2D UpPipeImage;        // Texture loading;  
       Texture2D DownPipeImage;        // Texture loading;  

Texture2D 에셋을 선언해서 텍스쳐를 사용하려고 하는 것은 기존과 다를 바가 없다.

2. game.cpp

가. Init()함수

텍스쳐 파일을 로딩하자

        // 파이프 이미지
    DownPipeImage = LoadTexture("Assets/Pipe-up350.png");      // 아래쪽에 있는 파이프이다. 에셋의 파일명은 위쪽을 바라보는 의미에서 pipe-up이다. 
    UpPipeImage = LoadTexture("Assets/Pipe-down350.png");      // 위쪽에 있는 파이프이다. 에셋의 파일명은 아래쪽을 바라보는 의미에서 pipe-up이다. 

나. ~Game()메소드

소멸자에는 텍스쳐를 업로딩하는 것을 잊지 말자

Game::~Game()
{
    UnloadTexture(DownPipeImage);       // Texture unloading
    UnloadTexture(UpPipeImage);       // Texture unloading
    UnloadTexture(backgroundImage); // Texture unloading
    UnloadMusicStream(music);   // Unload music stream buffers from RAM
    pipes.clear();
} 

인자를 포인터로 변환하기

1. obstacle.h

private형에 멤버변수로 Texture2D 변수를 포인터로 하였다. 변수에 스타(*)를 붙이면 포인터 변수이다.

Game클래스에서는 Texure2D의 값을 멤버로 하였다면, 여기에서는 포인터를 멤버변수로 하여, 앞으로는 Game클래스에서 실존하고 있는 Texure를 그 주소만 가져오는 변수를 멤버로 만든 것이다.

     // 텍스처 로드하기 
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
    Texture2D* UpPipePtr;        // Texture loading;  
    Texture2D* DownPipePtr;        // Texture loading;  

public 형에 있는 Init함수에서도 인자를 포인터로 받게 하였다.

    void Init(Texture2D* upPipeAddr, Texture2D* downPipeAddr);

2. obstacle.cpp

가. Init()메서드

현재 Obstacle 클래스가 멤버변수로 선언한 UpPipePtr, DownPipeP도 포인터 변수이고, 인자로 가져오는 upPipeAddr, downPipeAddreh 포인터 변수이다. 따라서 그냥 대입해 주면 된다.

void Obstacle::Init(Texture2D* upPipeAddr, Texture2D* downPipeAddr)
{
    // 생략 
 
    UpPipePtr = upPipeAddr;
    DownPipePtr = downPipeAddr;
 
     // 아래도 생략
}

나. Draw() 메서드

아래와 같이 만들어 주었다.

// 드로잉 메서드 
void Obstacle::Draw()
{
    if (type == ObstacleType::top) {
        // DrawRectangle(upPipe.x, upPipe.y, upPipe.bWidth, upPipe.bHeight, DARKGREEN);
        DrawTextureRec(*UpPipePtr, topRectImg, Vector2{float(upPipe.x), float(upPipe.y)}, WHITE);  // 천장 파이프 그리기  
        DrawText(TextFormat("Addr : %p", UpPipePtr), upPipe.x, upPipe.y + upPipe.bHeight, 20, RED);
    }else if (type == ObstacleType::bottom) {
        // DrawRectangle(downPipe.x, downPipe.y, downPipe.bWidth, downPipe.bHeight, DARKGREEN);
        DrawTextureRec(*DownPipePtr, bottomRectImg, Vector2{float(downPipe.x), float(downPipe.y)}, WHITE ); // 바닥 파이프 그리기 
        DrawText(TextFormat("Addr : %p", DownPipePtr), downPipe.x, downPipe.y -20, 20, RED);
    }else {
        DrawTextureRec(*UpPipePtr, topRectImg, Vector2{float(upPipe.x), float(upPipe.y)}, WHITE);
        DrawTextureRec(*DownPipePtr, bottomRectImg, Vector2{float(downPipe.x), float(downPipe.y)}, WHITE );  // 천장과 바닥 파이프 그리기 
        DrawText(TextFormat("Addr : %p", UpPipePtr), upPipe.x, upPipe.y + upPipe.bHeight, 20, RED);
        DrawText(TextFormat("Addr : %p", DownPipePtr), downPipe.x, downPipe.y - 20, 20, RED);
    }
}

실제변수의 앞에 스타(*)를 넣으면, 그 주소에 대한 값을 반환한다. 이를 역참조라고 한다.

예를들어, DownPipePtr은 주소에 불과한데, 여기에다가 앞에 스타(*)를 넣어줌으로써 그 주소에 해당하는 값을 가져오는 것이다. 여기에서는 바로 텍스쳐일 것이다.

즉 우리는 game클래스에서 만든 텍스쳐에 대하여 그 주소를 가져옴으로써 아무리 많은 Obstacle 클래스(=장애물 파이프)를 만들어도 똑같은 텍스쳐를 사용하게 되는 것이다.

파이프를 그리고 난 뒤에는 DrawText 함수를 이용하여 그 파이프에 해당하는 주소값을 화면에 보여주게 하였다. 화면에 보여주면 각각의 파이프의 텍스쳐가 어떤 주소를 가지는지 시각적으로 이해하기 편할 것이다.

다. UnloadTexture() 삭제

실제 텍스쳐값은 Game클래스에 생성하고 또 소멸하므로, 게임클래스에서 Obstacle클래스에 있는 UnloadTexture()를 불러올 필요가 없다. 따라서 이것은 삭제하면 된다.

주소값 인자로 넘겨 주기

우리는 게임클래스에서 텍스쳐를 실제 값으로 받아서 이를 램에 올렸다. 그렇다면 이를 포인터로 넘겨주려면 어떻게 하면 될까?

앰퍼서드(&)문자를 실제 값의 변수 앞에 추가하면 된다. 다음과 같다.

pipe.Init(&UpPipeImage, &DownPipeImage);  

Game클래스에서 위와 같은 요령으로 각 pipe의 Init를 불러오는 구문을 바꿔주면 된다.

결과

포인터로 텍스쳐 불러오기

각 장애물의 주소값1)이 다 같은 것을 볼 수 있다.

그리고 이제 터미널에서 더 이상 정신사나운 파일입출력을 안한다. 속이 편하다. 지금까지 너무 컴퓨터를 학대했다는 것을 알 수 있다.

지금까지의 소스파일은 다음과 같다.

포인터를 이용하여 최적화하기

다음번에는 상속을 이용하여 다른 아이템 만들기를 해볼 것이다.

1)
정확히 표현하면, 장애물이 사용하고 있는 텍스쳐의 주소값
로그인하면 댓글을 남길 수 있습니다.

raylib/flappybird/포인터를_이용하여_최적화하기.txt · 마지막으로 수정됨: 2023/11/20 00:08 저자 이거니맨