Replicated

[Drag Down] 기본 세팅 #1 : Character Base, Player 본문

언리얼 엔진/Drag Down (캡스톤 디자인)

[Drag Down] 기본 세팅 #1 : Character Base, Player

라구넹 2025. 3. 19. 22:44
// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/DDCharacterBase.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Physics/DDCollision.h"

// Sets default values
ADDCharacterBase::ADDCharacterBase()
{
	bReplicates = true;

	// Pawn
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	// Movement
	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(1500.0f, 500.0f, 0.0f);
	GetCharacterMovement()->JumpZVelocity = 450.f;
	GetCharacterMovement()->AirControl = 0.35f;
	GetCharacterMovement()->MaxWalkSpeed = 500.f;
	GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
	GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
}

3인칭 및 기본 캐릭터 베이스 설정

 

플레이어

ADDCharacterPlayer::ADDCharacterPlayer()
{
	bReplicates = true;

	// Camera
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(RootComponent);
	CameraBoom->TargetArmLength = 400.0f;
	CameraBoom->bUsePawnControlRotation = true;

	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
	FollowCamera->bUsePawnControlRotation = false;

	// Input
	static ConstructorHelpers::FObjectFinder<UInputMappingContext> InputMappingContextRef(TEXT("/Script/EnhancedInput.InputMappingContext'/Game/Input/IMC_Default.IMC_Default'"));
	if (nullptr != InputMappingContextRef.Object)
	{
		MappingContext = InputMappingContextRef.Object;
	}
    
    // Capsule
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
	GetCapsuleComponent()->SetCollisionProfileName(CPROFILE_CSCAPSULE);
    
    // Mesh
	GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -100.0f), FRotator(0.0f, -90.0f, 0.0f));
	GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
	GetMesh()->SetCollisionProfileName(TEXT("NoCollision"));

	static ConstructorHelpers::FObjectFinder<UInputAction> InputActionJumpRef(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/Actions/IA_Jump.IA_Jump'"));
	if (nullptr != InputActionJumpRef.Object)
	{
		JumpAction = InputActionJumpRef.Object;
	}

	static ConstructorHelpers::FObjectFinder<UInputAction> InputActionShoulderMoveRef(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/Actions/IA_Move.IA_Move'"));
	if (nullptr != InputActionShoulderMoveRef.Object)
	{
		ShoulderMoveAction = InputActionShoulderMoveRef.Object; 
	}

	static ConstructorHelpers::FObjectFinder<UInputAction> InputActionShoulderLookRef(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/Actions/IA_Look.IA_Look'"));
	if (nullptr != InputActionShoulderLookRef.Object)
	{
		ShoulderLookAction = InputActionShoulderLookRef.Object;
	}

	static ConstructorHelpers::FObjectFinder<USkeletalMesh> CharacterMeshRef(TEXT("/Script/Engine.SkeletalMesh'/Game/Characters/Mannequins/Meshes/SKM_Quinn.SKM_Quinn'"));
	if (CharacterMeshRef.Object)
	{
		GetMesh()->SetSkeletalMesh(CharacterMeshRef.Object);
	}

	static ConstructorHelpers::FClassFinder<UAnimInstance> AnimInstanceClassRef(TEXT("/Game/Characters/Mannequins/Animations/ABP_Quinn.ABP_Quinn_C"));
	if (AnimInstanceClassRef.Class)
	{
		GetMesh()->SetAnimInstanceClass(AnimInstanceClassRef.Class);
	}

	// ASC
	ASC = nullptr;
}

3인칭용으로 카메라와 스프링암 부착

인풋 매핑 컨텍스트와 인풋 액션 가져오기

스켈레탈 메시와 애니메이션 인스턴스 클래스 설정

* ASC는 PlayerState에서 관리. 게임에서 버프, 디버프나 상태 관리 등을 위함.

 

UAbilitySystemComponent* ADDCharacterPlayer::GetAbilitySystemComponent() const
{
	return ASC;
}

소유하는 ASC 리턴하는 함수

 

void ADDCharacterPlayer::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);
	SetASC();
	SetGASAbilities();
}

PossessedBy는 서버에서만 실행되고, 컨트롤러가 폰에 빙의될 때 발동됨

PlayerState가 이미 만들어져 있을 시점이니 어빌리티 시스템 설정 가능(SetASC에서 PlayerState에서 어빌리티 시스템 컴포넌트를 가져옴)

 

