실시간 캐릭터 교대 연구

Woogle·2023년 11월 21일
0

언리얼 엔진 5

목록 보기
47/59

📄 실시간 캐릭터 교대

  • 원신처럼 여러 명의 캐릭터를 실시간 교대하는 시스템을 연구했다.

  • 플레이어 컨트롤러에서 캐릭터를 여러 명 스폰 후, 조작할 캐릭터에 빙의(AController::Possess)하고 위치를 바꾸는 방식으로 구현했다.

  • 싱글플레이에서 문제 없이 작동하는 플레이어 컨트롤러 코드를 공유해본다.


📄 게임 시작

  • 플레이어 컨트롤러가 여러 명의 캐릭터를 스폰한다.

    • 초기 스폰 위치를 지정하기 위해 FTransform을 선언했다. (SquadInitTransform)
  • 처음 스폰한 캐릭터에 빙의한다.

  • 여러 캐릭터를 빙의하기 때문에 Default Pawn을 AI가 플레이어 캐릭터 위치를 찾는 용도로 사용했다. (TargetingPawn)

✏️ Header

public:
	// Target for Enemy AI attack (Because player character can be swapped at runtime)
	UPROPERTY(BlueprintReadOnly)
	APawn* TargetingPawn;

protected:
	virtual void BeginPlay() override;

private:
	void SetupSquad();

	/** Character Class */
	UPROPERTY(EditAnywhere, Category = "Squad")
	TSubclassOf<class APholderonCharacter> SquadCharClass0;

	UPROPERTY(EditAnywhere, Category = "Squad")
	TSubclassOf<APholderonCharacter> SquadCharClass1;

	UPROPERTY(EditAnywhere, Category = "Squad")
	TSubclassOf<APholderonCharacter> SquadCharClass2;

    /** Spawned Characters */
	TStaticArray<APholderonCharacter*, 3> SquadCharacters;

	UPROPERTY()
	APholderonCharacter* CurrentCharacter;

  	// Player characters that is waiting for battle locate here.
	UPROPERTY(EditDefaultsOnly)
	FTransform SquadInitTransform { FRotator::ZeroRotator, FVector(9999.f), FVector::OneVector };

✏️ CPP

void APholderonPlayerController::BeginPlay()
{
	Super::BeginPlay();
	if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
	{
		Subsystem->AddMappingContext(ControllerMappingContext, 0);
	}
    TargetingPawn = GetPawn();
	SetupSquad();
}

void APholderonPlayerController::SetupSquad()
{
	UWorld* World = GetWorld();
	if (World == nullptr || TargetingPawn == nullptr) return;

	// Spawn 3 squad characters.
	if (SquadCharClass0 && SquadCharClass1 && SquadCharClass2)
	{
		FActorSpawnParameters SpawnParams;
		SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		SpawnParams.Owner = this;
		SquadCharacters[0] = World->SpawnActor<APholderonCharacter>(SquadCharClass0, TargetingPawn->GetActorTransform(), SpawnParams);
		SquadCharacters[1] = World->SpawnActor<APholderonCharacter>(SquadCharClass1, SquadInitTransform, SpawnParams);
		SquadCharacters[2] = World->SpawnActor<APholderonCharacter>(SquadCharClass2, SquadInitTransform, SpawnParams);
	}
    if (SquadCharacters[0])
    {
        Possess(SquadCharacters[0]);
    }
}

📄 캐릭터 빙의

  • 캐릭터에 빙의(AController::Possess)하면 CurrentCharacter로 지정된다.

  • Targeting Pawn을 빙의한 캐릭터에 붙인다.

✏️ Header

protected:
	virtual void OnPossess(APawn* aPawn) override;

✏️ CPP

void APholderonPlayerController::OnPossess(APawn* aPawn)
{
	Super::OnPossess(aPawn);
	CurrentCharacter = Cast<APholderonCharacter>(GetPawn());
	if (TargetingPawn)
	{
		TargetingPawn->AttachToActor(Parent, FAttachmentTransformRules::SnapToTargetIncludingScale, FName("pelvis"));
	}
}

📄 입력 바인딩

  • 플레이어 컨트롤러 입력의 대부분은 CurrentCharacter에 전달되어 행동을 실행한다.
  • SwapCharacter 입력은 플레이어 컨트롤 내에서 처리한다.

✏️ Header

