UE5FPS(End) - Enemy / UI

JUSTICE_DER·2023년 3월 8일
0

🌵UNREAL

목록 보기
26/42
post-thumbnail

Unreal Engine & C++ Tutorial - 1st Person Shooter Game
https://www.youtube.com/watch?v=4HoJIgyclZ4


Enemy Animation

  • Enemy는 시작하면 바로 Idle/Walk를 실행한다.

  • Idle/Walk는, Speed값에 따라 Idle과 Running 사이의 모션을 취하도록 만든, Idle_Running애니메이션을 포함한다.

  • 하지만, Speed의 기본값은 0인데, 어디에서도 해당 Speed값을 바꿔주는 곳이 없었고, 이제 추가한다.

  • 위쪽 덩어리부터 보면,
    Initialize문을 먼저 추가해서, 해당 Animation이 처음 실행될 때,
    본인 Pawn Character를 BP_FPSCharacter로 캐스트할 수 있다면,
    캐스트해서 refFPSEnemy로 둔다는 것이다.

  • 아래쪽 덩어리는 Animation이 업데이트 될 때마다 실행되고,
    speed의 값을 계속해서 SET하는데, 그 speed의 값은 refFPSEnemy의 CurrentVelocity의 Length이다.

이게의미는 없다고 생각하는게

CurrentVelocity = dir.GetSafeNormal() * MovementSpeed;

CurrentVelocity는 0아니면, 위의 MovementSpeed 고정값인 2가지 경우만 존재한다.
그러므로 Speed도 2가지 애니메이션밖에 보여주지 못할 것이다.

달리는 모션을 취하면서 오게 된다.


Dealing Enemy

  • 헤더에 아래의 코드들을 추가한다.
    캐릭터의 HP와, 피격함수이다.

  • 피격함수의 구현

  • Enemy의 헤더에는 이전에 미리 추가해둔 코드를 활용한다.

  • 똑같이 아래처럼 만든다.

  • OnHit함수를 작성하기 전에,
    아래처럼, 충돌시 함수를 호출하게 된다는 것을 보고간다.
    의문점은, 호출을 하는건 알겠는데, 매개변수들을 어떻게 채워서 호출하는가이다.

//DamageCollision이 다른 컴포넌트와 겹치게 된다면(충돌한다면) OnHit실행
DamageCollision->OnComponentBeginOverlap.AddDynamic(this, &AFPSEnemy::OnHit);

해당 OnHit을 OnSensed로 바꿔서 실행해보니 어느정도 이해가 되었다.
OnComponentBeginOverlap로부터 AddDynamic을 할 수 있는 함수는
OnHit과 같은 형식을 무조건 갖추어야한다.
이건 추측이지만, 아마도 OnComponentBeginOverlap을 하면,
자동으로 매개변수가 채워져 함수로 넘겨주는 방식일 듯 하다.

  • OnHit함수이고, 다음과 같이 작성하였다.
    Collision에 들어온 Actor가 OtherActor인데,
    해당 OtherActor중에 AFPSCharacter로 캐스트할 수 있다면,
    AFPSCharacter액터의 DealDamage함수를 실행한다.

작성중에, 인자가 넘겨지는 것은 알겠는데,
왜 OtherComp가 아니고, OtherActor가 캐릭터인지 이해가 되지 않았다.
내가 못찾는건지 공식문서에서 전혀 찾을 수가 없었다.
https://forums.unrealengine.com/t/what-do-these-function-arguments-do/366890
여기서 보면 HitComp는 액터일 필요가 없고 컴포넌트이므로 Enemy의 사각 Collision을 의미할 것이고,
OtherActor는 실제로 해당 Collision에 들어온(충돌한) Actor,
OtherComp는 Actor가 아니라도 들어온 Component를 의미하는 것 같다.

  • Projectile에서도 같은 작업을 한다.
    여기서의 OnHit도 사용자정의함수이고, 똑같은 방식으로 AddDynamic되었다.
    Enemy에 Projectile이 닿으면, 피해를 입히고, 현재 Projectile이 파괴된다.

  • Enemy가 가할 수 있는 데미지는 5.0이다.
    피통이 보이지 않아서 정상적으로 동작하는지 알 수는 없다.

5씩 줄어들긴 하는 모습이다.


