공부/Unreal

24.08.26

월러비 2024. 8. 27. 15:36

UI

UI 거리 비례 조정

  • CEnemy_AI
    • 생성된 위젯은 코드로 생성한 것이기 때문에 함수에서 사용할때 마다 해당 위젯을 상속한 클래스로 ‘캐스트’해서 저장한 다음에 사용해야 한다.
    • 카메라를 가져오는 방법
      1. 플레이어 컨트롤러를 통해 가져온다.
      2. 게임 플레이 스테틱스의 ‘플레이어 카메라 메니저 가져오기’ 함수 사용
    • 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 명령이 들어갔냐를 말한다.
          • 최적화와 관계가 있다.
      • 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 로 변환하는 마지막 과정이다.
          • 화면은 한 점을 기준으로 상(y : +1) 하(y : -1) 좌(x : -1) 우(x : +1)이다.
            • 해상도는 기기마다 다르니 이렇게 계산이 된다.
            • 3D 상으로는 한 점을 기준(0)으로 정면(+1)만 그린다.
              • 2D 화면상으로 뒤통수는 보이지 않기 때문이다.
              • 때문에 ‘깊이’는 자신부터 앞에만 그린다.
              • 이것을 임의로 계산해야하는 상황이 있다. ⇒ ‘UI’ (3D에서 2D로 변환해야한다.)
              • 반대의 경우 (2D에서 3D로 변환해야한다.) : 마우스 클릭(2d의 마우스를 클릭하면 3d 공간상의 좌표가 생성되어야한다.)
      • RS : 3D를 2D로 변환하는 과정이다.
        • 이 작업은 GPU가 하는게 아니라, 그래픽 카드 안에 ‘레스터라이징 장치’가 따로 있다.
      • PS : 2D로 변한 화면에서 ‘픽셀들’을 처리하는 쉐이더다.
        • GPU가 처리한다.
      • OM : 모니터에 직접적으로 비추는 과정이다.

직교 투영 확인하는 법

  • 카메라 - 디테일 - 카메라 세팅 - 투영 모드 : 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 관계다.

자잘한 이론

  • 상속 받아서 생성한 클래스가 있다면 부모 클래스를 한번 살펴봐라
    • 필요한 기능들이 그곳에 있을 수 있다.
  • virtual로 선언된 함수를 주로 살펴보자

'공부 > Unreal' 카테고리의 다른 글

24.08.28  (0) 2024.08.30
24.08.27  (6) 2024.08.28
24.08.23  (0) 2024.08.24
24.08.22  (0) 2024.08.24
24.08.21  (3) 2024.08.23