버퍼를 이용한 선입력 기능
버퍼 구조체
USTRUCT(BlueprintType)
struct FBufferedInput
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Buffer")
ECharacterState InputState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Buffer")
EAttackType InputAttack;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Buffer")
float BufferedTime;
FBufferedInput(): InputState(ECharacterState::Normal),InputAttack(EAttackType::None), BufferedTime(0.f) {}
FBufferedInput(ECharacterState InState, EAttackType InAction, const float InTime): InputState(InState), InputAttack(InAction), BufferedTime(InTime) {}
};
- 캐릭터 상태, 입력된 액션, 입력 시간으로 구성된 버퍼 선언
- 비어 있는 버퍼용 생성자
- 저장된 버퍼용 생성자
버퍼 로직 추가
//BaseCharacter.h
#pragma region Buffer
UPROPERTY()
FBufferedInput InputBuffer;
UPROPERTY()
float BufferThreshold;
#pragma endregion
//BaseCharacter.cpp
ABaseCharacter::ABaseCharacter():
CurrentActivatedCollision(-1),
BufferThreshold(0.5f),
...
{
...
}
void ABaseCharacter::ExecuteBufferedAction()
{
const float CurrentTime=GetWorld()->GetTimeSeconds();
const int32 InputAction=static_cast<int32>(InputBuffer.InputAttack);
//Execute Buffered Input Action
if (InputAction>=0&&InputAction<15&&(CurrentTime-InputBuffer.BufferedTime<=BufferThreshold))
{
ExecuteActionByIndex(InputBuffer.InputState,InputAction);
//UE_LOG(LogTemp,Warning,TEXT("Execute Buffered Input(Index: %d)"),InputAction);
}
// Clear Buffer
InputBuffer=FBufferedInput();
}
void ABaseCharacter::ExecuteActionByIndex(ECharacterState InState, const int32 Index)
{
if ((CurrentCharacterState==ECharacterState::Normal&&GetCharacterMovement()->IsFalling()==false)||
(CurrentCharacterState==ECharacterState::Normal&&GetCharacterMovement()->IsFalling()&&Index>7&&Index<14)||
(CurrentCharacterState==ECharacterState::Hitted&&Index==15)||
(InState==ECharacterState::Normal))
{
if (Index==15&&StatusComponent->GetBurstMeter()>=2500)
{
StatusComponent->AddBurstMeter(-2500);
}
//UE_LOG(LogTemp,Warning,TEXT("Attack1 Called !!"));
ServerRPCAction(InState,GetWorld()->GetGameState()->GetServerWorldTimeSeconds(), Index);
// Play Montage in Owning Client
if (HasAuthority()==false&&IsLocallyControlled()==true)
{
CurrentCharacterState=InState;
// bCanAttack=false;
OnRep_CurrentCharacterState();
PlayActionMontage(InState,Index);
}
}
else
{
//UE_LOG(LogTemp,Warning,TEXT("Buffered Action Type: %d, Buffered Input Index: %d"),InState,Index);
InputBuffer=FBufferedInput(InState, static_cast<EAttackType>(Index),GetWorld()->GetTimeSeconds());
}
}
void ABaseCharacter::OnRep_CurrentCharacterState()
{
switch (CurrentCharacterState)
{
case ECharacterState::Normal:
GetCharacterMovement()->SetMovementMode(MOVE_Walking);
ExecuteBufferedAction();
break;
case ECharacterState::Hitted:
// GetCharacterMovement()->SetMovementMode(MOVE_None);
PlayActionMontage(ECharacterState::Hitted,0);
break;
case ECharacterState::Dead:
// GetCharacterMovement()->SetMovementMode(MOVE_None);
PlayActionMontage(ECharacterState::Dead,0);
BattleComponent->ResetCombo();
default:
// GetCharacterMovement()->SetMovementMode(MOVE_None);
break;
}
}
- 버퍼 변수 및 입력 제한시간(Threshold) 변수 추가
- 액션 실행 함수(ExeucteActionByIndex)에서 현재 입력 액션을 수행할 수 없는 상황이라면 버퍼에 저장하도록 로직 추가
- 버퍼에 저장된 액션 실행하는 함수 추가 (ExecuteBufferedAction())
- 서버에서 클라이언트로 캐릭터 상태가 복제(Replication) ㅡ> 액션 실행 가능(Normal) ㅡ> 버퍼 우선적으로 검사
ㅡ> 버퍼에 저장된 입력이 있다면 ExecuteBufferedAction 호출 ㅡ> 함수에서 제한 시간 검사 및 실행
키 조합을 통한 액션
- IMC에서 키 조합 관련 설정
- 이동+특정 공격(Attack8)
- 특정 키 지정: E
- Triggres Chorded Action 설정
- Chord Action에 원하는 액션 지정: 이동 액션(IA_Move)
- 특정 액션 + 특정 액션(IA_Burst)
- 우선 특정 키 지정: A
- Triggres Chorded Action 설정
- Chord Action에 원하는 액션 지정: 특정 액션(IA_Dodge)
캐릭터 상태에 따른 동작 다변화
void ABaseCharacter::ExecuteActionByIndex(ECharacterState InState, const int32 Index)
{
if ((CurrentCharacterState==ECharacterState::Normal&&GetCharacterMovement()->IsFalling()==false)||
(CurrentCharacterState==ECharacterState::Normal&&GetCharacterMovement()->IsFalling()&&Index>7&&Index<14)||
(CurrentCharacterState==ECharacterState::Hitted&&Index==15)||
(InState==ECharacterState::Normal))
{
...
}
else
{
...
}
}
void ABaseCharacter::Attack2(const FInputActionValue& Value)
{
if (GetCharacterMovement()->IsFalling())
{
ExecuteActionByIndex(ECharacterState::Attack,9);
}
else if (GetCharacterMovement()->MaxWalkSpeed==BalanceStats.MaxRunSpeed)
{
ExecuteActionByIndex(ECharacterState::Attack,2);
}
else
{
ExecuteActionByIndex(ECharacterState::Attack,1);
}
}
- 같은 키로 실행되는 액션 중에서 공중, 지상, 달리기 등의 상태에 따라 다른 액션이 나가도록 로직 구성
- 공중에 있는 상태: CharacterMovement에서 IsFalling() 함수로 공중인지 체크 후 알맞은 액션 실행
ㅡ> 추가로 공중일 때는 해당하는 특정 인덱스 범위의 액션만 실행 (지상 액션은 공중에서 실행 불가능해야 함) - 달리기 상태: 현재 MaxWalkSpeed를 확인하여 달리기에 해당하면 알맞은 액션 실행, 아니라면 기본 액션 실행
참고자료
'언리얼엔진(UE) > 프로젝트' 카테고리의 다른 글
미친 선인장과 분노의 버섯 - #17 콜리전 오버랩 이벤트 갱신 (0) | 2025.04.17 |
---|---|
미친 선인장과 분노의 버섯 - #16 캐릭터 스테이터스와 인게임 HUD 연동 (0) | 2025.04.16 |
미친 선인장과 분노의 버섯 - #14 더블 클릭을 통한 달리기 (0) | 2025.04.14 |
미친 선인장과 분노의 버섯 - #13 카메라 회전 트러블 슈팅 + 클라이언트 UI 상태 변화에 따른 업데이트 (0) | 2025.04.11 |
미친 선인장과 분노의 버섯 - #12 네트워크 환경에서의 동기화 및 UX(사용자 경험) 개선 (0) | 2025.04.10 |