UI
UI 거리 비례 조정
- CEnemy_AI
- 생성된 위젯은 코드로 생성한 것이기 때문에 함수에서 사용할때 마다 해당 위젯을 상속한 클래스로 ‘캐스트’해서 저장한 다음에 사용해야 한다.
- 카메라를 가져오는 방법
- 플레이어 컨트롤러를 통해 가져온다.
- 게임 플레이 스테틱스의 ‘플레이어 카메라 메니저 가져오기’ 함수 사용
- UI의 사이즈 비율은 최대 1부터 거리 / 최대 보여지는 거리 로 계산한다.
- 최대 거리는 ‘near ~ far’간 거리를 사용해도 되고, 임의로 설정해서 사용해도 된다.
- 헤더 파일 - ‘틱 라벨 렌더 스케일’ 함수 선언 - ‘최대 라벨 거리’를 선언하고 1000으로 초기화한다.
//header private: UPROPERTY(EditDefaultsOnly, Category = "Label") float MaxLabelDistance = 1000.0f; private: void Tick_LabelRenderScale();- CPP 파일 - ‘틱 라벨 렌더 스케일’ 함수 정의 - ‘라벨 위젯’의 ‘유저 위젯 오브젝트 가져오기’ 함수를 호출한 결과를 ‘파생 유저 위젯’ 클래스로 지정하여 ‘캐스트’ 함수 호출하고 해당 클래스로 변수를 선언하여 저장한다. - ‘라벨’이 비어있으면 실행하지 않는다. (생성이 안되어있을 수 있으니까) - 게임 플레이 스테틱스 클래스의 ‘플레이어 카메라 매니저 가져오기’ 함수에 ‘월드, 빙의된 플레이어 수(기본 빙의는 0이다.) ; 0)을 넣고 호출한 결과를 플레이어 카메라 매니저 클래스의 ‘카메라 매니저’ 변수를 선언하고 저장한다. - ‘카메라 매니저’의 ‘카메라 위치 가져오기’ 함수를 호출하여 ‘카메라 위치’ 변수를 선언하고 저장한다. - ‘컨트롤러 가져오기’ 함수 호출의 ‘타겟 위치 가져오기’ 함수를 호출하여 ‘타겟 위치’를 선언하고 저장한다. - 벡터 구조체의 ‘거리’ 함수에 ‘카메라 위치, 타겟 위치’를 넣고 호출한 결과를 ‘거리’ 변수를 선언하고 저장한다. - 1.0f - (’거리’ / ‘최대 라벨 거리’)를 계산한 결과를 ‘사이즈 비율’ 변수를 선언하고 저장한다. - ‘거리’가 ‘최대 라벨 거리’ 보다 커진다면 (라벨이 보여지는 거리를 넘어갔다) : ‘라벨’의 ‘보이기 설정’ 함수에 ‘보이기 설정 : 접기’로 설정하고 호출하고 반환한다. - ‘라벨’의 ‘보이기 설정’ 함수에 ‘보이기 설정 : 보이기’ 를 넣고 호출한다. - ‘라벨’의 ‘렌더 크기 설정’ 함수에 ‘벡터 2D : 크기 비율, 크기 비율’을 넣고 호출한다. - Tick 함수 확인 - ‘틱 라벨 렌더 크기’ 함수 호출
//CPP void ACEnemy_AI::Tick(float DeltaTime) { Super::Tick(DeltaTime); Tick_LabelRenderScale(); } void ACEnemy_AI::Tick_LabelRenderScale() { UCUserWidget_Enemy* label = Cast<UCUserWidget_Enemy>(LabelWidget->GetUserWidgetObject()); CheckNull(label); APlayerCameraManager* cameraManager = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0); FVector cameraLocation = cameraManager->GetCameraLocation(); //카메라 위치 FVector targetLocation = GetController()->GetTargetLocation(); //적 캐릭터 컨트롤러 위치 float distance = FVector::Distance(cameraLocation, targetLocation); //카메라와 타겟 거리 float sizeRate = 1.0f - (distance / MaxLabelDistance); //타겟과의 거리가 지정한 거리보다 멀어진다면 if (distance > MaxLabelDistance) { //Collapsed : 접는식으로 안보이도록 설정하는 기능이다. (Hidden은 아예 안보이도록 하는것이다.) label->SetVisibility(ESlateVisibility::Collapsed); return; } label->SetVisibility(ESlateVisibility::Visible); label->SetRenderScale(FVector2D(sizeRate, sizeRate)); } - WB_Enemy
- Name 텍스트 사이즈 ; 42 - ControllerName 텍스트 사이즈 : 40 - Boarder 렌더 트랜스폼 스케일 : 4.0 , 1.25
AI
- PlayerController 클래스가 있는것 처럼, AI도 AIController 클래스가 따로 있다.
- AIController가 Enemy 캐릭터에 ‘빙의’해서 캐릭터에게 명령을 내리는 것이다.
적 캐릭터 빙의 설정
- CAIController
- 컨트롤러를 캐릭터에 빙의시키고, 빙의시킨 캐릭터를 변수에 저장해서 사용한다.
- 생성 - AIContorller 상속
- 헤더 파일 - 생성자 선언 - ‘빙의 활성’ 함수를 선언하고 ‘폰’을 넣을 수 있도록 설정한다. - ‘빙의 해제 활성’ 함수 선언 - Enemy_AI 클래스의 ‘적’ 변수 선언
- CPP 파일 - 생성자, ‘빙의 활성’, ‘빙의 해제 활성’ 함수 생성 - ‘빙의 활성’ 함수 정의 - 빙의 테스트 로그 띄우기
- BP_CEnemy_Melee
- 디테일 - 폰 - ‘AI 컨트롤러 클래스’ : 생성한 CAIController 클래스 할당
시야 감지 설정
- CAIController
- 배열에 객체 오브젝트를 넣을때는 ‘포인터’로 넣어야한다.
- 헤더 파일 - AI 감지 컴포넌트 클래스 포인터 변수인 ‘감지’ 변수 선언 - AI 센스 정보_시야 클래스의 포인터 변수인 ‘시야’ 변수 선언
//header private: UPROPERTY(VisibleAnywhere) class UAIPerceptionComponent* Perception; class UAISenseConfig_Sight* Sight; //시야 감지 클래스- CPP 파일 - 생성자 확인 - ‘감지’의 ‘엑터 컴포넌트 생성’ 함수 호출 - ‘시야’에 ‘동적 할당 오브젝트 생성’ 함수를호출하고 저장한다. - ‘시야’의 ‘시야 범위’ 변수를 600으로 설정한다. - ‘시야’의 ‘시야 감지 잃는 범위’ 변수를 800으로 설정한다. - ‘시야’의 ‘좌우 보이는 각도 정도’ 변수를 45도(좌우 45도, 총 90도)로 설정한다. - ‘시야’의 ‘최대 감지 유지 시간 설정’ 함수에 2를 넣고 호출한다. - ‘시야’의 ‘소속 탐지’의 ‘적 탐지 확인’ 변수를 true로 설정한다. - ‘시야’의 ‘소속 탐지’의 ‘중립 탐지 확인’ 변수를 true로 설정한다. - ‘시야’의 ‘소속 탐지’의 ‘아군 탐지 확인’ 변수를 false로 설정한다. - ‘감지’의 ‘센스 구성’ 함수에 저장한 ‘시야’ 포인터 변수를 넣고 호출한다. - ‘감지’의 ‘우선적 감지 설정’ 함수에 ‘시야 포인터의 감각 정보 가져오기 함수 호출’을 넣고 호출한다.
//CPP //CreateDefaultSubobject : 생성자에서 동적할당하는 함수다. //모르면 생성자에서 객체생성할때 쓰는 함수라고 생각해라 Sight = CreateDefaultSubobject<UAISenseConfig_Sight>("Sight"); Sight->SightRadius = 600; Sight->LoseSightRadius = 800; //감지를 잃는 범위 Sight->PeripheralVisionAngleDegrees = 45; //좌우 45도 (즉, 90도) Sight->SetMaxAge(2); //감지 벗어나고 감지를 잃는 시간 //DetectionByAffiliation : 적 아군 판별 기능 (블루프린트에서는 설정할 수 없고, C에서만 가능하다.) Sight->DetectionByAffiliation.bDetectEnemies = true; Sight->DetectionByAffiliation.bDetectNeutrals = false; Sight->DetectionByAffiliation.bDetectFriendlies = false; //감지 환경설정 배열 생성 Perception->ConfigureSense(*Sight); //객체로 넣을때는 '포인터'로 넣어야한다. Perception->SetDominantSense(*Sight->GetSenseImplementation());
팀 그룹 설정
- 팀 ID중 ‘중립’을 0이나 255로 설정하면 헷갈릴 위험이 있기에, 플레이어를 ‘1’로 설정한다.
- 사람마다 다르게 설정한다.
- 플레이어는 팀 ID 생성 인터페이스를 추가했어야했지만, AIController를 사용하는 적 캐릭터는 AIController에 이미 인터페이스가 상속되어있기 때문에 추가 할 필요 없다.
- Enemy를 상속한 Enemy_AI에서 팀 ID 생성 - AIController에서 해당 클래스와 현재 폰을 넣고 ‘캐스트’한 결과를 ‘적’ 에 저장한다. - AIController에서 팀 ID 설정
- CPlayer
- 헤더 파일 - 팀 대리자 인터페이스 생성 클래스 헤더 파일 include - ‘팀 대리자 인터페이스’ 상속 선언 - ‘팀 ID’ 변수 선언하고 1로 초기화한다. - 팀 ID 생성 구조체의 ‘팀 ID 가져오기’를 재정의 선언하고 상수로 설정한다. : ‘팀 ID 생성’ 구조체에 ‘팀 ID’를 넘겨 반환하는 것으로 정의한다.
//header #include "GenericTeamAgentInterface.h" private: UPROPERTY(EditDefaultsOnly, Category = "Team") uint8 TeamID = 1; //플레이어 팀 ID는 기본적으로 1을 놓는다. (사람마다 다르게 지정한다.) FGenericTeamId GetGenericTeamId() const override { return FGenericTeamId(TeamID); } //플레이어 팀 ID를 외부에서도 쓸 수 있도록 한다. - CEnemy_AI
- 헤더 파일 - ‘팀 ID’ 변수 선언 후 2로 초기화 - ‘팀 ID 가져오기’ 함수 선언 후 ‘팀 ID’를 반환하는것으로 정의한다.
//header private: UPROPERTY(EditDefaultsOnly, Category = "Team") uint8 TeamID = 2; public: //외부에서 에너미의 팀 ID를 쓸 수 있도록 한다. FORCEINLINE uint8 GetTeamID() { return TeamID; } - CAIController
- CPP 파일 - ‘빙의 활성’ 함수 확인 - 현재 폰을 넣고 빙의한 캐릭터를 사용하기 위해 해당 캐릭터로 ‘캐스트’ 해서 ‘적’ 변수에 저장한다. - ‘팀 ID 생성 설정’ 함수에 ‘적의 팀 ID 가져오기 함수 호출’을 넣고 호출한다.
//CPP //폰에 빙의된 캐릭터 저장 Enemy = Cast<ACEnemy_AI>(InPawn); SetGenericTeamId(Enemy->GetTeamID());
UI 크기 오차와 가까이 갈때 머리에서 멀어지는 이유
- 렌더링 파이프라인 : 물체를 그리기 위한 절차
- 파이프라인 : 순차적으로 가는 절차
- 정점들을 IA로 보낸다.
- IA : 그릴 정점들 세팅 단계
- 렘에있는 데이터를 VRam에 보내는 단계다.
- 이때부터 렌더링 과정을 GPU에 맡긴다.
- IA에서 Draw 시작 명령을 내린다. (Draw Call)
- Draw 콜 수 : 한 프레임에 몇개의 Draw 명령이 들어갔냐를 말한다.
- 최적화와 관계가 있다.
- 렘에있는 데이터를 VRam에 보내는 단계다.
- VS : GPU에 ‘쉐이더’로 명령을 내리는 단계다.
- 3D상에 어떤 위치가 있을때 이 위치를 W → V → P를 거쳐 MDC로 나오게 된다.
- 모니터가 어떤 해상도일지 모르니 뒤는 그릴 필요가 없으니 앞만 그린다.
- 이 과정을 렌더링 파이프라인에서 자동으로 계산한다.
- 이 과정을 ‘Projection’이라 하고, 반대 과정을 ‘UnProjection’이라고 한다.
- W (World) : 회전가능한 직육면체라고 생각해라
- 오브젝트를 배치하면 ‘위치’가 생기고, 상 하 좌 우 앞 뒤로 늘어날 수있고, 회전도 가능하기 때문이다.
- V (View) : 내가 볼 위치 (눈), 내가 볼 방향
- 위치와 방향이기 때문에 ‘프로젝션’과 결합해서 보여준다.
- P (Projection) : 어떻게 투영시킬지에 대한 과정이다.
- 투영되는 반경은 ‘절두체’ 모양으로 되어있다.
- 투영이 시작되는 부분 : Near , 투영이 끝나는 부분 : Far
- Near부터 Far 까지 수직거리 한 점부터 니어와 파의 대각 각도 : 수직 시야각
- 절두체(2D)를 직육면체(3D)로 만들기 위해 필요한 요소
- Near를 늘린다. ⇒ 가까운건 크게보인다.
- Far는 줄인다. ⇒ 뒤로 갈수록 작게 보인다.
- 이 과정에서 ‘원근감’이 생긴다.
- 즉, 절두체의 모양을 ‘원근 투영 (Perspective)’이라고 부른다.
- 간단히 말해서, 삼각형을 직육면체로 바꾸는 ‘과정’을 통틀어서 말하는 것이다.
- NDC : 2D 로 변환하는 마지막 과정이다.
- W (World) : 회전가능한 직육면체라고 생각해라
- 화면은 한 점을 기준으로 상(y : +1) 하(y : -1) 좌(x : -1) 우(x : +1)이다.
- 해상도는 기기마다 다르니 이렇게 계산이 된다.
- 3D 상으로는 한 점을 기준(0)으로 정면(+1)만 그린다.
- 2D 화면상으로 뒤통수는 보이지 않기 때문이다.
- 때문에 ‘깊이’는 자신부터 앞에만 그린다.
- 이것을 임의로 계산해야하는 상황이 있다. ⇒ ‘UI’ (3D에서 2D로 변환해야한다.)
- 반대의 경우 (2D에서 3D로 변환해야한다.) : 마우스 클릭(2d의 마우스를 클릭하면 3d 공간상의 좌표가 생성되어야한다.)
- 3D상에 어떤 위치가 있을때 이 위치를 W → V → P를 거쳐 MDC로 나오게 된다.
- RS : 3D를 2D로 변환하는 과정이다.
- 이 작업은 GPU가 하는게 아니라, 그래픽 카드 안에 ‘레스터라이징 장치’가 따로 있다.
- PS : 2D로 변한 화면에서 ‘픽셀들’을 처리하는 쉐이더다.
- GPU가 처리한다.
- OM : 모니터에 직접적으로 비추는 과정이다.
- IA : 그릴 정점들 세팅 단계
직교 투영 확인하는 법
- 카메라 - 디테일 - 카메라 세팅 - 투영 모드 : Orthographic
- 원근감이 사라진 투영을 의미한다.
- 원근 투영은 투영 형태가 ‘절두체’ 이지만, 직교 투영은 투영 형태가 ‘직육면체’다.
- 앞뒤 거리만 잰다.
- 이것을 NDC 모양 (직육면체) 과정으로 오면 투영 형태가 똑같기 때문에 ‘원근감이 없다.’
UI 오차가 생기는 결론적인 이유
- WVP 과정에서 절두체가 직육면체로 변하는 과정에서 Near는 늘어나고, Far는 줄어들때 ‘앞 뒤’ 간격은 변하지 않아서 생기는 문제다.
- 변하는 과정에서 수직 시야각이 올라가서 오차가 생긴다.
- 지금은 이해하기 쉽게 사용했지만 원래는 oneprojection 수식이 따로 있다.
- 복잡한 내용이다.
AI 감지 시스템 설명
- AI 블루프린트 - ‘감지 컴포넌트’ - 디테일 - AI 퍼셉션 - 감지 환경 설정 : 설정한 감지 구성을 저장하는 옵션이다.
- 배열로 생성되고 요소 하나하나에 설정한 감각 구성을 저장한다.
- AI 데미지 감각 구성 : 빙의된 캐릭터가 ‘데미지’를 입었을때 감지할 설정이다.
- AI 듣기 구성 : 캐릭터가 ‘소리’를 들었을때 감지할 설정이다.
- 소리의 ‘발생자’는 ‘Make Noise’ 함수로 발생시킨다.
- AI 시야 구성 : 캐릭터의 ‘시야’에 들어왔을때 감지할 설정이다.
- AI 예측 감각 구성 : 감지가 될 만한 ‘상황’일때 감지할 설정이다.
- AI 터치 구성 : 캐릭터에 ‘닿았을때’ 감지할 설정이다.
- AI 팀 감각 구성 ; 팀 끼리 감지할 설정이다.
- AI 블루프린트 - ‘감지 컴포넌트’ - 디테일 - AI 퍼셉션 - 주도적인 감각 : 감지 환경설정이 여러개일때, 그 중에서도 가장 먼저 감지할 감지 정보를 할당하는 옵션이다.
AI 캐릭터 디버그 기즈모 확인 방법
- 게임 플레이 - ‘ 타자 버튼 클릭 : AI 디버그 화면이 출력된다.
- 게임 플레이 - ‘ 타자 버튼 클릭 - 숫자 키패드(오른쪽) 4 클릭 : 감지 정보를 ‘기즈모’로 확인할 수 있는 화면 출력
팀 그룹 설정 코드로 작성하는 법
- 블루프린트에서는 불가능한 방법이다.
- 팀 ID를 설정하고 싶은 캐릭터를 만든 클래스 - #include "GenericTeamAgentInterface.h” 추가 - public IGenericTeamAgentInterface 상속 추가 - 팀 ID 변수 생성 - 반환은 FGenericTeamId 구조체에 팀 ID를 넣고 반환한다.
- 팀 ID를 정하지 않는 중립은 255 순번이다.
- 아군 ; 0 / 중립 : 1 / 적? : 2
- 상속받은 곳에서 TeamID를 선언할 수 있도록 되어있다.
- 자료형은 uint8 이다.
- 보통은 FGenericTeamId(팀 ID)를 이용한다.
AI 이론
- 스스로 상태를 가지고 판단하고 행동하는 객체
- 자신 상태 → (조건) 상태 전이 → 상태 행동 ⇒ 이걸 스스로 진행한다.
- FSM : 유한 상태 머신
- 애니메이션에 사용한다.
- 상태들을 왔다 갔다 하면서 ‘순환’한다.
- Behavior Tree : 언리얼에서 쓰는 상태 구조다.
- root - Service : 상태를 판단하고 처리하는 노드 (여기서 작업이 나뉜다.)
- 시작과 끝이 존재한다.
- BlackBoard : 비헤이비어트리에서 쓸 변수들의 집합
- 비헤이비어 트리에서 사용할 변수들을 모아놓은 공간이다.
- 트리와 1 대 1 관계다.
- FSM : 유한 상태 머신
자잘한 이론
- 상속 받아서 생성한 클래스가 있다면 부모 클래스를 한번 살펴봐라
- 필요한 기능들이 그곳에 있을 수 있다.
- virtual로 선언된 함수를 주로 살펴보자