하아찡
[C++] 개인맵 및 제한시간 본문
언리얼 버전 5.5.3
서버언어 C++로 구성했습니다.
실행화면





개인맵을 만들어보고싶어서 시간제한이 있는 개인맵을 만들어봤습니다.
Room 클래스를 상속받은 ChaosDunjeon 클래스를 생성했습니다.
ChaosDunjeon .h
#pragma once
#include "Room.h"
class ChaosDunjeon :
public Room
{
public:
ChaosDunjeon();
~ChaosDunjeon();
void Init() override;
void UpdateTick() override;
virtual bool EnterRoom(ObjectRef object) override;
void MapTick();
int GetCurTime(){return curDunjeonTime;}
private:
int curDunjeonTime = 0;
uint64 DeadMonsterCnt = 0;
int dunjeonMaxTime = 5;
int DestKillPoint = 10;
virtual bool IsMonsterDie(uint64 objectid) override;
};
ChaosDunjeon .cpp
#include "pch.h"
#include "ChaosDunjeon.h"
#include "MapManager.h"
#include "RoomManager.h"
#include "Object.h"
#include "Player.h"
#include "SpawnTable.h"
#include "Monster.h"
ChaosDunjeon::ChaosDunjeon()
{
dunjeonMaxTime = dunjeonMaxTime * 60;
curDunjeonTime = dunjeonMaxTime;
uint64 index = 0;
// TODO 어디에 맵별로 데이터를 저장해두고 불러다 써야할듯 너무 지저분함.
vector<StructMonsterSpawn> spawnList = SpawnTable::LoadSpawnMonstersFromXML("BaseLevel.xml");
for (auto monster : spawnList) {
SetMonsterSpawner(index++, monster.monsterID, monster.x, monster.y, monster.z);
SetMonsterSpawner(index++, monster.monsterID, monster.x + 100, monster.y - 70, monster.z);
SetMonsterSpawner(index++, monster.monsterID, monster.x + 100, monster.y + 150, monster.z);
}
}
ChaosDunjeon::~ChaosDunjeon()
{
cout << "~ChaosDunjeon" << endl;
}
void ChaosDunjeon::Init()
{
MyRoom = GetRoomRef();
UpdateTick();
MapTick();
}
void ChaosDunjeon::UpdateTick()
{
Room::ProcessMonster();
if (DeadMonsterCnt >= DestKillPoint) {
StopMonsterSpawn = true;
CompleteLevel = true;
}
if (bRoomActive && MyRoom != nullptr) {
MyRoom->DoTimer(updateTick, &ChaosDunjeon::UpdateTick);
}
}
bool ChaosDunjeon::EnterRoom(ObjectRef object)
{
Room::EnterRoom(object);
if (bRoomActive == false) {
SetRoomActive(true); // 재접속일때 활성화
MyRoom = GetRoomRef();
UpdateTick();
}
return false;
}
void ChaosDunjeon::MapTick()
{
//LOG("DunjeonTIme : " << curDunjeonTime);
// 던전정보를 남은시간과 진행도를 보내줌.
float progressPer = DeadMonsterCnt / (float)DestKillPoint;
Protocol::S_DUNJEON pkt;
pkt.set_remaintime(curDunjeonTime);
pkt.set_progress(progressPer);
SendBufferRef sendBuffer = ClientPacketHandler::MakeSendBuffer(pkt);
Broadcast(sendBuffer, NoneObjectID); // 현재 진행상황을 클라이언트에게 보내줌
if ( 0 >= curDunjeonTime) {
CompleteLevel = true;
MyRoom->ClearJobs();
// TODO 던전 폭파
SetRoomActive(false); // 룸활성화 끝
for (auto object : _objects) {
// 던전에 있는사람 추방
if(object.second == nullptr) continue;
object.second->posInfo->set_x(-410);
object.second->posInfo->set_y(0);
object.second->posInfo->set_z(180);
Protocol::S_LEVEL_MOVE pkt;
Protocol::LevelType levelType = Protocol::LEVEL_TYPE_TEST;
Protocol::S_LEVEL_MOVE levelMove;
auto level = GMapManager->GetRoomManager(levelType);
levelMove.set_success(true);
levelMove.set_leveltype(levelType);
levelMove.set_levelname(level->GetLevelName());
// 클라에서 받은 위치값
object.second->movePosInfo->set_x(-410);
object.second->movePosInfo->set_y(0);
object.second->movePosInfo->set_z(180);
PlayerRef player = std::static_pointer_cast<Player>(object.second);
if(auto Session = player->session.lock()){
auto sendBuffer = ClientPacketHandler::MakeSendBuffer(levelMove);
Session->Send(sendBuffer);
}
}
return;
}
curDunjeonTime--;
if (MyRoom != nullptr) {
MyRoom->DoTimer(1000, &ChaosDunjeon::MapTick);
}
}
bool ChaosDunjeon::IsMonsterDie(uint64 objectid)
{
float monsterHp = _monsters[objectid]->objectInfo->hp();
if (monsterHp > 0) {
return false;
}
else {
DeadMonsterCnt++;
return true;
}
}
현재 맵 이동 로직은
클라이언트에서 요청(C_LEVEL_MOVE)를 서버가 받은뒤에 서버가(S_LEVEL_MOVE) 패킷을 호출해줌으로써 정상적인 맵이동이 가능해집니다. 그 후에 맵이동 완료된 패킷을 다시 클라이언트가 보내고 서버가 완료패킷을 받은 후 사용자에게 다른 사용자 데이터를 보내게 됩니다.
여기서 봐야할점은 제한시간이 끝난 이후 플레이어를 해당 맵에서 내쫓을때 필요한데, 서버에서 플레이어에게 (S_LEVEL_MOVE)패킷을 보내주면 자동으로 맵이동이 가능해집니다. 그래서 제한시간이 완료됐을때 맵에서 플레이어를 쫓을때 S_LEVEL_MOVE 패킷을 생성해서 각 플레이어에게 보내줍니다.
목표치는 몬스터 사냥숫자로 정해두었습니다. 현재는 10킬하면 더이상 해당 개인맵에서 몬스터가 생성되지않습니다.
그리고 매 초마다 플레이어에게 시간데이터와 얼마나 목표치를 달성했는지 퍼센트를 보내줍니다.
UpdateTick은 몬스터 움직임 및 어그로를 처리해주는 틱입니다. 해당 맵은 플레이어가 존재하지 않아도 일정시간동안 맵이 살아있기때문에 플레이어가 존재하지않는데 계속 연산시키는건 불필요한 연산임으로 플레이어가 나갈때 bActiveRoom을 false로 변경하면 해당 UpdateTick 더이상 호출되지 않습니다.
대신 플레이어가 맵에 다시 접속했을때는 활성화 시켜줘야하기때문에 SetActive(true) 함수를 호출해주고 다시 UpdateTick을 호출해주면 정상적으로 다시 몬스터와 어그로가 활성화 됩니다.
변경된 함수
RoomManager.cpp
void RoomManager::LeaveChanel(PlayerRef player)
{
//PlayerRef player = dynamic_pointer_cast<Player>(object);
if(player == nullptr) return;
//현재 접속된 채널을 가져옴.
uint64 ObjectChanel = player->GetMyChanel();
if(Rooms[ObjectChanel] == nullptr) return;
// 여기서 해당 유저를 디스폰해줌
Rooms[ObjectChanel]->LeaveRoom(player);
// 플레이어 수
uint64 enterCnt = Rooms[ObjectChanel]->GetEnterPlayerCnt();
if (enterCnt == 0) {
//방에 남은 사람이 없을경우 해당방은 사라짐.
if (!PrivateRoom) {
Rooms[ObjectChanel]->SetRoomActive(false);
DoTimer(Rooms[ObjectChanel]->GetUpdateTick(), &RoomManager::RemoveRoom, ObjectChanel);
}
else {
if (Rooms[ObjectChanel]->CompleteLevel) {
// 완료 후 나가면 제거
Rooms[ObjectChanel]->SetRoomActive(false);
DoTimer(Rooms[ObjectChanel]->GetUpdateTick(), &RoomManager::RemoveRoom, ObjectChanel);
}
else {
// TODO 완료되지않았는데 나갈경우 처리 남은시간 예약걸어두고 시간지나면 제거
//Rooms[ObjectChanel]->SetRoomActive(false);
shared_ptr<ChaosDunjeon> Dunjeon = dynamic_pointer_cast<ChaosDunjeon>(Rooms[ObjectChanel]);
Rooms[ObjectChanel]->SetRoomActive(false);
DoTimer((Dunjeon->GetCurTime() + 5) * 1000, &RoomManager::RemoveRoom, ObjectChanel);
}
}
}
LOG("Remove Object ID : " << player->objectInfo->object_id());
}
이제는 룸을 만들때 PrivateRoom인지 값을받고 설정이 됩니다.
PrivateRoom일경우엔 방번호를 캐릭터 고유 Index값으로 설정했습니다.
Objectid 로 사용하지않는 이유는 다시 껏다 켰을때 서버에서 새롭게 Objectid를 발급해주기때문에 이전에 접속했던 방번호를 알수가 없습니다.
그래서 고유값인 캐릭터 고유Index를가지고 방번호를 지정해주었습니다.
사실 나머지는 다른점이 없기때문에 해당 부분을 보시면
if (Rooms[ObjectChanel]->CompleteLevel) {
// 완료 후 나가면 제거
Rooms[ObjectChanel]->SetRoomActive(false);
DoTimer(Rooms[ObjectChanel]->GetUpdateTick(), &RoomManager::RemoveRoom, ObjectChanel);
}
else {
// TODO 완료되지않았는데 나갈경우 처리 남은시간 예약걸어두고 시간지나면 제거
//Rooms[ObjectChanel]->SetRoomActive(false);
shared_ptr<ChaosDunjeon> Dunjeon = dynamic_pointer_cast<ChaosDunjeon>(Rooms[ObjectChanel]);
Rooms[ObjectChanel]->SetRoomActive(false);
DoTimer((Dunjeon->GetCurTime() + 5) * 1000, &RoomManager::RemoveRoom, ObjectChanel);
}
목표치에 도달했을때 CompleteLevel값을 true로 가지고있기때문에 바로 방을 지워버립니다.
하지만 CompleteLevel값이 false일경우에는 개인맵이 아직 목표치에 도달히지 못한 경우이기때문에 맵을 살려둡니다. 대신 SetRoomActive(false)를 호출하여 더이상 UpdateTick이 호출되지 않도록 작업합니다.
'C++ > 이것저것서버테스트' 카테고리의 다른 글
[푸념] DB연결관련 (0) | 2025.04.01 |
---|---|
[C++] 몬스터 스포너 xml (0) | 2025.03.27 |
[C++] 인벤토리 - 3 (장비 장착) (0) | 2025.03.25 |
[C++] 인벤토리 - 2 (인벤토리 서버저장) (0) | 2025.03.25 |
[C++] 인벤토리 (1) | 2025.03.22 |