游戲引擎學習第224天

回顧游戲運行并指出一個明顯的圖像問題。

回顧一下之前那個算法
在這里插入圖片描述

我們今天要做一點預加載的處理。上周剛完成了游戲序章部分的所有剪輯內容。在運行這一部分時,如果觀察得足夠仔細,就會注意到一個問題。雖然因為視頻流壓縮質量較低,很難清楚地看到,但如果是在本地親自運行代碼,或者通過論壇上的種子下載高清版本的錄像來看,這個問題就會很明顯。

這個問題不是系統或代碼出現了錯誤,而是存在一個我們希望消除的視覺瑕疵。切換場景時,在轉場的瞬間會出現一個明顯的閃屏,不是干凈利落地從一個畫面跳到另一個,而是中間出現了突兀的“閃一下”。這是由于我們使用了按需加載的資源系統,在后臺從硬盤動態加載所需的素材。

尤其是在我們使用的 7200 轉的機械硬盤上,這樣的加載速度會相對較慢。雖然很多資源可能已經在緩存中了,但還是存在一幀或兩幀的延遲。這意味著素材線程需要時間從硬盤讀取圖像資源,再放入內存中用于渲染。而這個延遲,就導致了場景切換時的閃屏現象。

我們希望解決這個問題,避免這種中間空白的情況。一種傳統的做法是使用加載畫面,在每段內容前插入一個大的加載界面,并提前將所有資源一次性讀入。這雖然可行,但也有明顯缺點,比如浪費內存、體驗不流暢等。

但我們不希望使用加載畫面,目標是實現即時進入的體驗,一旦用戶點擊,就立即開始內容。因此,我們正在尋找替代方案,希望在不依賴加載畫面的前提下,實現平滑的場景過渡,這正是我們今天的目標。

調試器:進入 RenderCutScene,發現資源狀態未設置。

我們先從問題的實際表現開始說起。最直接的方式就是在調試器里觀察這個問題,因為它會在第一次調用 RenderCutscene 時就出現。

進入 RenderCutscene,其中調用了 RenderLayeredScene,在這個函數中,我們會按順序處理每一層圖像,并嘗試獲取每一層對應的位圖資源。在處理的過程中,通過資源系統的查詢機制,我們成功得到了對應資源的 ID,例如返回的是資源 251。這些步驟本身沒有問題,因為通過數據表(資源系統的核心結構)可以即時獲取這些信息。

接下來是問題的關鍵,我們嘗試通過 GetBitmap 函數根據這個 ID 獲取位圖內容。這時問題出現了:資源并沒有真正被加載到內存中。調用 GetBitmap 后,資源系統嘗試去訪問該資源,但失敗了,因為它當前還沒有被加載。查看這個資源的狀態,會發現它的狀態值是 0,說明它尚未初始化或加載。

雖然系統會在檢測到資源未加載時,發出加載請求,把這個資源列入加載隊列中,但這一幀的渲染已經來不及等這個加載過程完成。這就意味著這幀中我們將缺失這個位圖,從而造成場景切換時出現視覺上的跳幀或閃屏,這就是我們觀察到的問題的全部來源。

即便硬盤很快返回數據,只要我們在本幀中做出“資源未準備好”的判斷,我們也已經跳過了繪制這個資源的機會,所以這一幀的渲染內容就是不完整的。

要解決這個問題,其實辦法非常簡單——預取(prefetch)。我們的目標是提前通知資源系統:馬上就要用到哪些資源了,請先開始加載它們,爭取在真正需要它們進行渲染之前,就把它們加載到內存中。

特別是在過場動畫中,這個策略非常合理,因為過場動畫的內容是完全已知且固定的,我們完全知道什么時候需要用到哪些資源,所以沒有理由不提前告訴資源系統“準備好這些資源”。

總結起來,解決思路就是:

  • 明確即將使用的資源
  • 在渲染前通知資源系統進行加載
  • 給資源系統留出時間完成加載
  • 避免首幀渲染缺失資源導致的閃爍問題

通過這個方法,就能保證即使是第一幀播放動畫,也能順利且完整地呈現內容。
在這里插入圖片描述

game.cpp 中以人為加快的速度回放過場動畫。

我們現在需要做的其實并不復雜。首先,我們希望能以人為加快的速度播放過場動畫,這樣能更清楚地觀察問題的表現。

所以我們先將動畫加速播放,這樣就可以快速地看到動畫推進的過程。當動畫以更快速度播放時,我們能清晰地看到在某些時間點會出現閃爍的現象。這些小小的閃爍就是我們想要消除的畫面瑕疵。

因此,接下來的目標非常明確:去除這些閃爍。

要做到這一點,最關鍵的問題就是如何進行“預取”(prefetch)操作,也就是在資源真正需要被使用之前,提前告訴資源系統去加載它們。

這些閃爍的根本原因是資源在被請求時還沒有準備好,而渲染這一幀時又必須依賴這些資源,但它們還在硬盤中,或者正在加載過程里,導致這一幀無法正常渲染全部內容,從而出現閃屏。

我們知道這個問題只會在第一次嘗試使用某個資源時發生,所以只要能在它真正進入渲染流程前,提前一兩幀把它加載到內存,就可以完全避免閃爍。

由于在過場動畫中,所有資源的使用順序是完全已知的,我們完全可以在動畫開始前,就將即將使用的資源列表提前交給資源系統處理。資源系統接收到這個信息后,就可以開始異步加載它們,即使加載過程稍慢一些,也可以在真正需要用到前完成。

因此,我們的思路是:

  • 通過人為加速的方式確認問題點,更清楚地看到問題所在;
  • 識別出過場動畫中即將用到的資源;
  • 在動畫播放前或初始化階段,調用資源系統對這些資源進行預取;
  • 確保在正式渲染時,所有資源都已在內存中;
  • 最終實現順滑播放,無任何閃爍或缺失。

這就是我們要解決閃爍的具體做法和實施方案。目標明確,手段直接,可控性強,是一個高效且合理的優化步驟。
在這里插入圖片描述

第三個場景好像有點問題
在這里插入圖片描述

在這里插入圖片描述

game_cutscene.cpp 中引入 RenderCutsceneAtTime

我們這里其實已經有一個很方便的機制,可以用來處理這個問題。我們有一個現成的功能,只需要傳入當前的過場動畫時間,就能渲染出當下的場景。整個過程邏輯非常簡單,沒有太多復雜操作。

所以,解決方案也很直接:我們完全可以讓這個過場動畫渲染函數執行兩次。第一次是真正用于畫面的渲染,第二次則是在未來的某個時間點提前渲染,但結果不需要顯示,只是為了觸發資源加載。關鍵是我們不需要讓第二次渲染的結果進入畫面,也不需要提交它生成的圖像。只需要觸發渲染流程中對資源的引用,這樣資源系統就會預先加載這些資源。

這是因為我們在執行渲染函數時,其內部會構建一個“渲染組”,這個過程里會觸發資源系統的加載請求。而最終是否真正渲染到屏幕,并不影響資源加載請求的產生。所以只要在構建完渲染組之后把它丟棄,就能達到預取的效果,而且不影響實際畫面。

因此我們做了如下調整:

  • 把渲染過場動畫的邏輯單獨提取出來,封裝成一個內部函數,例如叫做 RenderCutsceneAtTime
  • 這個函數只在系統內部使用,不對外暴露,命名也不用太在意;
  • 新函數接收一個特定時間點,然后基于該時間點渲染一次場景;
  • 對外部調用而言,依然保留原本的 RenderCutscene 接口;
  • 使用新函數進行額外一次提前渲染,這次不提交渲染結果,只觸發資源預加載。

這樣一來,我們就獲得了一個簡單但有效的資源預取機制,能夠在真正渲染前把所有將要用到的資源加載進內存,確保在實際渲染時不會有閃爍或資源缺失。這個方法不增加額外的加載界面,也不會影響玩家體驗,同時對現有結構幾乎沒有干擾,是一個高效且優雅的改進方式。

RenderCutscene 中額外調用一次 RenderCutsceneAtTime,傳入 0 作為 RenderGroup,以進行位圖預加載。

現在我們有了一個新的選項,可以選擇讓渲染函數執行兩次。第一次是正常渲染當前時刻的過場動畫,第二次是往前看大約半秒甚至一秒,用于提前準備即將需要的資源。這樣做雖然可以解決之前資源未及時加載導致的閃爍問題,但也帶來了一個新問題:如果我們直接調用兩次渲染函數,會生成兩套繪制命令,而我們其實并不想真正渲染第二次,只是希望借此觸發資源加載。

為了解決這個問題,有兩種思路:

  1. 在資源加載層級,直接調用預取函數,而不依賴渲染流程生成命令。這種做法結構清晰,但需要改動比較底層的接口;
  2. 繼續使用現有的渲染流程,但把第二次的“渲染命令”直接丟棄,不寫入任何緩沖區,也不輸出到屏幕,只是借助它的訪問邏輯來觸發資源加載。

考慮到簡單性和復用已有邏輯的便利,我們嘗試第二種做法:傳入一個空的渲染組(Render Group 為 0),作為“空渲染”的標志信號。

具體實現如下:

  • 對渲染函數做了改造,使其支持 Render Group 為空;
  • 在生成渲染命令的地方添加條件判斷:只有在 Render Group 存在時才真正構建渲染命令;
  • 否則,直接走資源訪問的邏輯,并在不生成任何繪制數據的前提下執行 Prefetch 操作;
  • 在執行 Perspective 變換等處理時,也加了條件保護,防止空指針問題;
  • 這種方式最大程度避免了內存浪費,因為根本沒有生成臨時數據結構,也不會往渲染列表中推送無用內容。

最終,完整的邏輯就是:

  • 先執行一次正常的過場動畫渲染,輸出畫面;
  • 緊接著執行一次預取渲染,將時間提前約一秒;
  • 這次調用不產生任何繪制效果,只用于提前觸發資源加載;
  • 渲染函數內部自動識別傳入的 Render Group 是否為空,來決定是預取還是正常渲染。

