Godot入門到棄坑:殺不死我的讓我更強大


3樓貓 發佈時間:2024-04-19 14:33:13 作者:cameLcAsE Language

非布爾式的HP

這個名字是我編的。現在我們的主角要麼死要麼活,而沒有一個量化的HP。當然還是那句話,具體如何取決於設計。不過這裡我們試著來讓主角沒那麼容易死。
要實現生命值系統,我們不僅需要修改玩家的相關代碼,還需要在UI上有所表示。我們先來修改玩家的腳本。
HP一般來說是一個整數——即使UI以血條示人,實際的數據可能也不過是是當前HP和滿血的一個比值。因此我們除了需要一個變量來表示玩家當前的HP,還需要一個變量來確定玩家的HP最大值(滿血狀態)。
定義這兩個變量,為了方便調整HP最大值,我們把它暴露出來:

接受傷害

現在玩家受傷時不能直接讓它die了。我們提供一個方法來減少它玩家的HP。
現在我們可以把die的條件調整為只有在HP小於等於0時才die,因為受到傷害可能會大於當前的剩餘HP。不過為了避免一些問題,我們還是把玩家的生命值限制為非負數。這裡可以用到一個簡單的函數叫clamp,它可以把一個值限定到一個範圍內,如果不超出這個範圍就原樣返回,如果小於最小值就返回最小值,超過最大值就返回最大值。
另一方面,作為敵人的負鼠不能直接調用die了,而是調用這個方法給玩家傷害。我們可以定義一個damage屬性方便調整傷害:

UI

我們希望在UI上體現玩家的HP狀況。我們用素材中的櫻桃來代表HP。櫻桃看起來不那麼對稱,如果你不能接受也可以選擇用寶石素材。
新建一個Control場景開始做HP的UI,可以叫它HpGauge。
假設我們想讓HP的指示物橫向排布,有多少HP就顯示幾個。由於涉及多個元素,我們自然會想到用一個容器來裝它們。這裡就用之前介紹過的BoxContainer,由於我們需要橫向排布,所以這裡選用HBoxContainer(H代表horizontal)。圖片素材用TextureRect(“紋理矩形”)展示。
HP指示器很多時候出現在畫面左上方,所以我們把它錨定到左上角,然後稍微拉開一點距離。
我們給這個Container加上一個TextureRect作為子節點以便查看效果。現在作為”一點HP“的素材顯示在了容器中,但是它的默認設置並不是我們想要的。根據你對Container初始大小設定的值,此時HP的紋理的樣子可能已經變得奇怪了:
此時需要調整TextureRect的設置。注意這裡談的尺寸涉及到兩個東西。一個是TextureRect本身的大小(即TextureReact所確定的矩形的大小),另一個是TextureRect內部的紋理的大小。控制TextureRect的大小和紋理本身的尺寸主要依賴Expand Mode和Stretch Mode兩個屬性。
由於Container中的控件大小在某些時候會受容器控制,所以如果想看Expand Mode的效果可以單獨放一個TextureRect到場景中。
Expand Mode會控制如何確定TextureRect的最小尺寸。其默認值為Keep Size,即保持所選紋理的尺寸作為最小尺寸。Ignore Size即忽略紋理的尺寸,TextureRect的尺寸任意。
右側TextureRect縮放時忽略了紋理本身的大小,你把縮小得看不到都行

右側TextureRect縮放時忽略了紋理本身的大小,你把縮小得看不到都行

帶Fit(適應)的選項按照文檔所說(當然實際上也是)主要是放在容器中比較有用。我們在稍後放到容器中再說。
Strech Mode控制TextureRect內部的紋理如何拉伸。默認的Scale就是我們剛才看到的效果,它會讓紋理跟著TextureRect進行縮放。Tile可能比較少用,它會讓紋理鋪滿整個空間。Keep就是保持紋理尺寸不動,如果此時Expand Mode為Ignore Size,那麼就可以出現這種情況:
紋理本身大小不變,矩形變小,顯示在了外面

紋理本身大小不變,矩形變小,顯示在了外面

