Simple Shooter

Chaco22·2024년 1월 12일

UE5

목록 보기
5/8
post-thumbnail

Character Input And Camera

Character Input


먼저 캐릭터를 생성하기 위해 프로젝트 세팅에서 키 입력 설정을 해주었다.

C++ ShooterCharacter 파일을 만들고 키들을 바인드해주었다.

주의

C++파일을 처음 만들고 블루프린트로 바로 상속을 받는 작업을 할 때 에디터를 끄고나서 C++파일을 따로 빌드해주는 습관을 길러야겠다.
가끔 에디터에 다시 들어갔을 때 C++파일이 빌드가 되지 않아있어 상속해주었던 블루프린트에서 오류가 뜨는 경우가 잦다.

Character Camera


총을 들 오른쪽 어깨쪽에 카메라와 스프링암을 부착하고 소켓 오프셋을 약간 높여 정면이 잘 보이도록 만들어주었다.

Character Animation

Blend Space


먼저 캐릭터의 이동 부분을 자연스럽게 만들어 주기 위해서 BS_Locomotion을 만들고 세로를 Angle, 가로를 Speed로 나눠 각 Angle에 방향에 맞는 애니메이션을 넣고 Speed에 따라 뛰거나 걷고 멈추게 만들어 주었다.

Animation Blueprint


만든 블렌드 스페이스를 애니메이션 블루프린트의 AnimGraph에서 호출해 주고 애니메이션 블루프린트내에서 Angle과 Speed 변수를 만들어 상호작용 하도록 만들어 주었다.

애니메이션 블루프린트의 이벤트 그래프에서는 Pawn Owner의 속도나 각을 받아 Angle과 Speed 변수의 값을 수정하고 그에 따라 캐릭터가 움직일 수 있도록 설정해 주었다.

아직 인게임 캐릭터의 움직임에 부자연스러운 부분이 있어 캐릭터의 보폭 애니메이션의 보폭 거리와 시간을 보고 속도를 구해주었고

애니메이션의 속도에 맞게 Speed의 최대값과 그리드 분할을 조정해주었다.

Death

Death Animation


적절한 죽음 애니메이션을 찾아 BlendPosesBybool을 사용하여 블루프린트에서 새로 만든 함수인 IsDead을 연결해서 True일 경우에는 죽는 애니메이션이 연출되도록 만든다.


죽음은 체력이 0이거나 0보다 낮을 때 실행되게 만들기 위해서 IsDead 함수를 BluprintPure const로 만들어 체력이 0이거나 0보다 낮으면 true가 반환되도록 만들어준다.

BluprintCallable BluprintPure

UFUNCTION에서 사용되는 BluprintCallable은 C++에서 다룬 함수가 블루프린트에서도 호출이 가능하게 만들어준다.
BluprintPure는 BluprintCallable과 유사하지만 순수 노드에 사용되는 것으로 순수 노드는 실행핀이 없는 노드를 말한다.
즉 BluprintPure를 사용한 함수는 결과는 있지만 게임에서 직접 상태를 바꾸는 함수는 아니라는 것을 명시한다.
BluprintPure는 보통 const와 함께 사용된다.

AimOffsets


캐릭터가 바라보는 곳으로 에이밍을 해주기 위해 Idle_AO(AimOffsets)_Combat을 가져와 Pitch를 변수로 승격하고

이벤트 그래프에서 시퀀스로 항목을 나눠 Control Rotation에서 Actor Rotation을 뺀 값을 Pitch에 전달하였다.

Jumping

Locomotion


State Machine인 Locomotion을 생성하여 IsFalling 불리언에 따라 점프 애니메이션으로의 전환을 이뤄주고 Jumping에서 Airborned으로의 전환은 스테이트의 시퀀스 플에이어에 따른 자동 규칙(Automatic Role Based on Sequence Player in State)을 체크하여 Jumping 애니메이션이 끝나면 자연스럽게 전환될 수 있도록 작업하였다.

Refactoring

메인 AnimGraph에서는 Alive/Death 스테이트 머신을 추가하여 IsDead에 값에 따라 죽음 애니메이션으로 전환이 가능하게 하였다.

Aive state 안에는 Locomotion과 에임 오프셋을 넣어 좀 더 직관적이고 깔끔하게 리팩터링 하였다.

Gun And Shoot

C++파일 및 블루프린트 생성


C++파일 Gun을 생성하고 부모로 받아 BP_Rifle을 생성, 에셋을 만들어 주었다.

액터 스포닝 및 소켓 부착


먼저 캐릭터가 월드에 생겨날 때 총도 같이 생겨날 수 있도록 캐릭터 C++에서 Gun을 선언 및 Beginplay에서 정의해 주었다.

총을 소켓에 알맞게 부착해주기 위해 캐릭터의 스켈레톤 트리로 가서 알맞은 위치를 확인 후 새로운 소켓을 만들어 준다.

원래의 총을 없애고 우리가 만든 Gun을 부착해주기 위해 GetMesh의 HideBoneByName을 사용해 기존의 총을 없애주고 AttachToComponent를 활용해 새로만든 소켓에 Gun을 들려준다.

