*** 공부 방법 ***
1. 코딩을 해야 하는 부분은 첫 부분에 변수나 함수, 메소드에 대한 선언이 코드블럭으로 표시되어 있다. //ex) MakeFunction(); 2. 코드블럭 하단에는 해당 선언에 대한 구현 로직이 작성되어 있다. 처
hyrule.tistory.com
class CAnimation
- 여러 개의 CAnimationInfo를 들고 있으면서 필요할 때마다 모션 전환을 해주는 객체
* 애니메이션 구조
1. CAnimationSequence(그냥 프레임 데이터만 들고있는 클래스)
2. CAnimationInfo(해당 프레임들이 언제 한번 바뀌어야 하는지, 어떤 프레임에서 어떤 작업을 해줄지, 애니메이션 재생이 끝나면 어떤 작업을 해줄지, 등의 데이터를 들고있는 클래스)
3. CAnimation(여러 개의 CAnimationInfo를 들고 있으면서 각 상황에서(ex. 걷기, 뛰기, 점프) 어떤 애니메이션을 재생할지 등을 제어할 수 있는 클래스)
4. CGameObject(최종적으로 CAnimation 클래스를 들고 있으면서 상황에 따라 애니메이션을 바꾸도록 명령)
- CGameObject가 실제로 접근해서 사용할 객체이다. -> 외부에서의 접근을 모두 제한하고 friend처리
- template을 사용하는 메소드에서 CAnimationInfo를 사용하기 때문에, 헤더에서 CAnimationInfo를 포함시켜주어야 한다.
< m_OwnerObj >
- 이 애니메이션을 사용하고 있는 CGameObject의 포인터
< m_Scene >
- 지금 속한 CScene
< m_mapAnimation >
- unordered_map, 이름(문자열)과 CAnimationInfo 포인터를 저장.
-- 모션들을 모아서 미리 저장해놓고 필요한 애니메이션을 꺼내서 재생하기위한 용도
< m_CurrentAnimation >
- CAnimationInfo 포인터, 지금 재생 중인 애니메이션 정보
< FindInfo() >
- 이름을 받아서 m_mapAnimation에서 탐색해주는 기능
- 탐색 결과가 없으면 nullptr을 반환하고, 찾으면 CAnimationInfo 포인터를 반환한다.
* 아래의 메소드들은 모두 외부에서 사용할수 있어야 한다. *
< AddAnimation() >
- 인자
-- AnimInfoName(문자열)
-- Loop(기본값 true)
-- PlayTime(기본값 1.f)
-- PlayScale(기본값 1.f)
-- Reverse(기본값 false)
- 새 애니메이션을 만들고 받은 인자값을 집어넣는다. 동적 할당이므로 소멸자에서 지워줄 것
- 로직
-1. AnimInfoName으로 m_mapAnimation에 등록된 CAnimationInfo가 있는지 확인하고 있으면 return
-2. m_Scene이 등록되어 있으면 m_Scene을 통해 CAnimationSequence를 얻어온다.
-3. 없으면 ResourceManager을 통해 CAnimationSequence를 얻어온다.
-4. 제대로 받아왔는지 확인한 후 제대로 받아왔다면
-5. CAnimationInfo를 동적할당한 뒤 인자로 들어온 값들을 전부 입력해준다.
--- m_FrameTime의 경우 계산을 해서 넣어준다.
-6. 만약 이 CAnimationSequence가 처음 추가되는 애니메이션일 경우 m_CurrentAnimation으로 등록해 준다.
< SetPlayTime() >
< SetPlayScale() >
< SetPlayLoop() >
< SetPlayReverse() >
- 위 메소드들은 모두 이름과 Set 뒤의 변수값을 받아서 설정해주는 메소드이다.
< SetCurrentAnimtion() >
- 시퀀스의 이름을 받아서 해당 시퀀스로 바꿔주는 메소드이다.
- FindInfo() 메소드를 통해 CAnimationInfo를 찾고, 제대로 들어왔는지 확인한다.
- 제대로 들어왔다면 m_CurrentAnimation을 찾은 걸로 바꿔주고,
- 새로 재생되므로 m_Frame과 m_Time도 초기화 해준다.
- Notify 함수들의 Call 변수도 전부 false로 초기화 해준다.
< ChangeAnimation() >
- CAnimationInfo의 이름을 받아서 해당 애니메이션으로 바꿔주는 메소드이다.
- 위 메소드와 비슷한 기능을 수행한다.
-- 기존 애니메이션 초기화해주고, 교체한 뒤 교체한 애니메이션도 초기화 해준다.
< CheckCurrentAnimation() >
- 시퀀스의 이름을 받아서 지금 재생되는 시퀀스가 들어온 인자의 시퀀스와 같은지 여부를 bool로 반환하는 메소드.
< SetEndFunction() >
-- template 사용
-- 인자
--- Name: 문자열
--- obj: T*
--- Func: T의 void 메소드
-- 이름에 해당하는 CAnimationInfo를 찾아서 EndFunction을 등록해주는 메소드
< AddNotify() >
-- template 사용
-- 인자
--- Frame: int 타입, 어떤 프레임에 추가할 것인지
--- obj: T*
--- Func: T의 void 메소드
-- 이름에 해당하는 CAnimationInfo를 찾아서 Notify 함수를 추가해주는 메소드
class CGameObject
- m_Animation: CAnimation 포인터 타입 변수. 기본값은 nullptr(애니메이션을 사용 안할수도 있으므로)
-- 필요한 CGameObject에서는 동적할당해서 사용할 것이므로 소멸자에서 꼼꼼히 처리해줄 것
- CAnimation 관련 메소드에서 template을 사용하므로 헤더에서 CAnimation을 포함시켜주어야 한다.
< CreateAnimation() >
- 애니메이션을 사용할 게임오브젝트에서 동적할당하고,
- CAnimation의 변수 m_OwnerObj를 자신으로, m_Scene을 현재 씬으로 지정해준다.
< 이외의 CAnimation에 있는 메소드들 모두 >
- 해당 메소드들을 CGameObject에서 선언해주고,
- CAnimation에 전달만 해주는 메소드를 정의한다.
- m_Animation이 없을 경우에는 동작하지 않도록 예외 처리를 해준다.
< Update() >
- 만약 m_Animation이 존재하면
- m_Animation의 Update()를 호출하는 코드를 추가한다.
- 앞으로 CGameObject의 상속을 받는 클래스들은
- 반드시 부모 클래스를 하나하나 타고 오면서 CGameObject의 Update() 메소드까지 호출되도록 짜 준다.
class CAnimation
< Update() >
- CGameObject::Update() 메소드를 타고 들어올 것이다.
- 여기서 애니메이션 업데이트 처리를 해준다.
- 처리 순서
1. m_CurrentAnimation 안에 들어온 CAnimationInfo 구조체의 m_Time을 DeltaTime만큼 증가시킨다.
-- 여기에 추가로 CAnimationInfo 구조체의 m_PlayScale 을 곱해준다.
--- 만약 캐릭터가 슬로우에 걸린다던지 이동속도가 감소한다던지 했을 때 m_PlayScale을 그만큼 줄여주면 애니메이션의 재생 속도도 그만큼 느려질 것이다.
2. AnimationEnd: bool 타입, 애니메이션이 끝났는지를 확인할 변수를 코드 블록 안에서 임시로 선언
3. 만약 CCurentAnimation의 m_Time이 m_FrameTime보다 커졌다면 애니메이션의 프레임을 교체할 때가 되었다는 뜻이므로
-- m_Time을 처음부터 다시 재기 시작해주고,
--- 역재생이면 프레임을 하나 감소
---- 만약 프레임을 하나 감소시켰는데 0 미만이면 역재생 애니메이션 재생이 끝났다는 의미.
--- 정재생이면 프레임을 하나 증가 시켜준다.
---- 만약 프레임을 하나 증가시켰는데 최대 프레임 수 이상이면 정재생 애니메이션 재생이 끝났다는 의미
-- 두 상태 모두 재생이 끝났음이 확인되면 AnimationEnd 변수를 true로 바꿔준다.
--- 역재생일 경우 현재 프레임 번호가 0 미만일 때 / 정재생일 경우 현재 프레임 번호(배열의 인덱스 끝)가 최대 프레임과 같을 때
4. 이제 표시될 프레임 처리는 완료되었다. -> 이제 확인해야 할것은 바뀐 프레임에 Notify가 있는지 여부이다.
-- 기존 방식: AnimationNotify 구조체 안에 Frame 정보를 저장 -> 매 프레임 m_vecNotiry를 순회 돌면서 각각 원소들의 Frame이 일치하는지 여부를 확인
-- 새 방식: Notify 함수를 호출하고자 하는 프레임 번호와 m_vecNotify의 인덱스 번호를 일치시킴.
---ex) 3번쨰 애니메이션 프레임이라면 m_vecNotify[3]에 Notify 함수가 있는지 확인
--- 내가 생각하는 이 방식의 장점: Notify가 있는 프레임보다 Notify가 없는 프레임이 더 많을거 같음. 이렇게 해주면 Notify가 없는 프레임들의 처리속도가 더 빨라질 수 있다고 생각함.
frame 6개 notify 3개일 때
프렘 1 2 3 4 5 6(size 6)
노티 1 1 1 (size 3)
기존방식 18(프렘 x 노티)번 + call 초기화 3번(노티)
프렘 1 2 3 4 5 6(size 6)
노티 1 1 1 (size 6)
신규방식 12번(프렘 + 노티) + call 초기화 6번(노티)
프렘100 노티50일때
기존 프렘100 노티50
5000 + 50
신규 프렘100 노티100
200 + 200 = 400
-- 해당 Frame에 Notify가 있는지 확인(vector.empty() 사용) -> 있으면 call되었는지 확인 -> call 안되었으면 Notify 함수를 호출하고 call을 true로 전환
5. 이제 확인할 것은 Animation 재생이 끝났는지 여부
-- 아까 만든 AnimationEnd 변수를 활용
-- 끝났으면 이 애니메이션이 반복인지를 확인
--- 반복일 경우 역재생인지 아닌지를 확인해서 m_Time을 초기화하고
---- 정재생이면 프레임 0번을 세팅, 역재생이면 프레임 (전체 프레임 수 - 1)로 세팅
--- 반복이 아닐 경우 마지막 프레임에서 고정
-- Animation 재생이 끝났으므로 EndFunction이 있다면 호출한다.
6. notify 함수들의 call 여부를 모두 false로 초기화해 준다.
class CGameManager
- 아예 하는 김에 글로벌 TimeScale과 게임오브젝트별 TimeScale을 추가하자
< m_TimeScale >
- float 타입 변수. 생성자 기본값은 1.0f
- 이 값을 CGameManager::Update()때 계산된 DeltaTime에 곱해준다.
- 모든 프레임은 DeltaTime 기준으로 계산되므로, TimeScale값을 조절해주면 게임 자체의 속도가 그만큼 느려질 것이다.
-- 메뉴 등을 열었을 때 일시정지를 해야 한다면, m_TimeScale을 0으로 만들어 주면 된다.
- m_TimeScale 값을 받아오는 메소드와, 설정해주는 메소드 2개도 추가해준다.
class CGameObject
- 여기에도 마찬가지로 TimeScale을 추가시켜준다.
- 생성자에서 1.f로 초기화한다.
- 위와 마찬가지로 Update에서 DeltaTime에 곱해준다.
< Render() >
- 기존 코드를 수정한다.
- CGameObject의 Render() 메소드에서는 만약 m_Texture 주소가 있으면 텍스처 주소를 통해 이미지를 출력했었다.
- 똑같이 m_Animation이 있으면 m_Animation을 출력해준다. m_Animation을 우선적으로 체크하고, 없을 경우 m_Texture를 체크한다.
- 순서
1. m_Animation으로부터 현재 출력되는 CAnimationInfo를 받아온다.
2. 받아온 CAnimationInfo로부터 '현재 프레임'의 AnimationFrameData 구조체를 받아온다(어디부터 어디까지 출력해야할지를 저장해놓은 구조체)
3. AnimationFrameData로부터 텍스처 프레임의 사이즈를 구해 놓는다.
4. 렌더링의 기준점이 될 RenderLeftTop을 구한다.
5. 텍스처 타입이 sprite인지 frame인지 확인한다.
6. 컬러키 사용 여부를 확인한다.
7. 스프라이트 사용/미사용, 컬러키 사용 미사용의 4가지 경우의 수에 따른 출력 함수를 작성해준다.
-- Sprite 사용시에는 애니메이션의 프레임 별로 이미지에서 출력할 부분의 좌표를 바꿔주어야 하고
-- Frame 사용시에는 애니메이션의 프레임 별로 이미지 번호를 바꿔주어야 할 것이다.
- 드디어 애니메이션 구현 과정이 끝났다.
- 이제 이 애니메이션 파일을, 쓰고 싶은 CGameObject의 초기화 과정에서 사용하도록 해주면 된다.
- 잘 작동하는지 확인해보기 위해서, 첫 모션은 프레임 단위로 끊어져 있는 파일, 두번째 모션은 스프라이트 파일로 등록했다.
/////////////////// 애니메이션 생성 ///////////////////
//파일 이름을 넣어둘 배열
std::vector<std::wstring> FileName;
//파일 이름을 만들어낸다.
for (int i = 0; i < 11; ++i)
{
wchar_t Name[64];
swprintf_s(Name, L"Mouse/%d.bmp", i);
FileName.push_back(Name);
}
//애니메이션 파일을 동적 할당
CreateAnimation();
//이름을 정하고 파일 이름이 들어있는 배열을 전달한다.
m_OwnerScene->GetSceneResource()->CreateAnimationSequence("Cursor", "Cursor", FileName);
//프레임 수만큼 반복해서 표시할 위치를 알려준다.
for (int i = 0; i < 11; ++i)
{
m_OwnerScene->GetSceneResource()->AddAnimationFrame("Cursor", 32.f, 0.f, 32.f, 31.f);
}
//컬러키를 지정해준다.(이름만 넣으면 기본값인 true(사용) + 마젠타 색상으로 등록된다.
m_OwnerScene->GetSceneResource()->SetColorKey("Cursor");
//만든 애니메이션을 CGameObject의 m_Animation에 사용 등록한다.
AddAnimation("Cursor", "Cursor");
m_OwnerScene->GetSceneResource()->CreateAnimationSequence("PlayerRun", "PlayerRun", TEXT("Running.bmp"));
float x = 292 / 9;
float y = 41;
for (int i = 0; i < 9; ++i)
{
m_OwnerScene->GetSceneResource()->AddAnimationFrame("PlayerRun", x * i, 0.f, x, y);
}
m_OwnerScene->GetSceneResource()->SetColorKey("PlayerRun");
//애니메이션 설정
AddAnimation("PlayerRun", "PlayerRun");
이렇게 2개의 애니메이션을 등록해놓고,
플레이어가 움직일 때 애니메이션이 바뀌게 만들어 놓았다.
void CPlayer::MoveFront()
{
m_Pos += m_Dir * 400.f * DELTA_TIME;
ChangeAnimation("PlayerRun");
}
void CPlayer::MoveBack()
{
m_Pos -= m_Dir * 400.f * DELTA_TIME;
ChangeAnimation("PlayerRun");
}
물론, 아직 이동을 멈췄을 때 애니메이션이 원래대로 돌아오지 않는다. 이것도 처리해 주어야 한다.
'WIN32API FrameWork > 한단계씩 직접 구현' 카테고리의 다른 글
39. 배경화면 생성 및 플레이어 이동 (0) | 2022.05.28 |
---|---|
38-1. 애니메이션을 등록하는 다양한 방법들 (0) | 2022.05.28 |
37. 애니메이션 시퀀스 1 (0) | 2022.05.25 |
36. 이미지의 투명 처리 (0) | 2022.05.25 |
35. 더블 버퍼링 (0) | 2022.05.25 |