UI

  • UI 블루프린트를 다음과 같이 만든다.

  • 다음과 같이 UI 그래프를 만든다.
    UI는 BeginPlay처럼 게임이 시작할 때, 시작하는 BP노드는 없고,
    대신에 생성자처럼 UI가 생성이 된다면 실행하는 노드만 있다.

  • Progressbar를 누르고, Bind로 바인딩을 할 변수를 설정할 수 있지만,

  • Bind - Create Binding을 눌러서 해당 progressbar에 대한 그래프를 만든다.

참고로 이전프로젝트에서는 아래처럼 UI ref를 가져와서
Progressbar객체를 가져오고, Character BP에서 업데이트 하는 방식을 썼다.
지금은 UI 자체에서 Binding을 적용한다.

  • 간단하게 아래와 같이 % 화 시키는 작업만 한다.
    progressbar는 0~1까지밖에 표현을 못하기 때문에 Health값을 그대로 넣을 수가 없다.

  • 이전에 만들었던 게임모드에서
    UI객체를 생성하고, 뷰포트에 띄우는 작업을 진행한다.

  • 정상적으로 동작하긴 하는데, 알수없는 오류가 발생한다.

  • 해당 collision의 위치를 더 앞으로 당겨주니 오류가 바로 사라졌다.


UI 2

  • GameMode header파일에 다음과 같은 변수, 함수를 추가하였다.

  • Super::BeginPlay()로 기존 BeginPlay구문을 실행하도록 하였다.
    게임모드의 확장을 위해서 override했기 때문에 일종의 형식같은 행위.

  • 그리고 실제 기능을 추가한다.
    GetWorldTimerManager().SetTimer인데,
    애초에 이 자체로 Repeatable함수로서, 반복한다고 한다.
    bool 을 true값으로 두어야 반복함

GetWorldTimerManager().SetTimer(TimerHandle, this,
		&AGMUE5FPS::CountdownTimer, 1.0f, true, 1.0f);

즉 위의 코드의 설명은,
FTimerHandle클래스의 객체인 TimerHandle로 timer를 관리하는데,
CountDownTimer라는 함수를 실행할 것이고,
1.0f마다 실행할 것이다. 반복이 true이고,
처음에 1.0f만큼 딜레이를 하고 해당 반복을 실행할 것이다.

  • 그래서 1초마다 실행하는 CountDownTimer의 코드이다.
    그리고, 계속 0이하일때도 반복이 되기 때문에,
    멈추기 위해선, SetTimer로 지정했던 timer를 멈춰야하고,
    그게 TimerHandle이었기 때문에, 해당 객체로 멈춘다.
    그래서 시간이 0이되면, ResetTimer후에, ResetLevel을 호출한다.

  • ResetLevel은 다음과 같다.
    다음 레벨로 넘어간다. Level2는 미리 만들어놓은 다른 레벨

  • 강사의 의도는 모르겠으나,
    RestartGameplay가 호출되면,
    지든 이기든 다음 라운드로 넘어간다.

  • 캐릭터의 cpp파일에 hp가 다 떨어졌을때인 if문 내의
    restart game부분을 추가한다.
    게임모드를 직접 가져와서, RestartGameplay를 실행

  • 죽으니 정상적으로 Level2로 가는 모습이다.
    이제야 알았는데, 그냥 기본맵을 Level2로 두고, 부활하는 것이었다.
    Level2가 다음 스테이지가 아니라 그냥 현재 main stage였던 것
    그래도 정상 동작한다. 그냥 하자.

  • 타이머는 UI widget과 아직 연결하지 않았기 때문에 0으로 고정되어 표시된다.
    그래서 Health bar가 Character Ref를 사용하여 health를 참고했듯이,
    Timer텍스트는 GameMode를 참고하여 설정되도록 할 것이다.

  • TEXT도 바인딩을 할 수 있다.

  • 해당 바인딩들은 함수로 왼쪽에 뜨게 된다. 이름도 변경 가능.
    그리고 Blueprint로 read할 수 있도록 설정하고,
    count시 --되도록 설정했던, TimerCount값을 가져온다.

  • 아래와 같이 string값을 형식지어서 넘긴다.

  • 정상 동작

  • 찝찝한 것은, 현재 프로젝트 내에 Won이 true일 경우의 분기로 가는 길이 없다는 것이다.


오류 - HP under 0


Enemy의 콜라이더를 수정하고 해당 오류가 없어졌다가 다시 떴는데,
이유를 알 것 같다.
그 이유는, progress bar가 표현할 수 있는 0값보다
더 아래로 떨어졌기 떄문인 듯 하다.
= 캐릭터가 죽어서도 겹친다?


