UTimelineComponent를 활용해 압력판이 밟히면 Z축으로 내려가고, 떠나면 원래 위치로 복원되는 애니메이션을 구현했습니다.
압력판이 밟힐 때 GimmickStatTag가 변화하기 때문에 OnRep_StateTag에서 해당 로직을 호출하도록 구현했습니다.

C++ 위젯 클래스 없이 블루프린트만으로 구현했다.
WB_SangbagHP : GetPercent 함수에서 TargetSandbag의 StatComponent를 참조해 CurrentHP / MaxHP 비율을 반환BP_Sandbag : Event Tick마다 플레이어 카메라를 향하도록 WidgetComponent의 월드 로테이션을 설정. OnDead 이벤트에서 위젯을 숨김 처리


1차적인 원인
TryPlaceItemToSlot 내부의 호출 순서가 문제였다.
// 기존 (버그 있음)
CarryItem->PlaceOnTarget(this, Slot.AttachOffset); // Gimmick에 부착
EquipComp->Server_DropItem(HandSlot); // DetachFromActor 호출 → 방금 붙인 걸 다시 뜯어냄
CarryItem->SetActorRelativeLocation(Slot.AttachOffset); // 이미 detach된 상태에서 위치 설정
PlaceOnTarget에서 Gimmick에 AttachToActor를 완료했는데, 이후 Server_DropItem → HandleItemAttachment(false)가 DetachFromActor를 호출해 부착을 끊어버렸습니다.
그 상태에서 SetActorRelativeLocation을 호출하니 부착 기준점 없이 월드 좌표로 밀려나는 현상이 발생했고, 이 타이밍이 클라이언트마다 달랐기 때문에 어긋나는 정도도 제각각이었습니다.
수정
// 수정 후
EquipComp->Server_DropItem(HandSlot); // 먼저 캐릭터 손에서 분리
CarryItem->PlaceOnTarget(this, Slot.AttachOffset); // 그 다음 Gimmick에 부착 (간섭 없음)
// SetActorRelativeLocation 중복 호출 제거 (PlaceOnTarget 내부에서 처리)
Server_DropItem을 먼저 호출해 캐릭터와의 부착을 완전히 끊은 뒤 PlaceOnTarget을 호출하는 것으로 해결하게 했습니다.
Attach 대상과 상대 위치를
2차적 원인
여기저기서 Attach를 시도하고, Attach 대상과 상대 위치 복사를 따로 하고 있기에 복제되는 타이밍이 일치하지 않는 경우가 발생했습니다. 때문에 엉뚱한 위치에 붙거나 Attach 대상에 파뭍히는 등의 문제가 발생했습니다.
해결
중복되는 호출코드를 제거하고 Attach 대상과 상대 위치를 하나의 구조체에 담아서 복제하도록 변경했습니다.
#include "VGMissionItemCarry.h"
#include "Common/VGGameplayTags.h"
#include "Mission/Gimmick/VGMissionGimmickBase.h"
#include "Net/UnrealNetwork.h"
AVGMissionItemCarry::AVGMissionItemCarry()
{
PrimaryActorTick.bCanEverTick = false;
}
void AVGMissionItemCarry::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, PlaceInfo);
}
void AVGMissionItemCarry::PlaceOnTarget(AVGMissionGimmickBase* TargetGimmick, FVector TargetRelativeLocation)
{
if (!HasAuthority())
{
return;
}
if (UPrimitiveComponent* RootComp = Cast<UPrimitiveComponent>(GetRootComponent()))
{
RootComp->SetSimulatePhysics(false);
RootComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
// 캐릭터에서 분리 후 Gimmick에 부착
DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
Carrier = nullptr;
OnRep_Carrier();
FVGCarryPlaceInfo Info;
Info.AttachmentTargetActor = TargetGimmick;
Info.RelativeLocation = TargetRelativeLocation;
PlaceInfo = Info;
OnRep_PlaceInfo();
SetStateTag(VigilantMissionTags::ItemPlaced);
UE_LOG(LogTemp, Log, TEXT("[%s] item placed!"),*GetName());
}
void AVGMissionItemCarry::OnRep_PlaceInfo()
{
if (UPrimitiveComponent* RootComp =
Cast<UPrimitiveComponent>(GetRootComponent()))
{
RootComp->SetSimulatePhysics(false);
RootComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
AttachToActor(PlaceInfo.AttachmentTargetActor,
FAttachmentTransformRules::KeepWorldTransform);
SetActorRelativeLocation(PlaceInfo.RelativeLocation);
SetActorRelativeRotation(FRotator::ZeroRotator);
UE_LOG(LogTemp, Warning, TEXT("[%s] Attachment placed! At %s %s"),*GetName(),
*PlaceInfo.AttachmentTargetActor.GetName(),
*PlaceInfo.RelativeLocation.ToString());
}
#include <string>
#include <vector>
#include <list>
using namespace std;
int solution(vector<int> queue1, vector<int> queue2) {
int answer = -1;
list<int> q1, q2;
long long sum1 = 0, sum2 = 0;
for(int i = 0; i < queue1.size(); i++)
{
sum1 += queue1[i];
sum2 += queue2[i];
q1.push_back(queue1[i]);
q2.push_back(queue2[i]);
}
if((sum1 + sum2) % 2 != 0)
{
return answer;
}
int cnt = 0;
while(sum1 != sum2)
{
if(cnt > queue1.size() * 2)
{
return answer;
}
if(sum1 < sum2)
{
int num = q2.front();
q2.pop_front();
q1.push_back(num);
sum1 += num;
sum2 -= num;
}
else
{
int num = q1.front();
q1.pop_front();
q2.push_back(num);
sum2 += num;
sum1 -= num;
}
cnt++;
}
answer = cnt;
return answer;
}
정답률 96.7
테스트 1 〉 실패 (0.01ms, 4.2MB)
테스트 2 〉 통과 (0.01ms, 3.63MB)
테스트 3 〉 통과 (0.01ms, 3.59MB)
테스트 4 〉 통과 (0.01ms, 4.2MB)
테스트 5 〉 통과 (0.02ms, 4.15MB)
테스트 6 〉 통과 (0.02ms, 4.15MB)
테스트 7 〉 통과 (0.03ms, 4.21MB)
테스트 8 〉 통과 (0.06ms, 4.21MB)
테스트 9 〉 통과 (0.15ms, 4.21MB)
테스트 10 〉 통과 (0.23ms, 4.23MB)
테스트 11 〉 통과 (12.60ms, 9.53MB)
테스트 12 〉 통과 (6.67ms, 9.57MB)
테스트 13 〉 통과 (6.97ms, 11.9MB)
테스트 14 〉 통과 (7.56ms, 13.1MB)
테스트 15 〉 통과 (9.57ms, 14.6MB)
테스트 16 〉 통과 (9.90ms, 15.3MB)
테스트 17 〉 통과 (9.12ms, 15.8MB)
테스트 18 〉 통과 (31.33ms, 41.5MB)
테스트 19 〉 통과 (32.57ms, 41.7MB)
테스트 20 〉 통과 (34.79ms, 41.7MB)
테스트 21 〉 통과 (30.94ms, 42MB)
테스트 22 〉 통과 (36.54ms, 41.8MB)
테스트 23 〉 통과 (36.57ms, 42.8MB)
테스트 24 〉 통과 (38.29ms, 42.5MB)
테스트 25 〉 통과 (0.02ms, 4.15MB)
테스트 26 〉 통과 (0.02ms, 4.16MB)
테스트 27 〉 통과 (0.02ms, 4.21MB)
테스트 28 〉 통과 (16.79ms, 16.1MB)
테스트 29 〉 통과 (1.08ms, 4.51MB)
테스트 30 〉 통과 (14.84ms, 16MB)
삽입 삭제가 빈번하니 입력받은 vector를 list로 변경하는 과정을 넣었습니다.
양쪽 큐의 합이 홀수 이면 양쪽에 똑같이 나눌 수가 없으니 아예 연산을 하지 않도록 예외처리도 추가했고요.
이전에 풀었던 코드와 비교해도 크게 다른 것이 없는데, 반복 횟수 제한을 큐 크기(양쪽이 같음) 3에서 2로 변경한 것이 문제였던 걸까요?
#include <string>
#include <vector>
#include <list>
using namespace std;
int solution(vector<int> queue1, vector<int> queue2) {
int answer = -1;
list<int> q1, q2;
long long sum1 = 0, sum2 = 0;
for(int i = 0; i < queue1.size(); i++)
{
sum1 += queue1[i];
sum2 += queue2[i];
q1.push_back(queue1[i]);
q2.push_back(queue2[i]);
}
if((sum1 + sum2) % 2 != 0)
{
return answer;
}
int cnt = 0;
while(sum1 != sum2)
{
if(cnt > queue1.size() * 3)
{
return answer;
}
if(sum1 < sum2)
{
int num = q2.front();
q2.pop_front();
q1.push_back(num);
sum1 += num;
sum2 -= num;
}
else
{
int num = q1.front();
q1.pop_front();
q2.push_back(num);
sum2 += num;
sum1 -= num;
}
cnt++;
}
answer = cnt;
return answer;
}
State Tree에 대해 학습했습니다.
특징
UE 5 이전에는 잘 쓰이지 않았으나 5로 넘어오면서 Enhanced Input과 마찬가지로 활발히 사용되기 시작했다.
필기는 여기까지만 하고 나머지는 직접 따라서 만드는 시간을 가졌습니다.
정상적으로 동작하게 만드는데에 실패했기에 추후 복습을 진행할 예정입니다.