게임을 시작해 총의 위치를 확인하고 손에 딱 맞게 조정해준 뒤 BP_Rifle에 그 위치를 입력해주고 저장하면 그 뒤 총은 캐릭터의 손에 알맞게 스포닝된다.

Shoot 바인드 및 파티클 이펙트 스포닝


발사를 실행했을때 사용할 함수를 생성

마우스 좌클릭을 엔진에서 설정해주고 C++에서 좌클릭이 눌리면 PullTrigger()를 실행하도록 설정

엔진 Rifle에서 소켓의 존재와 이름을 살핀 뒤 UGameplayStatics::SpawnEmitterAttached를 사용하여 만들어둔 UParticleSystem* MuzzleFlash; 변수와 SkeletalMesh, 소켓 이름을 활용하여 마우스 좌클릭 시 이펙트가 나오도록 설정해 주었다.

PlayerViewPoint


3인칭 게임은 카메라가 캐릭터와 떨어져있기 때문에 발사했을때 총알이 나가는 곳이 카메라의 방향과 다를 수 있다.
카메라를 스폰해 총의 레이트레이싱에 도움을 주기 위해 Controller에 있는 GetPlayerViewPoint를 활용해 위치와 회전각을 가져오고 DrawDebugCamera를 활용해 발사했을 때 카메라가 보이도록 만들어 주었다.

LineTraceSingleByChannel


라인 트레이싱을 활용하여 총을 쐈을 때 설정해 놓은 범위인 MaxRange 안에 물체가 있으면 전에 사용했던 UGameplayStatics::SpawnEmitterAtLocation를 다시 사용 하여 ImpactEffect를 만들고 알맞은 효과를 선정하여 총이 맞는 부분에 이펙트를 남기도록 만들어 주었다.

Damage

TakeDamage


상대에게 대미지를 주기 위해 TakeDamge 함수를 사용한다.
TakeDamge 함수를 사용하기 위해서 먼저 DamageEvent 함수와 HitActor를 생성하고 TakeDamage에 입력해주었다.

가상 함수 (Virtual Method)

부모와 자식 사이에 같은 이름을 가진 함수가 있을때 C++는 효율성 문제로 포인터를 사용해 자식의 함수를 받는다 하여도 부모의 함수가 발동할 때가 있다. 그 때 그 부분을 확실히 명시하기 위해 함수에 사용하는 것으로 부모의 함수 앞에는 virtual을 붙이고 자식의 함수 뒤에 override를 붙여 오류 방지 및 확실한 명시를 나타낸다.

TakeDamage Override

Gun.cpp에서 작업을 마친 뒤 대미지를 받을 ShooterCharacter에서 MaxHealth와 Health를 만든 뒤 Beginplay에서 현재 체력을 최고 체력으로 만들어주고 public 영역에 TakeDamage를 가상함수로 선언 override를 해준다.
정의에서 부모로부터 결과를 받아 현재 체력을 깎고 확인을 위한 로그를 출력 후 받은 대미지를 반환해준다.
로그로 ShooterCharacter의 남은 체력을 확인하고 FMath::Min을 사용하여 0아래로는 체력이 내려가지 않는 모습을 확인했다.


이벤트 그래프에서 cast ShooterCaracter로 C++ ShooterCaracter의 IsDead 함수를 가져와 블루프린트의 IsDead 변수와 연결해준다.

AI

Create AI controller

먼저 C++클래스 ShooterAIController를 만들어준 뒤 블루프린트 자손인 BP_ShooterAIController를 만들어준다.
BP_ShooterCharacter에서 디폴트 AI 컨트롤러 클래스 값을 BP_ShooterAIController로 설정해준다.

SetFocus


생성된 AI가 Plyer를 계속 조준하도록 UGameplayStatics::GetPlayerPawn을 활용해 Player 폰을 가져오고 SetFocus로 계속 Player를 바라보도록 만든다.

LineOfSightTo And MoveToActor


다음은 NavMeshBoundsVolume로 AI가 이동 가능한 공간을 맵 전체로 설정해준다.

LineOfSightTo를 사용하여 AI의 시선에 Player가 있는지 확인하고 있다면 SetFocus로 Player를 바라보고 MoveToActor를 사용하여 거리를 두고 따라 다니도록 그렇지 않다면 멈추도록 만들어 주었다.

BehaviorTree And BlackBoard

BehaviorTree

BehaviorTree의 Selector에서 Player가 시야에 잡혀 위치가 특정될 때는 Plyer를 따라간다. Can See Player?의 관찰자 중단을 Both로 설정하여 나머지 두 자손보다 높은 실행순위를 부여하였다.

생성 C++파일


BTService_PlayerLocationIfSeen

LineOfSightTo를 사용하여 Owner의 시야에 Player가 잡히면 선택된 블랙보드 키(PlayerLocation)에 Player의 위치 벡터를 기록한다.

