게임개발일지/리그오브워치

[리그오브워치개발일지] #15 기본 게임 규칙 : 인게임 접속 후 시작

김진우 개발일지 2024. 1. 16. 11:25

Git

레포지토리 주소 : https://github.com/kjinwoo12/UE5Game_LeagueOfWatch
기준 태그 : 15_IngameStart

요구사항 확인

제한시간 안에 캐릭터를 선택하고 확인 버튼을 눌러 캐릭터를 확정하면 인게임으로 넘어가서 게임을 시작하게 된다. 플레이어는 인게임 레벨에 들어오면 이전글에서까지 만든 캐릭터 선택 화면에서 선택한 캐릭터에 빙의(possess)해서 게임을 시작하게 된다. 자세한 게임 규칙은 아직 정해진게 없기 때문에 캐릭터를 팀에 맞는 위치에 생성하고, 빙의하고, 움직이는 것까지 해볼 예정이다.

추가로 만들 것은, 오버워치와 롤 모두 초반 일정 시간동안 기지에서 벗어날 수 없는 시간이 존재하는데 이 기능 또한 추가할 예정이다.

정리하면 다음과 같다.

  • 캐릭터 선택 화면에서 모든 플레이어가 캐릭터를 선택하면 인게임으로 화면 전환
  • 플레이어가 선택한 캐릭터를 해당 플레이어의 팀 기지에 생성
    • 플레이어가 선택한 캐릭터와 소속 팀 정보 전달
    • 플레이어를 소속 팀 생성 위치에 생성
  • 플레이어가 자신이 선택한 캐릭터에 빙의(Possess)

구현

인게임으로 화면 전환

MultiplayLibrary 적용

클라이언트와 서버의 PlayerState와 GameState 동기화를 위해 MultiplayLibrary 를 적용한다. 이전글Github - MultiplayLibrary 참고

레벨 이동

레벨 이동의 경우에는 #13 유저 인터페이스 : 캐릭터 선택 (3) 선택 규칙 - 캐릭터 선택 완료 후 인게임 레벨로 전환에서 이미 구현했다.

캐릭터 생성

플레이어가 선택한 캐릭터와 소속 팀 정보 전달

인게임 레벨(Ingame)에서 플레이어가 선택한 캐릭터를 생성하기 위해서는 Ingame으로 레벨을 이동하기 전에 있었던 캐릭터 선택 레벨(CharacterSelection)에서 플레이어가 어떤 캐릭터를 선택했는지 데이터를 받아올 필요가 있다. 언리얼 엔진에서 레벨을 이동할 때 이전 레벨에서 데이터를 받아오는 방법은 OnSwapPlayerControllers를 사용하는 것이다. 단, OnSwapPlayerControllers에서는 CharacterSelection에 있었던 PlayerStateIngamePlayerState의 유효성이 보장되지 않기 때문에 Old PC(Old PlayerControllerNew PC(New Player Controller 사이에서 직접 데이터를 주고 받을 필요가 있다. 각각의 Player Controller에서 플레이어가 선택한 캐릭터와 소속 팀 정보를 교환해야 한다.

BP_GameMode_Ingame::OnSwapPlayerController

BP_PlayerController_Ingame

플레이어를 소속 팀 위치에 생성

특정한 위치에 플레이어를 스폰시켜 게임을 시작하기 위해서는 PlayerStart 액터를 사용하는 방법이 있다. 레벨에 PlayerStart 액터가 존재하면 PlayerController는 자동으로 PlayerStart의 위치에서 게임을 시작하게 된다.
Ingame 레벨의 경우에는 레드팀과 블루팀 각각의 스폰 지점이 있기 때문에 PlayerStart 액터도 두 개가 존재해야 한다. 레드팀 플레이어는 레드팀 PlayerStart에, 블루팀 플레이어는 블루팀 PlayerStart에 생성해야 하는데, 그 방법은 게임모드의 ChoosePlayerStart 함수를 오버라이드 하는 것이다. 참고: How to assign player to a specific player start

 

How to assign player to a specific player start

The more i learn about this engine the more i hate it. I’ve now wasted several hours trying to figure out how to spawn a pawn at a specific playerstart. The multiplayer-shooter example is some serious BS. Its riddled with errors. Just placing breakpoints

forums.unrealengine.com

 

ChoosePlayerController 함수의 기본적인 형태는 아래와 같다.

PlayerController를 파라미터로 전달받으면 PlayerController가 생성될 위치에 해당하는 액터를 반환한다. 반드시 액터가 PlayerStart일 필요는 없다. 먼저 레벨에 배치된 레드팀 생성용 PlayerStart와 블루팀 생성용 PlayerStart를 배치하고 PlayerController의 소속 팀에 따라 적절한 PlayerStart 액터를 반환하면 된다.

BP_GameMode_Ingame::ChoosePlayerStart

간단하게 PlayerStart를 상속받는 블루프린트를 하나 만들고 BPI_TeamSystem 인터페이스를 구현한 후 레벨에 블루팀용과 레드팀용 PlayerStart 하나씩 배치하면 끝이다.

플레이어가 자신이 선택한 캐릭터에 빙의

캐릭터 생성 후 빙의

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "IngameServerEvent.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UIngameServerEvent : public UInterface
{
    GENERATED_BODY()
};

/**
 * 
 */
class LEAGUEOFWATCH_API IIngameServerEvent
{
    GENERATED_BODY()

    // Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
    UFUNCTION(BlueprintNativeEvent)
    void AllPlayersReady();
};
void AIngameGM::PlayerStateUpdate_Implementation(AMultiplayPlayerController* PlayerController)
{
    Super::PlayerStateUpdate_Implementation(PlayerController);

    UE_LOG(LogTemp, Log, TEXT("ACharacterSelectionGM::PlayerStateUpdate"));

    if(!IsGameFullOfPlayers())
    {
        UE_LOG(LogTemp, Log, TEXT("ACharacterSelectionGM::PlayerStateUpdate - Players are not full"));
        return;
    }

    if(!IsAllPlayersSynced())
    {
        UE_LOG(LogTemp, Log, TEXT("ACharacterSelectionGM::PlayerStateUpdate - Players are not syncronized"));
        return;
    }

    AllPlayersReady();

    TArray<AActor*> foundActors;
    UGameplayStatics::GetAllActorsWithInterface(GetWorld(), UIngameServerEvent::StaticClass(), foundActors);
    for(AActor* foundActor : foundActors)
    {
        IIngameServerEvent::Execute_AllPlayersReady(foundActor);
    }
}

BP_PlayerController_Ingame::AllPlayersReady

BP_PlayableCharacter 수정

#2 캐릭터 조작#3 기본 게임 규칙 구현 : 플레이어 캐릭터에서 다뤘던 BP_PlayableCharacter를 Multiplay Libaray에 맞게 수정해야 한다.
캐릭터의 Possessed 이벤트에서 PlayerController가 캐릭터를 컨트롤할 수 있게 Enhnaced Input Local Player SubsystemAdd Mapping Context로 등록했으나 이 코드는 서버에서 동작하지 않는다. Possess를 서버에서 수행했을 때 Possessed 이벤트가 서버에서 호출되며 캐릭터를 조종할 수 없는 상태가 되기 때문에 관련 코드를 추가해줄 필요가 있다.

BP_PlayableCharacter::Possessed

BP_PlayableCharacter::OnRep_PlayerControllerForMultiplay

PlayerControllerForMultiplay 변수를 RepNotify로 설정해 멀티플레이에서 Possess를 실행할 경우 클라이언트에서 Enhnaced Input Local Player Subsystem를 설정할 수 있도록 한다.

완성 영상