這樣,我們就實現了一個零成本、低侵入的資源預取機制,有效解決了場景切換時的資源缺失問題,而且不會引入額外加載界面或內存開銷,整個邏輯既高效又優雅。

在這里插入圖片描述

在這里插入圖片描述

運行過場動畫,查看其效果。

我們現在來看一下最新的修改是如何起作用的,確認是否已經解決了之前的問題。現在可以看到,在所有場景切換過程中,已經沒有任何“穿插”或“穿幫”的情況發生了。所有的過渡都變得非常平滑,這是因為我們已經給予流媒體系統足夠的時間來完成加載和準備。

雖然在最開始的部分,似乎還是有一次輕微的異常——有一個畫面閃了一下,只有一個眼睛的內容——但這是有明確原因的。這一點我們需要單獨討論一下,因為雖然這不影響循環播放的部分(就像使用橢圓機時那樣,并不需要完美循環),但從另一個角度來看,我們還是需要去修復這個問題,原因稍微有點不同。

總的來說,除了開頭的那一處小問題外,其余部分已經完全按照預期運行,過渡流暢,沒有視覺上的錯誤,這證明了我們對系統加載節奏的調整是有效的。但為了完整性,我們仍需解決最開始那一幀的問題。

提問:“你們知道為什么開頭會出現那個圖像故障嗎?”

首先我們需要弄清楚,為什么只有在最開始的部分會出現那個異常情況,而其他地方都沒有。如果仔細思考一下,這個問題的原因其實是可以推斷出來的。

我們之所以在所有場景切換中都沒有問題,唯獨在開頭部分出現了那個問題,是因為在進行預加載或“向前看”(look-ahead)時,并沒有進行循環處理。也就是說,當播放進度走到最后一個片段時,系統并不會自動回到第一個片段進行預加載。循環邏輯的相關代碼其實存在,但它只在循環機制真正啟動的時候才會被調用,而在渲染播放的過程中,這段代碼并沒有被觸發。

因此,當渲染到最后一個片段時,系統并不會預判接下來要顯示第一個片段的內容,從而導致在首次渲染的過程中出現了那個畫面缺失或閃一下的問題。

不過,這種情況在正常情況下是可以接受的,因為通常我們并不需要讓整個片段循環播放。也就是說,如果不是打算循環,理論上這個小問題是可以忽略的。

但我們現在真正需要解決的,其實是首次出現那個異常畫面的問題,也就是第一次播放時的視覺閃爍。盡管不需要處理循環問題,但我們仍希望第一次播放時就能是平滑、無縫的,沒有任何突兀或加載不及時的現象,這才是我們真正想要修復的部分。

game_cutscene.cpp 中創建一個初始黑色畫面,持續 20 秒(加速播放)。

在這里插入圖片描述

我們意識到,為了確保初始化階段流媒體系統有足夠的時間進行預加載,我們需要一個真正“空”的場景。這個場景中沒有任何實際內容——沒有畫面,沒有圖層,沒有對象,甚至可以說就是一堆“零”,完全空白。

我們可以為這個空場景設置一個明確的持續時間。例如,在進行資源初始化(像 asset none 這樣的操作)時,將所有內容都設為零:坐標、圖層、狀態等全為空,然后再指定一個時間長度,用來作為這個空場景存在的時間。這段時間不需要太長,足夠流媒體系統完成一次向前預加載即可,也可以根據需要延長一些,比如加入一個較大的延遲,以確保它確實能夠起作用。

我們也發現,雖然這個場景存在,但如果沒有顯式使用或沒有在渲染流程中引用,它其實是不會被執行的。因此,我們還需要確保這個空場景確實被加入到了播放隊列或初始化流程中。

最后一部分的確認是,我們發現之前定義的某個空場景并沒有被使用,這是為什么延遲沒有生效的原因。一旦正確地將這個空場景插入流程,并賦予一個適當的持續時間,比如幾百毫秒的“黑屏”,那么整個系統在真正進入可視內容播放前就能完成預加載,從而避免開頭出現畫面閃爍或未加載完整的問題。

運行過場動畫,觀察延遲效果。

現在,我們已經在啟動階段加入了一個延遲,這樣就能保證系統在正式加載內容之前有足夠的時間進行準備。目前來看這個處理是有效的,已經能夠看到這段延遲被正確應用了。

接下來需要確保這段延遲時間足夠長,以便系統有足夠時間完成資源加載。理論上應該是沒問題的,但實際情況中我們并不完全確定這些資源的加載耗時究竟是多少。這也是我們需要進一步觀察和測試的重點。

為了更準確地判斷所需的延遲時長,我們打算查看系統在初始化階段到底花了多少時間加載資源。這樣可以確保延遲不會過短導致加載不全,也不會太長造成不必要的等待。

為了防止畫面在啟動時出現快速閃爍的情況(類似閃現),我們已經將某些設置調整回來,使其更平穩自然。

接下來我們會嘗試查找相關部分的代碼或日志,具體分析資源加載的時長是多少,然后根據這個信息進一步微調延遲時間,確保整個啟動流程在視覺和性能上都達到理想狀態。我們正在定位和查找那一部分,繼續分析下去。

game.cpp 中以正常速度播放過場動畫。

我們將啟動延遲時間調整回正常水平進行測試,發現如果設置為二十秒,顯然會太長,不實際。即使只是一秒鐘,體感上也顯得偏長,不過從實際效果來看確實就是一秒左右。

好處是,這樣處理之后,啟動時那個閃屏的情況已經完全消除,說明我們給流媒體系統提供了足夠的時間完成初始資源的加載,達到了預期目的。

當然,這個時間長度還需要不斷微調優化。我們也可以采用另一種方式:在啟動時顯式等待所有資源加載完成后再繼續執行。但考慮到性能和體驗上的平衡,可能沒有必要做到完全同步等待,只要提供略微充足的延遲時間即可。

現在的問題是,雖然加載邏輯已經順利運行,但有個現象比較奇怪:延遲時間雖然設置了,但主觀上感覺啟動過程仍然很快,幾乎在還沒來得及最大化窗口時就已經開始播放了。這可能是因為系統在初始化流程中的某些操作比我們預期更快,或是資源提前就被加載好了。

接下來我們還考慮是否能讓程序默認以全屏模式啟動,目前不確定是否已經具備這個功能。如果可以設置全屏啟動,那在視覺和用戶體驗上會更統一,尤其在消除啟動延遲感和加載痕跡方面會更加自然。

目前我們正在查看是否支持全屏切換的邏輯,例如是否有 toggle fullscreen 這樣的功能調用可以實現這一點。接下來將繼續探索和測試這部分的功能完善。
在這里插入圖片描述

在這里插入圖片描述

win32_game.cpp 中切換到全屏并觀察效果。

現在我們嘗試在創建窗口之后切換到全屏模式,如果需要的話,可以在創建窗口后立即調用 toggle fullscreen 來實現這一功能。

不過,盡管已經添加了全屏切換操作,啟動時仍然存在一些小問題,特別是在啟動畫面上,我們能看到一個白色的閃爍區域,這種閃爍顯得有些煩人,可能需要進一步優化去除這個現象。

盡管如此,重要的是,一旦開始播放過場動畫后,已經沒有出現那些視覺閃爍的小問題了。過場動畫播放時非常順暢,完全沒有任何加載或圖像中斷的現象。這表明流媒體系統有足夠的時間預加載內容,避免了之前的閃爍或不連續問題。

不過,我們也注意到,即使給系統預留了一秒鐘的時間,這段時間似乎還是有些短。雖然現在已經能保證動畫播放時流暢無阻,但如果能給系統更多的預加載時間,可能會更有助于提高穩定性和體驗感。所以,考慮到這一點,可能需要提供更長的延遲時間來確保一切都能加載完畢,而不僅僅是一個秒級的預留時間。

在這里插入圖片描述

在這里插入圖片描述

game_cutscene.cpp 中定義宏 CUTSCENE_WARMUP_SECONDS

我們可以考慮給系統更長的預加載時間,比如設置一個“過場動畫預熱時間”,這個時間可以用來確保系統完全準備好。具體來說,可以定義一個變量,比如“warmup seconds”,表示預熱時間的長度,并將其應用到多個地方。

在啟動時,可以先顯示一個空白畫面,持續一定的時間,這段時間就是預熱的時間。與此同時,我們也可以讓系統在這個時間內進行前置加載,確保所有資源都已準備好。通過這種方式,我們確保在過場動畫開始播放之前,系統已經充分加載并準備就緒。

這樣一來,我們就可以保證預加載的流暢性,避免出現任何加載不完全的情況,確保畫面在播放時是連續的,沒有任何閃爍或卡頓的問題。通過適當調整“預熱時間”,可以確保一切都能夠在最佳狀態下運行,讓動畫播放時始終保持平滑和無縫。
在這里插入圖片描述

win32_game.cpp 中添加 LayerCount 為 0 時清屏邏輯。

為了去除啟動時的煩人問題,我們決定采取兩個步驟來解決。首先,檢查渲染組是否得到了正確清理。我們不確定渲染組是否有清理操作,可能存在某些清除機制,也可能沒有。為了確保清理正常執行,我們計劃在渲染過程中顯式調用清理操作。

通常情況下,渲染時并不需要手動清理,因為我們會渲染整個場景,所有內容會被覆蓋,沒必要額外處理清理。但如果某些場景沒有任何圖層(即圖層計數為零),就會出現問題。為了避免這種情況,我們打算在渲染前檢查場景的圖層數量,如果圖層計數為零,就強制進行清理操作。

為了驗證這一操作是否生效,我們將清理顏色設置為灰色,這樣可以確保在清理過程中,屏幕顯示為灰色,便于觀察是否成功執行清理。通過這種方式,確保在“黑屏”啟動期間,屏幕內容被清理干凈,避免出現殘留的無效內容。

完成這一處理后,我們就能確保在啟動時畫面被正確清理,避免任何不必要的顯示問題,進而確保后續內容順利渲染。

win32_game.cpp 中排查閃屏問題。