void ADDCharacterPlayer::SetGASAbilities()
{
	if (ASC)
	{
		for (const auto& StartAbility : StartAbilities)
		{
			FGameplayAbilitySpec StartSpec(StartAbility);
			ASC->GiveAbility(StartSpec);
		}

		for (const auto& StartInputAbility : StartInputAbilities)
		{
			FGameplayAbilitySpec StartSpec(StartInputAbility.Value);
			StartSpec.InputID = StartInputAbility.Key;
			ASC->GiveAbility(StartSpec);
		}
	}
}

PossessedBy는 서버에서만 실행되고, GiveAbility는 서버에서만 가능함 (클라이언트 시도 불가)

PossessedBy에서 ASC 설정을 하는 이유 중 하나.

 

void ADDCharacterPlayer::OnRep_PlayerState()
{
	Super::OnRep_PlayerState();

	SetASC();
}

클라이언트는 PlayerState를 복제받는 타이밍에 SetASC

 

void ADDCharacterPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);

	EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump); 
	EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping); 
	EnhancedInputComponent->BindAction(ShoulderMoveAction, ETriggerEvent::Triggered, this, &ADDCharacterPlayer::ShoulderMove);
	EnhancedInputComponent->BindAction(ShoulderLookAction, ETriggerEvent::Triggered, this, &ADDCharacterPlayer::ShoulderLook); 

	SetupGASInputComponent();
}

인풋 액션을 함수와 바인딩

어빌리티 시스템 컴포넌트도 인풋 액션과 바인딩

 

void ADDCharacterPlayer::ShoulderMove(const FInputActionValue& Value)
{
	FVector2D MovementVector = Value.Get<FVector2D>();

	AddMovementInput(FollowCamera->GetForwardVector(), MovementVector.X);
	AddMovementInput(FollowCamera->GetRightVector(), MovementVector.Y);
}

void ADDCharacterPlayer::ShoulderLook(const FInputActionValue& Value)
{
	FVector2D LookAxisVector = Value.Get<FVector2D>();

	AddControllerYawInput(LookAxisVector.X);
	AddControllerPitchInput(LookAxisVector.Y);
}

Move -> wasd와 같은 입력을 받아 이동 인풋

Look -> 마우스 인풋을 받아 보는 방향을 설정

 

void ADDCharacterPlayer::SetupGASInputComponent()
{
	if (IsValid(InputComponent))
	{
		UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);

		//추가 gas 액션
		//EnhancedInputComponent->BindAction(~Action, ETriggerEvent::Triggered, this, &ADDCharacterPlayer::GASInputPressed, 인덱스);

		UE_LOG(LogDD, Log, TEXT("SetupGASInputComponent Succeed"));
	}
	else if (!IsValid(ASC))
	{
		UE_LOG(LogDD, Log, TEXT("Invalid ASC"));
	}
	else
	{
		UE_LOG(LogDD, Log, TEXT("Invalid InputComponent"));
	}
}

인풋을 통해 어빌리티를 발동시키는 경우 위 함수에서 연결

 

// Input Pressed RPC **************************************
void GASInputPressed(int32 InputId);

UFUNCTION(Server, Reliable)
void ServerGASInputPressed(int32 InputId);

void HandleGASInputPressed(int32 InputId);
// ********************************************************

어빌리티 사용 인풋은 RPC 사용 (이 경우 누르는 경우. 키에서 손을 때는 경우(Released)도 같은 방식으로 진행)

이유: 클라이언트는 입력을 받고, 렌더링만 하도록 할 것임. 어빌리티 발동은 서버에서만 하도록 함

 

void ADDCharacterPlayer::GASInputPressed(int32 InputId)
{
	if (HasAuthority())
	{
		HandleGASInputPressed(InputId);
	}
	else
	{
		ServerGASInputPressed(InputId);
	}
}

void ADDCharacterPlayer::ServerGASInputPressed_Implementation(int32 InputId)
{
	if (HasAuthority())
	{
		HandleGASInputPressed(InputId);
	}
}

void ADDCharacterPlayer::HandleGASInputPressed(int32 InputId)
{
	if (!ASC)
	{
		return;
	}

	FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId);
	if (Spec)
	{
		if (Spec->InputPressed) return;
		//UE_LOG(LogCS, Log, TEXT("[NetMode : %d], HandleGASInputPressed"), GetWorld()->GetNetMode());
		Spec->InputPressed = true;
		if (Spec->IsActive())
		{
			ASC->AbilitySpecInputPressed(*Spec);
		}
		else
		{
			ASC->TryActivateAbility(Spec->Handle);
		}
	}
}

클라이언트는 서버에 RPC로 어빌리티 발동 요청

서버는 그냥 발동