{{: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_에_사운드를_입히기|플래피버드에 사운드 입히기]]