하아찡

[언리얼5, C++] 맵 이동 본문

C++/이것저것서버테스트

[언리얼5, C++] 맵 이동

하아찡 2025. 2. 23. 23:14

언리얼 버전 5.5.3

서버언어 C++로 구성했습니다.


일단 클라이언트에서 맵이동해주는 액터를 하나 생성해줍니다.

저는 LevelChangerBase 클래스를 하나 추가했습니다.

 

LevelChangerBase.h

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"
#include "LevelChangerBase.generated.h"

/********************************************************
			클라이언트에서 Level 이동에 사용
********************************************************/
UCLASS(Blueprintable)
class LLLLLLLLLQQQ_API ALevelChangerBase : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ALevelChangerBase();

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Level")
	int32 TargetLevelIndex;	// 이동 레벨 명

    UFUNCTION(BlueprintCallable, Category = "Level")
    virtual void ChangeLevel();

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Collision")
    UBoxComponent* TriggerBox;

    UFUNCTION()
    void OnOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
                   UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
                   bool bFromSweep, const FHitResult& SweepResult);

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

public:
	FName GetIndexToLevelName(int32 index);
private:
	void InsertLevel(int32 Index, FName LevelName);

private:
	TMap<int32, FName> LevelList;

};

 

 

LevelChangerBase.cpp

#include "LevelChangerBase.h"
#include "MyGameInstance.h"
#include "MylllllllllqqqCharacter.h"
#include "lllllllllqqq.h"
#include "ServerPacketHandler.h"
#include "Kismet/GameplayStatics.h"


// Sets default values
ALevelChangerBase::ALevelChangerBase()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = false;

    // 충돌 박스 생성
    TriggerBox = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
    RootComponent = TriggerBox;
    TriggerBox->SetBoxExtent(FVector(100.f, 100.f, 100.f));
    TriggerBox->SetCollisionProfileName(TEXT("Trigger"));

    // 오버랩 이벤트 바인딩
    TriggerBox->OnComponentBeginOverlap.AddDynamic(this, &ALevelChangerBase::OnOverlap);

    
    
}

// Called when the game starts or when spawned
void ALevelChangerBase::BeginPlay()
{
    Super::BeginPlay();

    // 맴데이터 추가
    InsertLevel(1, "ThirdPersonMap");
    InsertLevel(2, "TestMap");

}

void ALevelChangerBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);

    if (TriggerBox)
    {
        TriggerBox->OnComponentBeginOverlap.RemoveAll(this);
    }
}

// 인덱스 번호로 레벨이름을 가져오는 함수
FName ALevelChangerBase::GetIndexToLevelName(int32 index)
{
    FName levelName = "";
    if (LevelList.Contains(index))
    {
		levelName = LevelList[index];
	}
    return levelName;
}

void ALevelChangerBase::InsertLevel(int32 Index, FName LevelName)
{
    LevelList.Add(Index, LevelName);
}

void ALevelChangerBase::ChangeLevel()
{
    if (TargetLevelIndex >= 0)
    {
        if (auto* GameInstance = Cast<UMyGameInstance>(GWorld->GetGameInstance()))
        {
            // 클라이언트가 가지고 있는 Player 정보 초기화

            FName levelName = GetIndexToLevelName(TargetLevelIndex);
            
            if (levelName != "") {

                //여기서 이제 특정 맵으로 움직였다는 패킷보냄
                GameInstance->_isMapLoading = true;             //맵 움직임
                Protocol::C_LEVEL_MOVE levelPkt;

                //현재 플레이어 정보를 넘겨줌
                *levelPkt.mutable_players() = *GameInstance->MyPlayer->GetPlayerInfo();
                levelPkt.set_leveltype(static_cast<Protocol::LevelType>(TargetLevelIndex));

                SEND_PACKET(levelPkt);
                UGameplayStatics::OpenLevel(this, levelName);
            }
        }

    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("TargetLevelName is not set."));
    }
}


void ALevelChangerBase::OnOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
    UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
    bool bFromSweep, const FHitResult& SweepResult)
{
    // 오버랩한 객체가 플레이어인지 확인
    
    if (AMylllllllllqqqCharacter* PlayerCharacter = Cast<AMylllllllllqqqCharacter>(OtherActor))
    {
        APlayerController* PC = Cast<APlayerController>(PlayerCharacter->GetController());

        // 로컬 클라이언트에서만 맵 이동
        if (PC && PC->IsLocalController())
        {
            ChangeLevel();
        }
    }
    
}

 

트리거 박스에 닿을경우 지정된 레벨로 이동시킵니다.

 


서버 코드를 보기전에 맵 이동하는 패킷을 하나 만들어줍니다.

message C_LEVEL_MOVE
{
	ObjectInfo players = 1;
	LevelType leveltype = 2;
}

message S_LEVEL_MOVE
{
	bool success = 1;
	string levelname = 2;
}

위와같이 지정해줬습니다.

클라이언트는 어느 맵으로갈지 맵 정수값을 전달해주고 서버는 성공값과 맵이름을 전달해줍니다.


 

서버측 추가된 코드

bool Handle_C_LEVEL_MOVE(PacketSessionRef& session, Protocol::C_LEVEL_MOVE& pkt)
{
	// TODO
	auto gameSession = static_pointer_cast<GameSession>(session);
	PlayerRef player = gameSession->player;
	if (player == nullptr)
		return false;

	RoomRef room = player->room.lock();
	if (room == nullptr)
		return false;

	// 입장 레벨 값
	Protocol::LevelType levelType = pkt.leveltype();

	ObjectRef object = std::static_pointer_cast<Object>(player);
	
	LOG("LevelType : " << pkt.leveltype());

	// 기존 채널에서 지움 -> 같은 채널에 있는 사람들에게 떠난다고 알려줌.
	GMapManager->DoAsync(&MapManager::LeaveMap, object);

	// 해당 레벨로 입장.
	GMapManager->DoAsync(&MapManager::JoinMap, levelType, object, false);
	return true;
}

 


결과

1번

 

2번

 

1번 gif를 보시면 새로운 맵으로 이동했을때 접속할 채널이 없을경우 새로운 채널을 만들어서 접속하게됩니다.

 

2번 gif를 보시면 새로운 맵으로 이동했을때 접속이 가능한 채널을 찾은 후 해당 채널로 입장하게 됩니다. 그리고 기존에 있던 맵은 사람이 없기때문에 서버에서 자원을 먹지 않도록 제거해 줍니다.

 

반응형