為了去除啟動時那個惱人的白色閃爍,我決定回到平臺層,仔細查看問題出在哪里。在我們的 WM_PAINT 代碼中,確實有一個調用 window to display buffer 的操作,但可以看到,這個過程使用了某種策略,這可能導致了初始化不完全的情況,進而產生了閃爍。

我懷疑這個顯示緩沖區并沒有被正確初始化。經過一些檢查,發現確實存在未初始化的情況,因此需要確保在調用 Win32ResizeDIBSection 時,將緩沖區清空,確保所有內容都被清理為零。

為了實現這一點,我認為 Windows 系統中可能存在一個類似 zero memory 的調用方法,用于將內存清零。雖然不完全記得具體的實現,但我猜測這可能是通過 C 運行時庫中的宏來實現的。具體可以使用 fill memory 函數來將內存區域清零,確保顯示緩沖區在每次刷新前都是干凈的。

這樣一來,可以確保顯示緩沖區的內容被完全清除,避免出現任何未初始化的閃爍現象,確保在啟動階段一切都能順利進行。
在這里插入圖片描述

在這里插入圖片描述

上網查詢 VirtualAlloc 相關內容。

在使用虛擬內存時,理論上應該可以請求操作系統自動返回一個已清零的內存區域。實際上,操作系統提供的 VirtualAlloc 函數會自動為我們分配并初始化為零的內存。所以我們不需要手動將內存清零,它本身就已經是清空的(即黑色的)。這個過程應該是自動完成的。

基于這一點,我認為我們無需再手動清理緩沖區,特別是清空為黑色的操作應該不再需要執行。內存初始化為零意味著它的內容已經是干凈的,理論上不需要額外的清除操作。

盡管如此,我仍然想確認這一點是否準確。盡管 MSDN(微軟文檔)上提到分配的內存會被自動初始化為零,但我并不完全信任文檔的描述,因此決定親自檢查一下,確認是否確實如文檔所說,內存已經初始化為零。

這時,我檢查了相關內容,盡管我之前可能誤解了一些地方,實際測試顯示,分配的內存確實已經是零初始化的,這也就意味著我們不需要再額外進行清除操作了。

調試器:進入 Win32ResizeDIBSection 并查看 Buffer->Memory

接下來,我決定通過檢查緩沖區的內存內容來確認它是否符合預期。首先,我打開了一個內存窗口,并將其移動到屏幕的上方,以便更清晰地查看內存的具體內容。

查看內存時,發現緩沖區的內存內容確實看起來像是已經被初始化為零。我將它粘貼出來并仔細檢查,確認它的內容大部分都是零,沒有看到很多非零的值。根據這一點,可以確定緩沖區的內存確實已經是零初始化的,這符合預期。

但是,盡管緩沖區內存看起來已經是零,問題依然存在。啟動時,仍然會出現那個討厭的白色閃爍現象。這就引發了一個新的問題:為什么即使內存已經是零,屏幕上還是會出現這種白色的干擾?這個問題需要進一步排查,找出真正的原因。
在這里插入圖片描述

在這里插入圖片描述

上網查詢 GetStockObject 函數。

