프로그래머스 - 숫자 변환하기
자연수 x를 y로 변환하려고 합니다. 사용할 수 있는 연산은 다음과 같습니다.
x에 n을 더합니다
x에 2를 곱합니다.
x에 3을 곱합니다.
자연수 x, y, n이 매개변수로 주어질 때, x를 y로 변환하기 위해 필요한 최소 연산 횟수를 return하도록 solution 함수를 완성해주세요. 이때 x를 y로 만들 수 없다면 -1을 return 해주세요.
이 문제는 BFS로 풀어야할 것 같습니다.
최소 횟수만 구한면 되고 최소 연산 횟수가 되는 경우의 수를 구하는 것도 아니니
현재 값과 연산 횟수로 pair를 만들어서 Priority_Queue에 넣어서 관리
x >= y 이면 answer은 무조건 -1
중복을 막기 위해 set을 사용합니다.
#include <string>
#include <vector>
#include <queue>
#include <set>
using namespace std;
int BFS(int startNum, int targetNum, int calcCnt, int addValue)
{
if(startNum == targetNum) return 0;
else if(startNum > targetNum) return -1;
set<int> visitedNum;
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
pq.push(make_pair(0, startNum));
while(!(pq.empty()))
{
pair<int, int> cur = pq.top();
pq.pop();
vector<int> newNums;
newNums.push_back(cur.second + addValue);
newNums.push_back(cur.second * 2);
newNums.push_back(cur.second * 3);
for(int num:newNums)
{
if(visitedNum.find(num) != visitedNum.end()) continue;
visitedNum.insert(num);
if(num == targetNum)
{
return cur.first + 1;
}
else if(num < targetNum)
{
pq.push(make_pair(cur.first + 1, num));
}
}
}
return -1;
}
int solution(int x, int y, int n) {
int answer = BFS(x, y, 0, n);
return answer;
}
매번 priority_queue & pair를 같이 쓸 때마다 first, second의 우선순위를 햇갈립니다.
오늘도 햇갈려서 시간을 지체했네요.
해당 프로세스가 네트워크 상에서 어떤 역할을 하고 있는 지를 의미합니다.
싱글(NM_StandAlone) 서버(NM_Listen, NM_DedicatedServer), 클라이언트(NM_Client)의 값이 들어갑니다
클라이언트는 해킹에 취약합니다.
보통 게임 핵을 만들 때는 서버와 클라이언트가 주고 받는 패킷을 분석합니다.
일반 공격과 스킬 패킷 속에서 공통 부분을 찾아 데미지일 것 같은 부분을 수정해서 데미지 핵을 만드는 식입니다.
이러한 해킹을 방지하기 위해, 중요한 로직은 서버 컴퓨터에서 처리해야 합니다.
VFX나 SFX 등은 클라이언트에서 처리해도 문제 없지만, 데미지 처리 로직과 같은 주요 로직들은 서버에서 처리합니다.
중요한 로직은 서버에서 처리
우리가 작성한 로직이 어디에서 동작하는 지 확인할 방법이 필요합니다.
클라이언트에서 동작하면 안 될 로직이 클라이언트에서 돌아가지 않게 현재 어디에서 동작 중인지를 확인하기 위해 NetMode를 사용합니다.
멀티 플레이 개발 시 가장 큰 걸림돌은 같은 코드가 여러 PC에서 돌아가게 된다는 것입니다.
이전까지 싱글 플레이로 만들 때는 하나의 PC에서만 돌아갔는데, 멀티 플레이는 클라이언트와 서버 여러 군데에서 돌아갑니다. 정확하게는 서버, 내 PC(클라이언트), 다른 클라이언트 PC로 나뉩니다.
이를 알기 위해서는 NetConnection과 NetDirver에 대해서도 알아야합니다.
이 둘은 NetMode뿐만 아니라 NetRole에서도 나오는 근간이라고 생각하면 된다고 합니다.
둘 다 서버, 클라이언트 비교할 때 사용하지만 NetMode는 월드 단위, 월드 클래스에 들어있는 속성,
NetRule은 액터 단위라고 합니다.
UWorld::InternalGetNetMode() 함수 살펴보기
ENetMode UWorld::InternalGetNetMode() const
{
if ( NetDriver != NULL ) // 넷드라이버가 설정되어 있으면
{
const bool bIsClientOnly = IsRunningClientOnly();
return bIsClientOnly ? NM_Client : NetDriver->GetNetMode(); // 클라 아니면 서버 둘 중 하나다.
}
...
}
if ( NetDriver != NULL )
=> 넷드라이버가 설정되어 있다 == Multiplay
=> 넷드라이버가 설정되어 있지 않다 == Standalone
NetDirver::GetNeMode() 살펴보기
// UNetDriver.cpp
ENetMode UNetDriver::GetNetMode() const
{
#if WITH_EDITOR
if (World && World->WorldType == EWorldType::PIE && IsServer())
{
FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(World);
if (WorldContext && WorldContext->RunAsDedicated) // 데디로 실행했다면
{
return NM_DedicatedServer; // 데디 서버 반환.
}
}
#endif
return (IsServer() ? (GIsClient ? NM_ListenServer : NM_DedicatedServer) : NM_Client);
// 서버인데, 클라이언트로 참여하고 있다? 리슨서버. 서버인데 참여하고 있지 않다? 데디서버.
}
월드가 존재하고, 월드 타입이 PIE이어야 하고, 또 IsServer를 호출해서 체크하고 있습니다.
현재 상태를 체크한 뒤 적절한 값을 반환해주네요.
IsServer() 살펴보기
// UNetDriver.cpp
bool UNetDriver::IsServer() const
{
// Client connections ALWAYS set the server connection object in InitConnect()
// @todo ONLINE improve this with a bool
return ServerConnection == NULL;
// 클라이언트는 무조건 서버 커넥션을 가지고 있다.
}
언리얼 엔진에서 클라이언틑 항상 ServerConnection을 가지고 있고, 서버는 가지지 않는 것으로 보입니다.
다른 PC와의 연결이 발생하면 그에 대응하는 UNetConnection 객체도 생성됩니다.
서버에 클라이언트가 접속하면 서버에는 ClientConnection 객체가, 클라이언트에는 ServerConnection 객체가 생성됩니다.
두 PC는 UNetConnection 객체를 통해 통신하게 됩니다.
UNetDriver는 생성된 UNectConnection 객체를 소유하고 관리하고 있습니다.
서버 PC에 생성된 UNetDirver는 접속한 클라이넡의 수 만큼 UNetConnection을 관리합니다.
클라이언트 PC에 생성된 UNetDirver는 ServerConnection 단 하나만을 관리합니다.
서버-클라이언트 구조에서 자명한 사실이라고 합니다.
클라이언트는 다른 클라이언트에 대한 연결(ClientConnection)이 없고 서버와의 연결(ServerConnection)만이 있기 때문에 서버를 통하지 않으면 다른 클라리언트와의 상호작용이 불가능합니다.
통신을 위해서는 NetDriver가 생성되어야 한다. 각 PC마다 NetDriver가 하나씩 생성되어야 합니다.
NetDriver에는 매개변수로 ClientConnection, ServerConnection을 보관한다
NetDriver.h
// UNetDriver.h
...
UCLASS(...)
class UNetDriver : public UObject, public FExec
{
...
/** Connection to the server (this net driver is a client) */
UPROPERTY()
TObjectPtr<class UNetConnection> ServerConnection;
/** Array of connections to clients (this net driver is a host) - unsorted, and ordering changes depending on actor replication */
UPROPERTY()
TArray<TObjectPtr<UNetConnection>> ClientConnections;
...
}
액터는 Owner가 설정되어 있지 않으면 통신이 불가능합니다.
// AActor.cpp
UNetConnection* AActor::GetNetConnection() const
{
return Owner ? Owner->GetNetConnection() : nullptr;
// 여기서 오너는 액터/폰/컨트롤러 등등이 될 수 있음.
// 코드에서 볼 수 있듯이 Owner가 지정되어 있지 않으면 통신이 안되게끔 되어 있음.
}
Pawn의 Owner는 결국 Controller이니 Controller의 GetConnection()을 호출합니다.
컨트롤러가 없으면 액터의 것으로 반환합니다.
class UNetConnection* APawn::GetNetConnection() const
{
// if have a controller, it has the net connection
if ( Controller ) // 컨트롤러가 있다면
{
return Controller->GetNetConnection(); // 컨트롤러의 GetNetConnection()을 호출.
}
return Super::GetNetConnection(); // 없다면 AActor::GetNetConnection() 함수 호출.
}
반면에 PlayerController는 빙의중인 Player가 존재하지 않을 때 no Owner가 됩니다.
UNetConnection* APlayerController::GetNetConnection() const
{
// A controller without a player has no "owner"
return (Player != NULL) ? NetConnection : NULL;
}
Dedicate 서버가 있을 때 서버는 한 클라이언트가 접속할 때마다 ClientConnection을 생성해서 관리합니다.
그 때 이 Connection은 하나의 PlayerController를 가집니다.
PlayerController의 Owning Connection == ClientConnection
이 PlayerController가 캐릭터에 빙의하며 소유관계(Ownership)를 가지고 이 캐릭터가 다른 액터들(무기 등)과 소유 관계를 맺으며 소유관계의 연결이 이루어집니다.
이렇게 ClinetConnection으로부터 무기 액터 등에 이르는 소유 관계를 패밀리라고 부릅니다.
// UWorld.cpp
bool UWorld::Listen( FURL& InURL )
{
#if WITH_SERVER_CODE
...
// Create net driver. 넷드라이버 만드는 부분이 있다~ 정도.
if (GEngine->CreateNamedNetDriver(this, NAME_GameNetDriver, NAME_GameNetDriver))
{
NetDriver = GEngine->FindNamedNetDriver(this, NAME_GameNetDriver);
...
}
...
}
NetMode는 월드의 속성입니다.
StandAlone
Client
Listen Server
Dedicated Server
NetMode는 월드에 관련 정보입니다.
그러나 우리는 월드가 아니라 액터 클래스나 컴포넌트 클래스이 맴버 함수 속에서 로직을 작성하는 경우가 대부분입니다.
그럼 그 액터가 서버 PC에 스폰되어있는지, 클라이언트 PC에 스폰 되어있는지,
더 나아가서 그 액터의 멤버 함수가 서버 PC에서 실행되고 있는지,
클라이언트 PC에서 실행되고 있는지도 파악해야 합니다.
이를 위해 NetRole이 존재합니다.
서버에 스폰된 애거가 가진 NetRole 속성 값은 언제나 Authority입니다.
서버에 스폰된 액터에서 수행될 로직은 "권한을 가지고 있다"를 뜻합니다.
게임에 중대한 영향을 끼치는 로직은 NetRole이 Authority일 때 수행해야 합니다.
NetRole이 Authority인 액터가 클래아이언트로 복제되었을 때,
클라이언트에 복제된 액터의 NetRole 속성 값은 Proxy입니다.
"허상"이라는 뜻입니다.
중대한 로직은 여기서 수행하면 안 됩니다.
게임에 중대한 영향을 끼치는 로직을 작성하려면, 해당 액터가 현재 어느 PC에 스폰되어서 로직이 돌고 있는지 구분해야 합니다.
이를 위해 현재 동작하는 컴퓨터에서의 룰을 로컬 룰(Local Role), 커넥션으로 연결된, 반대편 컴퓨터에서의 룰을 리모트 룰(Remote Role)이라고 합니다.
None
Authority
GameModeProxy는 2종류로 나뉩니다.
Autonomous Proxy
PlayerControllerSimulated Proxy
PlayerCharacter
Pawn의 경우 BeginPlay 시에는 빙의된 Controller가 없어서 Owner가 지정되어 있지 않습니다.
그러므로 이 시점에서는 Simulated Proxy입니다.
후에 Controller가 빙의되면서 OnPossess가 동작할 쯤에는 Owner가 설정되었고 그 Owner인 Controller는 서버의 ClientConnection과 연결되어 하난의 패밀리가 형성됩니다. 그러므로 Autonomous Proxy가 됩니다.
NetRole을 활용하여 정의된 중요 함수들
이전에 언급했듯이,
게임에 중대한 영향을 끼치는 데미지나 스폰 같은 로직은 서버 컴퓨터에서 실행되어야 합니다.
이를 위해 언리얼은 AActor::HasAuthority() 함수를 제공됩니다.
또한 입력 관련 로직이나 UI는 Autonomous Proxy에서 수행되어야 합니다.
이를 위해 AController::IsLocalController() 와 APawn::IsLocallyControlled() 함수가 제공됩니다.
// AActor.cpp
FORCEINLINE_DEBUGGABLE bool AActor::HasAuthority() const
{
return (GetLocalRole() == ROLE_Authority);
}
주의해야할 점은 HasAuthority()가 true라고 플레이어가 아니라는 것도, 멀티플레이라는 것도 확정지을 수 없다는 점입니다.
ListenServer일 때는 클라이언트 겸 서버니 true가 되고, Standalone의 경우에도 true가 나오고, Autonomous Proxy일 때도 true가 나옵니다.
TA 분반
Volume Material 만들기
포스트프로세스 글로벌 일루미네이션 - ScreenSpace는 베타 기능?
라이팅 환경은 플랫폼에 따라 잘 조절해야 한다.
Lumen은 모바일로는 부하가 심한 연산
ScreenSpace로 하면 매우 밋밋해 보인다?
PostProcess의 Ambient Occulsion 옵션은 루멘용이 아니다
일반적으로 자주 사용되는 기능들은 Lens에 몰려있습니다.
Bloom도 여기에 들어갑니다. 빛이 번지는 효과
BloomMethod - Standard는 경량화
BloomMethod - Convolution은 빛이 터지듯퍼져나가는 효과가 있습니다. 영상 같은 곳에서 사용
Bloom Method Standard 0일 때는 아무런 번 짐 없음. 값이 높아질 수록 번지는 정도가 심해집니다. 적절한 값을 찾는 것이 중요
Exposure 카메라 노출 관련
Chromatic Aberration
Depth of Field
초점 거리를 조절해서 가까운 것을 선명하게 먼 것을 흐릿하게 보이거나,
가까운 것을 흐릿하게 먼 것을 선명하게 하는 것도 가능합니다.