Keep Centered顧名思義就是始終保持紋理在矩形中間且不修改紋理本身的尺寸。帶Aspect(寬高比)的會保持紋理的寬高比。Keep Aspect Centered就是說保持寬高比(進行拉伸)的同時保持在中心,這幾種拉伸模式可能會比較常用:
最左側為Keep Centered,Rect拉大了,但是紋理本身保存尺寸不變且居中。中間為Keep Aspect,紋理會在保持寬高比的前提下縮放,但是位置不動。右側就是既按寬高比縮放又保持居中。
Keep Aspect Covered也會對紋理進行拉伸,但是超過矩形的部分會被遮住。
接下來我們放到容器中再來看這兩個屬性。在容器加入一個TextureRect,各屬性保持默認。前面也提到過,當控件位於容器中時,它的尺寸和位置就不再完全受自己控制。放入容器後,檢視面板的Layout菜單下面會多出來一批Container Sizing屬性:
這是控件在容器中才能(通過編輯器)進行調整的屬性。這些屬性會告訴容納它的父級容器應當如何安排它的大小。對於HBoxContainer來說,它主要控制水平方向上的排布(用Web的話來說就是”主軸“是橫軸),因此這裡Horizontal選項都不會起作用。當然,Contaienr Sizing部分的各屬性對於不同類型的容器來說效果也不同。
不過對於HBoxContainer來說垂直方向上仍有可以調整的餘地。默認的Fill決定了它會始終填滿容器的高度:
無論Expand Mode為何,Fill會使得高度始終和容器保持一致。這裡的兩個橙色方框是我同時選中HBoxContainer和其中的TextureRect顯示出來便於對比大小的,並不是什麼神奇功能。要同時選中場景樹中的節點,按下Control再選擇即可,和各種操作文件系統和列表的軟件的常用快捷鍵一樣,Shift可以自動連續選擇一系列相鄰節點。
Shrink會讓容器中的控件在對應方向上的尺寸縮小為最小尺寸。例如在設置了TextureRect的Expand Mode為Keep Size的情況下,將垂直方向上的sizing調整為Shrink Center,HBoxContainer中的TextureRect的高度就會和紋理本身保持一致而不是填滿容器的高度,且同時把它放在垂直方向上的中間:
不過如果設置了TextureRect的Stretch Mode為保持寬高比且居中,其實我們也可以看到紋理保持在中間。只不過此時TextureRect本身的高度是和容器一樣高的:
Shirink Begin和End類似,只不過位置不一樣。不贅述。
如果我們不想紋理被縮放拉伸而走樣,我們可能希望將Strech Mode和Expand Mode調整為保持紋理尺寸的模式,Expand Mode設置為Keep Size沒毛病,Stretch Mode設置成Keep Centered為佳(便於保持圖片在容器垂直方向上的中間)。
Keep Aspect Centered實際上在這裡效果一樣,只不過不是它本來的目的。由於在HBox容器中水平方向是HBoxContainer的“主軸”(這些容器和Web裡的flex容器有點像,借用一下主軸的說法),所以其子節點的寬度交給了它控制,所以其實也不會縮放。
此外Container Sizing中還有個Expand屬性沒有提到。這個選項的意思就是說這個控件要不要展開以佔據剩下所有的空間:
這兩個櫻桃除了左邊的勾選了Expand之外其餘設置相同。
如果容器中多個控件啟用了展開,那麼所有想展開的控件會平分剩餘空間:
現在左右兩個都啟用了Expand

現在左右兩個都啟用了Expand

最後,如果你希望櫻桃能夠隨著容器的大小變化而發生縮放的話,我們首先要修改TextureRect的Expand Mode,以使得它不再以紋理本身大小為最低要求。帶Fit的選項就會根據設置動態調整TextureRect的最小尺寸,例如文檔提到對於水平佈局比較好用的Fit Width(寬度適應)系列選項會無視紋理的高度,讓自己的寬度等於自己的高度。帶Proportional的就是會保持紋理的比例進行調整。此時Stretch Mode如果設置為默認的Scale,我們就可以看到紋理本身也就會跟著TextureRect進行縮放,當然保險起見我們也可以設置為限制最多的Keep Aspect Centered:
總之,控件的大小、位置是錨點、容器、自身設置共同作用下確定的。這些設置會產生很多種組合,在何時需要哪種設置還需要我們根據需要來確定。
這裡我自己還是選擇保持櫻桃的尺寸。

動態調整HP指示物個數

