{{:raylib:플래피버드그림입히기.png?600|플래피 버드 그림 입히기}} ~~stoggle_buttons~~ ===== 스타팅 파일 ===== * 이전 프로젝트는 [[raylib:flappybird:flappy_bird_게임_장애물_만들기|게임 장애물 만들기]]이다. * 스타팅 파일은 {{ :raylib:flappybird_3.zip |플래피버드 게임 장애물 만들기}}이다. ===== 플레이어 텍스쳐 입히기 ===== 사실 텍스쳐드로잉은 이미 메뉴 만들기에서 한번 해본 적 있다. 메뉴 만들기에 구현해 놓은 코드들을 bird 클래스에 가져오면 된다. ==== 1. bird.h ==== private 선언 부분에 다음과 같이 텍스쳐를 선언하게 만들었다. // 텍스처 로드하기 // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) Texture2D birdImage; // Texture loading; float halfLengthH = 18; float halfLengthW = 25; 애셋 폴더에 있는 새의 이미지가 가로 크기는 50픽셀이고 세로크기는 35이다. 따라서 가로를 반으로 나눈 값은 25로 했고, 세로를 반으로 나눈 값은 18로 했다. 이미 [[raylib:gui_와_메인메뉴|메뉴파일]]에서도 birdImage란 이름으로 선언했던 적이 있다. 따라서 컴파일을 하면 오류가 날 것이다. 메뉴파일과 다른 이름으로 Texture2D를 선언해도 되지만 나는 메뉴 파일에서는 기존에 쓰던 텍스쳐를 지우리고 했으므로 똑같은 이름을 사용했다. 만약 메뉴파일을 수정하기 싫은 사람은 birdImage가 아니라 다른 이름으로 선언해야 한다. ==== 2. bird.cpp ==== === 가. Init()함수 === 다음과 같이 텍스쳐를 불러온다. void Bird::Init() { // 최초의 포지션 정하기 pos = {60, float(GetScreenHeight()) / 2} ; // 그림으로 넣기 birdImage = LoadTexture("Assets/birddrawing.png"); // Texture loading } === 나. Draw()함수 === 다음과 같이 DrawTexture 함수를 불러오면 된다. DrawTexture는 현재 포지션인 x와 y값만 가져오면 된다. // 플레이어 그리기 void Bird::Draw() { if (isInvincible) { // DrawRectangle(pos.x - halfLength, pos.y - halfLength, halfLength * 2, halfLength * 2, GRAY); // 플레이어가 무적일 떄 DrawTexture(birdImage, int(pos.x) - halfLengthW, int(pos.y) - halfLengthH, GRAY); }else { // DrawRectangle(pos.x - halfLengthW, pos.y - halfLengthH, halfLengthW * 2, halfLengthH * 2, DARKGREEN); // 평상시 DrawTexture(birdImage, int(pos.x) - halfLengthW, int(pos.y) - halfLengthH, WHITE); } } DrawTexure()함수는 단순히 그림이 그려질 위치만 인자로 받는다. 그런데 이 그림이 그려질 위치는 왼쪽 위이므로, 이에 대한 조정이 필요하다 . 우리는 pos.x를 플레이어의 정중앙으로 잡았으므로 왼쪽시작점은 pos.x에서 halfLengthW일 것이다. 이런식으로 조정하면 된다. === 다. 텍스쳐 언로드 === 객체가 소멸될 때 텍스쳐도 언로드 하는 것을 잊지 말자 Bird::~Bird() { UnloadTexture(birdImage); // Texture unloading } ===== 장애물 그림 입히기 ===== ==== 1. obstacle.h ==== 똑같이 Texture2D를 priavte에 선언한다. // 텍스처 로드하기 // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) Texture2D UpPipeImage; // Texture loading; Texture2D DownPipeImage; // Texture loading; Rectangle topRectImg; Rectangle bottomRectImg; 그림 파일에서 가져올 텍스쳐 부분도 설정해야 하므로, 가져올 부분을 Rectangle로 선언하였다. ==== 2. obstacle.cpp ==== === 가. 기본 개념 === 에셋폴더에는 가로 100, 세로 350 픽셀짜리 위쪽 파이프와 아래쪽 파이프가 있다. 우리는 가로 너비는 그대로 하고 세로 너비는 랜덤하게 설정하였다. 그렇다면 각 랜덤하게 나오는 파이프에 걸맞게 텍스쳐도 해당 부분을 가져와야 한다. {{:raylib:파이프위치계산하기.png?600|파이프 위치 계산하기}} 만약 위쪽 파이프가 세로 높이가 250이라고 해보자. 그렇다면 아래를 향하고 있는 천장 파이프는 세로 100픽셀부터 350픽셀까지 250픽셀을 읽게 된다. 이를 수식으로 하면 이렇게 정리 할 수 있다. 천장 파이프 그림 파일: (350-파이프 높이) ~ (파이프 높이) 자 이를 토대로 코드를 짜보자. === 나. Init()함수 === DownPipeImage = LoadTexture("Assets/Pipe-up350.png"); // 아래쪽에 있는 파이프이다. 에셋의 파일명은 위쪽을 바라보는 의미에서 pipe-up이다. UpPipeImage = LoadTexture("Assets/Pipe-down350.png"); // 위쪽에 있는 파이프이다. 에셋의 파일명은 아래쪽을 바라보는 의미에서 pipe-up이다. topRectImg = {0.0f, 350.0f - float(upPipe.bHeight), 100.0f, float(upPipe.bHeight)}; // 위쪽 파이프 그림에서 가져올 부분이다. bottomRectImg = {0.0f, 0.0f, 100, float(downPipe.bHeight)}; // 아래쪽 파이프 그림에서 가져올 부분이다. 천장파이프는 y축 시작점이 (350 - 파이프 높이)이고 끝점이 (파이프 높이)임을 알 수 있다. 이렇게 하면 필요한 텍스쳐를 가져올 수 있다. === 다. Draw() 메서드 === 다음과 같이 DrawTextureRec()함수를 이용하였다. Init()함수에서 에셋에서 가져올 파이프의 해당 부분 영역을 구하였으므로 이를 원본소스에서 가져온 후 지정한 위치에 그리는 함수이다. // 드로잉 메서드 void Obstacle::Draw() { if (type == ObstacleType::top) { // DrawRectangle(upPipe.x, upPipe.y, upPipe.bWidth, upPipe.bHeight, DARKGREEN); DrawTextureRec(UpPipeImage, topRectImg, Vector2{float(upPipe.x), float(upPipe.y)}, WHITE); // 천장 파이프 그리기 }else if (type == ObstacleType::bottom) { // DrawRectangle(downPipe.x, downPipe.y, downPipe.bWidth, downPipe.bHeight, DARKGREEN); DrawTextureRec(DownPipeImage, bottomRectImg, Vector2{float(downPipe.x), float(downPipe.y)}, WHITE ); // 바닥 파이프 그리기 }else { DrawTextureRec(UpPipeImage, topRectImg, Vector2{float(upPipe.x), float(upPipe.y)}, WHITE); DrawTextureRec(DownPipeImage, bottomRectImg, Vector2{float(downPipe.x), float(downPipe.y)}, WHITE ); // 천장과 바닥 파이프 그리기 } } === 라. 텍스쳐 언로드 === 텍스쳐를 그래픽램에 계속 쌓이게만 하면 안되므로 필요 없는 텍스쳐 에셋은 그래픽램에 지워주어야 한다. == (1) obstacle.cpp == 다음과 같이 텍스쳐를 언로드 하는 함수를 public으로 구현해 준다(헤더파일에는 선언해주는 것도 잊지말자). void Obstacle::UnloadPipeTexture() { UnloadTexture(DownPipeImage); // Texture unloading UnloadTexture(UpPipeImage); // Texture unloading } == (2) game.cpp == 파이프가 화면 왼쪽으로 가면 텍스쳐를 언로드 하는 함수를 불러와 주면 된다. 다음과 같이 배열에서 파이르를 지우기 전에 언로드를 먼저 불러와 주자 // 장애물이 화면 밖으로 나가면 배열에서 제거 if (pipes[i].GetPipePosition() <= 0) { pipes[i].UnloadPipeTexture(); pipes.erase(pipes.begin() + i); // 장애물이 왼쪽 끝에 가면 배열에서 제거 } 참고로, 원래는 Obstacle클래스의 소멸자에서 UnloadTexture를 불러왔었는데 이렇게 하면 Obstacle객체가 생성되자마자 텍스쳐가 Unload된다. 왜 이렇게 되는지는 잘 모르겠다. 아마도 C++ 문법을 좀 더 공부해 본 후에 그 이유를 찾아 보아야 할 것 같다. 아는 사람은 댓글로 달아주면 좋겠다. ===== 배경화면 입히기 ===== game.cpp 에서 Draw()메서드 내에서 가장 먼저 배경화면을 그리면 그 위에 플레이어와 파이프가 그려질 것이다. 따라서 배경화면을 입히는 것은 어려운 것이 아니다. 다만 우리 에셋 폴더에 있는 background.png 파일은 800*600 사이즈이다. 그런데 우리는 스크린 사이즈를 1200 * 900으로 하였다. 딱 50%씩 더 커진 것이다. 이럴 때에는 어떻게 해야 할까? 배경화면을 1200 * 900으로 맞춰서 그려주는 것이 가장 좋겠지만, 이번에는 원본 소스를 확대하는 함수를 한번 사용해 보자. 아래 코드를 game.cpp에서 Draw() 메서드 내에서 최사안에 넣어주면 된다. DrawTexturePro(backgroundImage, Rectangle{0, 0, 800, 600}, Rectangle{0, 0, 1200, 800}, Vector2{0, 0}, 0, WHITE); DrawTexurePro()함수는 원본 소스의 원하는 영역을 가져와서 타겟이 되는 영역을 원본 소스보다 키울 수 있다. 위와 같이 대상 크기를 더 늘려주면 우리는 굳이 background.png를 수정하지 않고도 배경화면을 그릴 수 있다. 소멸자에는 UnloadTexture()함수로 텍스쳐를 그래픽 램에서 지워주는 것도 잊지 말자. ===== 결론 ===== 다음과 같이 그림을 입히니 이제 진짜 게임에 가까워졌다. {{:raylib:플래피버드그림입히기.png?600|플래피 버드 그림 입히기}} 지금까지의 소스파일이다. {{ :raylib:flappybird_4.zip |플래피버드 그림 입히기 소스파일}} 다음번에는 오디오를 구현해 보자 [[raylib:flappybird:flappy_bird_에_사운드를_입히기|플래피버드에 사운드 입히기]]