===== 스타팅 파일 =====
* [[raylib:flappybird:스프라이트로_애니메이션_구현하기|스프라이트로 애니메이션 구현하기]]에서 이어지는 강의이다.
* 굳이 윗 강의에서 이어질 필요는 없고 기존에 있던 파이프 장애물 로딩을 최적화할 것이므로 기존과 동일한 {{ :raylib:flappybird_6.zip |키보드로 메인메뉴 조작하기}} 프로젝트 파일을 사용한다.
===== 문제점 =====
기존의 프로젝트를 실행해서 터미널에 쏟아지는 문구를 보면, pipe그림 파일을 끊임 없이 로딩하고, 다시 언로딩하는 작업이 이루어지는 것을 볼 수 있다.
{{:raylib:파일io문제.png?600|파일 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를 불러오는 구문을 바꿔주면 된다.
===== 결과 =====
{{:raylib:포인터로텍츠려불러오기.png?800|포인터로 텍스쳐 불러오기}}
각 장애물의 주소값((정확히 표현하면, 장애물이 사용하고 있는 텍스쳐의 주소값))이 다 같은 것을 볼 수 있다.
그리고 이제 터미널에서 더 이상 정신사나운 파일입출력을 안한다. 속이 편하다. 지금까지 너무 컴퓨터를 학대했다는 것을 알 수 있다.
지금까지의 소스파일은 다음과 같다.
{{ :raylib:flappybird_7.zip |포인터를 이용하여 최적화하기}}
다음번에는 [[raylib:flappybird:상속을_이용하여_다른_아이템_만들기|상속을 이용하여 다른 아이템 만들기]]를 해볼 것이다.