Replicated

[ChronoSpace] TActorIterator를 이용한 LabyrinthKey 활성화 (Random Activation of Actor) (ClockworkLabyrinth) 본문

언리얼 엔진/ChronoSpace

[ChronoSpace] TActorIterator를 이용한 LabyrinthKey 활성화 (Random Activation of Actor) (ClockworkLabyrinth)

라구넹 2025. 2. 13. 05:16

일단 유니티 SetActive같은게 언리얼 액터에 없다는게 놀랍다

 

void ACSLabyrinthKeyActivator::SetActorActive(AActor* Actor, bool bActive)
{
	Actor->SetActorHiddenInGame(!bActive);
	Actor->SetActorEnableCollision(bActive);
	//SetActorTickEnabled(bActive);
}

Tick 쓸 거면 주석 해제하고 사용

저 대로 쓰면 액티베이트 조정 됨

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CSLabyrinthKeyActivator.generated.h"

UCLASS()
class CHRONOSPACE_API ACSLabyrinthKeyActivator : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ACSLabyrinthKeyActivator();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	void SetActorActive(AActor* Actor, bool bActive);

	void SetLabyrinthKey();

	UPROPERTY()
	TArray< TObjectPtr<class ACSLabyrinthKey> > Keys;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MaxKeyCount")
	int32 MaxKeyCount;
};

// Fill out your copyright notice in the Description page of Project Settings.


#include "Actor/CSLabyrinthKeyActivator.h"
#include "EngineUtils.h"
#include "Actor/CSLabyrinthKey.h"
#include "ChronoSpace.h"

// Sets default values
ACSLabyrinthKeyActivator::ACSLabyrinthKeyActivator()
{
	bReplicates = true;
    
    RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
    
	MaxKeyCount = 3;
}

// Called when the game starts or when spawned
void ACSLabyrinthKeyActivator::BeginPlay()
{
	Super::BeginPlay();
	
	if ( HasAuthority() )
	{
		SetLabyrinthKey();
	}
}

void ACSLabyrinthKeyActivator::SetActorActive(AActor* Actor, bool bActive)
{
	Actor->SetActorHiddenInGame(!bActive);
	Actor->SetActorEnableCollision(bActive);
	//SetActorTickEnabled(bActive);
}

void ACSLabyrinthKeyActivator::SetLabyrinthKey()
{
	for (TActorIterator<ACSLabyrinthKey> It(GetWorld()); It; ++It)
	{
		ACSLabyrinthKey* LabyrinthKey = *It;

		if ( LabyrinthKey )
		{
			SetActorActive(LabyrinthKey, false);
			Keys.Emplace(LabyrinthKey);
		}
	}

	int8 length = Keys.Num();

	if ( length < MaxKeyCount )
	{
		return;
	}

	//UE_LOG(LogCS, Log, TEXT("SetLabyrinthKey : %d"), length);

	int32 ActivatedKeysCount = 0;
	while ( ActivatedKeysCount < MaxKeyCount )
	{
		int8 Idx = FMath::RandRange(0, length);

		if ( !Keys.IsValidIndex(Idx) || Keys[Idx]->GetActorEnableCollision() )
		{
			continue;
		}

		SetActorActive(Keys[Idx], true);

		++ActivatedKeysCount;
	}
}

활성화 시킬 총량 조절 시 MaxCount 조절하면 된다

단 애초에 개수가 부족하면 return

 

월드에 배치하고

 

랜덤으로 오브젝트가 활성화 되는 걸 알 수 있다

 

이렇게 완성된 줄 알았는데 멀티에선 안된다

아까 만든 Unactive 함수로 동기화한게 동기화가 안된 것 같다

어차피 Activator에 SetActive 만든 것도 마음에 안든다 코드 고치자

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CSLabyrinthKey.generated.h"

UCLASS()
class CHRONOSPACE_API ACSLabyrinthKey : public AActor
{
	GENERATED_BODY()
	
public:	
	ACSLabyrinthKey();

	UPROPERTY(ReplicatedUsing = OnRep_bIsActive)
	bool bIsActive;

	UFUNCTION()
	void OnTriggerBeginOverlapCallback(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult);