為了找出白色閃爍的原因,需要進一步檢查一些可能的情況。特別是在切換全屏時,可能會涉及到窗口類中的一些設置。首先,我想檢查窗口類的定義,因為有時候在窗口類中可能會不小心設置了某些 hbrush,導致了不期望的效果。例如,我們可能設置了 CS_HREDRAW 和 `CS_VREDRAW,這些設置可能會導致一些問題。此時,我不確定是否真的需要這些設置。

在此情況下,我們希望的是窗口背景始終保持黑色。為了確保這一點,我們可以嘗試用一個黑色的畫刷來初始化窗口類,這樣每次繪制窗口時,背景都會自動填充為黑色。這樣就不需要擔心其他意外的顏色問題了。

為了實現這一目標,我們可以使用 hbrBackGround,它是一個窗口類的背景畫刷句柄。通過設置一個黑色畫刷(例如使用 GetStockObject 函數獲取一個標準的黑色畫刷),我們可以確保窗口背景始終是黑色的,從而避免白色閃爍的現象。

當我回憶起 Windows 編程的細節時,突然意識到通過 GetStockObject 函數可以很方便地獲取預設的黑色畫刷,這正是我所需要的。通過使用這個黑色畫刷,我們可以驗證是否能夠解決問題。

在這里插入圖片描述

win32_game.cpp 中使用 GetStockObject(BLACK_BRUSH) 繪制窗口背景。

接下來,我們可以嘗試使用一個黑色畫刷來解決問題。具體來說,就是在窗口類中指定一個黑色畫刷,讓它作為背景來使用。我們可以通過調用 GetStockObject 獲取一個黑色畫刷,并將其賦值給 hbr background,這樣窗口背景就會被填充為黑色。

只需設置這個黑色畫刷后,窗口的背景應該會保持黑色,確保沒有意外的顏色變化,也就避免了白色閃爍的問題。這個操作看起來很簡單,不需要太多復雜的設置。只要確保窗口使用的是黑色畫刷,就可以觀察它是否有效解決了啟動時的閃爍問題。
在這里插入圖片描述

運行過場動畫,確認啟動體驗不錯。

現在,通過使用黑色畫刷,成功去除了那個煩人的白色閃爍問題。這個問題之前可能是因為 Windows 系統沒有正確清理窗口內容,或者默認清理為白色背景,導致出現了不必要的白色區域。無論原因是什么,現在背景已經是黑色了,用戶點擊運行后,啟動體驗變得更加順暢,界面看起來很不錯。

然而,仍然偶爾能看到一點窗口擦除的痕跡,這雖然比之前的白色閃爍好很多,但依然不完美。這種現象并不令人滿意,仍然需要進一步檢查和優化,確保窗口的過渡和繪制過程更加平滑。

win32_game.cpp 中創建窗口但初始不可見。

接下來,計劃做一些優化,首先我打算在窗口創建時先不顯示它,這樣可以避免用戶看到窗口的閃爍。然后,在窗口準備好后,再讓它變為可見。通過這種方式,窗口的顯示過程會更加平滑,不會讓用戶看到不必要的閃爍。

我檢查了一下 set ws_visible,并沒有發現這些命令會讓窗口立刻變為可見。所以,我決定在切換全屏時使用 show windowsw show 命令來控制窗口的可見性。具體做法是先讓窗口進入全屏模式,然后再顯示它,這樣就避免了啟動時出現的任何閃爍現象。

通過這種方法,效果比之前好多了。整體體驗變得更加流暢,沒有看到之前的那些干擾現象。現在的效果讓我比較滿意,我覺得這樣就可以了。

在這里插入圖片描述

探討后續可能的開發方向。

現在我們的過場動畫播放效果已經相當不錯了,接下來有兩個可能的方向可以推進。

第一個方向是將動畫與背景音樂進行同步,但目前我們還沒有背景音樂軌道,因此暫時無法進行這一部分。要實現這一步,首先需要修復 Win32 的計時邏輯,確保其具有良好的一致性。之后,就可以將動畫按照某個時間基準進行同步,比如設定每個片段、每次鏡頭移動與音樂中的某些關鍵點對齊。但由于我們目前還沒有音軌,所以這一部分暫時不做。

因此更實際的做法是回到游戲代碼中,對整體結構進行規劃,使其可以支持過場動畫系統。我們需要構建一種邏輯系統,用于管理當前游戲的運行狀態,比如:當前是否在播放過場動畫?是否進入了游戲?是否處于角色選擇界面?等等。

接下來我們會回到核心游戲代碼中進行調整和結構搭建,以支持這些狀態切換的邏輯。雖然聽起來復雜,但由于我們在之前的 game cutscenes 系統中已經做了很多準備,所以這部分實現起來其實并不難。

舉個例子,我們只需要調整每段過場動畫的持續時間,就可以達到我們想要的節奏效果。現在設置的持續時間可能是默認的,比如每段動畫播放 20 秒。但等我們有了音樂軌之后,只要參考音樂的長度與自然斷點,調整這些時間值就可以了。

我們的動畫只是靜態圖像,不涉及口型同步,也不是節奏游戲,因此對時間精度的要求不高。只要大致對得上即可,不需要每毫秒精確匹配,這樣也大大簡化了實現的復雜度。

總結來說,下一步我們會回到游戲主邏輯中,構建一個狀態管理系統,為后續整合動畫、游戲流程及音樂做準備。至于同步音軌,只需在未來補上音樂后,微調每段動畫的持續時間即可。整體流程清晰,執行起來也相對簡單。

game.cpp 中切回游戲,解釋游戲開始的概念。

我們現在回到 game.cpp 中,開始思考如何處理游戲主邏輯,特別是從吸引模式(Attract Mode)過渡到正式游戲開始的流程。

當前的游戲結構已經有了“游戲開始”的基本概念。例如在某個狀態下,雖然畫面已經切換到游戲畫面,但主角并不會立即出現,只有在玩家按下某個按鍵(比如空格鍵)后,主角才會生成并加入游戲。這是為了預先支持多人控制的機制。雖然目前游戲不是一個真正的多人游戲,但系統已經設計為可以支持多個玩家的邏輯,體現出一種模塊化與可拓展性。

目前初始狀態下呈現的是一個“空白的游戲畫面”,接下來我們希望將這個部分替換為一個“過場動畫”,也就是讓游戲初始不再是等待玩家操作的空場景,而是播放吸引眼球的動態內容,直到玩家準備開始游戲。這樣一來體驗更加自然和完整。

這一改動還有助于設計其他系統,尤其是外部資源的預加載(pre-fetching)。在吸引模式播放期間,系統可以在后臺異步加載接下來游戲所需的關鍵資源,比如貼圖、音頻、關卡信息等,以避免后續切換時出現卡頓。

接下來的目標:

  1. 替換默認初始狀態:讓初始進入游戲時不是空白畫面,而是進入吸引模式,播放過場動畫。
  2. 完善“開始游戲”觸發邏輯:仍然保留通過玩家按鍵進入游戲的邏輯,只不過視覺上更具吸引力。
  3. 設計合理的資源加載機制:在播放吸引動畫期間啟動預加載流程,為即將開始的游戲做準備。
  4. 統一游戲狀態管理架構:通過狀態枚舉或狀態機等方式清晰管理游戲當前處于哪一階段(吸引、加載、游戲中、暫停等),以支持后續功能擴展。

這種改動雖然在代碼結構上不復雜,但在游戲流程體驗、系統性能優化、資源調度邏輯等方面都能帶來顯著提升。之后我們可以在此基礎上,進一步添加更多如菜單界面、場景切換、音效同步等邏輯。這個階段的調整對后續整個游戲架構的發展具有重要的指導意義。
在這里插入圖片描述

在這里插入圖片描述

game.h 中添加切換過場動畫與游戲的能力。

當前的目標是實現吸引模式(cutscene/attract mode)和正式游戲之間的切換。為此,需要引入一個動態變量用于判斷當前是否應處于播放吸引動畫狀態,或者已經進入了正式游戲流程。

考慮到游戲中主角的生成是玩家主動觸發的,因此可以利用是否存在“受控主角”(controlled heroes)作為判斷依據。游戲已有相關結構體或數組來記錄這些受控主角,通過遍歷這個數組并檢測是否有有效索引或對象存在,即可判斷是否已有玩家加入游戲。

邏輯思路如下:

  1. 定義一個布爾值變量,比如 heroes_exist,初始為 false
  2. 遍歷當前記錄的受控主角數據,檢測是否至少存在一個有效主角對象(代表玩家已經開始游戲)。
  3. 如果存在,就將 heroes_exist 設置為 true
  4. 使用 heroes_exist 作為關鍵判斷變量,在渲染邏輯或狀態判斷中切換:
    • 若為 false,則播放吸引模式的畫面和動畫;
    • 若為 true,則切入正式的游戲邏輯和畫面。

這種結構可以清晰地分離“未開始游戲”和“游戲已開始”的兩種狀態,從而保持狀態轉換的整潔性。

還需要注意控制流程的順序與狀態初始化的邏輯,確保判斷在主角真正生成之后才能觸發狀態切換。同時,這種方式也為后續狀態擴展(如暫停狀態、菜單界面、結束畫面等)提供了基礎框架。整體思路簡潔清晰,可擴展性強。
在這里插入圖片描述

在這里插入圖片描述

按下空格鍵
在這里插入圖片描述

運行游戲,加入游戲時注意資源預加載需求。

目前的邏輯是從播放場景動畫(cutscene)開始,直到玩家加入游戲,一旦加入,系統就切換到游戲狀態。這一過程基本邏輯已經實現,狀態切換也是可控的。

不過,目前也暴露出另一個潛在問題,即資源加載的延遲。由于在玩家進入游戲的那一刻,有些游戲資源可能尚未加載完畢,因此可能會出現部分畫面加載不完整或黑屏等現象。

雖然這并不是當前優先解決的問題——因為現在的游戲邏輯尚處于測試階段,還沒有真正加入敵人、行走、交互等完整功能——但這確實是后期必須處理的問題。尤其是在游戲玩法逐漸完善之后,需要確保進入游戲的那一刻資源已經就緒,體驗才不會被破壞。

相較而言,cutscene 的資源是可以預加載完再播放的,因為它是線性的,不需要實時交互,因此已經提前做了資源準備。而游戲本身因為是實時交互的復雜狀態機,所以在資源加載時機上要更為謹慎和設計合理。

當前階段的處理策略是先不動游戲部分的資源加載機制,專注確保 cutscene 部分可以完整、順暢地播放,并通過按鍵觸發來切換狀態。等到游戲內容更完善時,再將資源預加載系統引入到游戲主邏輯中,確保所有必要資源在進入游戲前都已經加載完成,從而避免切入后出現的殘缺畫面問題。

總結:

  • 當前從 cutscene 切入游戲的邏輯已經跑通;
  • 游戲資源的加載延遲會造成初始切換時的不完整畫面;
  • cutscene 部分已做資源預加載處理;
  • 游戲資源加載問題留待游戲架構更完整后統一解決;
  • 后續將考慮加入更完整的資源預取機制(例如進入游戲前預讀必要貼圖、模型等)。

game.cpp 中引入 DeleteLowEntity,按下 Esc 鍵時調用。

現在已經完成了片頭動畫(cutscene)的播放邏輯,游戲可以順利啟動并進入游戲狀態。接下來需要開始考慮一些更深入的功能,比如:

進入游戲之后,目前沒有任何方式能夠退出游戲。按下 Escape 鍵(Back 按鈕)不能返回、不能退出、也不能重新回到菜單狀態。因此,需要設計一個機制讓玩家可以從游戲中退出。

首先想到的方案是利用 Escape 鍵來作為“返回”或“退出”操作的觸發點。通過檢查按鍵綁定,確認 Back 按鈕確實是綁定在 Escape 鍵上的,因此可以用它來觸發退出邏輯。

目前控制器系統中通過 AddPlayer 的方式將玩家加入游戲,但沒有設計任何對應的“撤銷”或“刪除”機制。控制器添加之后便一直保留,無法移除,所以需要引入刪除玩家實體的能力。

查看現有邏輯后發現:

  • 已有 AddGroundedEntity 等類似的添加接口;
  • 但尚未有對應的刪除(DeleteEntity)接口;
  • 也沒有實現類似的回收機制(如 freelist);
  • 目前實體都是不斷追加到狀態里的。

于是決定為實體系統加入一個簡單的 freelist(空閑鏈表)機制來實現“偽刪除”。也就是說,不真正刪除內存中的對象,而是將其加入一個可復用的空閑列表中,以便下次重用。這種方式既簡單又符合現有架構思路,已在其他系統中使用過類似方法。

具體實現邏輯:

  • 每當按下 Escape 鍵,查找對應的玩家控制器;
  • 若找到,則移除其對應的游戲實體;
  • 在邏輯上將該實體從游戲狀態中刪除;
  • 后續可通過 freelist 機制復用這些實體,不再重復分配;
  • 同時在調用刪除接口時,需要傳遞當前的 game state 以便正確管理狀態。

這個處理邏輯是可擴展的,后續也可以在基礎上擴展為暫停菜單、重開游戲、返回主菜單等功能。

總結:

  • 增加了從游戲中退出(或移除控制器)的初步機制;
  • 設計了刪除實體的方法,通過 freelist 實現回收;
  • 以 Escape 鍵作為退出操作的觸發;
  • 后續可繼續拓展為菜單導航和完整生命周期管理;
  • 整體邏輯保持簡潔、清晰,方便后續維護和擴展。

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

運行游戲并嘗試讓主角死亡。

目前系統已經基本完成了角色加入、退出、以及從片頭動畫和游戲之間切換的邏輯測試。

現在的流程是:啟動游戲時默認播放片頭動畫,玩家通過輸入操作(如按下空格)創建一個角色并進入游戲世界。當按下退出鍵(Escape)后,可以將角色從游戲中移除,并回到片頭動畫狀態。

在這個測試過程中發現,雖然角色被邏輯上移除了,但實體并沒有真正被刪除,而是依然保留在世界狀態中。因此,通過反復創建和退出角色,可以看到所有生成的角色實體仍然存在,說明當前只是“假刪除”,并沒有徹底清理掉數據。

此外,還發現多個角色會重疊在一起,這是因為所有角色都被生成在同一個位置,彼此的碰撞體積重疊,導致互相卡在一起,這種行為并不理想。

當前結果的幾點總結如下:

  • 角色的添加和刪除流程已基本通暢,可以從片頭動畫切入游戲,也可以退出回到片頭;
  • 刪除邏輯采用的是簡單的“標記回收”方式,角色雖然不再控制,但實體還在游戲世界中存在;
  • 重復進入游戲會不斷創建新角色,未進行實體位置處理,導致重疊問題;
  • 該機制已可作為原型使用,后續可以進一步改進實體管理(真正清理內存或優化復用邏輯);
  • 可以考慮添加角色生成位置的邏輯,避免多個角色重疊;
  • 整體上,交互流暢度和體驗有明顯提升,已有較完整的開始-進入-退出流程。

總體上,這一階段的目標基本完成,下一步可以針對實體管理精度和生成邏輯做進一步優化。

game.cppgame_platform.h 中添加退出游戲功能。

當前的目標是完善游戲的退出機制,讓游戲在沒有任何角色且玩家主動請求退出時能夠正確關閉。

首先的設計思路是:當玩家在游戲中按下特定按鍵(如 Escape),觸發“退出請求”。此時,如果當前沒有任何活躍的英雄角色存在,則可以認為游戲應當終止運行,進入“游戲結束”狀態。

為此,計劃進行以下幾步:

  1. 捕捉退出請求
    在控制器處理邏輯中添加判斷:如果檢測到退出按鍵被按下(如 Escape 鍵),則設置一個 quitRequested 標志為 true,表示用戶想要退出游戲。

  2. 檢查是否還有角色存在
    添加邏輯判斷當前是否有任何已激活的英雄角色(controlled heroes)。如果列表中沒有任何角色,并且 quitRequested == true,就意味著可以安全退出。

  3. 修改游戲主循環退出標志
    在調用游戲更新和渲染邏輯之后,對結果進行檢查。如果檢測到 quitRequested 被設置為 true,則將全局運行標志 globalRunning 設為 false,使平臺層可以終止主循環。

  4. 平臺層支持退出信號
    在平臺層添加處理邏輯,允許從游戲層接收退出請求信號,并據此中止運行。

  5. 內存初始化確認
    確認在初始化 gameMemory 時將其全部清零,以確保 quitRequested 以及其他布爾標志在游戲啟動時為默認值 false,防止誤觸發。

這套機制的意義在于:

  • 讓游戲的生命周期更加清晰,從“初始狀態(片頭動畫)”到“角色加入”再到“退出”具備完整的流程;
  • 在退出時不依賴平臺層強制終止,而是由游戲邏輯自主判斷是否應退出,提升模塊解耦;
  • 為后續菜單系統或更多游戲狀態控制打下基礎;
  • 實現平臺和游戲模塊之間的簡單通信橋梁(例如 quitRequested 標志),從游戲主動傳遞狀態到外部平臺。

接下來的工作主要集中在具體代碼實現上,包括在控制器輸入處理邏輯中加入判斷、在游戲主循環中加入狀態檢查,以及在平臺層主函數中處理 globalRunning 變化并優雅終止程序。整體方向和結構已清晰可行。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

運行游戲并嘗試退出。

當前邏輯的問題出現在退出游戲的判斷條件上:即使仍然存在英雄角色,游戲依然退出了。這顯然不符合預期,因為應該只有在沒有任何英雄角色存在,并且玩家明確請求退出的前提下,游戲才應該真正結束。

我們需要詳細分析邏輯流程并指出當前可能存在的問題和修正思路:


當前預期邏輯(設計目標):

  1. 玩家按下“退出鍵”(如 Escape) → 設置 quitRequested = true
  2. 游戲中檢查:如果 controlledHeroes(受控英雄列表)中沒有任何角色 并且 quitRequested == true → 游戲退出(globalRunning = false

實際情況:

  • 玩家在游戲過程中按下“退出鍵”
  • 游戲直接退出,即使還有英雄存在
  • 說明 controlledHeroes 檢測邏輯未能正確攔截退出條件

Escape 檢查的不對

game.cpp 中檢查 Esc 鍵是否被按下。

現在的退出邏輯之所以出現問題,是因為沒有對鍵盤輸入進行“去抖動”處理(debounce),導致程序在連續的兩幀中先刪除了英雄角色,然后立即檢測到“沒有英雄 + 請求退出”,從而直接關閉了游戲。這種邏輯在沒有適當判斷按鍵狀態變化的情況下很容易出現。


當前行為的問題概述:

  • 第一次按下 ESC 鍵 → 英雄被移除
  • 下一幀繼續檢測 ESC 鍵仍處于“按下”狀態 → 滿足“沒有英雄 + 請求退出”的條件 → 游戲退出

這個過程發生得非常快,玩家幾乎沒有反應時間,游戲就意外退出了。


原因詳解:

  1. 按鍵持續為“按下”狀態
    系統邏輯中并沒有區分“按下的一瞬間”和“持續按下”,所以 ESC 按鍵在連續幀中被視為仍然按下,導致邏輯被多次觸發。

  2. 缺少去抖動處理(Debounce)
    沒有檢測按鍵是否是“新按下的”,即沒有從“未按下 → 按下”的轉變判斷。


修正策略:

需要改為判斷鍵是否是**“剛剛按下”**的一幀,而不是當前是否處于按下狀態。可以使用 wasPressed 類型的接口或布爾標志來實現這一點。


實現建議:

  • 使用 wasPressed(KEY_ESCAPE) 或類似方式判斷是否是“剛按下”狀態:
if (wasPressed(KEY_ESCAPE)) {// 只在按下瞬間觸發的邏輯
}

這樣就可以避免因 ESC 長時間按住而導致多幀觸發的副作用。

  • 可選優化邏輯流程:
if (wasPressed(KEY_ESCAPE)) {if (heroExists) {removeHero();} else {requestQuit();}
}

確保只有當按鍵被“新觸發”時才執行后續操作。


總結:

當前游戲在處理 ESC 鍵退出邏輯時沒有進行去抖動處理,導致在兩幀內連續觸發“刪除角色”和“退出游戲”的操作,造成了誤判退出。通過添加 wasPressed 檢查,僅在按鍵從未按下變為按下的一瞬間觸發邏輯,即可有效解決這個問題,避免誤退出的情況發生。這樣也可以確保操作的可控性和用戶體驗。

在這里插入圖片描述

運行游戲并測試退出功能。

現在的退出邏輯分為兩種情況:常規退出和兩步退出。通過這兩種方式,至少現在可以順利進入游戲并且順利退出,提供了一個更穩固的游戲流程。

退出流程概述:

  1. 常規退出: 直接按下退出鍵,游戲結束。
  2. 兩步退出: 需要進行兩次操作才能退出,增加了確認步驟,避免誤退出。

這種結構的實現,確保了玩家可以在游戲中自由進出,體驗更加穩定。

game_cutscene.h 中引入 struct playing_cutscene,規范過場動畫狀態。

現在我們開始思考如何更正式地處理過場動畫(cut scenes),而不僅僅是隨機地將其插入游戲中。目標是讓過場動畫成為游戲的一部分,給玩家一個更清晰的體驗和理解。

過場動畫的結構:

  1. 過場動畫定義: 創建一個更正式的“過場動畫”結構,它將包含所有與過場動畫相關的信息。當前,這個過場動畫的內容不會特別復雜,但它為將來增加更多的功能提供了基礎。

  2. 需要的額外信息: 當前,過場動畫的一些數據(例如鏡頭索引等)還比較硬編碼(即直接寫死在代碼里)。因此,除了過場動畫的基礎信息外,還可以加入更詳細的內容,比如具體的過場鏡頭,場景的編號等。

  3. 場景數量: 可以設置一個場景的數量字段,用于控制有多少個過場鏡頭或場景,并確保過場動畫能夠按順序或預定的方式播放。

設計思路:

  • 為了使過場動畫看起來更有條理,可以在代碼中設計一個“過場動畫”類或結構體(比如“playing_cutscene”),它不僅能存儲場景索引、鏡頭等數據,還能進一步擴展,隨著需求的增加,可以更靈活地添加新功能。

通過這些方式,過場動畫將不再是游戲中的隨機插入部分,而是變成有目的、有結構的游戲內容。
在這里插入圖片描述

game_cutscene.cpp 中引入 MakeIntroCutscene

現在的目標是將過場動畫(cut scene)做得更加正式和結構化,以便在游戲中能夠更清晰地控制過場動畫的播放。

設計思路:

  1. 創建過場動畫的生成函數:

    • 創建一個函數,比如 makeIntroCutScene,用來初始化和構建過場動畫。這個函數將負責生成一個過場動畫對象(plainCutScene),并將相關的參數或信息封裝到這個對象中。
  2. 初始化過場動畫:

    • 在初始化時,設置過場動畫的場景數量(sceneCount)和場景列表(scenes)。這些數據可以從外部提供,也可以在函數內部默認生成。
    • 對于過場動畫對象的一些其他初始化參數,如 time,不需要顯式賦值,因為它們會自動處理。
  3. 返回過場動畫對象:

    • 初始化完過場動畫后,函數會返回該對象。這樣,就可以在游戲中調用這個函數來獲取當前播放的過場動畫對象。
  4. 具體使用:

    • 在游戲啟動時,可以通過調用這個函數來獲取一個“具體的”過場動畫對象,進而確定當前正在播放哪個過場動畫。這為后續更復雜的過場動畫控制打下基礎。

通過這種方式,可以在游戲中更加清晰地管理和控制過場動畫,而不是簡單地插入一些隨機的過場。

在這里插入圖片描述

將 Cutscene 作為參數傳入 RenderCutscene

現在的目標是對渲染過場動畫(cut scene)的方式進行一些調整和優化,使其更加靈活和清晰。具體步驟如下:

  1. 調整 renderCutScene 函數:

    • 修改 renderCutScene 函數,使其不再直接使用 tCutScene,而是接收一個具體的過場動畫(cut scene)對象。這意味著,在渲染時,會將具體的過場動畫數據傳遞給函數。
  2. 傳遞過場動畫數據:

    • 在調用 renderCutScene 時,需要傳遞具體的過場動畫對象 cutScene,而不是之前的 tCutScene。這樣,函數就能根據傳入的具體過場動畫對象來渲染不同的場景。
  3. 更新 renderCutScene 內部處理:

    • 在渲染過程中,確保過場動畫對象的各項數據能夠正確地傳遞和使用。更新函數,使其可以處理不同的過場動畫對象并顯示相應的內容。
  4. 清理和調整:

    • 做一些必要的清理工作,確保代碼整潔,過場動畫的傳遞和渲染流程更加順暢。需要確保傳入的過場動畫數據可以在渲染過程中正確使用,而不產生任何錯誤。

通過這樣的調整,過場動畫的控制變得更加靈活,能夠根據不同的需求進行渲染,而不是單純依賴固定的方式。這也使得后續可能的擴展和調整變得更加容易。

在這里插入圖片描述

在這里插入圖片描述

game.h 中初始化 CurrentCutscene

我們現在的目標是進一步整理和完善過場動畫(cutscene)系統的結構,使其更加模塊化、清晰易用,便于后續擴展和維護。主要做了以下幾方面的工作:


架構調整

  1. 替換舊的 tCutScene
    將原來硬編碼的 tCutScene 替換為新的 playingCutScene,并通過 currentCutScene 變量來跟蹤當前正在播放的過場動畫。

  2. 游戲初始化時指定初始過場動畫:
    在游戲初始化階段,如果游戲狀態還未初始化,則調用 makeIntroCutScene() 創建一個初始的引導過場動畫,并賦值給 currentCutScene。這樣一來,游戲啟動時就有明確的過場動畫狀態。


渲染邏輯重構

  1. renderCutScene 接口調整:
    修改 renderCutScene 函數,使其不再使用固定的結構,而是接收 gameState.currentCutScene 作為參數,確保渲染的是當前活躍的過場動畫內容。

  2. 消除類型錯誤:
    在嘗試訪問 gameState.tCutScene 時出現了類型錯誤,因為新結構中沒有這個成員。于是改用 advanceCutScene() 等輔助函數去處理當前過場動畫,避免直接訪問不存在的字段。


錯誤處理和類型定義

  1. 類型缺失處理:
    編譯器報錯提示“currentCutScene”類型缺失,這是因為結構體中沒有預先定義它。為解決此問題,手動將其加入結構定義,確保編譯器能夠識別和使用該字段。

  2. 合理忽略默認值:
    對某些未初始化值不做強制默認值處理,例如 t 時間字段,依靠結構體初始化自動設置為 0.0,減少冗余代碼。


下一步邏輯準備

  1. 準備 advanceCutScene 函數:
    為當前過場動畫推進添加 advanceCutScene,用于控制進度、切換鏡頭等功能。這種方式更可控,也為后續設計更復雜的場景邏輯打下基礎。

整體效果

目前已經實現以下目標:

  • 游戲啟動即有明確的過場動畫狀態。
  • 渲染邏輯不再依賴硬編碼字段,使用結構體引用更加靈活。
  • 構建了可擴展的系統,后續可以自由添加新的過場動畫邏輯。
  • 消除了關鍵的類型錯誤,使代碼結構更穩定可靠。

整體上,這一輪改動為過場動畫系統打下了干凈、清晰的基礎,便于未來添加更多復雜行為或多階段演出效果。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

注意:過場動畫不再支持熱重載。

現在我們遇到一個潛在問題,這種做法在未來可能會給我們帶來一些麻煩,特別是在熱重載(hot loading)方面。


熱重載的局限性

  1. 結構體數據可能會移動:
    由于我們將數據結構直接寫在函數外面,這些數據在程序運行期間有可能被重新加載或移動,而在熱重載過程中,這些變化可能無法被正確保留或更新。

  2. 缺乏 C 語言層面的支持:
    當前使用的語言環境(例如 C)對熱重載的支持較弱,無法像一些高級語言或引擎那樣在不重啟的情況下安全、靈活地重新加載代碼和數據。

  3. 數據“烘焙”導致靜態綁定:
    我們將數據“烘焙”(bake)進結構中,這使得相關數據變得更靜態,不容易動態替換或重載。這種處理方式在數據頻繁變動或開發階段需要熱更新的情況下會帶來不便。


潛在應對方法

  1. 將數據寫入函數內部:
    如果我們擔心這些結構體在熱重載時位置或狀態丟失,可以選擇將這些數據放進具體的函數體中,而不是靜態存儲。這樣可以在重新加載函數時重新初始化這些數據,從而避免因外部定義帶來的失效問題。

  2. 手動管理熱重載:
    可以通過某種方式為這些結構體或資源手動添加一個“重載鉤子”或者在重載后主動刷新這些結構數據。雖然繁瑣,但可以解決部分熱重載失效的問題。


實際影響分析

  1. 目前影響不大但值得關注:
    當前的系統可能還沒有頻繁依賴熱重載特性,所以短期內不會有太大問題。但長期來看,如果希望頻繁使用熱重載提升開發效率,這種寫法的確會成為潛在障礙。

  2. 屬于語言設計限制:
    這是由于語言本身在運行時處理數據的方式決定的,并非代碼邏輯問題。C 本身在運行時對熱更新支持就非常有限,靜態定義的數據結構在模塊重載過程中容易失效或不一致。


總結

我們當前的實現方式雖然功能正確,但可能會在將來因熱重載需求而面臨一些挑戰,主要體現在:

  • 數據不再熱重載安全;
  • 結構體定義靜態化,難以動態更新;
  • 缺乏語言級支持導致難以解決根本問題。

盡管暫時不影響開發,但如果未來要做大規模熱更新優化,可能需要重新考慮數據和函數的組織方式。這是一個值得提前注意和記錄下來的技術點。

game_cutscene.cpp 中添加 AdvanceCutscene,以支持多個過場動畫。

我們現在需要一個機制來讓過場動畫(cutscene)隨時間推進。之前是直接在代碼中硬編碼的,但現在我們把它做得更正式一些,這樣我們就能支持多個不同的過場動畫,而且不會全部都被固定寫死在程序中。


過場動畫時間推進邏輯整理

  1. 引入更正式的數據結構:
    我們把過場動畫抽象成了一個結構體,現在不再是簡單地用某些硬編碼的變量來控制,而是可以更靈活地定義多個動畫。

  2. 實現推進函數:
    創建了一個推進函數,用來根據 dt(delta time,幀間隔時間)來更新過場動畫當前時間 t。邏輯很簡單,就是把 dt 加到當前的 t 上:

    cutting.t += dt;
    

    當前階段,這個函數還不處理其他事情,僅僅更新時間,這為后續拓展動畫效果(比如切換鏡頭、過渡等)打下基礎。

  3. 沒有添加其他行為:
    目前只做了時間推進的基礎框架,沒有處理播放進度、切換狀態、轉場等高級邏輯。


當前結果

  • 現在的系統已經能夠支持多個不同的過場動畫實例,而不是只能播放一個固定的動畫。
  • 過場動畫狀態能隨著游戲時間正確推進。
  • 沒有對現有流程造成影響,系統運行回到了正常狀態,具備良好擴展性。

未來可以拓展的方向

  • 支持動畫狀態切換(比如播放完自動進入下一個)。
  • 加入動畫觸發機制,例如特定事件觸發某段動畫。
  • 結合劇情或 UI 反饋,讓動畫與游戲邏輯更加緊密結合。
  • 添加控制動畫暫停、跳過、回放等功能。

總之,這一階段主要是將原本臨時硬編碼的過場動畫處理方式轉化為一個更靈活、可擴展的系統,雖然目前功能簡單,但已經為后續開發打好了基礎。
在這里插入圖片描述

在這里插入圖片描述

運行游戲,測試新功能,并考慮讓過場動畫重置。

目前已經可以順利進入游戲并從游戲中返回,但存在一個新的問題:過場動畫(cutscene)在中途進入游戲再退出時,并不會重啟。也就是說,動畫狀態被保留了下來,這可能不是我們期望的行為。


現階段的表現與問題

  • 進入游戲再退出后,過場動畫不會從頭開始播放,而是從上次停留的時間點繼續。
  • 這會導致像“歡迎”字樣的元素已經移出屏幕,回來時無法再次看到。
  • 例如在退出時,畫面中“welcome”已經飄到畫面右側快看不見了,回來時它仍然是那個狀態,用戶錯過了內容。

理想的行為目標

  • 每次返回主界面或重新啟動動畫時,應當讓動畫狀態歸零,從頭開始播放。
  • 將來如果實現標題畫面(title screen),可以在播放標題幾秒后自動開始動畫,形成自然的過渡。
  • 關鍵是要有一個機制,在狀態從“非過場動畫”切換回“播放過場動畫”時,重新初始化動畫。

需要補充的功能點

  • 當前的結構中沒有明確標記“狀態轉換”,也就是說,系統無法識別何時從“游戲狀態”返回到了“播放動畫狀態”。
  • 需要添加一個狀態轉變檢測機制,例如通過上一次狀態與當前狀態的比較來判斷是否需要重啟動畫。
  • 也可以引入一個狀態機機制,使動畫播放邏輯更清晰,并方便管理進入/退出游戲、開始/停止動畫等切換。

當前的系統優點

  • 當前實現方式非常干凈,避免了因狀態不一致而產生的問題,例如“游戲以為自己在運行中,實際上并沒有角色存在”這種錯誤狀態。
  • 系統不會出現混亂的狀態判斷,邏輯清晰、穩定。

當前的限制與接下來的工作

  • 尚未實現“狀態過渡檢測”邏輯,所以動畫不會根據場景跳轉而重啟。
  • 接下來的任務就是添加這種檢測機制,以便在切換到動畫播放狀態時正確地重新初始化動畫。

當前時間緊張(只剩大約五分鐘開發時間),暫時不展開這部分的實現,但已經明確了問題所在與解決方向,之后可以直接著手構建過渡檢測機制,實現動畫狀態的合理重啟邏輯。

提問:是否可以添加 BlockUntilLoaded 類函數,確保較慢的電腦在過場動畫開始前加載完所有圖層?

目前正在考慮是否有必要在過場動畫或游戲開始前加入一個“阻塞直到加載完成”的機制,以確保即使在低性能設備上也能順利加載所有渲染層(layers),避免畫面缺失或卡頓。


已有的加載控制機制

  • 實際上系統中已經存在類似的機制,可以控制渲染內容在準備好前不進行顯示。
  • 這個機制的實現是通過 game_render_group 來完成的。
  • game_render_group 在某種程度上起到了“阻塞直到資源準備好”的作用,可以確保所有需要的圖層、圖像等都加載完成后再開始渲染。

使用時機與優化方向

  • 是否啟用這樣的阻塞機制,要視具體優化目標而定。
    • 如果優先考慮用戶體驗,避免初始動畫卡頓或貼圖缺失,使用該機制是有益的。
    • 如果更關注啟動速度,可能希望跳過某些資源的等待,讓它們異步加載。
  • 當前結構已經為這種需求提供了良好的基礎,只要適當調用 game_render_group,即可實現阻塞等待功能。

后續考慮

  • 如果決定更正式地引入加載檢測邏輯,可以擴展當前系統,例如:
    • 在進入特定狀態(如播放過場動畫)前,調用一個專門的“加載屏幕”或“資源準備確認”模塊。
    • game_render_group 增加明確的加載狀態判斷,比如 is_ready 標志。
  • 這種設計可以進一步提升跨平臺表現,尤其對性能較低的設備更為友好。

總結來說,已經具備了一個可用的機制來處理“加載前阻塞”的問題,未來只需根據具體的運行需求決定是否啟用它,并可按需擴展其能力。

game_cutscene.cpp 中關閉預加載邏輯。

當前的討論集中在如何通過已有機制處理資源加載緩慢時的視覺問題,特別是在沒有啟用預加載(prefetch)功能的情況下,可能出現的畫面撕裂或資源缺失現象。


已有機制說明

  • 系統內部已有一套預加載(prefetch)機制,能提前加載資源,防止游戲初始階段或切換場景時出現貼圖、圖層丟失等現象。
  • 在啟用預加載的狀態下,畫面渲染非常平滑,不會出現明顯的加載故障。
  • 如果手動關閉預加載機制,例如模擬在一臺性能很低的設備上運行,會立即看到場景中資源缺失造成的可視化“撕裂”或“空白”。

當前改進思路

  • 為了讓這種情況下的體驗更合理,可以在檢測到資源尚未加載完成時,暫時不渲染畫面,從而避免顯示錯誤的中間狀態。
  • 實現方式是在每一幀渲染前檢查資源狀態,如果發現有必要資源未準備就緒,就直接跳過該幀的渲染,等資源加載完成后再開始渲染。

效果與意義

  • 這樣做的好處是,即便在慢速或老舊設備上運行,也不會看到“閃爍”、“丟貼圖”等問題,而是看到一個平穩等待的過程。
  • 本質上這等于用時間換體驗:與其展示半成品的畫面,不如干脆延遲顯示,保證完整性。
  • 這一策略尤其適用于開場動畫、過場演出等對視覺完整性要求高的內容。

后續擴展可能

  • 可以在“等待資源加載完成”的過程中添加提示信息,例如加載動畫、文本提示等,提升用戶感知上的流暢性。
  • 還可以細化資源狀態的判斷邏輯,例如只等待當前幀需要的資源,而非全部資源,提高加載效率。

總結來說,已經具備一個可以利用的判斷與控制機制,只需簡單邏輯改動即可實現“資源未加載完則暫停渲染”的效果,從而有效提升整體視覺體驗,特別適用于低性能環境下的過渡場景處理。

game.cpp 中檢查 AllResourcesPresent 后再進行 TiledRenderGroupToOutput

當前內容圍繞優化資源加載與渲染邏輯展開,尤其是當資源尚未完全加載完畢時,如何避免畫面撕裂、錯位或貼圖缺失等問題,并提供更平滑的過渡體驗。


當前處理方式說明

  • 在進行 tab 相對于輸出位置的操作時,加入了一步判斷邏輯:如果所有資源都已加載,則進行渲染,否則暫停
  • 這種處理方式在運行時會導致一些非常短暫的“卡頓”或“停頓”,但由于持續時間極短,基本不會被察覺,對體驗影響非常小。
  • 實際運行中已經觀察到,在資源未加載完成前不會渲染任何內容,成功避免了資源缺失帶來的可視化異常。

特殊場景測試

  • 在按下空格鍵觸發新場景或內容加載時,同樣的機制也會生效,系統會等資源準備就緒后才渲染,從而確保畫面完整性。
  • 唯一的例外是**地面瓦片(ground tiles)**的加載,這部分是作為一個單獨過程進行的,因此會在主資源準備完后略微延遲顯示。這屬于結構層面不同步的問題。

改動小結

  • 當前的實現邏輯非常簡潔,只需簡單判斷 all assets present 即可控制渲染流程,不影響主邏輯結構。
  • 該策略默認兼容未來的性能較低設備,使其也能流暢過渡,哪怕在加載時稍有停頓。

預加載機制的回歸與兼容性

  • 一旦恢復啟用預加載(prefetch)機制,整個流程將恢復為完全無停頓狀態,因為所有資源在正式渲染前已被加載完畢。
  • 即便禁用預加載,對于慢速系統而言,也不會出現明顯視覺缺陷,因為此方案通過主動等待機制掩蓋了資源加載過程。

最終結論

  • 該改動是穩妥且有益的,兼顧性能與視覺體驗。
  • 代碼結構簡潔,邏輯清晰,未來可輕松擴展為帶提示或加載動畫的形式。
  • 當前實現已考慮到最壞情況,無需進一步特殊處理,屬于可以保留并長期使用的通用優化方案。

整體來看,這是一次從用戶體驗角度出發的細致優化,兼顧了系統性能差異的適配性,同時保持了代碼的靈活性與簡潔性。
在這里插入圖片描述

提問:有沒有辦法使用 SIMD 清除后備緩沖區?

目前我們討論的是使用 SIMD 指令(如 rep 指令或其他清屏方式)清空后備緩沖區(back buffer)的問題。總結如下:


關于清空后備緩沖區的方式

  • 確實可以使用 SIMD(Single Instruction Multiple Data)指令來清除后備緩沖區,例如使用 memset 等基于 SIMD 的優化方法。
  • 但這并不一定總是最快的方式。例如使用 rep stos(重復存儲指令)在某些平臺上可能性能更優。
  • 實際上,哪種方式更快通常取決于具體硬件架構與 CPU 優化情況,必須通過實際測試來驗證。

當前邏輯中的考慮

  • 在本項目中,清除后備緩沖區的操作僅在未渲染任何內容的情況下發生
  • 因此,該操作性能影響非常小,不構成瓶頸,不存在對清除效率的高要求。
  • 即便清除操作較慢,也幾乎不會對整體幀率或用戶體驗產生明顯影響。

總體結論

  • 是的,確實可以使用 SIMD 優化清除操作,但是否使用要看實際情況。
  • 對當前場景而言,清除性能并不是核心問題,因為它只在沒有其他渲染內容時執行,且執行頻率極低。
  • 預計未來也不會出現對清除速度有嚴格要求的場景,因此目前的實現方案已足夠穩定可靠,無需進一步優化。

這一點體現出在游戲或圖形系統開發中,性能優化應該是按需進行的,不必為極少數場景提前過度優化。

提問:是否會有動畫過場,還是僅僅是縮放鏡頭?

目前的過場動畫主要只是一些鏡頭推進(zooming shots),沒有真正意義上的敵人出場。如果非要說有的話,也就是在某一兩個鏡頭中,畫面上有些靜態元素,例如沙地上的腳印或帽子的位置,這些可能被視作敵人留下的痕跡,但它們本身并不具備動作或互動性。


詳細總結如下:

  • 當前過場動畫的內容以鏡頭推進為主,即通過畫面拉近、平移等方式展示場景。
  • 沒有設計帶有行為的敵人登場,敵人并不直接參與過場動畫
  • 如果一定要從中找出與“敵人”有關的元素,可能只有一兩個畫面:
    • 比如沙地上的痕跡或物品(如帽子)可能暗示敵人的存在。
    • 這些僅是靜態線索,用于營造氛圍或鋪墊劇情,而非實際的敵對互動。
  • 整體而言,過場動畫在目前階段更偏向于視覺表現與情緒烘托,而非敘事沖突或戰斗展示。

換句話說,這些鏡頭是為了讓場景更具戲劇性或沉浸感而存在的,而不是用來展示敵人或敵對事件的。

提問:是否可以從桌面淡入游戲初始黑屏?如果做不到,淡入淡出過場也很酷。

目前確實可以實現從桌面漸變過渡到游戲初始的黑屏畫面,但是否采用這種方式,則取決于具體的設計目的。


詳細總結如下:

  • 技術上是可行的,可以實現從桌面漸變過渡到黑屏的效果。

    • 一種方法是獲取當前桌面背景的內容,然后通過在其上方創建一個**分層窗口(layered window)**來實現漸變效果。
    • 或者簡單地在游戲啟動初期先展示一個窗口,然后控制其透明度逐漸降低,最終過渡到游戲畫面。
  • 設計層面并不推薦輕易使用“淡出”效果

    • 個人風格上不太喜歡“fade to black”(淡出到黑),因為這種過渡效果在視覺語言中有特定用途,不應濫用。
    • 在現代電影中,“淡出”或“畫面擦除”(wipe)這類過渡手法都非常少見,通常只在特定情緒或情節場合使用。
      • 比如表達時間流逝、場景切換的情感緩沖等。
    • 若沒有強烈的情緒驅動或敘事理由,這種過渡會顯得老派甚至多余。
  • 對當前場景來說:

    • 如果游戲初始是黑屏,那其實已經具備了過渡的“純凈感”,不一定需要從桌面“漸變”進來。
    • 除非有特別明確的風格化需求技術演示目的,否則沒必要添加這類過渡。

總結來說,技術上完全可以實現桌面到黑屏的淡出過渡,但是否采用,取決于對游戲敘事節奏、風格表達的權衡。在多數情況下,這樣的視覺效果并不必要,甚至可能會影響節奏或顯得多余。

win32_game.cpp 中添加 FadeOut

要實現桌面到黑屏的淡出效果,可以通過創建一個全屏窗口并逐漸調整其透明度來實現。以下是步驟和思路的總結:

1. 創建一個全屏窗口

  • 首先,創建一個新的窗口,并將其調整為覆蓋整個屏幕。這可以通過設置窗口大小為屏幕的尺寸來實現。
  • 使用窗口類創建窗口,可能需要為窗口指定合適的類,以確保窗口可以正確地顯示。

2. 設置窗口層疊和透明度

  • 創建一個分層窗口(layered window)。這種窗口允許設置透明度,可以通過調整其透明度來實現從桌面到黑屏的漸變效果。
  • 使用UpdateLayeredWindow函數來更新窗口的透明度。通過這種方式,窗口會逐漸變得透明,直到最終消失,達到淡出效果。

3. 避免復雜的窗口捕獲

  • 可以通過不捕獲背景或其他窗口的內容,而是簡單地創建一個覆蓋全屏的窗口來避免操作系統不兼容的問題。
  • 捕獲屏幕內容并在窗口中渲染可能會因為操作系統的不同而不穩定,使用全屏覆蓋窗口的方式更為簡便且可靠。

4. 實現思路

  • 通過這種方法,可以創建一個全屏窗口,然后逐漸降低其透明度,使其看起來像是從桌面過渡到黑屏。
  • 這種方式的好處是實現簡單,不依賴操作系統的具體細節(比如屏幕捕獲),且適用于較舊的操作系統(如XP及其以上)。

5. 使用這種方法的潛在問題

  • 這種方法在某些情況下可能不適用于游戲開發,因為它涉及到的窗口管理和層疊效果可能影響游戲的其他方面,特別是對于更復雜的圖形和渲染系統。
  • 盡管這種方法技術上可行,但在實際游戲開發中,通常不建議使用,因為它可能會導致不必要的視覺過渡,并且可能不符合現代游戲設計的流暢性需求。

總結來說,技術上是可以通過創建全屏窗口并調整其透明度來實現淡出效果,但這不一定是最好的解決方案,特別是在游戲開發中,可能會有更合適的方式來處理場景切換。
在這里插入圖片描述

上網查詢 UpdateLayeredWindow 用法。

為了實現淡出效果,可以通過創建一個分層窗口(layered window)并更新它的透明度來逐步實現窗口的淡出。然而,實際操作中遇到了一些問題,下面總結了整個過程以及問題分析:

1. 創建和更新分層窗口

  • 使用UpdateLayeredWindow函數來實現窗口的透明度漸變。這個函數允許在操作系統層面更新窗口,使其成為透明的。
  • 為了讓窗口過渡到黑屏,可以通過不斷調整窗口的透明度來實現。
  • 在操作系統中調用該函數時,需要傳遞窗口的句柄、設備上下文(DC)、大小等信息。

2. 使用透明度來實現淡出效果

  • 通過逐步改變窗口的透明度,創建一個從桌面背景到黑色屏幕的淡出效果。透明度是通過alpha通道控制的,逐漸降低透明度來模擬淡出效果。
  • 具體做法是通過設置AlphaBlend,指定透明度的逐步變化。

3. 問題與錯誤排查

  • 在實際操作中,遇到了一些問題,比如窗口沒有顯示出來,盡管代碼中已經設定了透明度更新。
  • 調用UpdateLayeredWindow時傳遞的參數可能沒有正確配置,導致窗口無法更新。尤其是在**設備上下文(DC)**的傳遞和處理上,可能需要傳遞正確的設備上下文,而不是簡單地傳遞0。
  • 嘗試過多種方法,包括使用零作為設備上下文的參數,然而沒有成功顯示窗口。

4. 其他可能的解決方案

  • 可能需要創建一個有效的設備上下文(DC),或者通過設置更具體的圖形上下文來繪制窗口的內容。使用兼容的DC對象來確保窗口能夠正確渲染。
  • 另一個思路是,創建一個新的繪圖區域,并確保使用正確的DC來繪制。這可以通過設置兼容的DC來實現,從而避免透明度更新失敗。

5. 最終的計劃

  • 由于時間限制,無法在當前會話中完成所有操作,決定將這個問題推遲到下次繼續解決。
  • 計劃進一步思考是否有更簡單的方式來處理窗口的淡出效果,例如不需要創建復雜的DC或直接用更簡單的方式來填充黑色背景,避免過多的復雜計算和操作。

總結來說,盡管代碼本身沒有報錯,但實際操作中由于設備上下文的處理和透明度的配置問題,導致窗口未能正確顯示。接下來將繼續進行調試,嘗試更合適的方式來實現黑屏淡出的效果。
在這里插入圖片描述

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/79093.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/79093.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/79093.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【小沐學GIS】基于C++繪制三維數字地球Earth(QT5、OpenGL、GIS、衛星)第五期

🍺三維數字地球系列相關文章如下🍺:1【小沐學GIS】基于C繪制三維數字地球Earth(OpenGL、glfw、glut)第一期2【小沐學GIS】基于C繪制三維數字地球Earth(OpenGL、glfw、glut)第二期3【小沐學GIS】…

OpenAI 最新發布的 GPT-4.1 系列在 API 中正式上線

每周跟蹤AI熱點新聞動向和震撼發展 想要探索生成式人工智能的前沿進展嗎?訂閱我們的簡報,深入解析最新的技術突破、實際應用案例和未來的趨勢。與全球數同行一同,從行業內部的深度分析和實用指南中受益。不要錯過這個機會,成為AI領…

【力扣】day1

文章目錄 27.移除元素26. 刪除有序數組的重復項 27.移除元素 26. 刪除有序數組的重復項 我們仔細看一下這兩道題的最后的返回值,為什么第一題返回slow 而第二題返回slow1 最后的返回值該如何返回絕對不是憑感覺,我們自己分析一下第一個slow,從0位置開始, 遇到val值就開始和fas…

完全無網絡環境的 openEuler 系統離線安裝 ClamAV 的詳細步驟

準備工作(在外網機器操作) 1. 下載 ClamAV RPM 包及依賴 mkdir -p ~/clamav-offline/packages cd ~/clamav-offline/packages# 使用 yumdownloader 下載所有依賴包(需提前安裝 yum-utils) sudo dnf install yum-utils -y sudo y…

3.2.2.2 Spring Boot配置視圖控制器

在Spring Boot中配置視圖控制器可以簡化頁面跳轉跳邏輯。通過實現WebMvcConfigurer接口的addViewControllers方法,可以直接將URL映射到特定的視圖,而無需編寫控制器類。例如,將根路徑"/"映射到welcome.html視圖,當訪問應…

數據庫—函數筆記

一,數據庫函數的分類 內置函數(Built-in Functions) 數據庫系統自帶的函數,無需額外定義即可直接調用。 聚合函數:對數據集進行計算(如 SUM, AVG, COUNT)。 字符串函數:處理文本數據…

YOLOv2訓練詳細實踐指南

1. YOLOv2架構與原理詳解 1.1 核心改進點 YOLOv2相比YOLOv1的主要改進: 采用Darknet-19作為backbone(相比VGG更高效)引入Batch Normalization提高穩定性與收斂速度使用anchor boxes機制代替直接預測邊界框引入維度聚類確定anchor boxes尺寸…

詳解如何復現DeepSeek R1:從零開始利用Python構建

DeepSeek R1 的整個訓練過程,說白了就是在其基礎模型(也就是 deepseek V3)之上,用各種不同的強化學習方法來“雕琢”它。 咱們從一個小小的本地運行的基礎模型開始,一邊跟著 DeepSeek R1 技術報告 的步驟,…

MCP Server 開發實戰 | 大模型無縫對接 Grafana

前言 隨著大模型的飛速發展,越來越多的 AI 創新顛覆了過往很多產品的使用體驗。但你是否曾想過,在向大型語言模型提問時,它能否根據你的需求精準返回系統中的對應數據?例如,當用戶查詢 Grafana 服務時,模型…

塊存儲、文件存儲和對象存儲的特點、應用場景及區別

塊存儲、文件存儲和對象存儲的特點、應用場景及區別 塊存儲 特點:塊存儲將數據分割成固定大小的塊,每個塊都有唯一的標識符。數據以塊為單位進行讀寫操作,適合需要高性能和低延遲的場景。 應用場景:數據庫存儲、虛擬機磁盤、高性能…

OpenCv--換臉

引言 在當今數字化時代,圖像處理技術的發展日新月異。換臉技術作為其中一項極具趣味性和挑戰性的應用,吸引了眾多開發者和愛好者的目光。OpenCV 作為一款強大的開源計算機視覺庫,為我們實現換臉提供了豐富的工具和方法。本文將深入探討如何使…

安卓基礎(SQLite)

基礎 import sqlite3# 連接到數據庫 conn sqlite3.connect(mydatabase.db) cursor conn.cursor()# 執行查詢 cursor.execute("SELECT * FROM users") rows cursor.fetchall()for row in rows:print(row)# 關閉連接 conn.close() 創建一個繼承自 SQLiteOpenHelpe…

QuickAPI 核心能力解析:構建數據服務化的三位一體生態

在企業數據資產化運營的進程中,如何打破數據開發與共享的效率瓶頸,實現從 “數據可用” 到 “數據好用” 的跨越?麥聰軟件的 QuickAPI 給出了系統性答案。作為 SQL2API 理念的標桿產品,QuickAPI 通過SQL 編輯器、數據 API、數據市…

《計算機視覺度量:從特征描述到深度學習》—生成式人工智能在工業檢測的應用

2022 年 11 月 30 日一個很重要的標志事件就是chatgpt的出現,打開了生成式人工智能的開端。這也許會是一個歷史性的時刻,今天是2025年4月,過去兩年多,那個時刻目前回想還是對本人造成了沖擊,一個完全有自主分析能力的生…

【軟件測試】自動化測試框架Pytest + Selenium的使用

Pytest Selenium 是一種常見的自動化測試框架組合,用于編寫和執行 Web 應用程序的自動化測試。Pytest 是一個強大的 Python 測試框架,而 Selenium 是一個用于瀏覽器自動化的工具,二者結合使用可以高效地進行 Web 應用的功能測試、UI 測試等。…

煤礦濕噴砂漿攪拌機組創新設計與關鍵技術研究

引言&#xff1a;濕噴工藝在煤礦支護中的革命性意義 在深部煤礦巷道支護領域&#xff0c;濕噴混凝土技術以其回彈率低&#xff08;<15%&#xff09;、粉塵濃度小&#xff08;<10mg/m&#xff09;的顯著優勢&#xff0c;逐步取代傳統干噴工藝。作為濕噴工藝的核心設備&am…

如何處理ONLYOFFICE文檔服務器與Java Web應用間的安全認證和授權

如何處理ONLYOFFICE文檔服務器與Java Web應用間的安全認證和授權&#xff1f; 處理 ONLYOFFICE 文檔服務器與 Java Web 應用之間的安全認證和授權&#xff0c;通常涉及以下幾個關鍵步驟和技術&#xff1a; 1. JWT (JSON Web Token) 認證 啟用 JWT&#xff1a; ONLYOFFICE 文檔…

無參數RCE

無參數RCE&#xff08;Remote Code Execution&#xff0c;遠程代碼執行&#xff09; 是一種通過利用目標系統中的漏洞&#xff0c;在不直接傳遞用戶可控參數的情況下&#xff0c;實現遠程執行任意代碼的攻擊技術。與傳統的RCE攻擊不同&#xff0c;無參數RCE不依賴外部輸入參數…

OL9設置oracle23ai數據庫開機自啟動

1、設置oracle用戶的環境變量信息 [oracleOracleLinuxR9U5 ~]$vim ~/.bash_profile # Set Oracle environment variables for Oracle 23c AI export ORACLE_HOME/opt/oracle/product/23ai/dbhomeFree export ORACLE_SIDFREE export PATH$ORACLE_HOME/bin:$PATH export LD_LIB…

AI agents系列之智能體框架介紹

1. 引言 智能體AI Agents框架通過賦予自主系統動態感知、推理和行動的能力&#xff0c;徹底改變了AI領域。本節將探討智能體框架的核心概念&#xff0c;并重點介紹為什么開源解決方案對現代AI開發的創新和可擴展性至關重要。 1.1 什么是智能體框架&#xff1f; 智能體框架代…