Day 6 배경 스프라이트 출력하기, 스프라이트 애니메이션 및 타일 맵(Exercise)
2D 게임에서 종종 사용하는 트릭으로 스크롤되는 배경이 있다. 배경 스크롤은 세계가 커다랗다는 인상을 만들어 내며, 무한히 스크롤되는 게임에서 이 테크닉을 종종 사용한다. 배경을 스크롤하는 가장 쉬운 방법은 배경을 화면 크기의 이미지 세크면트로 분할 하는 것이다. 이 이미지 세그먼트는 스크롤이라는 환상을 만들기 위해 프레임마다 위치가 변경된다.
BGSpriteComponent.h
BGTexture 구조체는 각 배경 텍스처와 그 텍스처의 오프셋값을 갖고 있다. 오프셋은 스크롤 효과를 위해 텍스처의 위치를 프레임마다 갱신하는 데 사용한다. SetBGTextures에서는 오프셋 값을 초기화하고, 각각의 배경을 이전 배경의 오른쪽에 위치 시킨다. 각 텍스처의 x좌표 오프셋은 화면의 너비 * count이다.
이 코드는 각 배경 이미지의 너비가 화면 너비와 같다고 가정하지만, 가변 크기에도 대응하도록 코드를 수정할 수 있다. Update 코드에서는 한 이미지가 화면에서 완전히 벗어나는 시점을 고려하면서 각 배경의 오프셋을 갱신한다. 이렇게 하면 이미지를 무한히 반복할 수 있다. 마지막 if 문에서 텍스처가 화면을 완전히 벗어나면 오프셋 값을 마지막 배경 텍스처의 오른쪽 위치로 초기화한다.
Draw 함수는 단순히 각 배경 텍스처를 SDL_RenderCopy를 사용해서 그리는데, 배경의 위치는 프레임마다 오프셋을 기준으로 조정되며, 이를 통해 배경의 스크롤이 가능해진다.
일부 게임에서는 시간차 스크롤(parallax scrolling)을 구현한다. 이 방법은 배경에 여러 레이어를 사용한다. 각 레이어는 다른 속도로 스크롤 되며, 이를 통해 배경에 깊이가 있는 것 같은 환상을 준다. 시간차 효과를 구현하고자 여러개의 BGSpriteComponent를 다른 그리기 순서값들로 지정해서 하나의 액터에 연결한다. 그런 다음 각 배경에 대해 다양한 스크롤 속도를 사용하면 시간차 스크롤 효과 구현이 완료된다.
시간차 스크롤 배경과 함께 우주선을 그리는 것을 구현해보자. 코드는 하이브리드 액터/컴포넌트 모델, SpriteComponent, AnimSpriteComponent, 시간차 스크롤의 구현을 포함한다. 그리고 우주선 오브젝트인 Ship이라는 Actor 서브클래스를 포함한다. Ship 클래스는 왼쪽/오른쪽 속도와 위쪽/아래쪽 속도를 제어하고자 2개의 속도 변수를 포함한다.
Ship.h
Ship 생성자는 mRightSpeed와 mDownSpeed를 0으로 초기화하고 우주선 텍스처를 가진 AnimSpriteComponent를 생성한다.
키보드 입력은 우주선의 속도에 직접적인 영향을 미친다. 게임은 우주선을 위아래로 움직이기 위해 W와 S를 사용하며 좌우로 움직이기 위해 A와 D키를 사용한다. ProcessKeyBoard 함수는 이 키보드 입력을 받아와서 적절히 mRightSpeed와 mDownSpeed를 갱신한다.
그리고 Game::ProcessInput 함수에서 mShip->ProcessKeyBoard(state)로 호출해서 키보드 값을 입력받는다. (state는 const Uint8* state = SDL_GeyKeyboardState(NULL);)
Ship::UpdateActor 함수는 퐁 게임과 비슷하게 우주선의 이동을 구현한다.
이동은 게임에서 일반적인 특성이기에 UpdateActor 함수에서 구현하기보다는 컴포넌트로써 이동을 구현하는 것이 좋다.
배경은 Game::LoadData 함수에서 일반적인 액터(서브클래스가 아니다)로 구현한다.
캐릭터의 걷기와 점프, 펀치를 키보드 입력으로 캐릭터의 행동을 제어한다. Actor 와 AnimSpriteComponent 컴포넌트를 참조하는 Skeleton 서브 클래스를 만든다. 그리고 순환과 비순환, 애니메이션 텍스처를 저장하기 위해 map 컨테이너를 이용해서 캐릭터의 행동을 key 값으로 갖고(walk, jump, punch) 그 행동에 대응해서 텍스처와 순환 여부를 매핑하는 mAnimMap 멤버 변수를 AnimSpriteComponent에 선언한다. 그리고 현재 캐릭터의 상태(행동)을 저장해서 mAnimMap 함수에 전달하기위해 string mCurrAnim 멤버 변수를 선언한다.
그리고 AnimSpriteComponent 생성자에서 mCurrAnim를 STOP으로 선언하고 Update 함수에서 mCurrAnim를 확인해서 캐릭터가 "STOP" 상태가 아닐때 Update를 하도록 Update 함수를 바꿔준다. 그리고 순환 여부를 확인해서 애니메이션을 재시작할지 아니면 멈출지를 판단한다. 그리고 SetAnimTextures 함수에서 텍스처와 순환 여부(isLoop)를 저장한다. 저장은 Skeleton::Skeleton 생성자에서 한다.
Skeleton.h
Skeleton 생성자에서 애니메이션 텍스처를 받아서 SetAnimTexture 함수를 이용해 캐릭터의 행동과 텍스처를 매핑한다.
그리고 Ship 서브클래스처럼 키보드로부터 입력받아 캐릭터의 행동을 바꿔준다.
해당 연습 문제는 CSV파일을 읽어서 벡터에 저장하는 것이 제일 힘들었다. 그것은 따로 정리해서 글을 작성하도록 할 것이다. 먼저 SpriteComponent를 상속받는 TileMapComponent라는 새 컴포넌트를 만들었다.
TileMapComponent.h
TileMapComponent 서브클래스는 CSV파일로부터 맵의 정보를 받아 저장하는 벡터와 각 타일의 너비와 높이 값, 그리고 맵의 너비가 갖는 개수와 높이가 갖는 개수가 멤버 변수로 되어있다. 그리고 Draw 함수를 오버라이드한다.
먼저 SetMap 함수에서는 CSV파일로부터 숫자들을 받아서 벡터에 저장하는 것을 구현했다.
그리고 Draw 함수는 벡터에서 숫자를 받아서 그에 맞는 타일을 Tiles.png 파일에서 가져와서 그리는 역할을 한다. 여기서 SDL_RenderCopyEx함수를 사용하는데 srcrect값과 dstrect값에 변화를 줘서 그려야 될 타일과 그려야될 부분을 정해준다.
그리고 Game::LoadData 함수에서 CSV파일과 Tiles.png파일을 받아서 저장하는 것을 구현한다.
안녕하세요 GameProgramming in c++ 책을 공부중인데 시간차 스크롤(parallax scrolling)부분 코드가 잘이해가 안되는데 조금만 설명해주실수있나요 ?
배경 그림을 offset을이용해서 움직여주고 끝에도달했을때 처음으로 옮겨서 무한으로 옮긴다는 아이디어는 이해가가는데 코드를 보면 막상 이해가 잘안가서요 ㅠ. 디버깅을 계속해봐도 이해가 잘안가네요