現在的問題是如何根據具體的HP值來調整容器中的東西的個數。毫無疑問我們要寫代碼。這裡可以像之前那樣,把裡面的TextureRect單獨做成一個場景,然後在需要的時候實例化出來加入到容器中。不過這裡我就用更復雜的辦法來做一下,當然控制容器的代碼都是類似的,只不過不做成單獨的場景的話像上面提到的TextureRect的各種屬性我們得用代碼調整。
給HpGauge新建一個腳本。這裡我們肯定要引用一下HBoxContainer。寫代碼前先想象一下我們會怎麼使用它。我們的玩家受傷時我們想要通過一個方法來告訴HP計量器HP發生了變化你應該做出改變。
因此定義這樣一個方法,接受一個表示新的hp值的整數型參數。HP有多少我們就顯示幾個東西。這裡我用笨辦法做,所以我暴露一個材質屬性作為要顯示的東西的材質。
首先需要考慮更新後的hp和當前的hp的大小關係。如果新的HP大於當前的HP,那麼我們需要顯示更多的指示物,反之我們要減少指示物。
通過容器的引用我們可以獲知其中有多少個子節點,以此確定目前在應用新的HP值之前HP為多少。如果新的HP多於當前的HP,那麼我們就要加入新的TextureRect;否則,我們要移除從右往左最後幾個櫻桃:
看起來稍微有點複雜,我們慢慢來看。首先調用container的get_children方法獲得一個包含了container的子節點的數組,也就是我們現在的櫻桃。len函數返回數組長度,也就是有幾個櫻桃、幾點HP——當然這裡我們可以省略這個步驟,讓玩家直接把受傷害之前的HP傳進來,你可以作為練習換個思路寫這裡的代碼。
delta是更新後的hp和當前hp的差值。如果以後要做加血的道具,那麼我們的hp也可能會增加,所以這裡都要考慮到(實際上初始化的時候也需要)。如果差值大於0(HP增加了),那麼我們就需要創建幾個新的TextureRect。
首先用TextureRect類型的構造函數來構造新節點,然後用代碼設置前面講過的expand_mode和stretch_mode。具體的名稱可以查看文檔。然後把材質屬性給它。最後把它作為子節點加入到容器中。
如果差值小於0(HP減少了),我們就要刪掉幾個HP指示物。這裡代碼只有兩行,但是看起來也有點複雜。數組的slice方法會對數組進行切片——很形象,就是從數組中拿一部分出來。很遺憾的是GDScript沒有實現Python那樣的切片語法糖。所以只能調用這個方法。
sliece方法的第一個參數是從哪裡開始切(包含這個索引本身),-1就是數組的最後一個元素。第二個參數是切到哪裡(不包含這個索引),注意這裡的delta是負值,所以是+ delta,從右往左切!這裡的第三個參數在我們這種從右往左切的情況下必須指定。第三個參數是所謂步長(step),也就是每次“前進”多少,默認值1的意思是每次索引加1,也就是從左往右切!這裡要往左切所以要把-1放到第三個參數上。最後在循環內部,銷燬對應的節點即可。

另一種做法

在程序中頻繁地構造和銷燬對象可能會對性能有一定影響。由於這裡的HP指示物本身沒有什麼內部狀態可言(它只是單純地顯示圖片),所以我們可以重複利用創建過的TextureRect。
我們創建一個數組用於保存可用的(沒有顯示在畫面中的)TextureRect。在需要更新HP的UI時,如果我們有足夠多的可用TextureRect節點,那麼我們直接把它拿出來放到容器中。pop_back會彈出(移除並返回)數組最後一個元素。
如果不夠多,我們首先新建缺少的。然後再把已經創建的全部拿出來。
在需要減少HP指示器時,我們首先把它從容器中拿出來。注意,remove_child不會銷燬節點,它只是讓它離開場景樹。從容器中拿掉之後,我們再把它放到texture_rects備用。
這樣一來就不需要頻繁地銷燬和構造新的節點了。如你所見,這樣做的代碼都要多寫不少,實際上你還可以選擇直接隱藏部分節點。
但是必須要強調的是,過早優化是(開發)效率的大敵,就這點消耗來說,對於現今的設備來說都是小菜一碟。在開發初期或者需要快速完成開發的時候這種“優化”實際上可能很浪費時間,我們必須時刻做出權衡。

配置UI

遊戲運行時的UI會和遊戲場景出現在同一場景中。為了不影響其它場景,我們把UI放在一個CanvasLayer(還記得它嗎)中,然後給它一個紋理資源。

適時更新UI

現在我們需要在玩家hp發生變化時讓UI也隨之變化。我們已經不止一次用到了信號,輕車熟路。我們定義一個hp發生變化的信號,然後我們在主場景中讓hp控件連接它:
受到傷害時發出信號:
在主場景腳本中連接:
由於update_hp的函數簽名剛好和hp_changed對應,所以這裡直接連接即可。但是最後不要忘了,給hp計量器進行初始化!另一方面如果你也寫了復活的代碼,也要在重生時進行這些初始化!
現在,我們的HP計量器就做好了!




© 2022 3樓貓 下載APP 站點地圖 廣告合作:asmrly666@gmail.com