簡體   English   中英

面對EDT如何管理游戲狀態?

[英]How to manage the game state in face of the EDT?

我正在Java平台上開發實時策略游戲克隆,並且我對放置在哪里以及如何管理游戲狀態有一些概念性問題。 游戲使用Swing / Java2D作為渲染。 在當前的開發階段,沒有模擬,也沒有AI,只有用戶才能更改游戲狀態(例如,建造/拆除建築物,增加/刪除生產線,組裝機隊和設備)。 因此,可以在事件分發線程中執行游戲狀態操縱,而無需任何渲染查找。 游戲狀態還用於向用戶顯示各種匯總信息。

但是,由於我需要引入模擬(例如,建築進度,人口變化,車隊移動,制造過程等),因此在Timer和EDT中更改游戲狀態肯定會減慢渲染速度。

可以說仿真/ AI操作每500毫秒執行一次,我使用SwingWorker進行大約250毫秒的長度計算。 我如何確保在模擬與可能的用戶交互之間沒有關於游戲狀態讀取的競賽條件?

我知道可以通過SwingUtilities.invokeLater()調用將模擬結果(少量數據)有效地移回EDT。

游戲狀態模型似乎太復雜了,以至於不能僅在各處使用不可變值類。

是否有相對正確的方法來消除此讀取競爭條件? 也許在每個計時器滴答聲中進行完整/部分游戲狀態克隆,或者將游戲狀態的生存空間從EDT更改為其他線程?

更新:(根據我的評論)該游戲可與13位AI控制玩家,1位人類玩家一起運行,並具有大約10000個游戲對象(行星,建築物,設備,研究等)。 例如,一個游戲對象具有以下屬性:

World (Planets, Players, Fleets, ...)
Planet (location, owner, population, type, 
    map, buildings, taxation, allocation, ...)
Building (location, enabled, energy, worker, health, ...)

在一種情況下,用戶在此星球上建造新建築物。 由於需要更改地圖和建築物集合,因此在EDT中執行此操作。 與此並行,每500毫秒運行一次仿真,以計算分配給所有游戲星球上建築物的能量分配,這需要遍歷建築物集合以進行統計收集。 如果計算出分配,則將其提交給EDT,並分配每個建築物的能量場。

只有人類玩家的交互才具有此屬性,因為無論如何,AI計算的結果都會應用於EDT中的結構。

通常,75%的對象屬性是靜態的,僅用於渲染。 其余的可通過用戶交互或模擬/ AI決定進行更改。 還確保在上一個步驟寫回所有更改之前,不會啟動新的仿真/ AI步驟。

我的目標是:

  • 避免延遲用戶交互,例如,用戶將建築物放置在地球上並且僅在0.5秒后獲得視覺反饋
  • 避免通過計算,鎖定等待等阻塞EDT。
  • 避免集合遍歷和修改,屬性更改的並發問題

選項:

  • 細顆粒物鎖定
  • 不變的收藏
  • 揮發性領域
  • 部分快照

所有這些對模型和游戲都有優點,缺點和原因。

更新2:我在談論這個游戲。 我的克隆在這里 屏幕截圖可能有助於想象渲染和數據模型之間的交互。

更新3:

我將嘗試給出一個小的代碼示例來澄清我的問題,因為從注釋中似乎被誤解了:

List<GameObject> largeListOfGameObjects = ...
List<Building> preFilteredListOfBuildings = ...
// In EDT
public void onAddBuildingClicked() {
    Building b = new Building(100 /* kW */);
    largeListOfGameObjects.add(b);
    preFilteredListOfBuildings.add(b);
}
// In EDT
public void paint(Graphics g) {
    int y = 0;
    for (Building b : preFilteredListOfBuildings) {
        g.drawString(Integer.toString(b.powerAssigned), 0, y);
        y += 20;
    }
}
// In EDT
public void assignPowerTo(Building b, int amount) {
    b.powerAssigned = amount;
}
// In simulation thread
public void distributePower() {
    int sum = 0;
    for (Building b : preFilteredListOfBuildings) {
        sum += b.powerRequired;
    }
    final int alloc = sum / (preFilteredListOfBuildings.size() + 1);
    for (final Building b : preFilteredListOfBuildings) {
        SwingUtilities.invokeLater(=> assignPowerTo(b, alloc));            
    }
}

因此,重疊是在onAddBuildingClicked()和DistributionPower()之間。 現在,假設在游戲模型的各個部分之間有50種這種重疊的情況。

聽起來這可以從客戶端/服務器方法中受益:

玩家是客戶-交互和渲染就在那端。 因此,播放器按下一個按鈕,請求將轉到服務器。 來自服務器的答復返回,並且播放器的狀態被更新。 在發生這些事情之間的任何時候,都可以重新繪制屏幕,​​並且它反映了客戶端當前所知道的游戲狀態。

AI同樣是客戶端-相當於機器人。

模擬是服務器。 它會不時地從客戶那里獲得更新,並更新世界的狀況,然后將這些更新適當地發送給每個人。 這是與您的情況有關的地方:模擬/ AI需要一個靜態的世界,許多事情同時發生。 在將更新發送回客戶端之前,服務器可以簡單地將更改請求排隊並應用它們。 因此,就服務器而言,游戲世界並沒有真正在實時變化,只要服務器明智地決定,游戲世界就在變化。

最后,在客戶端,您可以通過執行一些快速的近似計算並顯示結果(這樣就滿足了當下的需求),然后在服務器走動時顯示更正確的結果,來防止在按下按鈕和查看結果之間存在延遲。跟你說話。

