코드카타-둘만의 암호

#include <string>
#include <vector>
using namespace std;
// 1) skip 여부를 O(1)로 확인하기 위한 테이블 만들기
static vector<bool> BuildSkipTable(const string& skip) {
vector<bool> isSkip(26, false);
for (char ch : skip) {
isSkip[ch - 'a'] = true;
}
return isSkip;
}
// 2) 문자 1개를 규칙대로 변환하는 함수
static char ShiftChar(char c, int index, const vector<bool>& isSkip) {
int moved = 0;
while (moved < index) {
if (c == 'z') c = 'a';
else c = c + 1;
if (isSkip[c - 'a']) {
continue; // skip이면 카운트하지 않음
}
moved++; // skip이 아닐 때만 카운트
}
return c;
}
string solution(string s, string skip, int index) {
vector<bool> isSkip = BuildSkipTable(skip);
for (int i = 0; i < (int)s.size(); i++) {
s[i] = ShiftChar(s[i], index, isSkip);
}
return s;
}
현재 채팅 숫자야구 게임 진행도
2-1강과 2-2강을 해봤는데 아직까진 논리 구조가 명확하게 안 떠올라서 조금 더 머리가 깨져야 한다.
RPC(Remote Procedure Call)
간단히 말해, “호출하는 PC와 실행하는 PC가 달라도 되게끔 해주는 통신 기법”이라고 할 수 있다.
언리얼에선 게임에 큰 영향을 끼치지 않는 일시적인 효과들에, 주로 코스메틱(사운드, 파티클 등)에 사용한다.
Call과 Invoke
Call
- 정적인 의미를 가진다.
- 컴파일 타임에 어떤 함수인지, 호출하는 곳과 실행하는 곳이 정해져야 한다.
- 지금까지 정의했던 일반적인 전역 함수와 멤버 함수가 이에 해당한다. 즉, 직접적으로 함수를 호출하고 실행해야 한다.
Invoke
- 동적인 의미를 가진다.
- 런타임에 어떤 함수인지, 호출하는 곳과 실행하는 곳이 어딘지 정해진다.
- 예를 들어, 함수 포인터, 동적 바인딩, RPC 같은 개념들이 이에 해당한다.
- 즉, 간접적으로 함수를 호출하고 실행해야 한다. RPC도 마찬가지로 "RPC를 Invoke"라고 표현한다.
Actor Ownership
네트워크 멀티플레이가 적용되려면, 액터는 서버에서 스폰되고 bReplicated가 true여야 한다.
서버에서 스폰된 후 SetOwner(PlayerController) 함수를 호출해야 Client-Owned Actor가 된다.
📌PlayerController가 Local PlayerController와 같다면, Client-Owned Actor 이다.
- 예: 내 플레이어 캐릭터
📌PlayerController가 Local PlayerController와 다르다면, Owned by different client 이다.
- 예: 내 화면에 보이는 친구의 플레이어 캐릭터
📌서버에서 스폰되었지만 SetOwner() 함수 호출이 없으면, Server-Owned Actor 이다.
- 예: 보물상자 액터
NetMulticast, Server, Client 키워드
UFUNCTION() 매크로와 함께 사용되는 키워드들이다.
키워드는 “해당 원격 PC에서 RPC를 실행시켜 달라는 요청”을 뜻한다.
실제로 실행될지는 표를 통해 판단해야 한다.
NetMulticast
- “서버를 포함한 모든 클라이언트에서 해당 RPC를 실행시켜주세요”라는 요청이다.
Server
- “서버에서 해당 RPC를 실행시켜주세요.”라는 요청이다.
Client
- “클라이언트에서 해당 RPC를 실행시켜주세요.”라는 요청이다.
자주 쓰이는 케이스
RPC가 클라이언트에서 호출되고 서버에서 실행되어야 하는 경우에는 Server 키워드를 사용해야 한다.
- ClientConnection이 소유하고 있는 액터(Client-Owned)에서 RPC가 호출되어야 한다.
- _Validate() 함수에서 RPC를 실행할지 말지 결정한다.
RPC가 서버와 모든 클라이언트에서 실행되어야 하는 경우에는 NetMulticast 키워드를 사용해야 한다.
- 서버에서 호출해야 한다.
- 부하가 심하므로, 빈번하게 호출되게끔 코드를 작성하는 건 권장되지 않는다. ex) Tick() 함수 내에서 NetMulticast RPC 호출.
RPC가 서버에서 호출되고 클라이언트에서 실행되어야 하는 경우에는 Client 키워드를 사용해야 한다.
- 서버에서 호출해야 한다.
WithValidation
📚UFUNCTION() 매크로와 함께 사용되며, 서버에서 실행되는 RPC의 경우에 해당 키워드를 작성하는 것이 권장된다.
이 키워드가 붙은 RPC를 구현 할때는 _Implementation() 함수와 _Validate() 함수로 나뉘어 진다.
_Validate() 함수는 해당 RPC가 서버에서 실제로 실행할지 말지 결정한다.
서버 실행 로직은 무조건적으로 신뢰되기 때문에, 위변조를 막기 위한 방어막 역할을 한다.
Unreliable VS Reliable
📚UFUNCTION() 매크로와 함께 사용되는 키워드이다.
RPC는 기본적으로 Unreliable 키워드를 기준으로 동작한다. 아무것도 안쓰면 Unreliable이다.
Unreliable은 원격 PC에서 무조건 실행되리라는 보장이 없다.
반면, 원격 PC에서 무조건 실행되어야 하는 로직이라면 Reliable 키워드를 작성한다.
📌Unreliable의 활용 예시
- 코스메틱(이펙트, 사운드 등)처럼 게임에 큰 영향이 없는 로직.
📌Reliable의 활용 예시
- 충돌, 데미지, 스폰처럼 게임에 큰 영향을 끼치는 로직.
멀티플레이 채팅 구현
// CXPlayerController.h
...
class CHATX_API ACXPlayerController : public APlayerController
{
...
public:
...
UFUNCTION(Client, Reliable)
void ClientRPCPrintChatMessageString(const FString& InChatMessageString);
UFUNCTION(Server, Reliable)
void ServerRPCPrintChatMessageString(const FString& InChatMessageString);
protected:
...
};
// CXPlayerController.cpp
...
#include "EngineUtils.h"
...
void ACXPlayerController::SetChatMessageString(const FString& InChatMessageString)
{
ChatMessageString = InChatMessageString;
//PrintChatMessageString(InChatMessageString);
if (IsLocalController() == true)
{
ServerRPCPrintChatMessageString(InChatMessageString);
}
}
...
void ACXPlayerController::ClientRPCPrintChatMessageString_Implementation(const FString& InChatMessageString)
{
PrintChatMessageString(InChatMessageString);
}
void ACXPlayerController::ServerRPCPrintChatMessageString_Implementation(const FString& InChatMessageString)
{
for (TActorIterator<ACXPlayerController> It(GetWorld()); It; ++It)
{
ACXPlayerController* CXPlayerController = *It;
if (IsValid(CXPlayerController) == true)
{
CXPlayerController->ClientRPCPrintChatMessageString(InChatMessageString);
}
}
}
NetMulticast RPC를 통한 서버 접속 알리기
// CXGameStateBase.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "CXGameStateBase.generated.h"
/**
*
*/
UCLASS()
class CHATX_API ACXGameStateBase : public AGameStateBase
{
GENERATED_BODY()
public:
UFUNCTION(NetMulticast, Reliable)
void MulticastRPCBroadcastLoginMessage(const FString& InNameString = FString(TEXT("XXXXXXX")));
};
// CXGameStateBase.cpp
#include "CXGameStateBase.h"
#include "Kismet/GameplayStatics.h"
#include "Player/CXPlayerController.h"
void ACXGameStateBase::MulticastRPCBroadcastLoginMessage_Implementation(const FString& InNameString)
{
if (HasAuthority() == false)
{
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (IsValid(PC) == true)
{
ACXPlayerController* CXPC = Cast<ACXPlayerController>(PC);
if (IsValid(CXPC) == true)
{
FString NotificationString = InNameString + TEXT(" has joined the game.");
CXPC->PrintChatMessageString(NotificationString);
}
}
}
}
// CXGameModeBase.h
...
class CHATX_API ACXGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
virtual void OnPostLogin(AController* NewPlayer) override;
};
// CXGameModeBase.cpp
#include "Game/CXGameModeBase.h"
#include "CXGameStateBase.h"
void ACXGameModeBase::OnPostLogin(AController* NewPlayer)
{
Super::OnPostLogin(NewPlayer);
ACXGameStateBase* CXGameStateBase = GetGameState<ACXGameStateBase>();
if (IsValid(CXGameStateBase) == true)
{
CXGameStateBase->MulticastRPCBroadcastLoginMessage(TEXT("XXXXXXX"));
}
}
그 다음 BP_GameModeBase > Details > Game State Class에 BP_GameStateBase 지정해 준다.
'TIL' 카테고리의 다른 글
| 3월 17일자 TIL (0) | 2026.03.17 |
|---|---|
| 3/16일자 TIL (0) | 2026.03.16 |
| 3/12일자 TIL (0) | 2026.03.12 |
| 3/11일자 TIL (0) | 2026.03.11 |
| 3/10일자 TIL (0) | 2026.03.10 |