안녕하세요 이영훈입니다
이번 시간에는 적 생성과 AI 제어를 위한 FSM을 제작해 볼텐데요
이번 단원에서는 적 액터를 생성하고
적의 AI 제어를 위한 유한상태기계
즉 FSM 기반으로 각 상태를 구분하고
상태별로 기능을 구현하는 방법에 대해 학습하겠습니다
먼저 적 액터를 생성하고 외관을 적용해 보겠습니다
적 액터 생성 및 FSM 기반의 상태 구분
먼저 적 클래스를 만들어 보도록 할게요
Tools에서 C 클래스를 만들고 캐릭터로 만들겠습니다
Next라고 하시고 Enemy라고 할게요
Enemy에는 먼저 외관이나 충돌체 이런 것들을 지정을 해 줘야 되는데
원래 캐릭터니까 기본적으로
다 갖추고 있겠죠
그래서 Mesh 부분만 우리가 설정을 해 주도록 하겠습니다
들어와서 여기 캐릭터, 플레이어를 만들었을 때 어떻게 했는지 기억을 해보시면
플레이어 보면
Capsule, Mesh 그리고 Movement Comp, Arrow
이렇게 네가지 컴포넌트가 미리 배치가 되어 있었어요
거기에서 Mesh 컴포넌트에 이렇게 SKM Quinn을 넣어 줬는데
똑같이 Quinn을 사용해 보도록 하겠습니다
이거 복사를 하고요
여기서 복사를 하셔도 돼요 복사하시고
다시 클래스로 옵니다
여기다가 문자열을 이렇게 해서 이 주소로 로드를 할 건데요
그러면 여기에서는 메시 에셋을 로드해서
메시에 적용하고 싶다
이렇게 해 보도록 할게요
이걸 하기 위해서 ConstructHelpers라는 것을 사용했죠 이렇게 하고
Fobject finder를 사용합니다
그리고 여기다가 꺾쇠 이렇게 넣어 주시고요
이름은 TempMesh라고 하겠습니다
여기다가 Text라고 작성을 하고요
사실 여기 적은 내용을 여기다가 넣어 주면 되겠죠
이렇게 해서 이렇게 로드를 할 수 있겠죠
그리고 여기에 들어가는 것은 실제로 우리가 로드할 정보인
SkeletalMesh를 이렇게 작성해 주시면 되고요
U를 붙여야 됩니다
로드를 했는지 성공했는지 확인을 하겠습니다
만약에 TempMesh가 Success 성공을 했다면
이것을 우리 GetMesh에다가
반영을 해 주면 되겠죠
이렇게 해서 SetSkeletalMesh해서
TempMesh에 Object 이렇게 넣어 주시면 됩니다
그리고 이 녀석의 위치를 이제 보정을 해 줄 건데요
기본적으로 오른쪽을 바라보고 중앙 하단이 이 녀석의 중점이기 때문에
밑으로 내려주고 그 다음에 회전도 시켜줬습니다
이거는 동일한 일을 할게요
GetMesh를 가져오고 여기에서 SetRelativeLocationAndRotation 함수 죠
이 값을 세우겠습니다 Fvector
그 다음에 FRotator
얘는 0, 0, 90이고요
-90이고요
그리고 Rotator는 보시면은
Pitch 이 롤 순서죠
이것은 Pitch가 y라고 했어요 그리고 U가 z니까
y, z, x 순서입니다
그러니까 0, -90, 0 이렇게 넣어 줘야겠죠
이렇게 설정을 해 주도록 할게요
자 그리고 언리얼로 와서 컴파일 한번 해 주고요
그 다음에 이제 Enemy를 블루프린트로 만들어서 확인해 보겠습니다
이렇게 하고
Enemy를 마우스 오른쪽 눌러서 Create Blueprint 해 주겠습니다
블루프린트 폴더에 가서 BP_Enemy라고 하고요
그러면 이렇게 잘 설정이 된 걸 볼 수가 있죠
일단 컴파일을 해 줍니다
그리고 Enemy를 레벨에 배치를 해 볼게dy
Enemy는 플레이어를 바라보도록 180도 회전해 주겠습니다
`이렇게 배치를 해 보고요
실행을 해 볼까요?
이렇게 해서 가까이 가면 이런 식으로 되고요
총을 쏴 볼게요 그러면은 총dp 맞지 않습니다
뚫고 나가죠
그리고 카메라도 좀 이상해요 이렇게 돌려 보면은
이렇게 됩니다 갑자기 훅 이렇게 오죠
이건 왜 그러냐면
이런 개념이에요
두 가지인데 일단 총이 안 맞는 것은
우리가 총을 쏠 때 Visibility라는 형태로 라인 트레이스를 쐈어요
그래서 그 Trace channel response죠
Trace response 속성을 한번 봐야 될 것 같고요
그 다음에 두 번째는 카메라가 이렇게 회전을 한 다음에
탁 갑자기 가까이 오게 되는데
그것은 플레이어가 이렇게 있고 플레이어 카메라가 이렇게 있죠
그러면 Enemy가 이렇게 오게 된 거에요
돌리다 보면 그러면은 이 카메라가
이렇게 이동을 하게 돼요 이렇게 가까이 이렇게
이것도 Trace channel과 상관이 있습니다
그래서 두 가지를 설정을 해 주도록 할 거에요
그러면 Enemy를 더블 클릭해서 연 다음에
Enemy에 보면 Capsule Component를 보시면 여기 컬리션 설정 있죠
여기 보면은 여기 Trace response 부분에 보면
Visibility는 Ignore 되어있고
카메라는 Block이 되어있어요 이것 때문에 그렇거든요
이거를 다르게 처리를 하겠습니다
그래서 Custom이라고 하시고요
그 다음에 Visibility는 Block
그 다음에 카메라는 Ignore 반대로 해 줄게요
그리고 Mesh에도 충돌체가 있기 때문에
여기에 있는 것도 같이 처리를 하면 좋을 것 같은데요
이렇게 열어서 일단은 Custom으로 해서
일단 둘 다 Ignore로 해놓겠습니다
사실 Mesh 같은 경우에는 이거를 NoCollision 해도 상관없을 것 같아요
이렇게 컴파일 하고요 다시 한번 실행해 보겠습니다
그리고 총을 쏘면은 Enemy가 총에 맞죠
그리고 카메라로 이렇게 회전해 봐도 걸리지 않아요
이렇게 지정을 해 줄게요
이렇게 Enemy를 배치를 하고요
그 다음에 Enemy에게 상태라는 개념을 만들어서
이렇게 처리를 할까 하는데요
그러면 먼저 설명을 하겠습니다
상태머신 보통 FSM이라고 하죠
Finite State Machine이라고 해요
이렇게 작성을 할 수가 있고요
앞에 인 이렇게 붙으면 인피니티죠
근데 인이 없으면은 유한이 됩니다
그래서 이거를 뭐 그대로 번역한다면 유한 상태 기계
혹은 머신 이런 식으로 번역을 해요
줄여서 상태머신 이렇게 부르기도 합니다
그래서 이걸 줄여서 FSM이라고 하는데요
언리얼에서는 보통 애니메이션이나 이런 제어를 할때는
FSM 기반으로 제어를 하고요
그래서 우리도 일단 AI 를 만들어 볼 건데
FSM 기반의 AI 를 만들겠습니다
그러면 이렇게 이제 상태머신을 만들 때는 3가지 어떤 요소가 있어요
3요소가 있는데 하나는 이제 상태입니다
이건 영어로 State라고 하죠
그리고 조건이 있어요
그리고 전이라는 개념이 있습니다
전이는 그냥 옮겨 가는 거에요
그러면 조건은 뭐 Condition 이렇게 부르고요
전이는 Transition이라고 합니다
영어로도 이렇게 기억하고 계시면 좋을 것 같고요
그럼 이거를 이제 만들어 보도록 할게요
상태머신을 이용해서 다이어그램 형태로 만들 건데
기본적으로 Enemy의 AI는요 기본적으로 대기가 있고요
이동 그 다음에 공격을 할 수 있어요
그리고 피격을 받을 수 있고요 뭐 데미지죠
그리고 이제 죽을 수가 있습니다
이렇게 다섯가지 상태를 만들 거고요
영어로는 이건 Idle이라고 할 거고요
이동은 Move 공격은 Attack
피격은 Damage라고 할게요
죽음은 Die 이런 용어를 좀 써보도록 하겠습니다
그러면 상태를 그릴 때는 네모 박스를 이용하는데요
이렇게 이게 상태입니다
처음엔 대기 상태겠죠?
그리고 대기에서 이제 전이할 수 있는 것은 이동이에요
그래서 이동 상태가 있고 이렇게 이동을 합니다 이렇게 전이를 합니다
그러면 어떤 조건이 있어요
어떤 조건이냐면 대기에서는 주인공을 찾을 겁니다
주인공을 찾는 조건 그래서 만약 주인공이 있다면
그 다음에 이동 상태로 전이하겠다 뭐 이런 식으로 이제 구현할 거고요
이동한 다음에 공격을 할 수 있는 거리가 있겠죠
가까워지면 공격을 할 수 있으니까요
그러면은 이제 공격 상태로 전이를 하는 거죠
그리고 공격하다가 만약에 이제 공격을 하려고 했더니
공격거리에 있지 않아요
플레이어가 도망을 갔어요
그러면 다시 쫓아가야겠죠
그래서 여기서는 다시 이동으로도 전이를 할 수가 있습니다
이러한 형태로 이제 기본적인 움직임을 가질 거고요
그 다음에 이제 피격이나 죽음 같은 경우에는
외부적인 요인이죠 이것은 외부
여기에서 우리가 만든 예제에서는 주인공이 총을 쏴요
그럼 총을 쏴서 플레이어가 Enemy에게 데미지를 입히는 거죠
그러면은 이제 데미지 상태가 되고
그러면 체력이라는 개념이 있으면 좋겠죠
체력을 이제 체력이 다 달지 않았으면 피격 상태로 가는 거고
만약에 체력이 0이 되었어요
그럼 죽음 상태로 가서 처리를 한다던지
그런 식의 흐름을 가져갑니다
그러니까 그러면은 뭐 Any State가 있다면
어떤 상태에서든 왜냐면은 피격과 죽음은 대기를 할 때도 갈 수도 있고
이동, 공격 마찬가지죠
언제 어느 상태에서도 들어갈 수 있는 거죠 그렇죠?
그래서 여기서 이제 피격을 하고요
그리고 피격 이후에는 죽음 상태가 될 수도 있는 거죠
바로 이렇게 죽을 수도 있고요
이러한 형태로 우리가 구현을 좀 해 보도록 할 겁니다
그래서 이거부터 구현할 건데
저는 이번에는 Enemy에다가 Enemy 클래스를 만들었는데
여기다가 컴포넌트를 하나 붙이겠습니다
Enemy FSM이라는 컴포넌트를 하나 붙일 거에요 만들어야겠죠 이렇게
그래서 컴포넌트 쪽에서 이걸 구현해서 어떻게 그러면은
본체인 캐릭터 클래스와 그 다음에 거기 붙어있는 FSM 간에 어떤
서로 어떻게 정보를 주고 받을 수 있는지 그런 것들도 같이
학습을 해 보도록 하면 좋을 것 같아요
그래서 컴포넌트를 하나 만들게요
Tools에서 여기 내려 보시면은 컴포넌트가 두 개가 있습니다 그렇죠?
액터와 Scene Component 두 가지가 있는데
액터 컴포넌트는 그냥 존재하는 개념이고요
Scene Component는 트랜스폼까지 같이 있는 거에요
그래서 우리는 트랜스폼은 필요 없죠
그래서 액터 컴포넌트를 상속받은 녀석으로 만들겠습니다
Next 하고요 이거는 EnemyFSM 이렇게 작성을 할까요?
EnemyFSM을 만들었는데요 그러면 상태를 만들어야겠죠
상태를 만들 때 가장 편안한 형태는 열거형을 사용하는 거에요
그래서 열거형을 좀 써볼 건데
언리얼에서는 이 열거형을 제공해주고 있습니다
U-Innerm이라고 하고요 이렇게 매크로가 있는데요 이렇게
이걸 이용해서 enum class로 할 겁니다
enum은 뭐 스코프가 있고 없고라는 개념이 있잖아요
그래서 스코프도 enum으로 클래스 형태로 제작을 하겠습니다
그래서 이 Enemy State란 이름으로 작성을 할게요
그리고 이건 블루프린트에서도 뭐 보고 싶다 그럴 때는
블루 프린트 타임이라고 작성해 주시면 되고요
그 다음에 이렇게 작성할 때는 무조건 unit 8, Unsigned이죠
이렇게 이제 구현이 되어야 합니다 이거는 규칙이에요
이렇게 작성을 하시고 보통 이거 만드실 때는 여기만 이름을 바꾸시면 되겠죠
그리고 열거형은 접두어를 E로 시작하게 하시면 됩니다
이렇게 작성을 할게요
그럼 여기에다가 Idle, Move, Attack, Damage, Die 이렇게
다섯가지를 넣을 거고요
뭐 이렇게는 콤마를 해도 되고 안 해도 됩니다 그냥 해줄게요
그리고 여기에 이름을 만약에 블루프린트 상에서
다르게 표현하고 싶다 그럴 때는 UMETA라는 키워드
매크로죠? 매크로를 이용해서 디스플레이 네임을 변경을 할 수 있어요
이건 선택인데 안 해도 상관없습니다
대소문자 바꾸는건 Ctrl Shift U 하면은 대문자로 바뀌어요
단축키들이 있으니까 이렇게 작성하셔도 괜찮습니다
이렇게 해서 일단 작성을 해주고요
그러면 이것이 이제 자료형이 돼요 열거형이니까
이걸로 변수를 만들게요 좀 내려가서
여기다가 public을 치겠습니다
이렇게 하시고 State라고 할게요
해당 State를 가지고 이제 분기를 할 거거든요
그래서 어떻게 할 거냐면 여기 소스로 와 보시면
Tick이 있습니다 여기도 컴포넌트에도 Tick이 있어요
그럼 여기에서 작성을 해주는데 스위치 케이스문을 이용해 보겠습니다
스위치라고 하고 탭 누르신 다음에
그럼 이렇게 자동으로 이제 인텔센스 기능이 있어요
자동 완성 기능이 여기다가
State라고 적으시고 탭을 한번 더 누르고
그다음에 엔터 이렇게 하시면 자동으로 쭉 나옵니다
일단 이거는 디폴트는 필요 없고요
여기다가 작성을 해줄건데 여기다가 Idle이라고 할 건데
저는 여기가 이제 Tick이잖아요
그래서 Tick Idle이라는 이름으로 함수를 이렇게 구현을 해줄게요
이거는 어떻게 된 거냐면 이제 Tick은 매번 들어오는 곳이죠
그러면 현재 상태가 어떤 거냐에 따라서
그 일만 하겠다 뭐 이런 식으로 이제 기계적인 제어를 하는 거죠
이거를 그림으로 표현한다면 뭐 여기다가 적으면 이렇게 되는 거죠
이렇게 신호가 이렇게 들어오는데요
이 State가 무엇이냐에 따라서 이렇게 해서
분기가 이렇게 되는 거예요 신호가
다섯 가지니까 이렇게 이건 Idle 뭐 이런 식으로 이렇게
신호가 처음에 이제 State가 Idle이라면
그럼 Idle 쪽으로 신호가 이렇게 가는 거고요
만약에 State 값을 다른 걸로 바꿨어요 Move로 바꿨어요
이거를 Move로 변경을 했다면 그러면 신호가 Move 쪽으로 흘러가는 거죠
이런 신호를 만들 수가 있는데 이게 이제
유한 상태 기계라는 거에요
그래서 각 상태마다 이렇게 함수를 만들어서
제어를 해주면 좋을 것 같습니다
여기다 그대로 작성을 해도 되지만 그러면 여기가 너무 길어지니까요
함수를 이렇게 만들어서
제어를 해보도록 하겠습니다
FSM 기반의 상태별 기능 구현
이렇게 만들었으니까요 이거 전부 다 구현해주도록 하겠습니다
헤더로 가서 여기다가 구현부를 만들게요 선언부를 만들게요
이렇게 작성하시고
이렇게 다 만들었습니다
구현도 해줘야 겠죠
이렇게 작성을 하고요
그러면 이제 이걸 이용해서 처리를 하면 됩니다
먼저 이렇게 하면은 밑에 이제 함수가 다 만들어져 있을 겁니다
그러면은 여기다가 하나씩 이제 구현해주면 돼요
유한 상태 머신의 장점은 이렇게 기능별로 뜯어 가지고
따로따로 이제 각각 구현을 할 수가 있고요
그것을 이제 흐름을 제어 흐름을 통해서
그 일을 했다가 저 일을 했다가 이렇게
신호로 바꿔주면서 처리를 할 수 있다는 거죠
근데 단점은 이게 복잡해질 수 있고
그리고 가속성이 좀 좋지 않죠
어떻게 흐름을 이어가고 있는지 한눈에 보이지 않는게
문제가 있는 부분인데 하지만 이게 가장 기본이 되고요
하나씩 제어를 해 볼게요
Idle부터 갑니다
Idle에서는 이렇게 할 거에요
일단 시간이 흐르다가요
만약에 1초가 지났어요
그러면 주인공을 찾을 겁니다
주인공을 찾고 싶다
그래서 만약에 찾았다면
Move 상태로 전이 하고 싶다
이런 일을 할 거고요
일단 한번 다 적어 볼까요?
Move에서는요 이동을 해야겠죠
목적지를
향해서 이동하고 싶다
그리고 목적지와의 거리를 구할 거에요
계속 이동을 하다가 끝나면 안되겠죠
가까이 가면 멈춰야 되니까요
목적지와의 거리를 구하고
만약에 그 거리가 공격 가능 거리라면
근데 이렇게 쓰면 조금 구현하기가 좀 그렇겠죠
공격 가능 거리보다 이하라면
그러면 공격 상태로 전이 하고 싶다
이런 식으로 구현하면 될 것 같고요
그러면 크게 보면 이렇게 두 가지로 나누는 거죠 기능이
이동하는 기능과 그 다음에 조건을 체크해서 전이를 하는
그러한 형태로 이렇게 나눠집니다
여기도 마찬가지죠
시간이 흐르는 게 1초가 지낸 것까지가 기본적인 구현이고
그 다음에 주인공을 찾아서 전이를 하는 거죠
Tick Attack도 해볼까요?
여기는 나중에는 좀 많이 바꾸겠지만 기본적으로
시간이 흘러서 공격을 한다 이렇게 처리해 볼게요
시간이 흐르다가
만약 공격 대기 시간이 되면
그렇다는 것은 현재 시간이 공격 대기 시간보다 커지면
이렇게 할게요 현재 시간이
그러면 공격을 할 겁니다
공격은 간단하게 로그를 출력해 줄게요
Attack 이런 식으로
그리고 현재 시간을 초기화 해야겠죠
이게 이제 하나의 일이고요
두 번째는 공격에서는 공격을 하고 나서
목적지와의 거리를 또 구할 거에요
그래서 목적지와의 거리를 구하고
만약에 그 거리가 공격 범위를 벗어나면
반대죠 공격 가능 거리
이걸로 그대로 쓴다면 거리가
공격 거리 보다 뭐 크다 이렇게 하면 되겠죠
그러면은 이동할 겁니다
상태로 전이하고 싶다 이러한 일을 할 거고요
일단 시간이 흘러야 되겠죠
그리고 보면은 공격 대기 시간, 공격 가능 거리 이런 것들이 있습니다
그래서 이거를 먼저 구현을 해 보도록 할 건데요
그리고 여기 보면은 주인공을 찾아야 되겠죠
찾았다가 기억하고 있어야 되겠죠
그래야지 목적지를
그 사용을 할 수 있는 거니까요
사실 여기다가 주인공이라고 썼지만 이것은 이제 목적지입니다 목적지
이렇게 쓸게요
그래서 헤더에다가 저 관련된 변수들을 좀 만들어 주도록 하겠습니다
먼저 new property로
class ATPSPlayer
이게 타겟이에요
그리고 이동을 할 건데 사실 이동하기 위해서는 본체가 필요해요
이것은 컴포넌트니까 컴포넌트는 바로 이동을 할 수 없거든요
이동하는 것은 컴포넌트에 있는 것이 아니라
자기를 소유하고 있는 소유자의 Movement 컴포넌트 쪽에 있잖아요
이동하는 기능이 그래서 자기 소유자를 기억해야 됩니다
그래서 그거를 하기 위해서 new property라고 하고요
이 컴포넌트의 소유자는 Enemy죠
그래서 class AEnemy라고 하고 이건 어차피
나 자신이니까 Me라고 하겠습니다
대문자로 사용할게요
이렇게 해주고 아까 시간도 있었죠 float column time
float Attack Range라고 할게요
공격 가능 거리 이거는 한 150cm, 1.5m 정도 주고요
그 다음에 처음에 1초를 기다려야 되죠
이건 Find time이라고 하겠습니다
이건 1초 그리고 이런 것들은 나중에
기획자가 수정할 수 있는 내용이죠 Find time 이런 것들은
이렇게 해서 기획자가 이걸로 변경할 수 있도록
Find time 말고도 또 있죠
Attack Range 이것도 사실 기획자가 바꿀 수 있는 내용인 것 같아요
그리고 Find time 말고
공격 대기 시간은 우리가 만들었나요
공격 대기 시간 있죠
Delay Time 이런 거 만들어야 될 것 같습니다
여기서 Attack Delay Time
이거는 일단 1초로 해주겠습니다 이런 식으로
뭔가 밸런스에 관련된 정보들은 이렇게 이 property 붙여가지고
기획자가 건드릴 수 있도록 이렇게 해주면 좋을 것 같습니다
여기는 0 이라고 안 썼지만 기본적으로 0으로 초기화가 되고요
그럼 이것들을 이제 다 채워 볼게요
이것만 BeginPlay에서 채우고
이것은 이제 Idle 대기 상태에서 채우면 될 것 같습니다
그러면 이쪽으로 가서
BeginPlay에서 일단은 Me를 채워보겠습니다
Me는 소유자 Owner라고 하죠
Owner가 있어요 자기를 소유하고 있는 소유자
이 컴포넌트를 소유하고 있는 것은 Enemy라는 캐릭터죠
그러니까 이거를 캐스팅을 해서 가지고 있으면 됩니다
자 이렇게
이런거 사용할 때는 항상 헤더를 추가를 해주셔야 됩니다
저는 자동으로 일단 추가를 할게요
이런 식으로 추가를 해주십시오
이거는 기본이니까요 항상 이런거 하실 때는
클래스를 이렇게 새롭게 사용하실 때는
구현을 하시는 부분에서 전부 다 헤더로 추가해 주셔야 됩니다
이렇게 해서 캐스팅을 하고요
Me를 채웠고 그러면은 다시 Idle로 가서
이 기능을 구현해 보도록 할게요
일단 시간은 흘러야 되겠죠 시간은 CurrentTime
여기다가 = GetWorld에서 델타 세컨드
이렇게 가져와도 되고요
아니면 그냥 델타 세컨드 변수를 사용하셔도 돼요
이렇게 보면 여기 있죠 이렇게 하셔도 됩니다
가져오고요 그 다음에 1초가 지났다면
1초가 아니라 Find time이죠
Find time이 보다 커졌다면 이렇게 하면 되겠죠
If CurrentTime이 Find time보다 크다면
그러면 이제 목적지를 찾아볼거에요
그리고 이거는 주기니까 0으로 일단 초기화를 해주고요
그러면 찾아보도록 할게요
여기 보면은 UGameplaystatics를 이용하면 편하겠죠
UGameplaystatics에 보면
여기 보면 GetActorOfClass가 있습니다
이걸 이용하면 되겠죠 GetWorld
주인공이니까 Player Pawn을 찾아도 되고요
Player Character 찾아도 되고 이렇게 찾아도 돼요
이렇게 해서 주인공은 ATPSPlayer 이렇게 되죠
여기에서 UClass를 넣으면 되니까요
StaticClass를 이렇게 넣어주시면 되겠습니다
이것도 사용하려면 이렇게 헤더를 추가를 해줘야 되고요
이 녀석도 헤더를 추가하겠습니다
헤더 추가는 제가 계속적으로 말씀드렸던 부분이니까
이렇게 지금 이렇게 추가했습니다
이렇게 해주시면 되겠습니다
그러면 찾았잖아요 그럼 이거를 이제 담아야겠죠
우린 타겟이라는 변수를 만들었으니까요
Target 이렇게 받으면 되는데
보시면 GetActorOfClass는요 반환하는 것이 액터의 포인터에요
그러니까 여기서 이렇게 하면 안 되고 캐스팅을 해야겠죠 캐스트
이렇게 담아주면 되겠습니다
이렇게 해서 담았고요
그래서 이 타겟이 null이 아니라면
이렇게 해서 타겟이 null이 아니라면 그러면 State를
Move State로 이렇게 전이를 해주면 됩니다
전이라는 게 다른 게 아니고요
이 값을 바꿔주는 것을 얘기한다고 보시면 돼요
그리고 전이를 할 때는 같이 시간을 사용하고 그러거든요
이동하고 거리 구하고 그리고 공략할 때 시간도 또 쓰고 그렇죠?
여기 있는 CurrentTime을 좀 재활용을 할 겁니다
그래서 일단 시간을 0으로 이렇게 해서 넘겨주도록 할 거에요
근데 이제 매번 이렇게 하면은 시간이 좀 많이 걸리겠죠
작성할 때 이렇게 다 작성을 해야 되니까
그래서 이거를 하는 기능을 하나 만들어 줄게요
이쪽으로 와서
여기다가 void, SetState라는 함수를 하나 만들겠습니다
이거는요 EEnemyState를 newState 이렇게 받을 거고요
이걸 구현을 해줄게요 이렇게
여기 있네요 여기다가 현재 State에다가
이렇게 new State를 넣어주도록 할 거고요
그 다음에 CurrentTime을 0 초기화하는 일까지
같이 한 번에 하도록 하겠습니다
그럼 이 setState를 호출해 주면은 이제 이거는 자동으로 하겠죠
그래서 기존에 했던 내용을 이렇게 변경을 할게요
이렇게 하면 시간 초기화까지 같이 하게 됩니다
그러면 이제 목적지를 향해 이동을 해 볼게요
이 Move로 넘어왔을 겁니다
이동을 해 보겠습니다
그러면 이동하는 거는 이제 방향을 정해서 그 방향으로 가는 거죠
목적지를 알고 있죠 타겟이에요
타겟에 보면
GetActorLocation 이렇게 해서 가져올 수 있고요
여기 빼기 나의 GetActorLocation 이렇게 해서 가져오면 되겠죠
이러면 이제 Direction을 구할 수 있고요
Fvector로 해서 Direction이라고 하겠습니다
그리고 움직일 건데 이건 캐릭터로 구현이 되어있기 때문에
Enemy가 그래서 위에 add Movement input을 이용하면
간단하게 움직일 수 있겠죠
여기다가 Direction을 넣겠습니다
그러면은 실제로 이제 Direction을 넣었는데
여기서는 자동으로 알아서 얘가 이제
크기를 normalizing 해서 사용하거든요
그러니까 여기서는 실제로 여기 안에는 길이가 있겠죠
근데 혹시 모르니까 이거를 normalizing 해주고 싶으면
get safe normal을 이용하면 원본은 그대로 유지하고
이렇게 normalizing한 사본을 반환하는 형태로 됩니다
그래서 이렇게 쓰고요
그러면 이 Direction은 실제로 떨어진 거리를 얘가 가지고 있겠죠
그래서 목적지와의 거리는요
dist 라고 한다면 이건 Direction의 사이즈 입니다
사이즈 함수에 가보시면 이런 식으로 되어있어요
사실 거리를 구하는 공식은 이렇게 각 항목 별로 다 곱하고요
이렇게 다 곱하고 그 다음에 그거를 루트를 씌우면
실제 그 거리를 구할 수가 있거든요
그 다음에 이제 이거를 Attack Range보다 이하라면
이렇게 하죠 dist가 Attack Range보다 이하니까 이렇게 써야겠죠
이렇게 하고 그러면은 공격 상태로 전이하면 되겠죠 setState
이렇게 해서 공격이니까 Attack 이렇게 하면 되겠죠
그리고 이것도 해볼게요
공격을 할 겁니다 시간이 흘러야 겠죠 동일합니다
CurrentTime
GetWorld에서 DeltaTimeSecond 이렇게 할게요
그래서 현재 시간이 CurrentTime이
공격 대기시간 Attack Delay Time 이거보다 커졌어요
그러면은 이제 공격을 할 거고
공격은 그냥 로그를 출력할게요
UE_Log
렉이 걸려가지고 잠깐만 기다려 볼까요?
이렇게 작성을 하시고 그 다음에 여기다가 텍스트로
Attack 이렇게 표현을 해보도록 하겠습니다
그리고 UE_LOG 같은 경우는 뒤에 세미콜론을 굳이 안 붙여도 되긴 합니다
왜냐하면 이 매크로인데 이 매크로그가
끝에 세미콜론이 아마 자동으로 붙을 거예요
그래서 안 붙여도 됩니다
근데 그래도 붙여도 상관없고요
그 다음에 현재 시간은 초기화 하겠습니다
CurrentTime은 0 이렇게 그래야지 주기가 되죠
이렇게 계속해서 반복해서 공격을 할 거 아니에요
이거는 이제 공격하는 일이고요
그 다음에 이거랑 별개로 주인공이 도망갔어요
그러면은 따라가야겠죠
그래서 여기다가 거리를 구할게요
거리 구하는 거는 이렇게 하면 되겠죠
float dist는
여기도 이제 랭스 이렇게 구하고 싶은데
보면 이렇게 이미 여기서 뺐었잖아요
근데 이제 여기는 없죠 해줘야죠
그래서 여기 보면은 좀 다른 걸로 해볼까요 이번에는?
액터에 보면 dist라는 함수가 있습니다
이걸 이용해도 되고 여기 보면은 이제 두 개의 위치를 넣으면 되는데요
이렇게 할 수도 있고요 혹은 내가 혹은 뭐 타겟이
그 녀석과 얼마나 거리가 멀리 있느냐 이걸로 구하는 함수도 있어요
그래서 뭐 Me에서 보면
Me에서 dist2
get dist2 이렇게 distance2 이런 함수가 있습니다
여기다가 이제 타겟을 넣어주시면 돼요
반대로 해도 됩니다
타겟이 Me를 해도 돼요
이렇게 반대로 해도 상관없고요
저는 이렇게 할게요
Me가 내가 타겟과 얼마나 멀리 있느냐고 물어보는 거죠
그러면 값을 반환 해주고요
그래서 이거를 공격 가능 거리랑 비교를 해보겠습니다
dist가 Attack Range 보다 커졌다면
그러면 이제 도망간 거 잖아요
그러면은 따라가야죠 그래서
setState 이렇게 해서 Move 쫓아가겠습니다
그러면 이걸 좀 로그로 출력을 해보고 싶어요
근데 UE_LOG 말고 화면에 이렇게 노출하는 로그로 하고 싶은데요
여기 보면은 이렇게 한번 해볼게요
tick 컴포넌트 쪽에다가 아래쪽에다가 작성을 해볼게요
여기서는 이제 현재 상태를 화면에 로그로 출력하고 싶다
일단 현재 상태를 문자로 바꿔야겠죠 Fstring
strState라고 하고요
이건 Enum인데요
이렇게 열광으로 된 녀석을 State 이거를
문자열을 바꾼 기능이 있습니다
여기 보면은 UEnum이라고 작성하시고요
대소문자 잘 보세요 뒤에는 소문자에요
이렇게 한 다음에 as string
여러분 여기 있죠
get value as string 이런 함수가 있습니다
여기다가 State를 넣으시면 되고요
템플릿으로 되어 있어서 여기다가 꺽쇠해서
템플릿을 이렇게 넣어 주셔도 되고
이걸 생략해도 상관 없습니다
이렇게 해서 State를 이렇게 문자열을 바꾸고요
그러면은 이렇게 돼요 이 문장이 이제 출력이 됩니다
이거를 출력을 해볼게요
Draw Debug String이라는 함수가 있는데
이거는 이제 실제로 문자열을 화면에 탁 찍는
그 특정 위치에다가 탁 찍는 그런 기능입니다
그래서 이거를 사용해 볼 거고요 GetWorld 하시고
그 다음에 두 번째 여기에 이제 위치가 되죠
벡터가 들어가니까요 위에 GetActorLocation 이렇게 받으신 다음에
여기다가 조금 저는 약간 위쪽에다 출력하고 싶어요
머리 정도에다가 그래서 Me의 아니죠
Fvector로 추가를 해줄게요
위쪽 이라는 것은 z 값이죠 0, 0, 0
조금 위에다가
이렇게 50cm 정도 위로 올릴까요? 이렇게
그리고 여기다가 str State를 출력을 합니다
뒤에는 그냥 이렇게 하셔도 돼요
근데 여기 보면은 지속시간이 있거든요 Duration이
그래서 이게 이제 -1이 아니라 0으로 넣어줘야 돼서
여기를 좀 넣겠습니다 그래서 이렇게 넣고
0 넣어도 되죠 이렇게 하고 그 다음에
색깔을 지정해 줘야 되는데 기존이 화이트네요
F-color라고 하고 보면은 여기 색깔이 있습니다
원하시는 색을 주시면 되고요
저는 cyan으로 해볼게요
약간 푸릇푸릇한 색으로 이렇게 하고 그 다음에 여기를
Duration을 0 이렇게 해주시면 되고요
쉐도우 주시려면 true 이렇게 넣어주시면 되겠습니다
뒤에는 이제 폰트 크기를 더 키울거냐 그런건데 1이면은 1배이고요
그 다음에 1. 몇 하면 몇 배 이렇게 되겠죠
저는 그대로 해보겠습니다 이렇게 하고요
컴파일 해서 어떻게 되나 한번 볼까요?
성공했고요 실행을 하면
오지 않네요 다시 볼게요 아 컴포넌트를 붙이지 않았네요
Enemy 컴포넌트 이거 FSM 컴포넌트를요 Enemy에다가 붙여줘야겠죠
여기다가 Enemy의 헤더로 와서요 여기다가
Uproperty EditAnywhere라고 하겠습니다
그 다음에 여기다가
class UEnemyFSM이죠
이렇게 해서 FSM이라고 하겠습니다
이거를 이제 구현을 해줄게요
생성을 해야겠죠 여기서 생성자 제일 아래쪽에다가
FSM 이거 똑같아요
CreateDefaultSubobject
이렇게 하시고 이게 이제 UEnemyFSM이라고 하시고요
이름은 FSM이라고 하겠습니다
사실 영어만 쓸 때는 텍스트를 뭐 그냥 생략해도 상관없는데
그냥 다 적어 줄게요
이렇게 하고 다시 한번 컴파일 할게요
이건 액터 컴포넌트이기 때문에 어테치를 하지 않아도 돼요
할 수도 없고요
이렇게 하고 어 오류가 났네요
아 헤더 추가를 안했네요
이거를 헤더를 이렇게 추가하겠습니다
실행해 보면 적이 이제 오죠
Move 그리고 Attack 상태가 바뀌는게 보이시죠 이렇게
어우 잘 따라옵니다 이렇게 처리를 한 다음에요
이제 타격을 해야겠죠
기본적으로 움직이는 건 다 됐는데
주인공이 이제 적을 타격했을 때 어떤 일이 바로 발생을 해야 될 것 같아요
그래서 일단 EnemyFSM 쪽에다가
데미지를 입을 수 있는 함수를 하나 만들어줄게요
void OnDamageProcess
이렇게 할까요?
Attack Damage라고 써도 되는데 이렇게 한번 적어보겠습니다
이렇게 한 다음에 실제로 이제 데미지 값을 int로 받을게요
Damage 이렇게 구현부를 만들고요
데미지가 되면은 이제 이 상태를 이제 바꿔주면 되겠죠
그러면 데미지 들어왔을 때 바꾸려면 뭔가 있어야 되겠죠
그냥 바로 할 수가 없잖아요
체력이라는 개념이 있어야 체력을 소진한 후에
체력이 0이 되었으면 Die로 가고
0이 안되었으면 데미지로 가는거죠
그래서 체력도 하나 만들어주겠습니다
int HP라고 하고 일단 2라고 할게요
두 번 맞으면 죽는거죠
들어와서 HP에다가 데미지를 빼주고요
만약에 HP가 0보다 커요 그러면은 setState 해서 데미지로 보내고요
그리고 크지 않아요 else에요
그러면 0보다 이하라는 거죠 0 이하라는 거니까 죽어야겠죠
그럼 setState 해서
nm.. 아니 이거는 다시 적을게요
Die 이렇게 보내면 되겠습니다
그럼 이제 보냈으니까 여기를 구현을 해줘야겠죠
데미지는 이렇게 할 거에요
일단 시간이 흐르다가
현재 시간이 만약에 데미지 딜레이 시간이 되면
크면 다시 이동 상태로 전이하고 싶다 이렇게 되야겠죠
죽었을 때는 일단은 저는 죽었을 때
바닥으로 쭉 내려주고 싶어요
바닥으로 아래 방향으로 이동하고 싶다
이걸 구현할 거고요 하나는
두 번째는 시간을 잴 겁니다
그래서 시간이 흐르다가
만약 현재 시간이 죽음 딜레이 시간이 되면
칸트 방식으로 파괴하고 싶다
누구를? 나를 Me를 이렇게 구현을 해보도록 할 거에요
그럼 데미지부터 한번 해볼게요
먼저 구현부터 해볼까요?
여기도 CurrentTime GetWorld의
DeltaTimeSeconds
이렇게 누적을 해주면 시간이 흐르겠죠?
저번에 얘기했지만 이렇게 해서 누적해서 1이 되는 순간이 1초가 지난 거에요
만약에 현재 시간이 그러면 여기 변수가 있어야 겠네요
데미지 딜레이, 죽음 딜레이라는 변수가 있어야 겠네요
헤더로 가서 두 개를 추가를 해주겠습니다
이렇게 해서 2번 복사를 하시고
이거는 데미지 딜레이라고 하고요
이거는 죽음 딜레이, 다이 딜레이 이렇게 작성을 해주도록 하겠습니다
데미지 딜레이는 넉넉하게 2초 정도 주고요
다이 딜레이도 2초 정도 주도록 하겠습니다
어택도 넉넉하게 2초라고 할까요? 좀 빠른 거 같은데
다이 2초라고 하겠습니다
그러면 마저 구현부를 만들어 볼까요?
이렇게 하면 이제 딜레이 시간을 만들어 줬으니까
CurrentTime이 커졌어요
뭐 보다? 데미지 딜레이 타임보다
그러면 이동 상태로 전이를 하겠습니다
이렇게 해서 setState, Move 이렇게 하면 되겠죠
어떻게 보면 이렇게 되면 총에 맞으면
2초 정도 가만히 서 있을 거에요 지금은
나중에 애니메이션을 붙이면은
리액션하는 행위를 하고 이제 다시 돌아오는 그런 개념인데
지금은 그런 애니메이션이 없으니까
이런 식으로 일단 구현을 해보도록 할게요
그 다음에 죽음 상태도 마저 구현을 해볼까요?
아래 방향으로 일단 이동하고 싶습니다
그러면은 우리는 이거는 P=P0 vt 이렇게 이동시키면 되겠죠
먼저 P0를 만들게요 Fvector
P0 이거는 GetActorLocation
어? Me에서 해야 되죠? GetActorLocation 이렇게 가져옵니다
그 다음에 vt를 만들게요
Fvector에 vt는요 일단 아래 방향이에요
Fvector에 보면 다운 벡터가 있습니다
이게 다운 벡터는 이렇게 되있네요
0, 0, -1 이렇게 되어있습니다
그 다음에 여기다가 움직여야겠죠
그래서 한 200정도 줄게요 크기를
이것도 변수를 빼도 되는데 이거는 그냥 이렇게 적어 주겠습니다
너무 변수가 많은 것 같아 가지고
그 다음에 델타 세컨드 곱해야겠죠?
GetWorld에 DeltaTimeSeconds
이렇게 해서 vt를 만들었습니다 그렇죠?
이게 v죠 방향 곱하기 스피드 이게 v고 벨로시티
그 다음에 t는 델타 세컨드를 사용하면 됩니다
이동을 해 볼게요
Me의 SetActorLocation
이렇게 해서 P0 vt 하면은 아래쪽으로 쭉 이동을 하게 될 겁니다
시간을 측정해서 파괴를 할 건데요
먼저 또 시간이 흘러야 됩니다 CurrentTime
GetWorld의 델타 이거 그대로 쓰면 되겠죠?
이렇게 하고요
시간을 측정합니다
CurrentTime이 die Delay Time보다 커졌어요
이렇게 해서 원래 Ctrl K D 하면
자동으로 이제 정렬해 주거든요
이런 기능으로 써줘도 됩니다
이렇게 해서 이렇게 되면은 나를 파괴하면 되겠죠?
Me를 Destroy 하겠다
이렇게 구현을 해 주시면 되겠습니다
그러면 한번 실행해서 결과를 보도록 할게요
컴파일 하고
아 그러고 보니까 총에 맞는 일을 안 했습니다
플레이어로 가서 여기죠
여기 OnIAFire에서 히트를 한 다음에
여기에서 이제 부딪힌 상대방이 적이에요 Enemy에요
AEnemy다 라면
AEnemy에 FSM에
OnDamageProcess
함수를 이렇게 콜을 해 주겠다는 거예요
1점 데미지, 데미지를 1점
적에게 데미지를 1점 데미지죠
데미지를 1점 주고 싶다
이런 일을 할 건데 그거는 결국은 이 일을 하는 겁니다 자 먼저 그러면은
부딪힌 상대방이 Enemy인지 알아야겠죠? 자 이것은 이렇게 할 수 있어요
hit info에 보면 get actor가 있습니다 이게 이제 부딪힌 상대방이에요 상대방의 actor입니다
이거를 캐스팅을 해보면 검증할 수 있겠죠? cast 라고 하고요
AEnemy 이렇게 캐스팅을 해보면 되겠죠
헤더를 이렇게 추가를 해주십시오
헤더는 제가 이렇게 올려서 어떻게 추가하고 있는지 보여드릴께요
이거를 추가했습니다
그럼 auto 해가지고 이거 Enemy라고 이렇게 받을게요
이게 포인터니까 이렇게 적어주면 더 좋겠죠?
auto라고 그냥 써도 되는데
네 auto 그리고 레퍼런스 같은 경우에는 이렇게 작성을 해줘야 되죠
이렇게 쓰겠습니다
그리고 만약에 Enemy가 null이 아니에요
그러면은 실제로 Enemy인거죠
이렇게 캐스팅을 시도해보고
그럼 Enemy에 보면 FSM이 있죠
이런식으로 여기서 바로 접근해서 사용해도 되는데요
사실 이렇게 하는 거는 그렇게 좋은 방법은 아니에요
개체제한 프로그래밍에서는 상대방의 속성에 접근하고
또 거기에서 또 다른 속성에 접근하고 이런식으로 타고 들어가서
뭔가 값을 처리하는 거는 좋은 방법은 아닙니다
그래서 좀 더 나은 방법은 Enemy 헤더에다가 v를 구현하고
그 다음에 여기서는 콜을 해주는 형식으로 하면 좋겠죠
그래서 여기다가 void TakeDamage라는 함수를 이렇게 만들까요?
이렇게 구현을 한번 해주도록 할게요
그래서 트릭 데미지에서는
FSM의 OnDamageProcess에다가 데미지를 전달해 주겠습니다
이렇게 하는 게 이제 좀 더 개체 제한스럽죠
그리고 외부에서는 Enemy에 TakeDamage를 호출해주면 되겠죠
이렇게 구현해 보도록 하겠습니다
근데 이게 TakeDamage를 또 딴 데 쓸 수도 있으니까
이름은 OnMy 이런 식으로 써줄까요
이름을 이런식으로 바꿔서 쓰겠습니다
그러면 플레이어 쪽에서는 Enemy에
OnmytakeDamage를 호출해주면 되겠죠
1점 주겠습니다
전부 다 저장을 해주시고요
컴파일을 해본 다음에 결과를 볼게요
잘 오고요 총에 탁 맞으면 멈춰 있죠
그리고 2초 후에 다시 옵니다
그 다음에 탁 쏘면 죽어야 되는데 바닥으로 내렸는데
파괴는 되는데 못 내려가죠
그건 왜 그러냐면 CapsuleComponent가 바닥에 부딪히고 있어서 그래요 사실
그래서 그것까지 마무리를 해줄게요
이거는 이렇게 하고 Enemy FSM으로 와서
여기 die로 넘어가는 부분은 여기 있죠
OnDamageProcess 여기서 die로 넘어갈 때
Me에 보면은 캡슐이 있습니다 CapsuleComponent
이 녀석의 SetCollisionEnabled를
이렇게 해서 NoCollision이라고 바꿔줄게요
어차피 이제 죽었으니까 이거 쓰지 않을 거니까요
이거 쓰려면은 이 CapsuleComponent를 가지고 와야 돼요 그렇죠?
그럼 이거를 이제 헤더를 추가를 해줘야 됩니다
그래서 여기서는 추가를 해줄게요
이 CapsuleComponent 이렇게 해서 헤더를 이렇게 추가를 해주면
오류가 없어지는데 여러분들은 보셔야겠죠
Components에 CapsuleComponent 이렇게 되어있습니다
이렇게 작성하시면 돼요
그럼 오류가 없어지고요
컴파일 해서 결과를 볼게요
탕 탕 쏘면 쭉 내려가죠
그리고 파괴 이렇게 됩니다
그리고 이거 총알은 삭제를 해놓겠습니다
지금 분리 넥터가 필요없죠
여기까지 입니다
이번 단원에서 학습한 내용을 정리하도록 하겠습니다
적 액터 생성 및 FSM 기반의 상태 구분 적 액터 생성 적 액터의 외관을 적용하는 방법: Quinn을 사용해서 적을 만듦
FSM 기반의 상태 구분 유한 상태 기계의 구성 요소: 상태, 조건, 전이 적 액터의 상태를 처리하기 위한 Tick에서 현재 상태를 분기 처리하는 방법
FSM 기반의 상태별 기능 구현 유한 상태 머신 장점: 기능별로 구분하여 각각 구현할 수 있음 단점: 복잡하고 가속성이 떨어짐
대기, 이동, 공격, 피격, 죽음 상태 구현 각 상태별로 구현부를 나누고 독립적으로 구현함 어떤 조건이 성립했을 때 그에 맞는 전이가 이루어지게 함