<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 簡述 本章闡述Shooter Game中的房間相關操作,包括以下內容: 1. 創建房間 2. 加入房間 3. 游戲回放 游戲運行過程中的邏輯,在下一章節《游戲性框架》中做具體闡述。 [TOC] # 房間的創建 ## 主菜單實現 ![](https://box.kancloud.cn/72734a2f2fd130f859cb031860371fb9_461x344.png) 如圖,這是Shooter Game的房間創建主界面。 該界面對應了FShooterMainMenu類。由于本文不希望太多涉及Slate編程,故只解析這部分代碼大概含義。 再次重復:Slate編程的復雜度遠遠高于UMG,而UMG能夠實現的界面的復雜程度也許能夠超過你的想象。不到萬不得已,請不要輕易決定使用Slate。 整個界面實際上是一個控件:一個SShooterMenuWidget。該Widget分兩步創建這個界面: 1. 界面實例化: ~~~ SAssignNew(MenuWidget, SShooterMenuWidget) .Cursor(EMouseCursor::Default) .PlayerOwner(GetPlayerOwner()) .IsGameMenu(false); ~~~ 2. 填充Menu內容。 復雜度較高的主要是后者。在解析之前首先簡單闡述一下Menu的原理: > Menu實際上是一組根據父控件狀態動態生成子控件的大型控件集合 也就是說,如果我們直觀地繪制一個Menu,其實我們可以看作是這樣一幅圖: ![](https://box.kancloud.cn/2ad510935e434058b5ece92a6947b6ad_290x215.png) 即,在父控件中的OnClick函數掛一個響應,如果父控件被按下,那就動態生成子控件。 最容易實現的思路就是,用這樣的偽代碼完成: ~~~ 父控件=生成父控件(); 父控件.OnClicked([&](){生成子控件();}); ~~~ 但是如果每個菜單的子控件都不相同,這就會有一大堆的類要出現:每個子菜單是一個單獨的類。 為了避免這樣的復雜度,從設計上將“數據”與“實例化控件”的操作分離。 數據是說的FShooterMenuItem類,其包含了以下內容 * 一系列的響應代理,如OnConfirmMenuItem等。用于指定選擇特定菜單選項時執行的函數。 * 這些相應代理通過MenuHelper類來完成綁定 * 一系列的引用: * 指向子菜單的FShooterMenuItem數組的引用 * 指向當前生成的Slate控件的引用 隨后,創建了MenuHelper類,提供了MenuHelper::AddMenuItemSP函數用于實現菜單的添加: ~~~ MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("FFALong", "FREE FOR ALL"), this, &FShooterMainMenu::OnUIHostFreeForAll); MenuHelper::AddMenuItemSP(MenuItem, LOCTEXT("TDMLong", "TEAM DEATHMATCH"), this, &FShooterMainMenu::OnUIHostTeamDeathMatch); ~~~ 此處即添加了如下圖所示的兩個按鈕的**數據表示**,并與當前對象的OnUIHostFreeForAll綁定,這其實是在創建一個數值上的樹。 ![](https://box.kancloud.cn/cc4324efe45bd6bd2988ddd3b682c5cd_390x68.png) 請讀者務必注意,此時僅僅只有數據,而**沒有**真的創建控件! 真正的控件創建是在最后的BuildAndShowMenu函數完成。 換句話說,首先在內存中構建基于FShooterMenuItem的一棵“數值樹”,然后菜單控件動態地根據這個樹、當前選擇的選項等來動態地生成控件。 關于主菜單的實現,暫且討論到這里。 ## 會話創建 當選擇Free For All或是Team Death Match后,實際上完成了下圖所示的過程(不分析單機模式下代碼): ![](https://box.kancloud.cn/5d00d4c8ba66bcfa4dc63387bd82d457_1060x397.png) 這個時候我們就有必要來研究一下兩個和網絡聯機相關的類:AGameSession和OnlineSubsystem。 OnlineSubsystem簡稱OSS,是對一系列網絡聯機子系統的抽象,例如Steam、Xbox Live等。關于這部分的更多介紹,請參考官方文檔:[OnlineSubSystem概述](https://docs.unrealengine.com/latest/CHN/Programming/Online/index.html) 1. 在線子系統并不是一個完整的服務器框架,對于這個問題的理解,可以參考Steam。Steam可以提供聯機匹配、搜索服務器等操作,但是Steam本身并不是承載服務器實例運行的程序。 2. 在線子系統實質上不需要直接訪問,其可以借助AGameSession來訪問。 為了抽象對于OnlineSystem的訪問, 提供了一個AGameSession類。GameSession的字面意義是“會話”,從語義上說,其代表了一個開放的、可供加入的服務端。可以想象一個服務器列表,這里面的每一項都是一個會話。 ![一個服務器列表案例](https://box.kancloud.cn/5eb7d396dbc5c9377c3dcba0ed8e616f_500x281.jpg) 從使用上說,根據Shooter Game的范例,開啟一個服務端的步驟并不復雜: 1. 從UWorld獲取當前的GameMode實例,然后從GameMode中獲取當前的會話Session: ~~~ AShooterGameSession* UShooterGameInstance::GetGameSession() const { UWorld* const World = GetWorld(); if (World) { AGameModeBase* const Game = World->GetAuthGameMode(); if (Game) { return Cast<AShooterGameSession>(Game->GameSession); } } return nullptr; } ~~~ 2. 準備房間創建用的信息,然后調用AShooterGameSession::HostSession以創建一個會話 ~~~ TravelURL = InTravelURL; bool const bIsLanMatch = InTravelURL.Contains(TEXT("?bIsLanMatch")); //地圖描述 const FString& MapNameSubStr = "/Game/Maps/"; const FString& ChoppedMapName = TravelURL.RightChop(MapNameSubStr.Len()); const FString& MapName = ChoppedMapName.LeftChop(ChoppedMapName.Len() - ChoppedMapName.Find("?game")); if (GameSession->HostSession(LocalPlayer->GetPreferredUniqueNetId(), GameSessionName, GameType, MapName, bIsLanMatch, true, AShooterGameSession::DEFAULT_NUM_PLAYERS)) ~~~ 3. AShooterGameSession::HostSession中,通過調用OnlineSubSystem來完成Session創建 ~~~ IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) { CurrentSessionParams.SessionName = InSessionName; CurrentSessionParams.bIsLAN = bIsLAN; CurrentSessionParams.bIsPresence = bIsPresence; CurrentSessionParams.UserId = UserId; MaxPlayers = MaxNumPlayers; IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface(); if (Sessions.IsValid() && CurrentSessionParams.UserId.IsValid()) { HostSettings = MakeShareable(new FShooterOnlineSessionSettings(bIsLAN, bIsPresence, MaxPlayers)); HostSettings->Set(SETTING_GAMEMODE, GameType, EOnlineDataAdvertisementType::ViaOnlineService); HostSettings->Set(SETTING_MAPNAME, MapName, EOnlineDataAdvertisementType::ViaOnlineService); HostSettings->Set(SETTING_MATCHING_HOPPER, FString("TeamDeathmatch"), EOnlineDataAdvertisementType::DontAdvertise); HostSettings->Set(SETTING_MATCHING_TIMEOUT, 120.0f, EOnlineDataAdvertisementType::ViaOnlineService); HostSettings->Set(SETTING_SESSION_TEMPLATE_NAME, FString("GameSession"), EOnlineDataAdvertisementType::DontAdvertise); HostSettings->Set(SEARCH_KEYWORDS, CustomMatchKeyword, EOnlineDataAdvertisementType::ViaOnlineService); OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate); return Sessions->CreateSession(*CurrentSessionParams.UserId, CurrentSessionParams.SessionName, *HostSettings); } } ~~~ 下面需要回答幾個問題: 1. 創建房間的管理代碼應當放在哪里? * 當前世界在切換到真正的地圖前就會被銷毀,因此需要交給整個游戲的主管——GameInstance類 2. AShooterGameSession類對OnlineSubsystem的抽象是否是無意義的? * 并不是無意義的。OnlineSubSystem提供的大量異步調用和異常處理,是通過代理實現。AShooterGameSession將會負責向這些代理掛載處理函數,并處理這些代理中的一部分。 3. 一定需要自行實現ShooterGameSession類嗎?什么時候實現? * 在需要“搜索房間”這樣的操作時,考慮實現這樣的功能。如果是定向地進行網絡同步(每次只是往同一個ip對應的服務端進行鏈接),也許并不需要實現。Shooter Game默認的實現并不包含網絡數據包傳輸等功能。 4. 沒有Steam這些東西怎么辦? * UE提供了FOnlineSubsystemNull作為實現。Null依然提供了基礎的Session創建等功能。 # 加入房間 理解了房間的創建,那么加入房間就相對來說比較容易了。 房間的加入分為兩個部分:房間的搜索和會話的連接。 Shooter Game的房間搜索同樣是基于封裝過OnlineSubSystem的AShooter,其創建了一個專門的服務器瀏覽器控件SShooterServerList。 ## 開始搜索 開始搜索的代碼位于SShooterServerList::BeginServerSearch,實質上調用過程如下: ![](https://box.kancloud.cn/233aa8cab7a396dc8390be938d6800c0_631x303.png) 所以實際上依然是調用OnlineSubSystem的接口IOnlineSessionPtr完成。 IOnlineSession定義了OnlineSubsystem的可用接口函數,粗略來說,有以下值得一看的: 1. 開房間 * CreateSession函數:添加一個新的Session會話,通過傳入的參數設置各種狀態 2. 加入房間 * JoinSession函數 3. 設置房間 * UpdateSession函數 4. 關閉房間 * DestroySession函數 更多的函數請參考[OnlineSubSystem會話與玩家匹配接口](https://docs.unrealengine.com/latest/CHN/Programming/Online/Interfaces/Session/index.html) ## 更新搜索結果 核心代碼位于SShooterServerList::UpdateSearchStatus,整理代碼后如下: ~~~ void SShooterServerList::UpdateSearchStatus() { //獲取GameSession AShooterGameSession* ShooterSession = GetGameSession(); if (ShooterSession) { int32 CurrentSearchIdx, NumSearchResults; EOnlineAsyncTaskState::Type SearchState = ShooterSession->GetSearchResultStatus(CurrentSearchIdx, NumSearchResults); switch(SearchState) { case EOnlineAsyncTaskState::InProgress: StatusText = LOCTEXT("Searching","SEARCHING..."); bFinishSearch = false; break; case EOnlineAsyncTaskState::Done: // 結束了搜索 //..省略填充代碼 break; case EOnlineAsyncTaskState::Failed: // intended fall-through case EOnlineAsyncTaskState::NotStarted: StatusText = FText::GetEmpty(); // intended fall-through default: break; } } if (bFinishSearch) { OnServerSearchFinished(); } } ~~~ 可以看出,這里其實是根據GetSearchResultStatus的結果,做出是顯示等待字符串還是顯示服務器列表的處理。 ## 加入房間 加入房間同樣是通過IOnlineSubsystem實現。故不再重復闡述 ## 總結 總體而言,房間管理系統實際上已經被OnlineSubSystem實現得差不多了。Shooter Game通過一個比較完整的案例,闡述了如何基于已有的OnlineSubsystem來實現自己的基于房間的架設和游戲方案。 # 游戲回放 虛幻引擎將游戲回放系統稱為Demo。關于這個系統的介紹文檔:[虛幻引擎的回放系統](https://docs.unrealengine.com/latest/CHN/Engine/Replay/index.html) 大致原理如下: 1. 錄制的游戲必須是網絡聯機游戲(或是支持聯機),典型的測試方式是,一個客戶端連入后,看到的世界與單機運行時一致。這意味著游戲本身已經設定了數據的同步。 2. 錄制的內容實質上是將從服務端到客戶端的數據包進行記錄,在下一次回放時,重新發送數據包,從而實現指定時間段內狀態的還原。 3. 開啟錄制的方式是在控制臺中使用DemoRec命令。 當使用DemoRec錄制Demo后,Shooter Game可以在Demo菜單中看到已經錄制的Demo并請求回放,具體實現方式如下: ## Demo發現 ![](https://box.kancloud.cn/0e2acc0980c6dc26d02ea906c5f042b8_836x274.png) 從圖中可知,SShooterDemoList首先請求FNetworkReplayStreaming類的單例(Get()),然后通過GetFactory().CreateReplayStreamer()來創建INetworkReplayStreamer實例。這個實例可以在開始時創建,然后在生命周期內一直持有。 在需要查詢當前有哪些Demo的時候,需要通過調用INetworkReplayStreamer的EnumerateStreams來獲取所有可以讀取的Demo,調用如下 ~~~ ReplayStreamer->EnumerateStreams( EnumerateStreamsVersion, FString(), FString(), FOnEnumerateStreamsComplete::CreateSP( this, &SShooterDemoList::OnEnumerateStreamsComplete ) ); ~~~ 注意,這里由于查詢Demo是一個耗時較長的過程,故采用了回調的模式。當完成查詢后,會調用該回調。函數原型如下: ~~~ void SShooterDemoList::OnEnumerateStreamsComplete(const TArray<FNetworkReplayStreamInfo>& Streams) ~~~ 通過遍歷返回的Streams可以查詢到總共有哪些回放文件可以使用 ## Demo播放 在控制臺中,可以使用Demoplay命令來播放指定Demo。而C++中的播放,Shooter Game給出的范例如下: ~~~ UShooterGameInstance* const GI = Cast<UShooterGameInstance>(PlayerOwner->GetGameInstance()); if ( GI != NULL ) { FString DemoName = SelectedItem->StreamInfo.Name; // Play the demo GI->PlayDemo( PlayerOwner.Get(), DemoName ); } ~~~ 而最終調用的是UGameInstance的PlayReplay函數,參數為前文Demo發現中的FNetworkReplayStreamInfo中的名字。 Shooter Game自己封裝的原因是為了在播放前先調用ShowLoadingScreen顯示載入畫面。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看