請注意,實際上並不一定要通過TCP / IP互聯網來實現,只是可以從這些角度考慮。

或者,您可以負責在仿真過程中將數據保持一致在數據庫上,因為在構建數據時已經考慮了鎖定和一致性。 sqlite之類的東西可以作為非網絡解決方案的一部分工作。

我認為您不應該讓World存儲任何數據或對任何對象本身進行更改,它僅應用於維護對某個對象的引用,並且當需要更改該對象時,請讓Player進行更改以直接對其進行更改。 在這種情況下,您唯一需要做的就是同步游戲世界中的每個對象,以便當玩家進行更改時,其他玩家無法進行更改。 這是我在想的一個例子:

玩家A需要了解一個星球,因此它向世界詢問該星球(如何取決於您的實現)。 World返回對玩家A要求的Planet對象的引用。 玩家A決定做出更改,因此它進行了更改。 假設它增加了一座建築物。 將建築物添加到Planet的方法是同步的,因此一次只能有一個玩家可以這樣做。 該建築物將跟蹤其自身的構建時間(如果有),因此幾乎可以立即釋放Planet的添加構建方法。 這樣,多個玩家可以同時在同一個星球上請求信息,而不會互相影響,並且玩家幾乎可以同時添加建築物而不會出現太多滯后。 如果有兩個玩家正在尋找放置建築物的位置(如果這是您的游戲的一部分),那么檢查位置的適合性將是一個查詢,而不是更改。

很抱歉,如果這不能解決您的問題,我不確定是否理解正確。

如何實現管道和過濾器體系結構。 管道將過濾器連接在一起,如果過濾器不夠快,則將請求排隊。 處理發生在過濾器內部。 第一個過濾器是AI引擎,而渲染引擎由一組后續過濾器實現。

在每個計時器滴答中,都會根據所有輸入(時間也是輸入)並在第一個管道中插入一個副本來計算新的動態世界狀態。

在最簡單的情況下,渲染引擎被實現為單個過濾器。 它只是從輸入管道中獲取狀態快照,並將其與靜態狀態一起呈現。 在實況游戲中,如果管道中有不止一個,則渲染引擎可能希望跳過狀態,而在進行基准測試或輸出視頻時,您將希望渲染每個狀態。

您可以將渲染引擎分解成的過濾器越多,並行性就會越好。 甚至有可能分解AI引擎,例如,您可能希望將動態狀態分為快速變化和緩慢變化狀態。

該體系結構為您提供了良好的並行性,而無需進行大量同步。

這種體系結構的一個問題是,垃圾回收每次都將頻繁地凍結所有線程,從而可能扼殺從多線程中獲得的任何優勢。

看來您需要優先級隊列來對模型進行更新,在該隊列中,用戶的更新優先於模擬和其他輸入的更新。 我聽到您說的是,在其他輸入(否則為模擬)可能需要花費比一個模擬步驟更長的時間的情況下,用戶始終需要用戶對其動作立即進行反饋。 然后在優先級隊列上同步。

不確定我是否完全了解您要查找的行為,但是聽起來您需要狀態更改線程/隊列之類的東西,以便所有狀態更改都由單個線程處理。

為狀態更改隊列創建一個api,例如SwingUtilities.invokeLater()和/或SwingUtilities.invokeAndWait(),以處理狀態更改請求。

我認為gui中的反映方式取決於您要尋找的行為。 例如,由於當前狀態為$ 0,所以無法提取資金,或者在處理提取請求時,向用戶彈出該帳戶為空的信息。 (可能不使用該術語;-))

最簡單的方法是使仿真足夠快以在EDT中運行。 首選程序!

對於兩線程模型,我建議將域模型與渲染模型同步。 渲染模型應保留有關域模型的數據。

更新:在模擬線程中鎖定渲染模型。 遍歷渲染模型更新,如果情況與預期有所不同,則更新渲染模型。 遍歷完成后,解鎖渲染模型並安排重新繪制。 請注意,在這種方法中,您不需要龐大的監聽器。

渲染模型可以具有不同的深度。 在一個極端情況下,它可能是一幅圖像,而更新操作只是用新的圖像對象替換單個引用(例如,這不會很好地處理大小調整或其他淺層交互)。 您可能不必費心檢查項目是否已更改,而只是更新更新。

如果快速更改游戲狀態(一旦您知道將其更改為什么),則可以像對待其他Swing模型一樣對待游戲狀態,並且只能在EDT中更改或查看該狀態。 如果更改游戲狀態的速度不是很快,那么您可以同步狀態更改並在Swing Worker /計時器中進行(而不是EDT),也可以在單獨的線程中進行,其處理方式與EDT類似(此時,查看使用BlockingQueue處理更改請求)。 如果UI不必從游戲狀態中檢索信息,而是通過偵聽器或觀察器發送渲染更改,則最后一個更為有用。

是否可以逐步更新游戲狀態並且仍然具有一致的模型? 例如,在渲染/用戶更新之間重新計算行星/玩家/艦隊對象的子集。

如果是這樣,您可以在EDT中運行增量更新,該更新僅在允許EDT處理用戶輸入和渲染之前僅計算狀態的一小部分。

在EDT中進行每次增量更新之后,您將需要記住仍有多少模型需要更新,並在EDT上安排新的SwingWorker,以便在執行任何未決的用戶輸入和渲染之后繼續進行此處理。

這將使您避免復制或鎖定游戲模型,同時仍使用戶交互保持響應。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM