Possession, Respawn, Avatar Actor 변경과 ASC 초기화
6강. Possession, Respawn, Avatar Actor 변경과 ASC 초기화
1. 이번 강의에서 해결할 문제
ASC와 Attribute가 정상인데 Ability가 소유 Client에서만 실행되지 않거나 Montage가 이전 Pawn에서 재생되는 경우가 있습니다. PlayerState의 ASC가 현재 Character를 Avatar로 모르기 때문입니다. 서버와 Client는 PlayerState를 알게 되는 시점이 다르므로 한쪽 초기화만으로 충분하지 않습니다.
2. 학습 목표
3. 핵심 개념
PlayerState 패턴에서 Owner Actor는 ASC를 실제 소유하는 AGASPlayerState, Avatar Actor는 현재 월드에서 행동하는 AGASCombatCharacter입니다. InitAbilityActorInfo(Owner, Avatar)는 Ability가 Controller, Movement, Anim Instance와 현재 몸을 찾는 캐시를 갱신합니다.
ActorInfo 용어와 두 머신의 초기화 시점
| 용어 | 역할 | 이 과정의 객체 |
|---|---|---|
| Owner Actor | ASC Component와 장기 Ability·Effect 상태의 수명을 소유 | AGASPlayerState |
| Avatar Actor | Ability가 현재 위치·Movement·Anim·Montage를 실행할 몸 | AGASCombatCharacter |
| Ability Actor Info | Owner·Avatar·Controller·Anim Instance 등 자주 쓰는 참조 캐시 | InitAbilityActorInfo가 구성 |
| Possession | 서버 Controller가 Pawn을 제어하도록 연결하는 과정 | 서버의 PossessedBy 호출 시점 |
| PlayerState Replication | Client가 서버 PlayerState 참조를 전달받는 과정 | Client의 OnRep_PlayerState 호출 시점 |
| Respawn | 같은 플레이어 상태에 새 Pawn을 연결하는 과정 | Owner는 유지되고 Avatar가 교체될 수 있음 |
서버와 Client는 같은 함수를 같은 프레임에 호출하지 않습니다.
서버
새 Character Spawn → Controller Possess → PossessedBy
→ PlayerState ASC 조회 → InitAbilityActorInfo(PlayerState, 새 Character)
→ 서버가 Ability 지급·Effect 적용 가능
소유 Client
Character Proxy와 PlayerState 참조 수신 → OnRep_PlayerState
→ 같은 Client World의 ASC 조회
→ InitAbilityActorInfo(PlayerState, 현재 Character)
→ Local Predicted Ability가 Local Control·Anim·Movement 참조 사용
관찰 Client도 PlayerState 알림을 받을 수 있지만 Local Predicted 입력 주체는 아닙니다. ActorInfo 초기화는 상태 접근과 표현에 필요할 수 있으므로 실행 위치를 로그로 구분하고 “OnRep이므로 소유 Client만”이라고 단정하지 않습니다.
4. 구현
void AGASCombatCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
InitializeAbilityActorInfo(); // 서버
}
void AGASCombatCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
InitializeAbilityActorInfo(); // 소유 Client와 관찰 Client
}
void AGASCombatCharacter::InitializeAbilityActorInfo()
{
AGASPlayerState* GASPS = GetPlayerState<AGASPlayerState>();
if (!ensure(GASPS)) return;
UAbilitySystemComponent* ASC = GASPS->GetAbilitySystemComponent();
if (!ensure(ASC)) return;
ASC->InitAbilityActorInfo(GASPS, this);
AbilitySystemComponent = CastChecked<UGASAbilitySystemComponent>(ASC);
Attributes = GASPS->GetAttributes();
BindAbilitySystemDelegates();
}
초기화 코드 한 줄씩 이해하기
PossessedBy는 서버에서 새 Controller가 이 Pawn을 Possess한 뒤 호출되므로 서버 초기화 진입점입니다.OnRep_PlayerState는 Client가 PlayerState 참조 변경을 수신한 뒤 호출되므로 Client에서 안전한 재시도 지점입니다.GetPlayerState<AGASPlayerState>()가 실패하면 ActorInfo를 반쪽만 만들지 않고 반환합니다. 어떤 NetMode·Role에서 실패했는지 로그를 추가합니다.InitAbilityActorInfo(GASPS, this)는 PlayerState를 Owner, 현재 Character를 Avatar로 지정합니다. 인자 순서를 바꾸면 상태 수명과 Ability 실행 대상이 뒤섞입니다.- Character의 편의 포인터
AbilitySystemComponent와Attributes는 PlayerState가 소유한 실제 객체를 가리킵니다. Character에 두 번째 ASC나 AttributeSet을 만들지 않습니다. - Delegate는 ActorInfo가 유효해진 뒤 바인딩하되 함수가 여러 번 호출될 수 있으므로 기존 Handle을 먼저 해제해야 합니다.
초기화 직후 다음 로그를 임시로 추가하면 Owner·Avatar와 실행 위치를 한 줄로 비교할 수 있습니다.
UE_LOG(LogTemp, Display,
TEXT("ASC Init NetMode=%s Role=%s Local=%d PS=%p ASC=%p Owner=%s Avatar=%s"),
*UEnum::GetValueAsString(GetNetMode()),
*UEnum::GetValueAsString(GetLocalRole()),
IsLocallyControlled(), GASPS, ASC,
*GetNameSafe(ASC->GetOwnerActor()),
*GetNameSafe(ASC->GetAvatarActor()));
포인터 주소는 한 프로세스·한 World 안에서 Respawn 전후 동일 객체인지 비교하는 진단값입니다. 서버와 Client는 별도 객체이므로 서로 다른 창의 주소가 같아야 한다고 기대하지 않습니다.
Delegate를 다시 바인딩하기 전에 기존 Handle을 해제합니다.
void AGASCombatCharacter::EndPlay(const EEndPlayReason::Type Reason)
{
UnbindAbilitySystemDelegates();
Super::EndPlay(Reason);
}
서버가 새 Pawn을 Possess하면 같은 Owner ASC에 새 Avatar를 지정합니다. 이전 Pawn의 UI·입력 Delegate는 Pawn 쪽에서 해제하되, PlayerState에 남겨야 할 Cooldown과 Ability는 무조건 지우지 않습니다.
5. 코드가 동작하는 이유
서버는 Possession에서 PlayerState와 Pawn 관계를 알고, Client는 PlayerState 복제 알림 뒤 안전하게 접근할 수 있습니다. 양쪽 호출이 같은 Owner와 현재 Avatar를 사용하면 Local Predicted Ability의 Local Control 검사와 Montage 대상이 올바르게 설정됩니다.
6. 실패 사례와 해결
7. 직접 실습
실습 목표
Listen Server와 원격 Client에서 ActorInfo 초기화 시점을 구분하고, Respawn 뒤 PlayerState ASC는 유지되며 Avatar만 새 Character로 바뀌는지 검증합니다.
시작 전 상태
2인 Listen Server PIE에서 서버·Client Output Log를 구분합니다. 로그에는 NetMode, Role, Local Control, PlayerState·ASC 포인터, Owner·Avatar 이름이 있어야 합니다. 테스트 Ability 지급은 서버에서만 수행합니다.
1단계: 따라 하기
첫 Spawn에서 서버는 PossessedBy, Client는 OnRep_PlayerState 경로로 초기화되는지 진입 로그를 나눠 남깁니다. 두 위치 모두 Owner 이름은 PlayerState, Avatar 이름은 현재 Character여야 합니다.
2단계: 값 바꿔 보기
원격 Client의 Character를 서버에서 파괴하고 같은 PlayerController에 새 Character를 Possess합니다. 서버 프로세스 안에서 PlayerState·ASC 포인터는 전과 같고 Character·Avatar 포인터는 달라져야 합니다. Client에서도 자기 World 기준으로 같은 관계를 확인합니다.
3단계: 직접 적용
Respawn을 세 번 반복하고 Attribute 변경 Delegate의 호출 수를 기록합니다. 한 번의 Health 변경에 Callback이 여러 번 호출되면 BindAbilitySystemDelegates 전에 이전 Handle을 제거하고 EndPlay에서도 Pawn 수명 구독을 해제합니다.
4단계: 스스로 확인
막혔을 때
| 증상 | 원인 후보 | 확인과 해결 |
|---|---|---|
| Client에서 ASC가 null | PlayerState 복제 전 초기화 또는 Interface 반환 오류 | BeginPlay만 쓰지 말고 OnRep_PlayerState와 PlayerState 타입을 확인합니다. |
| Ability는 있지만 Local Predicted 실행 실패 | ActorInfo의 Owner·Avatar 또는 Local Control 캐시가 오래됨 | 현재 Avatar 이름과 Controller를 로그로 확인하고 재초기화합니다. |
| Montage가 죽은 Pawn에서 실행 | Respawn 뒤 Avatar 미교체 | 새 Possession과 OnRep 경로에서 InitAbilityActorInfo가 호출됐는지 확인합니다. |
| Health UI가 Respawn마다 여러 번 갱신 | Delegate 중복 바인딩 | Handle 저장·Unbind·EndPlay 순서를 점검합니다. |
8. 이해 점검 질문 3개
9. 핵심 요약
다음 강의 연결
다음 강의: AttributeSet 설계와 Attribute의 역할에서는 방금 연결한 ASC가 소유할 Resource·Combat Stat·Meta Attribute의 수명과 변경 경계를 설계합니다.
Possession, Respawn, Avatar Actor 변경과 ASC 초기화 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.