	UFUNCTION()
	void OnTriggerEndOverlapCallback(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

	UFUNCTION()
	void Interact();

	void SetActive(bool bActive);

protected:
	UPROPERTY(VisibleAnywhere, Category = "Trigger", Meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class USphereComponent> SphereTrigger;

	UPROPERTY(VisibleAnywhere, Category = "Mesh", Meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class UStaticMeshComponent> StaticMeshComp;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<class UWidgetComponent> InteractionPromptComponent;

	UFUNCTION()
	void OnRep_bIsActive();

	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	float TriggerRange = 100.0f;
};


// Fill out your copyright notice in the Description page of Project Settings.


#include "Actor/CSLabyrinthKey.h"
#include "Character/CSCharacterPlayer.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SphereComponent.h"
#include "Physics/CSCollision.h"
#include "Subsystem/CSLabyrinthKeyWorldSubsystem.h"
#include "Blueprint/UserWidget.h"
#include "Components/WidgetComponent.h"
#include "Net/UnrealNetwork.h"
#include "ChronoSpace.h"

// Sets default values
ACSLabyrinthKey::ACSLabyrinthKey()
{
	bReplicates = true;

	// SphereTrigger
	SphereTrigger = CreateDefaultSubobject<USphereComponent>(TEXT("GravitySphereTrigger"));
	SphereTrigger->SetSphereRadius(TriggerRange, true);
	SphereTrigger->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
	RootComponent = SphereTrigger;
	SphereTrigger->SetCollisionProfileName(CPROFILE_CSTRIGGER);
	SphereTrigger->SetIsReplicated(true);

	// Static Mesh
	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	StaticMeshComp->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
	StaticMeshComp->SetupAttachment(SphereTrigger);
	StaticMeshComp->SetCollisionProfileName(CPROFILE_CSCAPSULE);
	StaticMeshComp->SetIsReplicated(true);

	static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/Mesh/StaticMesh/BlockSphere.BlockSphere'"));
	if (StaticMeshRef.Object)
	{
		StaticMeshComp->SetStaticMesh(StaticMeshRef.Object);
	}

	float MeshRadius = 50.0f;
	float MeshScale = (TriggerRange / MeshRadius) * 0.75f;
	StaticMeshComp->SetRelativeScale3D(FVector(MeshScale, MeshScale, MeshScale));

	SphereTrigger->OnComponentBeginOverlap.AddDynamic(this, &ACSLabyrinthKey::OnTriggerBeginOverlapCallback);
	SphereTrigger->OnComponentEndOverlap.AddDynamic(this, &ACSLabyrinthKey::OnTriggerEndOverlapCallback);

	// Widget
	InteractionPromptComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("InteractionPromptComponent"));
	InteractionPromptComponent->SetupAttachment(SphereTrigger);
	InteractionPromptComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 100.0f));

	static ConstructorHelpers::FClassFinder<UUserWidget> InteractionPromptWidgetRef(TEXT("/Game/Blueprint/UI/BP_InteractionPrompt.BP_InteractionPrompt_C"));
	if (InteractionPromptWidgetRef.Class)
	{
		InteractionPromptComponent->SetWidgetClass(InteractionPromptWidgetRef.Class);
		InteractionPromptComponent->SetWidgetSpace(EWidgetSpace::Screen);
		InteractionPromptComponent->SetDrawSize(FVector2D(500.0f, 30.f));
		InteractionPromptComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	}

	InteractionPromptComponent->SetVisibility(false);

	SetActive(false);
}

void ACSLabyrinthKey::OnTriggerBeginOverlapCallback(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
	ACSCharacterPlayer* Player = Cast<ACSCharacterPlayer>(OtherActor);

	if ( Player )
	{
		Player->OnInteract.Clear();
		InteractionPromptComponent->SetVisibility(true);
		Player->OnInteract.AddDynamic(this, &ACSLabyrinthKey::Interact);
	}
}

void ACSLabyrinthKey::OnTriggerEndOverlapCallback(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	ACSCharacterPlayer* Player = Cast<ACSCharacterPlayer>(OtherActor);

	if (Player)
	{
		InteractionPromptComponent->SetVisibility(false);
		Player->OnInteract.Clear();
	}
}

void ACSLabyrinthKey::Interact()
{
	UCSLabyrinthKeyWorldSubsystem* LabyrinthKeySubsystem = GetWorld()->GetSubsystem<UCSLabyrinthKeyWorldSubsystem>();

	if (LabyrinthKeySubsystem)
	{
		LabyrinthKeySubsystem->SetLabyrinthKeyCount((LabyrinthKeySubsystem->GetLabyrinthKeyCount()) + 1);

		UE_LOG(LogCS, Log, TEXT("ACSLabyrinthKey - Interact : %d"), LabyrinthKeySubsystem->GetLabyrinthKeyCount());
	}

	Destroy();
}

void ACSLabyrinthKey::OnRep_bIsActive()
{
	if ( GetWorld() )
	{
		UE_LOG(LogCS, Log, TEXT("[NetMode : %d] OnRep_bIsActive, %d"), GetWorld()->GetNetMode(), bIsActive);
	}
	
	SetActorHiddenInGame(!bIsActive);
	SetActorEnableCollision(bIsActive);
	//SetActorTickEnabled(bIsActive);
}

void ACSLabyrinthKey::SetActive(bool bActive)
{
	if ( HasAuthority() )
	{
		bIsActive = bActive;
		OnRep_bIsActive();
	}
}

void ACSLabyrinthKey::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	DOREPLIFETIME(ACSLabyrinthKey, bIsActive);
}

SetActive 설정을 Key에서 해준다

Replicated 되어 있으면 처음 입장할 때부터 Replication 되니까 Key 각각에서 bIsActive 관리를 해주는게 맞다

 

void ACSLabyrinthKeyActivator::SetLabyrinthKey()
{
	for (TActorIterator<ACSLabyrinthKey> It(GetWorld()); It; ++It)
	{
		ACSLabyrinthKey* LabyrinthKey = *It;

		if ( LabyrinthKey )
		{
			Keys.Emplace(LabyrinthKey);
		}
	}

	int8 length = Keys.Num();

	if ( length < MaxKeyCount )
	{
		return;
	}

	//UE_LOG(LogCS, Log, TEXT("SetLabyrinthKey : %d"), length);

	int32 ActivatedKeysCount = 0;
	while ( ActivatedKeysCount < MaxKeyCount )
	{
		int8 Idx = FMath::RandRange(0, length);

		if ( !Keys.IsValidIndex(Idx) || Keys[Idx]->GetActorEnableCollision() )
		{
			continue;
		}

		Keys[Idx]->SetActive(true);

		++ActivatedKeysCount;
	}
}

액티베이터는 Key의 함수를 발동시키도록 바뀌었다

 

동기화 됐다