위의 SHooterAIController.cpp의 Tick에 있던 LineOfSightTo를 이곳으로 이동하였다. 틱마다 실행보다는 Service를 이용하여 0.4~0.6초 단위로 PlayerLocation을 가져온다.


BTService_PlayerLocation
Owner의 시야에 Player가 잡혀 Can See Player? Sequence로 내려오면 선택된 블랙보드 키(LastKnownPlayerLocation)에 Player의 위치 벡터를 기록한다.

BTTask_ClearBlackBoardValue

선택된 블랙보드 키의 값을 지운다.

BTTask_Shoot

GetAIOwner로 Owenr를 가져온 뒤 AShooterCharacter로 캐스팅 선언 후
ShooterCharacter의 Shoot()함수를 가져왔다.

AimOffsets 적용

적들에게도 AimOffsets을 적용하기 위해 블랙보드키인 PlayerLocation을 Player로 바꿔 액터를 직접 전달하도록 하였다.

End Game

플레이어가 승리 조건을 달성하거나 패배 조건을 달성했을때 게임의 끝을 알리기 위해 원래의 SimpleShooterGameModeBase에 PawnKilled함수를 추가하여 자손 KillEmAllGameMode C++파일을 만들고 BP_SimpleShooterGameModeBase의 부모를 KillEmAllGameMode로 변경하고 이름도 BP_KillEmAllGameMode로 변경하였다.

Dead Character


캐릭터가 죽은 뒤 더 이상 컨트롤이 이뤄지지않도록 DetachFromControllerPendingDestroy() 함수를 사용했고 캡슐의 콜리전을 없앤다.

Win Condition

ShooterPlayerController


C++ 파일인 ShooterPlayerController를 생성하여 GameHasEnded 함수가 호출되면 승리 조건에 따라 Widget을 띄우고 GetWorldTimerManager().SetTimer()로 5초 뒤 레벨을 다시 시작하도록 만든다.

GameEnd


KillEmAllGameMode의 PawnKilled 함수에서 폰이 죽으면 플레이어인지 확인한 뒤 플레이어면 컨트롤러를 가져와 EndGame(flase)함수를 호출하고 월드 내 모든 AIController를 확인하여 AI가 모두 죽은 경우 EndGame(true)함수를 호출한다.
EndGame 함수에서 조건을 확인 한 뒤 GameHasEnded 함수를 호출한다.

Sound

Spawn Sound


먼저 사운드를 다루기 전 Spawn Sound가 들어갈 PullTrigger에서 라인트래이스부분과 컨트롤러 호출 부분을 따로 메소드로 만들어 리팩토링 해주었다.
전에 파티클을 넣었을때와 동일하게 총알이 나가는 소리와 총알에 맞는 소리를 구현하기 위해 SpawnSoundAttached와 SpawnSoundAtLocation를 사용하여 공간을 마련하였다.

Sound Cue


6종의 발사 소리와 임팩트 소리를 랜덤 재생할 수 있도록 만들어 소리가 다양하도록 만들고 모듈레이터를 사용하여 소리의 볼륨과 피치 편차를 조절해 주었다.

Background Sound

레벨에 Ambient Sound Actor를 배치하고 사운드를 설정해준다.
레벨 어느곳에서도 일정한 사운드를 재생한다.

UI

Crosshair And HPBar


WBP_HUD를 생성 패널의 가운데에 십자를 넣어 크로스헤어를 생성하고 좌측 아래에 프로그래스바를 생성하여 잔여 체력을 표시하도록 하였다.

크로스헤어는 ShooterPlayerController에서 BeginPlay부분에서 실행되며 GameHasEnded가 호출될 경우 화면에서 사라진다.

RemoveFromViewport()는 GC(Garbage Collection)에서 어떤 문제가 있는지 코드를 빌드하는 도중 출력창에서 더 이상 사용하지 않는 것을 권고하는 내용을 발견했다. 구글링을 통해 RemoveFromParent()로 변경하였다.


ShooterCharacter에서 GetHealthPercent를 생성하여 체력의 백분율을 구한다.

ShooterCharacter를 캐스트하여 GetHealthPercent를 가져와 프로그래스바에 잔여 체력을 표시한다.

Texture Streaming Pool Size

게임을 실행시키면 Texture Streaming Pool이 초과한다는 내용이 뜬다.
기본적으로 Texture Streaming Pool은 1000으로 맞춰져 있어 사용하는 자원이 초과해 뜨는 메시지다.
콘솔 cmd에 r.Streaming.PoolSize 2000을 적어 임시로 늘리는 것이 가능하다.
기본값을 늘리는 방법은 프로젝트 폴더 - Config - DefaultEngine.ini 에서 r.Streaming.PoolSize = 1000 이 부분의 숫자를 바꾸는 것이라는데 현재는 다른곳에서 다루는지 찾을 PoolSize에 관련한 코드를 찾을 수 없었다.

레벨 블루프린트에서 위와 같이 늘려주었다.




아래 본문
텍스처 스트리밍 풀 늘리는 법

0개의 댓글