ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Camera Interp location
    Game Programming/언리얼 2023. 10. 21. 21:56

    아이템을 주울 때 최종 위치를 구현했다.

    void AMyCharacter::SelectButtonPressed()
    {
    	if (TraceHitItem)
    	{
    		auto TraceHitWeapon = Cast<AWeapon>(TraceHitItem);
    		TraceHitWeapon->StartItemCurve(this);
    	}
    }

    아이템을 줍는 키를 누르면 StartItemCurve 함수를 호출한다.

    void AWeapon::StartItemCurve(AMyCharacter* Char)
    {
    	/* Store a handle to the Charater*/
    	Character = Char;
    	ItemInterpStartLocation = GetActorLocation();
    	bInterping = true;
    	SetItemState(EItemState::EIS_EquipInterping);
    
    	GetWorldTimerManager().SetTimer(
    		ItemInterpTimer, 
    		this, 
    		&AWeapon::FinishInterping,
    		ZCurveTime);
    
    	// Get initial Yaw of the Camera
    	const double CameraRotationYaw{ Character->GetFollowCamera()->GetComponentRotation().Yaw };
    	// Get initial Yaw of the Item
    	const double ItemRotationYaw{ GetActorRotation().Yaw };
    	// Initial Yaw offset between Camera and Item
    	InterpInitialYawOffset = ItemRotationYaw - CameraRotationYaw;
    }

    StartItemCurve 함수는 아이템의 위치와 회전 차이를 가져오고 타이머를 동작시킨다.

    void AWeapon::ItemInterp(float DeltaTime)
    {
    	if (!bInterping)
    		return;

    bInterping 변수가 false이면 tick에서 돌아가는 ItemInterp함수가 호출되지 않는다.

    	case EItemState::EIS_EquipInterping:
    		PickupWidget->SetVisibility(false);
    		ItemMesh->SetSimulatePhysics(false);
    		ItemMesh->SetEnableGravity(false);
    		ItemMesh->SetVisibility(true);
    		ItemMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
    		ItemMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    		// Set AreaSphere properties
    		AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
    		AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    		// Set CollisionBox properties
    		CollisionBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
    		CollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    		break;

    중력의 영향을 받지 않도록 물리 상태를 설정했다.

    	// Get initial Yaw of the Camera
    	const double CameraRotationYaw{ Character->GetFollowCamera()->GetComponentRotation().Yaw };
    	// Get initial Yaw of the Item
    	const double ItemRotationYaw{ GetActorRotation().Yaw };
    	// Initial Yaw offset between Camera and Item
    	InterpInitialYawOffset = ItemRotationYaw - CameraRotationYaw;

    카메라 정면을 바라보도록 회전량을 보간하기 위해 회전차이를 구했다.

     

    Tick에서 동작하는 ItemInterp함수

    void AWeapon::ItemInterp(float DeltaTime)
    {
    	if (!bInterping)
    		return;
    
    	if (Character && ItemZCurve)
    	{
    		ZCurve와 타이머를 동기화 시켜 Z값 이동이 Curve와 비례하도록한다.
    		const float ElapsedTime = GetWorldTimerManager().GetTimerElapsed(ItemInterpTimer);
    	
    		const float CurveValue = ItemZCurve->GetFloatValue(ElapsedTime);
    		아이템 시작 위치
    		FVector ItemLocation = ItemInterpStartLocation;
    		케릭터의 손 위치
    		const FVector HandInterpLocation{ Character->GetHandInterpLocation() };
    		
            아이템과 케릭터의 손 위치간의 편차
    		const FVector ItemToHand{ FVector(0.f, 0.f, (HandInterpLocation - ItemLocation).Z) };
    		편차를 크기로 바꾼다.
    		const float DeltaZ = ItemToHand.Size();
    		
            현재 아이템의 위치
    		const FVector CurrentLocation{ GetActorLocation() };
            현재 위치와 손 위치의 X, Y 값을 보간한다.
    		const float InterpXValue = FMath::FInterpTo(
    			CurrentLocation.X, 
    			HandInterpLocation.X, 
    			DeltaTime, 
    			1.f);
    
    		const float InterpYValue = FMath::FInterpTo(
    			CurrentLocation.Y,
    			HandInterpLocation.Y,
    			DeltaTime,
    			1.f);
    
    		변경 할 아이템 위치로 Set
    		ItemLocation.X = InterpXValue;
    		ItemLocation.Y = InterpYValue;
    
    		if (GEngine)
    			GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("%f %f"), ItemLocation.X, ItemLocation.Y));
    		
            Curve값과 아이템과 손위치간의 편차를 곱해 Z값이 Curve의 값대로 상승, 하강 하도록한다.
    		ItemLocation.Z += CurveValue * DeltaZ;
    		
            아이템의 위치를 세팅한다.
    		SetActorLocation(ItemLocation, true, nullptr, ETeleportType::TeleportPhysics);
    
    		카메라의 Z축 회전량을 가져오고, 카메라의 정면을 바라 보도록 편차를 더 해준다.
    		const FRotator CameraRotation{ Character->GetFollowCamera()->GetComponentRotation() };
    
    		FRotator ItemRotation{ 0.f, CameraRotation.Yaw + InterpInitialYawOffset, 0.f };
    		SetActorRotation(ItemRotation, ETeleportType::TeleportPhysics);
    		
            아이템 크기 커브
    		if (ItemScaleCurve)
    		{
                타이머와 커브를 동기화 시켜 스케일을 세팅한다.
    			const float ScaleCurveValue = ItemScaleCurve->GetFloatValue(ElapsedTime);
    			SetActorScale3D(FVector(ScaleCurveValue, ScaleCurveValue, ScaleCurveValue));
    		}
    	}
    }

    타이머 동작이 완료되면 호출되는 FinishInterping함수

    void AWeapon::FinishInterping()
    {
    	Tick에서 ItemInterp 함수가 호출되지 않도록한다.
    	bInterping = false;
    	
        케릭터가 아이템을 집도록한다.
    	if (Character)
    	{
    		Character->GetPickupItem(this);
    	}
    	아이템 스케일을 1로 마춘다.
    	SetActorScale3D(FVector(1.f));
    }

    아이템의 최종 위치는 아래와 같이 구현했다.

    아이템의 최종위치

    FVector AMyCharacter::GetCameraInterpLocation()
    {
    	const FVector CameraWorldLocation{ FollowCamera->GetComponentLocation() };
    	const FVector CameraForward{ FollowCamera->GetForwardVector() };
    	// Desired = CameraWorldLocation + Forward * A + Up * B
    	return CameraWorldLocation + CameraForward * CameraInterpDistance + FVector(0.f, 0.f, CameraInterpElevation);
    }

    CameraWorldLocation + Forward * A + Up * B (A는 카메라의 정면으로부터 떨어진 거리, B는 위로부터 떨어진 거리)

    FVector AMyCharacter::GetHandInterpLocation()
    {
    	const USkeletalMeshSocket* HandSocket = GetMesh()->GetSocketByName(FName("hand_rSocket"));
    	if (HandSocket)
    	{
    		FQuat fquad;
    
    		FTransform transfrom = HandSocket->GetSocketTransform(GetMesh());
    		const FVector HandWorldLocation{ transfrom.GetLocation()};
    		const FVector HandForward{ transfrom.TransformRotation(fquad).Vector().ForwardVector};
    		const FVector HandUp{ transfrom.TransformRotation(fquad).Vector().UpVector };
    		// Desired = CameraWorldLocation + Forward * A + Up * B
    		return HandWorldLocation + HandForward * HandInterpDistance + HandUp * FVector(0.f, 0.f, HandInterpElevation);
    	}
    	return FVector();
    }

    나는 손으로 아이템이 부착 되는 효과를 내려고 코드를 수정했다. 손의 행렬을 읽어와서 포어드, 업 벡터를 구했다.

     

    언리얼 에디터

    Z Curve를 만들었다.

     

    Z Curve 에디터

     

    AddKey를 추가해 Z Cruve의 (t, y)위치를 수정 할 수 있다.

    Z Curve 에디터

    좌측 상단의 Zoom To Fit 기능을 사용해서 Key를 누른 후 버튼을 누르면 Key에 맞는 해상도로 변경이된다.

    전체 Key를 드래그 후 Zoom To Fit 버튼을 누르면 전체 키에 맞는 해상도로 변경된다.

     

    Z Curve 에디터

    Key를 선택하면 기울기를 조절할 수 있는 막대선이 나오는데, Break를 선택하면 왼쪽과 오른쪽 막대를 각각 조절 할 수 있다.

    'Game Programming > 언리얼' 카테고리의 다른 글

    총알 클래스 제작  (0) 2023.10.22
    Create Door Trigger  (1) 2023.10.20
    문 소켓 추가  (0) 2023.10.19
    문 물리 작용 업데이트  (0) 2023.10.19
    무기 교체  (0) 2023.10.18
Designed by Tistory.