이전 프로젝트에서도 이런 상황에 대비했고, Clamp로 이를 해결했었다.

  • isValid를 사용하여 Character Ref가 가능하다면
    = 죽어서 Destroy되지 않았다면,
    return node로 progressbar에 해당 health값을 return한다.

해결됨.


오류 - 바인딩 'None was not found on`

바인딩을 설정할 때, 잘못해서 여러개를 설정할 경우,
함수부분에서 그냥 delete만 눌러서 삭제했다고 생각하는 경우 생기는 오류.


함수에서 지웠어도,
직접 UI에서 bind을 지워야 한다.

직접 해보니 스택 순서대로 나중에 만들어진것이 먼저 지워지게 된다..


Enemy UI

  • player의 UI가 아닌, 적에 대한 UI가 보이게 해본다.
    다음과 같이 progress bar를 만든다.

  • Enemy 블루프린트에서 widget component를 추가한다.

  • 보이지는 않지만, 위치정보가 뜨고 이동할 수도 있다.
    UI클래스를 지정하면 그제서야 보이게 되고, bar를 뜨게 한다.

  • 그대로 Enemy 그래프쪽으로 가서, 아래처럼
    widget object를 가져오는데, Enemy Health인 것만 추려서 가져온다.

  • 100으로 나누고, 추가로 percent 작업을 또 해야한다.

이전에 player의 healthbar를 위해서 UI 내부의 그래프를 바인딩 했었다.
여기서는 직접 progress bar에 바인딩 하는 것이었기 때문에 set Percent로 값을 바꾸어주지 않지만,
상단의 경우에는 set Percent로 넘겨주어야 하는 것 같다.

  • 정상적으로 피통이 보인다.

승리 포인트

  • Door를 통과하면 승리하도록 할 것이고,
    Door라는 액터 CPP클래스를 만들었다.
    헤더는 아래와 같이, Mesh와 충돌Box, 그리고 projectile에서 복붙한 onHit함수다.

  • CPP파일에 다음과 같이 include들을 추가한다.

  • FPSCharacter cpp파일에서 했던 것처럼 각 객체를 만들어두고,
    계층구조를 설정한다.

  • BeginPlay에서는, Hit된다면 OnHit함수를 호출하도록 delegate걸어놓았고,

  • OnHit함수를 아래처럼 구성한다.
    드디어 Restart Gameplay true의 분기가 사용된다.

Door는 여태까지 했던 명령어들의 집합체이다.
캐릭터에서 mesh선언과 계층구조를 만들어둔 것과,
projectile, enemy에서 hit관련 함수와 add Dynamic을 사용한 것,
game mode 객체를 가져와서 사용한 것까지 종합적으로 구성이 되어있다.

  • BP로 Door를 상속받게 생성한다.
    Mesh와 Collider의 크기를 조정한다.

collider가 문과 딱 붙어있어서 제대로 동작하지 않을 줄 알았지만,
잘 적용되는 모습.


오류 - animation 또 멈춤


쭉 한 키를 누르고 이동하다보면, 움직였던 팔이 멈춘채로 움직이게 된다.

애니메이션의 문제인듯 하다.
Loop부분도 없고, 다른 애니메이션들은 정상적으로 반복된다.


배우면서

C++만으로 작성하기에는 타이핑 해야할 것이 엄청 많았고,
오타도 VS가 잘 잡아주지 않아서 BP가 더 편리하다고 느꼈다.
심지어 CPP 함수가 정확히 뭘 하는지에 대한 설명이 공식문서에 잘 나와있지 않아서 더 힘들게 느껴졌다.

언리얼은 거의 모든 기능이 만들어져 있지만,
그 기능을 찾는 것은 C++보다, BP가 훨씬 쉽기 때문이다.
하지만, BP로만 게임을 만든다는 것은 뭔가 코딩하는 맛이 나지를 않는다.
CPP로 클래스를 정의하고, 해당 클래스를 BP객체로 만드는 방식이 효율적이라는 생각이 든다.
변수나 사용자 정의 멤버함수들은 C++에서 선언하고, 해당 변수들을 사용하여 이미 있는 기능들을 쓰려고 한다면, BP를 쓰는 것이 맞는 것 같다.

CPP에서 못한다면 BP로 하면 되지 않는가?
알아갈수록 재밌다.

The End


참고한 사이트

profile
Time Waits for No One

0개의 댓글