기존의 머티리얼에서는 어두운 장소에서 Gimmick들이 잘 보이지 않는다는 문제가 있었습니다.
이에 머티리얼에 Emissive 파라매터를 추가한 뒤 각 상태에 따라 다른 값을 줄 수 있도록 코드를 추가했습니다. 에디터 상에서 수정할 수 있도록 매크로도 작성을 해두었고요.

UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Mission|Effect")
FLinearColor InactiveColor = FLinearColor::White;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Mission|Effect")
FLinearColor ActiveColor = FLinearColor(0.f, 1.f, 1.f);
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Mission|Effect")
FLinearColor CompleteColor = FLinearColor::Black;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Mission|Effect")
FLinearColor InactiveEmissiveColor;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Mission|Effect")
FLinearColor ActiveEmissiveColor;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Mission|Effect")
FLinearColor CompleteEmissiveColor;
void AVGMissionGimmickBase::OnRep_GimmickStateTag()
{
// Todo State 변경에 따른 피드백 처리
if (!BodyDynMat)
{
// [Fix] 메시에 머티리얼이 없을 경우 GetMaterial(0)이 nullptr → Create 크래시 방지
UMaterialInterface* BaseMaterial = MeshComponent ? MeshComponent->GetMaterial(0) : nullptr;
if (!BaseMaterial)
{
return;
}
BodyDynMat = UMaterialInstanceDynamic::Create(BaseMaterial, this);
if (!BodyDynMat)
{
return;
}
MeshComponent->SetMaterial(0, BodyDynMat);
}
FLinearColor Color = InactiveColor;
FLinearColor EmissiveColor = InactiveEmissiveColor;
if (GimmickStateTag == VigilantMissionTags::GimmickCompleted)
{
Color = CompleteColor;
EmissiveColor = CompleteEmissiveColor;
MeshComponent->SetOverlayMaterial(nullptr);
}
else if (GimmickStateTag == VigilantMissionTags::GimmickActive)
{
Color = ActiveColor;
EmissiveColor = ActiveEmissiveColor;
}
BodyDynMat->SetVectorParameterValue(TEXT("Color"), Color);
BodyDynMat->SetVectorParameterValue(TEXT("EmissiveColor"), EmissiveColor);
}
현재 배치된 미션들의 조건상 기존 일반 발판의 '플레이어가 내려올 시 밟혀서 내려간 발판을 다시 올린다'는 로직은 아무 쓸모가 없고 오히려 방해가 되는 요소가 되었습니다. 해당 로직을 주석 처리하였습니다.
void AVGMissionGimmickPressure::OnReleased()
{
if (!HasAuthority())
{
return;
}
if (bToggleMode)
{
return;
}
// 이미 완료된 경우 상태 되돌리지 않음
if (GimmickStateTag == VigilantMissionTags::GimmickCompleted)
{
return;
}
// UE_LOG(LogTemp, Warning, TEXT("[Pressure:%s] OnReleased"), *GetName());
// SetStateTag(VigilantMissionTags::GimmickInactive);
}
Statue는 정답 각도에 도달하면 Complete 상태가 되면서 더 이상 상호작용이 안 되도록 막혀 버리는 로직이 존재했습니다.
그러나 팀원분께서 그러면 너무 단순해지니 정답 각도여도 그것을 표시하지 말고 모든 석상이 정답 각도일 때만 Complete로 전환하는 것이 어떻냐는 의견을 주셨습니다.
그래서 해당 기능을 추가했습니다. 관련 미션도 구현을 해두었고요.
void AVGMissionGimmickStatue::RotateToTarget(float DeltaTime)
{
if (GimmickStateTag != VigilantMissionTags::GimmickActive)
{
return;
}
FRotator Current = GetActorRotation();
FRotator Target = FRotator(Current.Pitch, TargetAngle, Current.Roll);
FRotator New = FMath::RInterpConstantTo(Current, Target, DeltaTime, RotationSpeed);
SetActorRotation(New);
// 목표 각도 도달 시 회전 완료
// FRotator::NormalizeAxis : 각도 값을 -180~180 범위로 정규화해줍니다.
float YawDiff = FMath::Abs(FRotator::NormalizeAxis(New.Yaw - TargetAngle));
if (YawDiff < AngleTolerance)
{
SetActorRotation(Target);
SetActorTickEnabled(false);
// 서버에서만 정답 체크
if (HasAuthority())
{
if (IsAtAnswerAngle() && !bIsOnAnswerStop)
{
SetStateTag(VigilantMissionTags::GimmickCompleted);
}
else
{
SetStateTag(VigilantMissionTags::GimmickInactive);
}
}
}
}
이 때 Statue에서 사용하는 메쉬의 축이 뒤틀려 있다는 것을 확인했습니다. 문제는 Gimmick의 Root가 MeshComponent라 이를 수정해서 사용할 수가 없었다는 점입니다.
RootComponent로 USceneComponent를 새로 추가하니 MeshComponent의 Transform에서 Location과 Rotation을 수정할 수 있게 되었습니다.(수정전에는 Scale만 존재했습니다.)
Niagara의 Mesh Location을 사용해 Altar에 배치될 메쉬의 모양을 파티클로 표현하는 기능을 구현하려고 했습니다만 현재 많은 시행착오를 겪은 끝에 실패 중입니다.
임시로 사용 중인 Cone을 표시하는 모습

