AVGMissionSandbag — LastContributor가 항상 nullSpawnRewardItems()에서 LastContributor or RewardItemClass is Missing 에러가 출력되는 문제가 발생했습니다.
원인
TakeDamage에서 StatComponent->ApplyDamageToStat()은 호출하고 있었지만, LastAttacker에 공격자를 저장하는 RegisterAttacker() 호출이 누락되어 있었습니다.
때문에 OnSandbagDefeated.Broadcast(LastAttacker)를 날려도 LastAttacker가 항상 nullptr인 상태였습니다.
// TakeDamage 내부에 추가
if (HasAuthority() && EventInstigator)
{
if (AVGCharacterBase* AttackerCharacter = Cast<AVGCharacterBase>(EventInstigator->GetPawn()))
{
RegisterAttacker(AttackerCharacter);
}
}
Server RPC가 무시되는 문제레버, 제단 등 기믹에 상호작용이 전혀 동작하지 않는 문제였습니다.
원인 — Unreal Server RPC Owner 규칙
UFUNCTION(Server, Reliable) RPC는 호출하는 클라이언트가 해당 액터의 Owner여야만 서버로 전송됩니다.
AVGMissionGimmickBase는 월드에 배치된 독립 액터로, SetOwner()를 호출한 적이 없어 Owner가 nullptr입니다.
따라서 클라이언트에서 Server_Interact()를 호출해도 RPC가 조용히 무시됩니다.
// 이 구조가 문제
void AVGMissionGimmickBase::OnInteractWith(AVGCharacterBase* Interactor)
{
if (!HasAuthority())
{
Server_Interact(Interactor); // Owner 없음 → 전송 안 됨
return;
}
}
RPC 방향별 Owner 요구사항 정리
| RPC 종류 | 호출 주체 | Owner 필요 여부 |
|---|---|---|
Server | 클라이언트 → 서버 | 필요 |
Multicast | 서버 → 모든 클라이언트 | 불필요 |
Client | 서버 → 특정 클라이언트 | 필요 |
Sandbag에서 구현된 Multicast_OnDead가 정상 동작했던 이유는 서버가 호출하는 Multicast는 Owner 제약이 없기 때문이었습니다.
수정 방향
기믹 자신의 RPC 대신, Owner가 캐릭터인 EquipmentComponent의 RPC를 중계자로 활용했습니다.
EquipmentComponent에 Server_InteractWithActor(AActor*, AVGCharacterBase*)를 추가하고, 내부에서 IVGInteractable 인터페이스로만 통신하면 결합도를 낮출 수 있습니다.
// GimmickBase::OnInteractWith 수정
if (!HasAuthority())
{
if (UVGEquipmentComponent* EquipComp =
Interactor->FindComponentByClass<UVGEquipmentComponent>())
{
EquipComp->Server_InteractWithActor(this, Interactor);
}
return;
}
// EquipmentComponent::Server_InteractWithActor_Implementation
void UVGEquipmentComponent::Server_InteractWithActor_Implementation(
AActor* TargetActor, AVGCharacterBase* Interactor)
{
if (TargetActor->Implements<UVGInteractable>())
{
if (IVGInteractable::Execute_CanInteract(TargetActor, Interactor))
{
IVGInteractable::Execute_OnInteract(TargetActor, Interactor);
}
}
}
해당 RPC 추가는 EquipmentComponent 담당 팀원분께 건의하여 수정을 허가받아, 수정되었습니다.
MissionItem 줍기 불가AVGMissionItemBase::OnInteractWith 내부 첫 줄이 if (!HasAuthority()) return;이어서, 클라이언트에서 호출되면 바로 종료됩니다. Gimmick 때와 같은 문제였습니다.
작업을 시작하기 전 Interact 관련 리팩토링이 진행되었기에 내일 머지 받는 대로 수정을 진행할 계획입니다.
SetMissionState에서 OnMissionStateChanged 이중 Broadcast
SetMissionState 내부에서 OnRep_CurrentStateTag()를 직접 호출하고, 그 안에도 Broadcast가 있는데, SetMissionState 자체에도 Broadcast가 한 번 더 있다. 서버에서 상태 변경 시 이벤트가 두 번 발생합니다.
HandleMissionStateChanged에 UFUNCTION() 누락
AddDynamic으로 바인딩되는 함수에 UFUNCTION()이 없으면 런타임에 바인딩이 조용히 실패합니다.
ClearContributers에서 LastContributor 미초기화
Contributors 배열만 비우고 LastContributor = nullptr을 누락했습니다. 미션 리셋 후 보상 스폰 시 댕글링 포인터 참조 위험이 있습니다.
현재는 모두 수정이 완료되었습니다.
현재 설계상 미션을 완료되면 다음 단계까지의 남은 시간이 감소해야 합니다.
문제는 해당 부분이 구현이 되어 있지 않았다는 점입니다. 코어를 담당하는 팀원분의 요청에 맞추서 Delegate를 작성(단순 시간 감소 값을 발송하는 델리게이트입니다.)한 후 관련 로직을 작성했습니다.
MissionData를 관리하는 DataAsset에 ClearReduceTime 변수 추가, MissionBase에 반환함수 추가, MissionSubsytem에서 Mission이 완료될 시 델리게이트를 방송했습니다.
TA 시간에 제작한 Voronoi 머티리얼을 아웃라인 오버레이로 활용했다.
Overlay Material이란?
원래 메시 위에 한 겹 더 씌우는 머티리얼이다. 메시의 Rendering 섹션에서 설정한다.
Overlay Material Max Draw Distance
오버레이 머티리얼이 카메라에서 얼마나 먼 거리까지 렌더링될지 제한하는 옵션이다.
상호작용 가능한 오브젝트 강조와 가까이 있을 때만 아웃라인을 표시하는 현재 요구사항에 딱 맞는 속성이다.
활용 예시
코딩 테스트
프로그래머스 - 연속된 부분 수열의 합
실패 코드
수열의 시작 인덱스와 끝 인덱스를 저장해서, 현재 합이 작으면 끝 인덱스 증가, 크면 시작 인덱스를 중가시켰습니다.
맨 마지막 인덱스의 인자가 k와 동일하고 그 이전에 k와 같은 인자가 없을 수도 있으니 예외처리를 추가했습니다.
#include <string>
#include <vector>
using namespace std;
vector<int> solution(vector<int> sequence, int k) {
vector<int> answer;
int sum = sequence[0];
int len = 1000000, limit = sequence.size() - 1;
int leftIdx = 0, rightIdx = 0;
while(leftIdx != limit && rightIdx != limit)
{
if(rightIdx < limit && sum < k)
{
rightIdx++;
sum += sequence[rightIdx];
}
else if(leftIdx < limit && sum > k)
{
sum -= sequence[leftIdx];
leftIdx++;
}
else if(sum == k)
{
if(len > rightIdx - leftIdx)
{
len = rightIdx - leftIdx;
answer = {leftIdx, rightIdx};
}
if(rightIdx + 1 < sequence.size())
{
rightIdx++;
sum+= sequence[rightIdx];
}
else
{
break;
}
}
}
if(len > 0 && sequence[limit] == k)
{
answer = {limit, limit};
}
return answer;
}
정답률은 85.3이었습니다.
테스트 1 〉 실패 (0.01ms, 3.68MB)
테스트 2 〉 통과 (0.01ms, 4.21MB)
테스트 3 〉 통과 (0.01ms, 4.21MB)
테스트 4 〉 통과 (0.02ms, 3.68MB)
테스트 5 〉 통과 (0.06ms, 4.21MB)
테스트 6 〉 통과 (0.07ms, 4.3MB)
테스트 7 〉 통과 (0.28ms, 5.02MB)
테스트 8 〉 통과 (0.55ms, 6.56MB)
테스트 9 〉 통과 (1.07ms, 9.79MB)
테스트 10 〉 통과 (2.66ms, 19.6MB)
테스트 11 〉 통과 (4.13ms, 36.2MB)
테스트 12 〉 통과 (4.28ms, 36.1MB)
테스트 13 〉 통과 (6.54ms, 36.1MB)
테스트 14 〉 통과 (4.53ms, 36.2MB)
테스트 15 〉 통과 (4.48ms, 36.1MB)
테스트 16 〉 실패 (3.23ms, 36.8MB)
테스트 17 〉 통과 (5.72ms, 36.8MB)
테스트 18 〉 통과 (0.01ms, 4.21MB)
테스트 19 〉 통과 (0.01ms, 3.67MB)
테스트 20 〉 통과 (0.01ms, 4.19MB)
테스트 21 〉 통과 (0.01ms, 4.21MB)
테스트 22 〉 통과 (0.01ms, 4.16MB)
테스트 23 〉 통과 (0.01ms, 4.16MB)
테스트 24 〉 통과 (4.49ms, 34.7MB)
테스트 25 〉 실패 (3.42ms, 34.6MB)
테스트 26 〉 통과 (5.03ms, 34.6MB)
테스트 27 〉 실패 (6.55ms, 34.6MB)
테스트 28 〉 통과 (4.16ms, 34.7MB)
테스트 29 〉 실패 (4.25ms, 34.7MB)
테스트 30 〉 통과 (5.88ms, 36.7MB)
테스트 31 〉 통과 (0.01ms, 4.2MB)
테스트 32 〉 통과 (0.01ms, 4.21MB)
테스트 33 〉 통과 (0.01ms, 4.14MB)
테스트 34 〉 통과 (0.01ms, 4.15MB)
EquipmentComponent에 Server_InteractWithActor RPC 추가 반영 후 기믹 상호작용 검증AVGMissionItemBase::OnInteractWith HasAuthority() 가드 제거 및 아이템 줍기 수정