하아찡
[언리얼5, C++] 맵 이동 본문
언리얼 버전 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번 gif를 보시면 새로운 맵으로 이동했을때 접속할 채널이 없을경우 새로운 채널을 만들어서 접속하게됩니다.
2번 gif를 보시면 새로운 맵으로 이동했을때 접속이 가능한 채널을 찾은 후 해당 채널로 입장하게 됩니다. 그리고 기존에 있던 맵은 사람이 없기때문에 서버에서 자원을 먹지 않도록 제거해 줍니다.
반응형
'C++ > 이것저것서버테스트' 카테고리의 다른 글
[언리얼5, C++] 몬스터 생성 (0) | 2025.03.04 |
---|---|
[언리얼5, C++] 맵 이동 - 2 (0) | 2025.02.25 |
[C++서버] 서버 채널 분할 (0) | 2025.02.21 |
[언리얼5] 채팅 (0) | 2025.02.20 |
[언리얼5] 서버 캐릭터 움직임 처리 - 2 (0) | 2025.02.18 |