protected:
	virtual void SetupInputComponent() override;

private:
  	/** Input */
	UPROPERTY(EditAnywhere, Category = "Input", meta = (AllowPrivateAccess = "true"))
	class UInputMappingContext* ControllerMappingContext;

	UPROPERTY(EditAnywhere, Category = "Input", meta = (AllowPrivateAccess = "true"))
	UInputAction* MoveAction;

	UPROPERTY(EditAnywhere, Category = "Input", meta = (AllowPrivateAccess = "true"))
	UInputAction* LookAction;

  	UPROPERTY(EditAnywhere, Category = "Input", meta = (AllowPrivateAccess = "true"))
	class UInputAction* JumpAction;

	UPROPERTY(EditAnywhere, Category = "Input", meta = (AllowPrivateAccess = "true"))
	UInputAction* SwapAction;

  	void MoveTriggered(const FInputActionValue& Value);
	void LookTriggered(const FInputActionValue& Value);
	void JumpTriggered();
	void JumpReleased();
	void SwapPressed(const FInputActionValue& Value);

✏️ CPP

void APholderonPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent))
	{
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &APholderonPlayerController::MoveTriggered);
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &APholderonPlayerController::LookTriggered);
  		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &APholderonPlayerController::JumpTriggered);
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &APholderonPlayerController::JumpReleased);
		EnhancedInputComponent->BindAction(SwapAction, ETriggerEvent::Started, this, &APholderonPlayerController::SwapPressed);
    }
}

  void APholderonPlayerController::MoveTriggered(const FInputActionValue& Value)
{
	if (CurrentCharacter)
	{
		CurrentCharacter->Move(Value);
	}
}

void APholderonPlayerController::LookTriggered(const FInputActionValue& Value)
{
	if (CurrentCharacter)
	{
		CurrentCharacter->Look(Value);
	}
}

void APholderonPlayerController::JumpTriggered()
{
	if (CurrentCharacter)
	{
		CurrentCharacter->Jump();
	}
}

void APholderonPlayerController::JumpReleased()
{
	if (CurrentCharacter)
	{
		CurrentCharacter->StopJumping();
	}
}

void APholderonPlayerController::SwapPressed(const FInputActionValue& Value)
{
	if (bCanSwap == false || CurrentCharacter == nullptr) return;
	if (Value.GetMagnitude() == 1.f && CurrentCharacter != SquadCharacters[0] && SquadCharacters[0]->GetCombatState() != ECombatState::ECS_Dead)
	{
		SwapCharacter(SquadCharacters[0]);
		SwapCharTimer_Start();
	}
	else if (Value.GetMagnitude() == 2.f && CurrentCharacter != SquadCharacters[1] && SquadCharacters[1]->GetCombatState() != ECombatState::ECS_Dead)
	{
		SwapCharacter(SquadCharacters[1]);
		SwapCharTimer_Start();
	}
	else if (Value.GetMagnitude() == 3.f && CurrentCharacter != SquadCharacters[2] && SquadCharacters[2]->GetCombatState() != ECombatState::ECS_Dead)
	{
		SwapCharacter(SquadCharacters[2]);
		SwapCharTimer_Start();
	}
}

📄 캐릭터 교대

  • 기존 캐릭터와 교대할 캐릭터의 Transform을 서로 바꿔준다.

  • 기존 캐릭터의 카메라 FOV와 Control Rotation을 유지해준다.

✏️ Header

private:
	void SwapCharacter(APholderonCharacter* NewChar);

✏️ CPP

void APholderonPlayerController::SwapCharacter(APholderonCharacter* NewCharacter)
{
	if (NewCharacter == nullptr || CurrentCharacter == nullptr) return;

	// Swap transform
	const FTransform CurrentTransform = CurrentCharacter->GetActorTransform();
	CurrentCharacter->SetActorTransform(SquadInitTransform);
	NewCharacter->SetActorTransform(CurrentTransform);

	// Keep camera FOV
	const float CurrentFOV = CurrentCharacter->GetCameraFOV();
	NewCharacter->SetCameraFOV(CurrentFOV);

	// Keep control rotation and possess
	const FRotator CurrentControlRotation = GetControlRotation();
	Possess(NewCharacter);
	SetControlRotation(CurrentControlRotation);
}

📄 그 외

profile
노력하는 게임 개발자

0개의 댓글