총을 발사하는 방식은 Ray, Projectile 2가지의 방법이 있다고 했고,
RayTracing 방식을 사용하려고 하였다.
이전에 Satellite 프로젝트에서 총구의 끝으로부터 앞으로 발사되는 Ray를 만든적이 있는데,
움직이는 애니메이션에서 총이 같이 흔들리므로 위처럼 앞으로만 쐈는데,
사방팔방으로 흩어져버리게 되었다.
따라서 3인칭 게임의 경우, 거의 대부분 Camera로 부터 RayTracing을 한다고 한다.
1인칭도 사용하는 경우가 있지만 보통 총이 고정되고서 쏘게 된다.
그 역할을 하는 코드이다.
GetPlayerViewPoint를 자세히 볼 필요가 있다.
함수에 함수가 들어가있다.그리고 그 함수는 반환값이 없지만, 매개변수의 값을 수정하는 역할을 한다.
결국 반환값은 없지만 반환을 하는 셈이 된다.
신기한 함수
그래서 총을 쏘고 나면, 위처럼 카메라 모양이 뜨게 된다.
참고로 DrawDebugCamera는 Camera위치를 출력하는 것이 아니라,
그냥 모양이 카메라라는 것이다.
LineTraceTestByObjectType
LineTraceTestByChannel
둘의 차이점을 이해한다.
Object는 특정 객체만. Trace하는 것이고,
Channel은 해당 Channel에 존재하는 객체들만. Trace하기 때문에,
일반적으로 범용적인 Channel이 더 유용하다.
Project Setting을 본다.
ObjectChannel은 말 그대로 객체간의 충돌을 설정하기 위한 용도고,
TraceChannel은 Line,Sphere Trace시에 Trace충돌을 설정하기 위한 용도이다.
[이전 글 참고]
따라서 Trace Channel을 설정한다.
엑터에 설정하고 적용시키려면, DefaultEngine.ini 파일을 참고해야한다.
[이전 글 참고]
설정한 Trace채널이 해당 파일에 보이게 된다.
그래서 몇번 채널인지 찾고 복사해서 붙여야만 한다.
채널을 가져와서 LineTraceByChannel의 인자로 넣는다
Location과 Rotation을 사용하여 End지점을 생성한다.
End지점은, 플레이어 카메라의 위치 + 카메라의 회전 벡터(회전과 방향을 가지고 있음) * 길이
위의 공식을 통해 계산해 둔다.
그리고 카메라의 위치인 location으로부터 End까지 이어지는
LineTrace를 진행한다.
충돌을 판정할 채널을 넣고, 충돌했는지는 DrawDebug로
Hit된 위치를 찍는다.
표시되는 모습
따라서 3인칭 FPS는 Camera의 Location, Rotation을 기반으로 Linetrace를 한다.
얻어오는 방법은 GetPlayerViewPoint
Hit된 위치에 Effect를 추가한다.
이전에 총구 소켓에 SpawnEmitterAttached를 사용해서 Effect를 부착했었는데,
이제는 SpawnEmitterAtLocation을 사용하여,
특정 위치에 소환할 수 있게 된다.
쏘는 이펙트와 타격 이펙트이다.
타격을 구현하기 위해서 사용할 함수이다.
TakeDamage는 Actor의 멤버함수이다.
4개의 변수가 필요하다.
Damage의 양/DamageEvent/가해자 컨트롤러/Damage를 가한 액터(총이나 총알)
LineTrace의 결과물인 Hit을 바탕으로, 누가 Hit되었는지를 가져온다.
그리고 HitActor의 TakeDamage를 통해, 피해를 가한다.
TakeDamage에 들어가는 변수로
해당 구조체의 생성자는 4개의 인자가 들어갈 수도 있다.
= 안들어가도 된다
위처럼 그냥 써도 동작한다.
4가지 인자는 각각
그래서 InShootDirection을 바탕으로 물리작용을 하여 적이 반대로 밀린다던지
그런 효과도 줄 수 있다고 한다.
DamageType을 설정하여 불이나 독, 폭발 이런 Damage 종류를 설정할 수도 있다.
공격할 때마다 로그가 뜨는 모습.
구현이 신기한게,
이전 Untitled 프로젝트에서는 TakeDamage를 Character 내부에 두었다.지금은 총 자체에서 발생한 Linetrace가 닿았다면,
닿은 객체에게 TakeDamage를 발생시키게 된다.이게 논리적으로 더 맞다고 본다.
[ + ]
https://community.gamedev.tv/t/include-engine-damageevents-h-for-fpointdamageevent/215157해당 내용을 진행하는데 대략 3시간이 소요되었다.
왜냐면 원인을 알 수 없는 오류가 발생했기 때문이다.
#include "Engine/EngineTypes.h" 를 추가하라는 공식 문서의 방법도 통하지 않았다.
virtual float TakeDamage
(float DamageAmount,
FDamageEvent const& DamageEvent,
AController* EventInstigator,
AActor* DamageCauser)
override;
AActor에 정의된 TakeDamage를 Character에 정의하였다.
의문이드는건 AActor에 TakeDamage가 virtual함수가 아님에도, override를 하였다.
그냥 명시적인 용도인가 / 다른 함수들도 다 이렇게 한다.
가상함수라는데 virtual이 다 붙어있지는 않다.. 왜?
그리고 해당 Character를 상속받은 클래스들이 본인만의 TakeDamage를 구현하도록 virtual을 붙인다.
위에서 HitActor를 ShooterCharacter로 수정하고,
ShooterCharacter에서 구현한 TakeDamage를 실행하도록 한다.
DamageToApply = FMath::Min(Health, DamageToApply);
데미지는 위처럼 설정하였다.
받는 데미지가 Health보다 크다면, Health값만큼 닳도록 하고,
만약 Health보다 데미지가 작다면, 그대로 Damage를 닳도록 한다.
따라서 Health가 0인 경우, 0의 데미지가 닳게 된다.
Untitled 프로젝트의 방식과 SimpleShooter의 방식은 거의 같다.
기본 이동 외에 다른 Animation을 추가한다.
상당히 반갑다. 절실히 배우고 싶은 부분이기 때문이다.
어떻게 캐릭터가 많은 조건이 얽힌 다수의 Animation을 관리할까.
사실 이게 강의를 듣게 된 가장 큰 이유였다.
총의 발사는 그 부분만 수정하면 되는데,
Animation은 서로 얽혀서 수정에 애를 먹을 것 같았기 때문이다.
배워보자.
위처럼 작성한다.
그냥 기존의 이동그래프에 BlendPosesByBool을 사용하여
bool값에 따른 애니메이션의 전환을 한다.
음...
기존에는 StateMachine을 사용하여 상태전환이나 더 복잡한 구현을 했었는데,
이동도 간단히 구현하고, 죽는 모션도 간단히 구현이 되었다..
아직 Jump모션이 없어서 그런가..
isDead라는 변수를 초기화해줘야하는데,
기존의 애니메이션그래프에선 플레이어의 speed와 angle값을
노드를 통해 가져올 수 있었다만,
현재 플레이어가 죽었는지를 확인하는 노드는 없다.
따라서 CPP 내부에 Blueprint Callable함수를 만들고,
해당 함수를 호출하여 확인하는 방법을 사용한다.
정확하게는 BlueprintPure 예약어를 사용한다.둘의 공통점은 Blueprint에서 호출할 수 있는 것이고,
둘의 차이는▷실행핀
이 존재하는가에 있다.
Pure는 실행핀이 없다.
보통 const함수와 같이 사용한다.
현재 IsDead는 죽었는지 상태만 확인하고,
bool값을 리턴하는 것 뿐이다.
즉, 어느 변수를 수정하지 않고, 단순히 값만 내뱉는,
실행시 변수값이 수정되지 않는 함수이기 때문에 const를 붙였고,
const라면 BlueprintPure로 설정하는 편이 깔끔하다.
굳이 실행과정 순서에 얽매일 필요가 없이,
항상 같은 기능, 같은 값을 내기 때문이다.
ShooterCharacter 클래스에 정의한 함수이므로
Cast후에, 위처럼 Pure를 사용하여 bool값을 가져온다.
정상적으로 적용된다.
참고
IsDead의 기준은 float형이라서 == 0 대신에
범위 연산자를 사용하여 지정했다.