ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Create Door
    Game Programming/언리얼 2023. 10. 15. 22:30

    cpp클래스에 Item 클래스를 상속받는 ACDoor 클래스를 선언했다.


    CDoor.h

    protected:
    	virtual void BeginPlay() override;
    	virtual void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent,
    		AActor* OtherActor,
    		UPrimitiveComponent* OtherComp,
    		int32 OtherBodyIndex,
    		bool bFromSweep,
    		const FHitResult& SweepResult) override;
    
    	virtual void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent,
    		AActor* OtherActor,
    		UPrimitiveComponent* OtherComp,
    		int32 OtherBodyIndex) override;

    상속 받을 함수를 가상함수로 재정의했다. 언리얼은 override 키워드까지 붙여줘야했다.

    하지만 이 때 문제가 발생했다. 

    부모에게 UFUNCTION을 주고 A, B객체에서 오버라이드 시키면 컴파일러는 어떤 자식에게 UFUNCTION을 주는지 인식할 수 없는 문제였다.

    구글 검색 내용

    따라서 부모 클래스에는 OnSphereOverlap, OnSphereEndOverlap를 제외하고 자식에서 각각 구현했다.

     

    	UFUNCTION()
    	void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent,
    			AActor* OtherActor,
    			UPrimitiveComponent* OtherComp,
    			int32 OtherBodyIndex,
    			bool bFromSweep,
    			const FHitResult& SweepResult);
    	UFUNCTION()
    	void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent,
    			AActor* OtherActor,
    			UPrimitiveComponent* OtherComp,
    			int32 OtherBodyIndex);

     

    private:
    	UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = Map, meta = (AllowPrivateAccess = "true"));
    	class USceneComponent* SceneComponent;
    	UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = Map, meta = (AllowPrivateAccess = "true"));
    	class UStaticMeshComponent* LeftDoorComponent;
    	UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = Map, meta = (AllowPrivateAccess = "true"));
    	class UStaticMeshComponent* RightDoorComponent;
    	UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = Map, meta = (AllowPrivateAccess = "true"));
    	class UStaticMeshComponent* CenterDoorComponent;
    
    	TArray<class UMaterial*> LoadMaterial;
    
    	TArray<class UMaterialInstanceDynamic*> DynamicMaterialInst;
    
    	//if 0 Open 1 Close
    	int8 nOpenClose;
    	//is Open?
    	bool bOpenClose;
    	//Timer Duration
    	float OpenCloseDuration;
    	//Timer for Door open,close
    	FTimerHandle DoorOpenCloseTimer;
    
    	void DoorOpenCloseFunc();
    	void CreateStaticComponent();

    컴포넌트의 루트가 될 USceneComponent를 선언하고, 문의 왼쪽, 오른쪽, 가운데 악세세리 UStaticMeshComponent 3개를 선언했다.

     

    에셋으로부터 읽어오는 Material을 저장하기 위해 TArray형식으로 LoadMaterl를 선언했다.

    다이나믹 머터리얼을 저장하기 위해 마찬가지로 DynamicMaterialInst를 선언했다.

     

    나머지 변수와 함수는 소스 파일을 보면서 같이 설명하겠다.

     


    ACDoor.cpp

    먼저 CreateStaticComponent를 설명하자면

    	SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneLocation"));
    	SceneComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
    	SetRootComponent(SceneComponent);

    USceneComponent를 생성하고, 루트 컴포넌트로 잡아준다.

    UStaticMesh* MeshToDoor = Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(), NULL, TEXT("/Game/Voyager/Demo/Levels/UndergroundBaseLevel/Meshes/SM_Door.SM_Door")));
    }

    UStaticMesh를 StaticLoadObject 함수로 에셋에서 읽어오는데, 

     

    언리얼 도큐먼트를 읽으니 아래와 같은 인자를 받았다.

    언리얼 공식 홈페이지

     

    그리고 메시를 SetStaticMesh 함수를 사용하여 StaticMeshComponent의 Mesh로 지정하고

    오른쪽 문의 경우 Z축 180도 회전을 시켜줘 쌍이 이루어지도록 했다.

    if (MeshToDoor && LeftDoorComponent && RightDoorComponent)
    {
        //Set Mesh to UStaticMeshComponent
        LeftDoorComponent->SetStaticMesh(MeshToDoor);
        RightDoorComponent->SetStaticMesh(MeshToDoor);
        RightDoorComponent->SetRelativeLocation(FVector(0.f, 0.f, 0.f));
    
        //Rotate 180 degree z axis
        FQuat fRot;
        fRot.Z = 180.f;
        RightDoorComponent->SetRelativeRotation(fRot);

    다음은 Material을 에셋으로부터 읽어오고, TArray LoadMaterial에 넣어줬다.

    //Set Material 
    {
        ConstructorHelpers::FObjectFinder<UMaterial> FoundMaterial(TEXT("/Game/Voyager/Demo/Levels/UndergroundBaseLevel/Materials/M_DemoWall_Inst_3.M_DemoWall_Inst_3"));
        if (FoundMaterial.Succeeded())
        {
            LoadMaterial.Push(FoundMaterial.Object);
        }
    }
    
    {
        ConstructorHelpers::FObjectFinder<UMaterial> FoundMaterial(TEXT("/Game/Voyager/Demo/Levels/UndergroundBaseLevel/Materials/M_GlasDoor_Inst.M_GlasDoor_Inst"));
        if (FoundMaterial.Succeeded())
        {
            LoadMaterial.Push(FoundMaterial.Object);
        }
    }

    로드한 Material을 TArray MaterialInst에 넣어주었다.

    if (LoadMaterial.Num() == 2)
    {
        DynamicMaterialInst.Push(UMaterialInstanceDynamic::Create(LoadMaterial[0], nullptr));
        DynamicMaterialInst.Push(UMaterialInstanceDynamic::Create(LoadMaterial[1], nullptr));
        DynamicMaterialInst.Push(UMaterialInstanceDynamic::Create(LoadMaterial[0], nullptr));
        DynamicMaterialInst.Push(UMaterialInstanceDynamic::Create(LoadMaterial[1], nullptr));
    
        LeftDoorComponent->SetMaterial(0, DynamicMaterialInst[0]);
        LeftDoorComponent->SetMaterial(1, DynamicMaterialInst[1]);
        RightDoorComponent->SetMaterial(0, DynamicMaterialInst[0]);
        RightDoorComponent->SetMaterial(1, DynamicMaterialInst[1]);
    }

    문 가운데 엑세서리도 마찬가지로 Material을 지정한 후, 엑세서리의 루트를 왼쪽 문으로 지정했다.

    if (CenterDoorComponent && LeftDoorComponent)
        CenterDoorComponent->AttachToComponent(LeftDoorComponent, FAttachmentTransformRules::KeepRelativeTransform);

    그 뒤, AMyCharacter 객체에서 충돌이 발생하면 bShouldTraceForDoors를 true로 설정했다.


    AMyCharacter.cpp

    void AMyCharacter::IncrementOverlappedDoorCount(int8 Amount)
    {
    	if (OverlappedDoorCount + Amount <= 0)
    	{
    		OverlappedDoorCount = 0;
    		bShouldTraceForDoors = false;
    		GEngine->AddOnScreenDebugMessage(1, 1, FColor::Green, FString::Printf(TEXT("IncrementOverlappedDoorCount Off")));
    	}
    	else
    	{
    		OverlappedDoorCount += Amount;
    		bShouldTraceForDoors = true;
    		GEngine->AddOnScreenDebugMessage(1, 1, FColor::Green, FString::Printf(TEXT("IncrementOverlappedDoorCount On")));
    	}
    }

    충돌이 발생한 상태에서 LineTrace를 하면 PickUpWidget이 꺼지도록 설정했다.

    void AMyCharacter::TraceForDoors()
    {
    	if (bShouldTraceForDoors)
    	{
    		FHitResult ItemTraceResult;
    		FVector HitLocation;
    		TraceUnderCrosshairs(ItemTraceResult, HitLocation);
    
    		if (ItemTraceResult.bBlockingHit)
    		{
    			GEngine->AddOnScreenDebugMessage(2, 1, FColor::Green, FString::Printf(TEXT("TraceForDoors bBlockingHit")));
    
    			ACDoor* HitItem = Cast<ACDoor>(ItemTraceResult.GetActor());
    
    			if (HitItem && HitItem->GetPickupWidget())
    			{
    				HitItem->GetPickupWidget()->SetVisibility(true);
    			}
    
    			// We hit an AItem last frame
    			if (TraceHitDoorLastFrame)
    			{
    				if (HitItem != TraceHitDoorLastFrame)
    				{
    					// We are hitting a different AItem this frame form last frame
    					// Or AItem is null
    					TraceHitDoorLastFrame->GetPickupWidget()->SetVisibility(false);
    				}
    			}
    			// Store a reference to HitItem for next frame
    			TraceHitDoorLastFrame = HitItem;
    		}
    		else
    		{
    			// No longer overlapping any items,
    			// Item last frame shold not show widget
    			TraceHitDoorLastFrame->GetPickupWidget()->SetVisibility(false);
    		}
    	}
    	else
    	{
    		GEngine->AddOnScreenDebugMessage(4, 1, FColor::Green, FString::Printf(TEXT("TraceHitItemLastFrame = nullptr Up Code")));
    		if (TraceHitDoorLastFrame)
    		{
    			GEngine->AddOnScreenDebugMessage(4, 1, FColor::Green, FString::Printf(TEXT("TraceHitItemLastFrame = nullptr")));
    			TraceHitDoorLastFrame->GetPickupWidget()->SetVisibility(false);
    			TraceHitDoorLastFrame = nullptr;
    		}
    	}
    }

    만약, LineTrace로 추적된 객체가 있는 상태에서 키보드 'T'를 누르면 ACDoor객체의 문을 여는 함수를 동작시켰다.

    void AMyCharacter::InteractObstaclePressed()
    {
    	if (TraceHitDoorLastFrame)
    	{
    		TraceHitDoorLastFrame->OpencloseDoor();
    	}
    }

    문을 여는 함수의 내부는 아래와 같다.

    타이머가 동작 중이라면 타이머 재 동작을 방지하고, 문이 열린 상태이면 문을 닫고 반대이면 문을 열도록 했다.


    CDoor.cpp

    void ACDoor::OpencloseDoor()
    {
    	if (bOpenClose == false)
    	{
    		bOpenClose = true;
    
    		if (nOpenClose == 1)
    			nOpenClose = 0;
    		else
    			nOpenClose = 1;
    		
    		GetWorldTimerManager().SetTimer(
    			DoorOpenCloseTimer,
    			this,
    			&ACDoor::DoorOpenCloseFunc,
    			OpenCloseDuration);
    	}
    }

    타이머 내부에 등록되는 함수는, 문 좌측 우측을 간단하게 이동시키며 문 열고 닫기를 구현했다.

    문의 이동거리가 설정 값 까지 도달하도록 타이머를 재 호출 시켜줬다.

    void ACDoor::DoorOpenCloseFunc()
    {
    	if (nOpenClose == 0)
    	{
    		FVector vecLeftPos = LeftDoorComponent->GetRelativeLocation();
    		FVector vecRightPos = RightDoorComponent->GetRelativeLocation();
    
    		GEngine->AddOnScreenDebugMessage(3, 1, FColor::Green, 
    			FString::Printf(TEXT("%f %f"), vecLeftPos.Y, vecRightPos.Y));
    
    		if (vecLeftPos.Y >= 1.f && -1.f >= vecRightPos.Y)
    		{
    			vecLeftPos.Y -= 1;
    			vecRightPos.Y += 1;
    			LeftDoorComponent->SetRelativeLocation(vecLeftPos);
    			vecLeftPos = CenterDoorComponent->GetRelativeLocation();
    			vecLeftPos.Y -= 1;
    			CenterDoorComponent->SetRelativeLocation(vecLeftPos);
    			RightDoorComponent->SetRelativeLocation(vecRightPos);
    			
    			if (LeftDoorComponent->GetRelativeLocation().Y != 0.f &&
    				RightDoorComponent->GetRelativeLocation().Y != 0.f)
    			{
    				GetWorldTimerManager().SetTimer(
    					DoorOpenCloseTimer,
    					this,
    					&ACDoor::DoorOpenCloseFunc,
    					OpenCloseDuration);
    			}
    			else
    				bDoorTimerActive = false;
    		}
    	}
    	else
    	{
    		FVector vecLeftPos = LeftDoorComponent->GetRelativeLocation();
    		FVector vecRightPos = RightDoorComponent->GetRelativeLocation();
    
    		GEngine->AddOnScreenDebugMessage(3, 1, FColor::Green, FString::Printf(TEXT("Door open")));
    
    		if (vecLeftPos.Y <= 100.f && -100.f <= vecRightPos.Y)
    		{
    			vecLeftPos.Y += 1;
    			vecRightPos.Y -= 1;
    			LeftDoorComponent->SetRelativeLocation(vecLeftPos);
    			vecLeftPos = CenterDoorComponent->GetRelativeLocation();
    			vecLeftPos.Y += 1;
    			CenterDoorComponent->SetRelativeLocation(vecLeftPos);
    			RightDoorComponent->SetRelativeLocation(vecRightPos);
    
    			if (LeftDoorComponent->GetRelativeLocation().Y != 100.f &&
    				RightDoorComponent->GetRelativeLocation().Y != -100.f)
    			{
    				GetWorldTimerManager().SetTimer(
    					DoorOpenCloseTimer,
    					this,
    					&ACDoor::DoorOpenCloseFunc,
    					OpenCloseDuration);
    			}
    			else
    				bDoorTimerActive = false;
    		}
    	}
    }

     

     

     

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

    클래스 배열 PickupWdigetBP 바인딩  (1) 2023.10.17
    Bind item name  (1) 2023.10.16
    UMG  (1) 2023.10.12
    아이템, 기본 무기 추가  (0) 2023.10.11
    점프 애니메이션 추가  (0) 2023.10.11
Designed by Tistory.