첫 번 째 오류 - 지정된 메쉬가 아닌 Altar의 메쉬에서 파티클이 재생된다.

이 오류의 원인을 알지 못해 여러 곳을 해맸습니다.
컴포넌트를 설정하는 코드
for (int32 i = 0; i < PlacementSlots.Num(); i++)
{
const FVGAltarPlacementSlot& Slot = PlacementSlots[i];
if (!Slot.RequiredItemHintEffect)
{
HintEffectComponents.Add(nullptr); // 인덱스 유지
continue;
}
UNiagaraComponent* NiagaraComp = NewObject<UNiagaraComponent>(this);
NiagaraComp->SetAutoActivate(false);
NiagaraComp->SetAsset(Slot.RequiredItemHintEffect);
NiagaraComp->SetupAttachment(RootComponent);
NiagaraComp->SetRelativeLocation(Slot.AttachOffset);
NiagaraComp->RegisterComponent();
// 메쉬 정보 전달 (Niagara에 User.Mesh 파라미터가 있을 경우)
UVGMissionItemDataAsset* ItemDataAsset = Slot.ItemDataAsset.Get();
if (ItemDataAsset)
{
NiagaraComp->SetVariableStaticMesh(FName("User.TargetMesh"), ItemDataAsset->ItemMesh);
UE_LOG(LogTemp, Warning, TEXT("TargetMesh = %s"),
*GetNameSafe(ItemDataAsset->ItemMesh));
}
NiagaraComp->Activate();
HintEffectComponents.Add(NiagaraComp);
}
이 코드에서 RegisterComonent와 Activate의 위치가 중요하다고 합니다.
Activate에서는
System Instance 생성/시작
Emitter 실행 시작
User Parameter 값 읽기
Data Interface 바인딩
Spawn/Update 루프 진입
의 과정이 진행됩니다. 때문에 Activate 이전에 메쉬를 설정해 주어야 올바른 값이 설정이 됩니다.
RegisterComonent는 나이아가라를 초기화하는 코드라고 합니다. 이 것 이전에 메쉬를 설정하면 초기화하는 과정에서 바인딩한 메쉬 정보가 덮어씌워질 가능성이 있다고 합니다.
때문에 수정 끝에 위의 코드가 되었지만 이는 문제를 해결해 주지 못했습니다.

여기저기 해매다 Source Mode를 Mesh Parameter Binding으로 수정 후 저장 & 컴파일을 실행하여 테스트를 하니
이랬던 것이

이렇게 됬습니다.

아직 임시 메쉬인 Cone 모양으로 변하지 않았지만 위치는 정상적으로 변경되었습니다.
로그를 확인해 보니
LogNiagara: StaticMesh data interface has no valid mesh - NiagaraDataInterfaceStaticMesh /Game/Test/Map/UEDPIE_2_TestMap.TestMap:PersistentLevel.BP_Altar_C_1.NiagaraComponent_0.NiagaraDataInterfaceStaticMesh_0
LogTemp: Warning: TargetMesh = Cone
LogNiagara: StaticMesh data interface has no valid mesh - NiagaraDataInterfaceStaticMesh /Game/Test/Map/UEDPIE_2_TestMap.TestMap:PersistentLevel.BP_Altar_C_1.NiagaraComponent_1.NiagaraDataInterfaceStaticMesh_0
LogTemp: Warning: TargetMesh = Cone
LogNiagara: StaticMesh data interface has no valid mesh - NiagaraDataInterfaceStaticMesh /Game/Test/Map/UEDPIE_2_TestMap.TestMap:PersistentLevel.BP_Altar_C_1.NiagaraComponent_2.NiagaraDataInterfaceStaticMesh_0
라고 하는 것을 보니 바인딩 쪽에서 또 문제가 발생한 것으로 보입니다.