공부/Unreal

24.08.12

월러비 2024. 8. 16. 19:01

1교시

무기 시스템 - 워프

  • 데칼은 Attachment에 있다.
    • DoAction의 BeginPlay에서 받아올거다.

마우스 위치 정보 가져오기 테스트

  • Attachment는 따로 소유하지 않을것이다.
    • BeginPlay에서 필요할때 꺼내서 쓸 것이다.
    • 같은 레벨은 서로 소유하지 않아야하기 때문이다.
  • 마우스의 움직이은 Tick 컴포넌트에서 계속 감지해야한다.
    • 이때, 마우스의 위치를 구해서 데칼을 마우스에 위치로 움직일것이다.
  • CDoAction
    • 헤더 파일 - BeginPlay의 매개변수에 Attachment 클래스 추가 - Tick 함수 선언하고 재정의할 수 있도록 virtual을 선언하고 정의한다.
    //header
    virtual void BeginPlay
    	(
    		class ACharacter* InOwner,
    		class ACAttachment* InAttachment,
    		const TArray<FDoActionData>& InDoActionDatas,
    		const TArray<FHitData>& InHitDatas
    	);
    	
    //틱에서는 필요한것만 재정의한다.
    virtual void Tick(float InDeltaTime) {}
    
    • CPP 파일 - BeginPlay 매개 변수 수정
  • CDoAction_Warp
    • 헤더 파일 - BeginPlay 부모 함수 override - Tick 부모함수 override - 카메라의 정보를 가져오기 위해 플레이어 컨트롤러 클래스의 ‘플레이어 컨트롤러’ 변수를 선언한다.
    //header
    private:
    	class UDecalComponent* Decal;
    	class APlayerController* PlayerController;
    
    • CPP 파일 - BeginPlay 함수 매개변수에 Attachment 추가 - 데칼 컴포넌트 자료형을 넣어 ‘컴포넌트 가져오기’ 함수를 호출하고 ‘데칼’ 변수를 선언하고 저장한다. - 매개변수로 들어온 ‘오너 캐릭터’의 ‘컨트롤러 가져오기’ 함수를 ‘플레이어 컨트롤러’ 자료형으로 선정하고 호출하여 ‘클레이어 컨트롤러’ 변수에 저장한다. - Tick 함수 정의 - 부모 Tick 함수 호출 - 마우스 히팅 결과를 저장할 ‘히트 결과’ 변수를 선언한다. - ‘플레이어 컨트롤러’의 ‘채널에 의한 마우스 커서 히팅 정보 가져오기’ 함수를 ‘VIsibility(보여지는 객체’ 전부 히팅되는 방식, 복합 충돌 여부, 히팅 결과를 저장할 변수’를 넣고 호출한다. - ‘히팅 결과’의 ‘블록 출돌했는지 확인’ 변가 false인지 확인한다.
    //CPP
    void UCDoAction_Warp::BeginPlay(ACharacter* InOwner, ACAttachment* InAttachment, const TArray<FDoActionData>& InDoActionDatas, const TArray<FHitData>& InHitDatas)
    {
    	Super::BeginPlay(InOwner, InAttachment, InDoActionDatas, InHitDatas);
    
    	//데칼 가져오기
    	Decal = CHelpers::GetComponent<UDecalComponent>(InAttachment);
    	PlayerController = InOwner->GetController<APlayerController>();
    
    }
    
    void UCDoAction_Warp::Tick(float InDeltaTime)
    {
    	Super::Tick(InDeltaTime);
    
    	//커서 히팅시키고 히팅정보 가져오기
    	FHitResult hitResult;
    	PlayerController->GetHitResultUnderCursorByChannel(ETraceTypeQuery::TraceTypeQuery1, false, hitResult);
    	CheckFalseResult(hitResult.bBlockingHit, false);
    
    }
    
  • CWeaponComponent
    • 웨폰 컴포넌트에서 DoAction 클래스를 소유하고 있으니, GetDoAction이 Null이 아니라면 DoAction의 Tick을 실행한다. → DoAction에서의 Tick은 재정의할 수 있도록 정의를 하지 않았고, 파생 클래스에서 Tick을 사용할 수 있게 하였으니 각각의 파생 Tick에서 사용할때 호출된다.
    • CPP 파일 - Tick 함수 확인 - GetDoAction이 null이 아니라면 GetDoAction을 호출해서 가져온 DoAction의 Tick 함수를 호출해라
    //CPP
    if (!!GetDoAction())
    	{
    		GetDoAction()->Tick(DeltaTime); //DoAction의 틱 이벤트 실행
    	}
    

가져온 마우스 좌표에 데칼 이동시키기

  • 월드 함수를 쓰는 이유 : 마우스 히팅 정보가 리턴되는 값이 월드 값이기 때문이다.
  • CDoAction_Warp
    • CPP 파일 - Tick 함수 확인 - ‘데칼’의 ‘월드 위치 설정’ 함수에 ‘히팅 결과’의 ‘위치’를 넣어 호출한다.
    //CPP
    //hitResult.Location : 임펙트는 '충돌한 위치'를 가져오고, 로케이션은 '충돌한 메쉬의 위치'를 가져온다.
    Decal->SetWorldLocation(location);
    

데칼이 벽에도 제대로 출력되도록 데칼 회전시키기

  • 월드 함수를 쓰는 이유 : 마우스 히팅 정보가 리턴되는 값이 월드 값이기 때문이다.
  • 벽은 단순하게 해도 괜찮기 때문에 충돌체의 위치를 가져와도 괜찮다.
  • CDoAction_Warp
    • CPP 파일 - Tick 함수 확인 - ‘데칼’의 ‘월드 회전 설정’ 함수에 ‘히팅 결과’의 ‘충돌 수직 벡터’의 ‘회전값 반환’ 함수 넣고 호출한다.
    //CPP
    //벽의 데칼 회전시키기
    Decal->SetWorldRotation(rotation);
    

커서 히팅 정보 가져오기 정리

  • 히팅 되면 True, 아니라면 false로 리턴시킨다.
  • CDoAction_Warp
    • 헤더 파일 - ‘커서 위치와 회전값 가져오기’ 함수를 ‘위치값, 회전값’을 받아서 선언
    //header
    private:
    	bool GetCursorLocationAndRotation(FVector& OutLocation, FRotator& OutRotation);
    
    • CPP 파일 - Tick 함수 확인 - 커서의 위치와 회전값을 저장할 ‘위치’ 와 ‘회전’ 변수를 선언하고 초기화한다. - ‘플레이어 컨트롤러’가 비었는지 확인한다. - ‘커서 위치와 회전 가져오기’ 함수에 ‘위치’ 와 ‘회전’ 변수를 넣은 결과가 false 라면 : 커서 히팅이 안된것이니 ‘데칼’의 ‘보여지기 설정’을 false로 하고 return 한다. - 히팅 되었다면 ‘데칼’의 ‘보여지기 설정’ 함수에 true를 넣는다, ‘데칼’의 위치와 회전을 ‘위치’ 변수와 ‘회전’ 변수에 들어온 값을 넣고 호출한다. - 커서 히팅정보를 가져오는 코드를 가져온다. - ‘히팅 결과’의 위치값을 매개변수로 받은 ‘위치’ 변수에 저장한다. - ‘히팅 결과’의 회전값을 매개변수로 받은 ‘회전’ 변수에 저장한다.
    //CPP
    void UCDoAction_Warp::Tick(float InDeltaTime)
    {
    	Super::Tick(InDeltaTime);
    
    	//커서의 위치와 회전값을 저장할 변수 선언 및 초기화
    	FVector location = FVector::ZeroVector;
    	FRotator rotation = FRotator::ZeroRotator;
    
    	//히팅이 안되있으면 데칼을 숨긴다.
    	if (GetCursorLocationAndRotation(location, rotation) == false)
    	{
    		Decal->SetVisibility(false);
    
    		return;
    	}
    
    	Decal->SetVisibility(true);
    
    	//커서 히팅 확인
    	//CLog::Print(hitResult.ImpactPoint, 9999);
    
    	//hitResult.Location : 임펙트는 '충돌한 위치'를 가져오고, 로케이션은 '충돌한 메쉬의 위치'를 가져온다.
    	Decal->SetWorldLocation(location);
    
    	//벽의 데칼 회전시키기
    	Decal->SetWorldRotation(rotation);
    
    }
    
    //커서의 위치와 회전값 가져오기
    bool UCDoAction_Warp::GetCursorLocationAndRotation(FVector& OutLocation, FRotator& OutRotation)
    {
    	CheckNullResult(PlayerController, false);
    
    	//커서 히팅시키고 히팅정보 가져오기
    	FHitResult hitResult;
    	PlayerController->GetHitResultUnderCursorByChannel(ETraceTypeQuery::TraceTypeQuery1, false, hitResult);
    	CheckFalseResult(hitResult.bBlockingHit, false);
    
    	//히팅 정보를 매개변수에 저장하기 => 레퍼런스 매개변수이기 때문에 값이 바뀐다.
    	OutLocation = hitResult.Location;
    	OutRotation = hitResult.ImpactNormal.Rotation();
    
    	return true;
    }
    

플레이어 - 워프 위치로 이동시키기

  • 이동할 위치는 DoAction 함수에서 가져오고, 실제 이동은 Begin_DoAction 함수에서 실행한다.
    • 몽타주가 실행되는 도중에 Begin_Action 이벤트가 실행될때 이동시키는 것이다.
  • CDoAction_Warp
    • 헤더 파일 - 부모의 DoAction 재정의 - 부모의 Begin_DoAction 재정의 - ‘위치로 이동’ 변수 선언
    //header
    public:
    	void DoAction() override; //DoAciton 재정의
    	void Begin_DoAction() override; //DoAciton 재정의
    	
    private:
    	FVector MoveToLocation;
    
    • CPP 파일 - DoAction 정의 - ‘상태 컴포넌트’의 ‘기존 모드 확인’ 함수를 호출해서 false인지 확인한다. - ‘DoAction 데이터’의 ‘요소 수 확인’ 함수를 호출해서 0개 이상인지 확인한다. - 부모의 DoAction 함수 호출 (액션 모드 실행) - ‘회전’ 변수 선언 - ‘커서 위치와 회전 가져오기’ 함수에 ‘위치오 이동, 회전’ 변수를 넣고 호출한 결과가 false 라면 : ‘DoActionDatas’의 0번쨰 순서의 ‘DoAction’ 함수에 ‘오너 캐릭터’를 넣고 호출한다. (워프 몽타주 실행하는 것이다.) - Begin_DoAction 함수 정의 - 부모 함수 호출 - ‘오너 캐릭터’의 ‘엑터 위치 설정’ 함수에 ‘위치로 이동’ 변수를 넣고 호출한다. - ‘위치로 이동’ 값을 초기화한다.
    //CPP
    void UCDoAction_Warp::DoAction()
    {
    	CheckFalse(State->IsIdleMode());
    	CheckFalse(DoActionDatas.Num() > 0); //액션 실행 데이터가 없다면 워프를 실행하지 않는다.
    
    	Super::DoAction();
    
    	FRotator rotation;
    	if (GetCursorLocationAndRotation(MoveToLocation, rotation))
    	{
    		DoActionDatas[0].DoAction(OwnerCharacter);
    	}
    
    	
    }
    
    void UCDoAction_Warp::Begin_DoAction()
    {
    	Super::Begin_DoAction();
    
    	OwnerCharacter->SetActorLocation(MoveToLocation); //워프로 이동 실행
    	MoveToLocation = FVector::ZeroVector; //워프이동 초기화
    }
    
  • ABP_Player
    • 위자드 모션 스테이트 머신 추가 - 위자드 블렌드 스페이스 연결 - 위자드 스테이트 머신 핀 끌고 ‘Save Pose’ 검색 - 블렌드 포즈에 Warp 핀 추가 - ‘Wizard 포즈 사용’ 검색 후 연결
  • 워프 몽타주
    • Begin_Action 노티파이 추가 - End_Action 노티파이 추가 - 파티클 : 샤인 아우라 추가
  • DA_Warp
    • DoActionDatas 추가 - 워프 몽타주 할당 - 워프 몽타주 실행 중 이동 정지

벽이나 바닥에 끼는 문제 방지 - 워프 위치 보정

  • 중심점이 캡슐 절반높이이니 캡슐의 절반 높이정도까지만 보정해주면 된다.
  • CDoAction_Warp
    • CPP 파일 - DoAction 함수 확인 - ‘오너 캐릭터’의 ‘캡슐 컴포넌트 가져오기’ 함수 호출의 ‘캡슐 중심 높이 가져오기’ 함수를 호출하고 ‘높이’ 변수를 선언하고 저장한다. - ‘위치로 이동’의 Z 값에 ‘높이’만큼 더한다.
    //CPP
    //캐릭터의 크기만큼 워프 위치를 보정한다. => 캡슐의 크기를 구한다.
    float height = OwnerCharacter->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
    MoveToLocation.Z += height; //캡슐 크기만큼 워프 z 위치를 보정한다.
    

워프하는 지점 바라보게하기

  • CDoAction_Warp
    • CPP 파일 - ‘바라보는 회전값 찾기’ 함수에 ‘오너캐릭터의 엑터 위치 가져오기 함수 호출, 위치로 이동’ 변수를 넣고 호출하고 그중, Yaw 값만 ‘요’ 변수를 선언하고 저장한다. - ‘오너 캐릭터’의 ‘엑터 회전 설정’ 함수에 ‘회전자(0, 요, 0)’을 넣고 호출한다.
    //CPP
    //워프하는 위치 바라보기
    	float yaw = UKismetMathLibrary::FindLookAtRotation(OwnerCharacter->GetActorLocation(), MoveToLocation).Yaw;
    	OwnerCharacter->SetActorRotation(FRotator(0, yaw, 0));
    

무기 시스템 - 콤보 수정

플레이어 타격시 위치 보정

  • 콤보 공격할때 충돌한 적이 밀려서 타격점을 벗어나는 문제가 있다.
    • 플레이어가 타격한 적들 중 가장 가까운 적을 바라보도록 설정한다.
    • 충돌이 끝났을때 확인한다.
  • Normalize는 3D 공간상에서 일반화이기 때문에 z축까지 1로 줄인다.
    • 평면상의 일반화는 Nomalize2D를 사용해야한다.
    • Yaw 평면의 방향을 일반화할때 사용했다.
  • 즉, 필요한건 타겟을 바라보는 Yaw의 회전값이니 Yaw값만 바라보는 회전값을 넣고 나머지는 컨트롤러의 회전값을 사용한다.
  • CDoAction_Combo 35 13
    • CPP 파일 - OnAttachmentEndCollision 함수 확인 - 각도를 -2.0f 로 저장하고 선언한다. - 바라볼 적을 저장할 캐릭터 클래스의 ‘타겟 후보자’ 변수를 null로 초기화하고 선언한다. - ‘맞았음’ 배열의 요소를 하나씩 꺼내서 ‘맞음’ 변수에 저장한다 : ‘맞음’ 변수의 ‘엑터 위치 가져오기’ 함수를 호출한 결과에서 ‘오너 캐릭터’의 ‘엑터 위치 가져오기’ 함수를 호출한 결과를 뺀 결과를 ‘방향’ 변수를 선언하고 저장한다. - ‘오너 캐릭터’의 ‘엑터 전방 벡터 가져오기’ 함수를 호출한 결과를 ‘전방’ 변수를 선언하고 저장한다. - ‘방향, 전방’ 변수를 ‘내적’ 함수에 넣고 호출한 결과를 ‘내적’ 변수를 선언하고 저장한다. - ‘내적’의 값이 0.75(45도)보다 작거나, ‘내적’ 값이 ‘각도’ 값보다 작다면 : 반복을 건너뛴다. - 조건을 모두 넘기면 ‘내적’ 값을 ‘각도’에 저장하고, ‘맞음’ 을 ‘타겟 후보자’ 변수에 저장한다. - ‘타겟 후보자’가 null이 아니라면 : ‘바라보는 회전값 찾기’ 함수에 ‘오너 캐릭터의 엑터위치 가져오기 함수 호출, 타겟 후보자의 엑터위치 가져오기 함수호출’을 넣고 호출한 결과를 ‘회전자’ 변수를 선언하고 저장한다. , ‘오너 캐릭터’의 ‘컨트롤러 가져오기’ 함수를 ‘플레이어 컨트롤러’로 지정하고 호출한 결과를 ‘컨트롤러’ 변수를 선언하고 저장한다. , ‘컨트롤러’가 null이 아니라면 : ‘컨트롤러’의 ‘컨트롤 회전 가져오기’ 함수를 호출한 결과를 ‘컨트롤 회전’ 변수를 선언하고 저장한다. , ‘회전자’ 함수에 ‘컨트롤 회전의 X 회전값, 회전자의 Z 회전값, 컨트롤 회전의 Y 회전값’을 넣고 호출한 결과를 ‘타겟’ 변수를 선언하고 저장한다. - ‘컨트롤러’의 ‘컨트롤 회전 설정’ 함수에 ‘타겟’을 넣고 호출한다.
    //CPP
    void UCDoAction_Combo::OnAttachmentEndCollision()
    {
    	Super::OnAttachmentEndCollision();
    
    	//타격 보정
    	float angle = -2.0f;
    	ACharacter* candidate = nullptr;
    
    	for (ACharacter* hitted : Hitted)
    	{
    		FVector direction = hitted->GetActorLocation() - OwnerCharacter->GetActorLocation();
    		direction = direction.GetSafeNormal2D(); //요 회전은 2D로 가져와야한다.
    
    		FVector forward = OwnerCharacter->GetActorForwardVector();
    
    		//정면에서 가장 가까운 적 탐색
    		float dot = FVector::DotProduct(direction, forward);
    		if (dot < 0.75f || dot < angle)
    		{
    			continue;
    		}
    
    		angle = dot;
    		candidate = hitted;
    
    	}
    
    	//보정 시작
    	if (!!candidate)
    	{
    		FRotator rotator = UKismetMathLibrary::FindLookAtRotation(OwnerCharacter->GetActorLocation(), candidate->GetActorLocation());
    
    		APlayerController* controller = OwnerCharacter->GetController<APlayerController>();
    
    		if (!!controller)
    		{
    			FRotator controlRotation = controller->GetControlRotation();
    			FRotator target = FRotator(controlRotation.Pitch, rotator.Yaw, controlRotation.Roll);
    
    			controller->SetControlRotation(target);
    		}
    	}
    
    	//충돌 리스트 초기화
    	Hitted.Empty();
    }
    

무기 시스템 - 활

활 밑준비

  1. 애니메이션 준비
    1. 블렌드 스페이스 생성
  2. 활 모델 준비
    1. 스켈레톤 선택할때 스켈레톤을 새로 만들지 말고 아니요 - 활 스켈레톤 선택 으로 진행한다.
    2. 활 메테리얼 할당
  3. 활을 등장시킬 소켓 추가
    1. Holster_Bow
  4. Bow 데이터 에셋 생성

워프

  • 카메라 관련 조정은 ‘플레이어 컨트롤러’에서 한다.
    • 컨트롤러가 ‘카메라’를 가지고 있다.
  • frustum(절두체) : 원근감이 있는 카메라 영상을 투영한다.
    • 화면에 투영하는 view 영역은 피라미드의 윗부분을 자른 형태로 투영된다.
      • 이러한 투영을 ‘원근 투영 (Perspective)’이라고 한다.
    • 좁은 부분 : Near / 넓은 부분 : Far
    • width와 height의 비율 : 종횡비(aspect)
      • W / H
      • fullHD : 1920 * 1080
        • 즉, 이때의 종횡비는 대략 1.7 : 1 이다.
    • near ~ far 수직선과 대각선 각도 : feild of view (FOV)
      • 크게 주면 줄수록 가로 세로 폭이 커진다.
      • 일반적으로 FOV 45도일때 H 값이 1배수에 맞게 조절을 한다.
      • 3D 공간상에서 마우스의 위치를 가져오는것은 FOV값의 비율에 따라 가져와진다.
        • 화면에 비춰지는 Near부터 마우스까지의 레이에서 히팅되는 물체의 정보를 가져오는것과 같다고 생각하면 될것같다.
      • ‘피킹’이라고 한다.

Impact와 Normal의 차이

  • Impact : 충돌체와 삼각형 정보를 가진 메쉬를 탐지했을때, ‘충돌체’의 위치 정보를 가져오는것이 ‘Impact’다.
    • 메쉬 위에 충돌체는 살짝 띄워져서 그려지기 때문에 메쉬를 탐지하는것보다는 정밀하지 않다.
    • 메쉬를 이루는 정밀한 삼각형의 위치를 가져오는것이 ‘Location’같이 Impact가 안붙은 변수들이다.

메쉬 확인 방법

  • 메쉬 파일 - 좌측 상단 라이팅 포함 버튼 클릭 - 와이어 프레임으로 변경

워프 벽이나 바닥에 끼는 문제

  • 캐릭터의 충돌체의 중심점은 ‘배’인데 중심점을 기준으로 위치이동을 하다가 충돌체끼리 겹쳐서 끼는 것이다.
    • 이동 직후 캐릭터의 위치만큼 올린다.
  • 충돌체가 있는 벽에 갖히는 이유 : 충돌체를 딱 맞춰서 디자인하기 때문이다.
    • 충돌체를 살짝 크게 만들어야한다.
    • 다른 해결법 : BlockingVolum : 접근을 못하게 물리적으로 막는 엑터다.

BlockingVolume

  • 물리적으로 접근을 못하게 막는 엑터다.
  • 엑터 배치 창 - BlockingVolume 배치
  • 배경을 디자인할때 충돌체보다 조금 크게 배치한다.

콤보 보정

  • 플레이어가 적을 타격할때 적을 바라보게 살짝 보정을 해야한다.
  • 적도 위치와 회전을 보정한다.

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

24.08.16  (0) 2024.08.20
24.08.13  (0) 2024.08.16
24.08.09  (0) 2024.08.10
24.08.08  (0) 2024.08.09
24.08.07  (0) 2024.08.09