前回 、AuthAndFriends
と P2PNAT
のプロジェクトにおける Shared 部分とそうでない部分の比較をしつつ、どこを読めばだいたい概要をつかめるかを整理しました。
今回はそのまま P2PNAT
を見ていきます。単純に自分がこの機能を使いたいと思っているためです。
初期化部分
前回の記事に習い、まずは以下をみていきます。
FGame
, FMenu
- コンストラクタ
- Create
- UpdateLayout
- Init
- OnGameEvent (EGameEventType::CheckAutoLogin イベントのみ)
FGame
FGame::FGame() noexcept(false)
{
Menu = std::make_unique<FMenu>(Console);
Level = std::make_unique<FLevel>();
P2PNATComponent = std::make_unique<FP2PNAT>();
CreateConsoleCommands();
}
void FGame::OnGameEvent(const FGameEvent& Event)
{
FBaseGame::OnGameEvent(Event);
P2PNATComponent->OnGameEvent(Event);
}
FP2PNAT
を利用しているようです。後ほど見ていきます。
FMenu
FMenu::FMenu(std::weak_ptr<FConsole> InConsole) noexcept(false):
FBaseMenu(InConsole)
{
}
void FMenu::Create()
{
CreateFriendsDialog();
CreateP2PNATDialog();
CreateNATDocsButton();
FBaseMenu::Create();
AuthDialogs->SetSingleUserOnly(true);
}
void FMenu::CreateP2PNATDialog()
{
const float FX = 100.0f;
const float FY = P2PDialogPositionY;
const float Width = 300.0f;
const float Height = 300.0f;
P2PNATDialog = std::make_shared<FP2PNATDialog>(
Vector2(FX, FY),
Vector2(Width, Height),
DefaultLayer - 2,
NormalFont->GetFont(),
BoldSmallFont->GetFont(),
TinyFont->GetFont(),
LargeFont->GetFont());
P2PNATDialog->SetBorderColor(Color::UIBorderGrey);
P2PNATDialog->Create();
AddDialog(P2PNATDialog);
}
void FMenu::CreateNATDocsButton()
{
Vector2 NATDocsButtonSize = Vector2(150.0f, 35.0f);
NATDocsButton = std::make_shared<FButtonWidget>(
Vector2(800.0f, 100.0f),
NATDocsButtonSize,
DefaultLayer - 1,
L"DOCUMENTATION",
assets::LargeButtonAssets,
NormalFont->GetFont());
NATDocsButton->SetOnPressedCallback([]()
{
FUtils::OpenURL(NATDocsURL);
});
NATDocsButton->SetBorderColor(Color::UIBorderGrey);
NATDocsButton->Create();
NATDocsButton->Hide();
}
void FMenu::OnGameEvent(const FGameEvent& Event)
{
FBaseMenu::OnGameEvent(Event);
if (P2PNATDialog) P2PNATDialog->OnGameEvent(Event);
}
void FMenu::UpdateLayout(int Width, int Height)
{
}
P2PNATDialog
と NATDocsButton
を擁していますが、NATDocsButton
に関しては、 Hide
されているため表示されていません。
また、AuthDialog
の SetSingleUserOnly(true)
が呼び出さており、アプリケーションごとに 1 ユーザーのみログイン可能に制限されているのが AuthAndFriends
サンプルとの相違点です。
では、ここから、新たに登場した FP2PNAT
と FP2PNATDialog
の初期化部分も見ていきます。
FP2PNAT
FP2PNAT::FP2PNAT()
{
}
void FP2PNAT::OnGameEvent(const FGameEvent& Event)
{
}
特にさらに依存するものはなさそうです。
FP2PNATDialog
FP2PNATDialog::FP2PNATDialog(
Vector2 DialogPos,
Vector2 DialogSize,
UILayer DialogLayer,
FontPtr DialogNormalFont,
FontPtr DialogSmallFont,
FontPtr DialogTinyFont,
FontPtr DialogTitleFont) :
FDialog(DialogPos, DialogSize, DialogLayer)
{
HeaderLabel = std::make_shared<FTextLabelWidget>(
DialogPos,
Vector2(DialogSize.x, 25.0f),
Layer - 1,
std::wstring(L"Header"),
L"Assets/wide_label.dds",
FColor(1.f, 1.f, 1.f, 1.f),
FColor(1.f, 1.f, 1.f, 1.f),
EAlignmentType::Left
);
HeaderLabel->SetFont(DialogNormalFont);
BackgroundImage = std::make_shared<FSpriteWidget>(
DialogPos,
DialogSize,
DialogLayer,
L"Assets/texteditor.dds");
NATStatusLabel = std::make_shared<FTextLabelWidget>(
DialogPos + Vector2(HeaderLabel->GetSize().x + HeaderLabel->GetSize().x - 170.0f, 0.0f),
Vector2(170.0f, 25.0f),
Layer - 1,
L"NAT Status: Unknown",
L"",
FColor(1.f, 1.f, 1.f, 1.f),
FColor(1.f, 1.f, 1.f, 1.f),
EAlignmentType::Left
);
NATStatusLabel->SetFont(DialogNormalFont);
Vector2 ChatInputFieldSize = Vector2(DialogSize.x, 30.0f);
Vector2 ChatInputFieldPos = DialogPos + Vector2(0.0f, DialogSize.y) - Vector2(0.0f, ChatInputFieldSize.y);
ChatInputField = std::make_shared<FTextFieldWidget>(
ChatInputFieldPos,
ChatInputFieldSize,
Layer - 1,
L"Chat...",
L"Assets/textfield.dds",
DialogNormalFont,
FTextFieldWidget::EInputType::Normal,
EAlignmentType::Left);
ChatInputField->SetBorderColor(Color::UIBorderGrey);
ChatInputField->SetOnEnterPressedCallback(
[this](const std::wstring& Value)
{
this->OnSendMessage(Value);
}
);
Vector2 ChatTextViewPos = HeaderLabel->GetPosition() + Vector2(0.0f, HeaderLabel->GetSize().y + 5.0f);
Vector2 ChatTextViewSize = Vector2(DialogSize.x, DialogSize.y - ChatInputFieldSize.y - HeaderLabel->GetSize().y - 10.0f);
ChatTextView = std::make_shared<FTextViewWidget>(
ChatTextViewPos,
ChatTextViewSize,
Layer - 1,
L"Choose friend to start chat...",
L"Assets/textfield.dds",
DialogNormalFont);
ChatTextView->SetBorderOffsets(Vector2(10.0f, 10.0f));
ChatTextView->SetScrollerOffsets(Vector2(5.f, 5.0f));
ChatTextView->SetFont(DialogNormalFont);
ChatInfoLabel = std::make_shared<FTextLabelWidget>(
ChatTextViewPos,
ChatTextViewSize,
Layer - 2,
L"",
L"",
FColor(1.f, 1.f, 1.f, 1.f),
FColor(1.f, 1.f, 1.f, 1.f),
EAlignmentType::Center
);
ChatInfoLabel->SetFont(DialogTitleFont);
std::vector<std::wstring> EmptyButtonAssets = {};
Vector2 CloseChatButtonSize = Vector2(150.0f, 35.0f);
CloseCurrentChatButton = std::make_shared<FButtonWidget>(
ChatTextViewPos + Vector2(ChatTextViewSize.x - CloseChatButtonSize.x - 10.0f, 10.0f),
CloseChatButtonSize,
Layer - 2,
L"CLOSE CHAT",
std::vector<std::wstring>({ L"Assets/solid_white.dds" }),
DialogNormalFont,
Color::UIHeaderGrey);
CloseCurrentChatButton->SetFont(DialogNormalFont);
CloseCurrentChatButton->SetBorderColor(Color::UIBorderGrey);
CloseCurrentChatButton->SetOnPressedCallback([]()
{
static_cast<FMenu&>(*FGame::Get().GetMenu()).GetP2PNATDialog()->CloseCurrentChat();
});
CloseCurrentChatButton->Hide();
}
void FP2PNATDialog::Create()
{
if (HeaderLabel) HeaderLabel->Create();
if (BackgroundImage) BackgroundImage->Create();
if (NATStatusLabel) NATStatusLabel->Create();
if (ChatTextView) ChatTextView->Create();
if (ChatInputField) ChatTextView->Create();
if (ChatInfoLabel) ChatInfoLabel->Create();
if (CloseCurrentChatButton) CloseCurrentChatButton->Create();
AddWidget(HeaderLabel);
AddWidget(BackgroundImage);
AddWidget(NATStatusLabel);
AddWidget(ChatTextView);
AddWidget(ChatInputField);
AddWidget(ChatInfoLabel);
AddWidget(CloseCurrentChatButton);
}
void FP2PNATDialog::OnGameEvent(const FGameEvent& Event)
{
}
上記ですでにログイン前の UI イメージを貼りましたが、以下はログインし Chat の往復をした場合のイメージです。
上記コードのコンストラクタと Create
メソッドの内容がなんとなく読み取れます。
上記のコードからイベントハンドラ部分だけひろっておきます。
FP2PNATDialog::FP2PNATDialog(
Vector2 DialogPos,
Vector2 DialogSize,
UILayer DialogLayer,
FontPtr DialogNormalFont,
FontPtr DialogSmallFont,
FontPtr DialogTinyFont,
FontPtr DialogTitleFont) :
FDialog(DialogPos, DialogSize, DialogLayer)
{
ChatInputField->SetOnEnterPressedCallback(
[this](const std::wstring& Value)
{
this->OnSendMessage(Value);
}
);
CloseCurrentChatButton->SetOnPressedCallback([]()
{
static_cast<FMenu&>(*FGame::Get().GetMenu()).GetP2PNATDialog()->CloseCurrentChat();
});
それぞれ、Chat の入力フィールドと、Chat 開始後の Close ボタンに関するイベント Callback です。
入力メッセージに関しては、おそらく EOS SDK に関わってくると思いますが、まだ P2P 機能の初期化も出てきていないのでいったん保留します。
P2P 接続が行われるのはどこか
ログイン後、Friends もログイン状態である場合には以下のような UI になります。以下の変化があります。
- 自身がログインすると
NAT Status
が Unknown
から変化する(私の環境だと Moderate
)
- Friends 一覧に
CHAT
ボタンが表示される
ログイン時
ユーザーのログイン状態に変化がある場合には、OnGameEvent
に FAuthentication
からのイベント発火が行われます。ログイン関連のイベントは多く存在しますが、P2PNATDialog
がまとめて処理しています。
void FP2PNATDialog::OnGameEvent(const FGameEvent& Event)
{
if (Event.GetType() == EGameEventType::UserLoggedIn)
{
UpdateNATStatus();
}
else if (Event.GetType() == EGameEventType::UserLoginRequiresMFA)
{
UpdateNATStatus();
SetFocused(false);
}
else if (Event.GetType() == EGameEventType::UserLoginEnteredMFA)
{
UpdateNATStatus();
}
else if (Event.GetType() == EGameEventType::UserLoggedOut)
{
UpdateNATStatus();
}
else if (Event.GetType() == EGameEventType::ShowPrevUser)
{
UpdateNATStatus();
}
else if (Event.GetType() == EGameEventType::ShowNextUser)
{
UpdateNATStatus();
}
else if (Event.GetType() == EGameEventType::NewUserLogin)
{
UpdateNATStatus();
}
else if (Event.GetType() == EGameEventType::CancelLogin)
{
UpdateNATStatus();
}
else if (Event.GetType() == EGameEventType::StartChatWithFriend)
{
}
}
void FP2PNATDialog::UpdateNATStatus()
{
if (NATStatusLabel)
{
FGame::Get().GetP2PNAT()->RefreshNATType();
}
}
void FP2PNAT::RefreshNATType()
{
EOS_HP2P P2PHandle = EOS_Platform_GetP2PInterface(FPlatform::GetPlatformHandle());
EOS_P2P_QueryNATTypeOptions Options;
Options.ApiVersion = EOS_P2P_QUERYNATTYPE_API_LATEST;
EOS_P2P_QueryNATType(P2PHandle, &Options, nullptr, OnRefreshNATTypeFinished);
}
void EOS_CALL FP2PNAT::OnRefreshNATTypeFinished(const EOS_P2P_OnQueryNATTypeCompleteInfo* Data)
{
if (Data)
{
if (Data->ResultCode != EOS_EResult::EOS_Success)
{
FDebugLog::LogError(L"EOS P2PNAT QueryNATType callback: error code %ls", FStringUtils::Widen(EOS_EResult_ToString(Data->ResultCode)).c_str());
}
}
else
{
FDebugLog::LogError(L"EOS P2PNAT QueryNATType callback: bad data returned");
}
}
NAT Status の更新
ついでなので NAT Status
ラベルの更新情報も見ておきます。実際にはこの流れではなく、FP2PNATDialog::Update
の方で処理されます。以下は、その中でラベルの更新のために NATType
を取得する先の処理です。
EOS_ENATType FP2PNAT::GetNATType() const
{
EOS_ENATType NATType = EOS_ENATType::EOS_NAT_Unknown;
EOS_HP2P P2PHandle = EOS_Platform_GetP2PInterface(FPlatform::GetPlatformHandle());
EOS_P2P_GetNATTypeOptions Options;
Options.ApiVersion = EOS_P2P_GETNATTYPE_API_LATEST;
EOS_EResult Result = EOS_P2P_GetNATType(P2PHandle, &Options, &NATType);
if (Result == EOS_EResult::EOS_NotFound)
{
return EOS_ENATType::EOS_NAT_Unknown;
}
if (Result != EOS_EResult::EOS_Success)
{
FDebugLog::LogError(L"EOS P2PNAT GetNatType: error while retrieving NAT Type: %ls", FStringUtils::Widen(EOS_EResult_ToString(Result)).c_str());
return EOS_ENATType::EOS_NAT_Unknown;
}
return NATType;
}
ドキュメントには以下の記述があります。よって、API を呼び出していますが、基本は事前に Query していたものの Cache を取得するだけという形のようです。
EOS_EResult::EOS_Success - if we have cached data
EOS_EResult::EOS_NotFound - If we do not have queried data cached
FriendsInfoWidget
続いて CHAT
ボタンですが、以下のように Friends を表示する Widget (これは Shared にある)に分岐がありました。
そこで StartChatWithFriend
のイベントが発火されていることがわかります。
void FFriendInfoWidget::Create()
{
#elif defined(EOS_SAMPLE_P2P) || defined (EOS_SAMPLE_VOICE)
const float ButtonSize = Size.x * 0.3f;
Vector2 ButtonOffset(Size.x - ButtonSize - 10.0f, Size.y * 0.25f);
FEpicAccountId FriendUserId = FriendData.UserId;
FProductUserId FriendProductUserId = FriendData.UserProductUserId;
std::wstring FriendName = FriendData.Name;
if (FriendProductUserId.IsValid() && FriendData.Presence.Status != EOS_Presence_EStatus::EOS_PS_Offline)
{
Button1 = std::make_shared<FButtonWidget>(
Position + ButtonOffset,
Vector2(ButtonSize, Size.y / 2.0f),
Layer - 1,
L"CHAT",
std::vector<std::wstring>({ L"Assets/button.dds" }),
SmallFont,
ChatButtonBackgroundColors[static_cast<size_t>(FButtonWidget::EButtonVisualState::Idle)]);
Button1->Create();
Button1->SetOnPressedCallback([FriendUserId, FriendProductUserId, FriendName]()
{
FGameEvent Event(EGameEventType::StartChatWithFriend, FriendUserId, FriendProductUserId, FriendName);
FGame::Get().OnGameEvent(Event);
});
Button1->SetBackgroundColors(ChatButtonBackgroundColors);
}
こちらも FP2PNATDialog::OnGameEvent
で処理されています。
void FP2PNATDialog::OnGameEvent(const FGameEvent& Event)
{
else if (Event.GetType() == EGameEventType::StartChatWithFriend)
{
CurrentChat = Event.GetProductUserId();
bChatViewDirty = true;
ChatTextView->Clear();
}
}
ここに関しては、Chat の UI の準備をしているだけで API に関しては何もしていないようです。
ここまでのまとめ
P2PNAT サンプルの初期化部分を中心に眺めてきました。
NAT P2P インターフェース | Epic Online Services ドキュメンテーション
現状、ここまででは上記の資料の「NAT タイプを決定する」の部分に出てきた API ぐらいしか使っておらず、これ以降に接続やデータ送受信が出てくると思われます。
今回、上記の資料を読む前にコードを見始めてしまったので順序が逆になってしまったのですが、キャッシュに関しても以下のように記載されていました。
EOS_P2P_QueryNATType が完了するとユーザーの NAT タイプの値がキャッシュされるので、関数 EOS_P2P_GetNATType を代わりに呼び出してこの値をすぐに返すことができます。これが正常に返されるのは EOS_P2P_QueryNATType が少なくとも 1 回は完了した場合のみです。EOS_P2P_GetNATType 関数の呼び出しには以下のパラメータを指定する必要があります。
次回も引き続き P2PNAT に関してみていければと思います。