最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

從零開始獨立游戲開發(fā)學(xué)習(xí)筆記(七十一)--Godot 學(xué)習(xí)筆記(四)--指南(二)-輸入

2023-11-24 21:51 作者:oyishyi  | 我要投稿

因為我是邊做游戲邊學(xué)的,所以其實感覺到最先難為我的其實是輸入,怎樣寫出最佳的輸入代碼。所以我先看輸入部分了。

1. InputEvent

通常來說,無論是 OS 還是平臺,操作輸入都是很復(fù)雜的事情。為了讓這件事好做一些,一個 InputEvent 的內(nèi)置類型因此而生。輸入事件會穿過引擎,在游戲的各個部分都能夠被接收,取決于需求。

這里先從一個小例子開始,這個例子處理輸入 esc 退出游戲的情況:

js

func _unhandled_input(): ? ?if event is InputEventKey: ? ? ? ?if event.pressed and event.keycode == KEY.ESCAPE: ? ? ? ? ? ?get_tree().quit()

然而,這有點復(fù)雜,判斷太多了。

使用內(nèi)置的 InputMap 更好,這個就是在之前教程里講過的,在項目設(shè)置里配置的,然后用 is_action_pressed 調(diào)用的,標(biāo)準(zhǔn)處理方式。

InputMap 可以讓處理多個 key,并且也方便維護(hù)(比如更換觸發(fā)鍵,只需要調(diào)整項目設(shè)置里的就行了)。此外,還能便利地在游戲中更換鍵位,which 幾乎每個現(xiàn)代游戲都必須要有。

1.1 輸入機制是如何工作的?

首先,用戶給一個輸入。然后 OS 獲取到輸入,傳遞給窗口,傳到 Viewport(root),后面的比較復(fù)雜用一張圖來說明吧:

其實這個圖為了避免太長做了一些修正,下面幾個事件是有順序的而不是并行的。順序如下:

  1. 如果 Viewport 是一個窗口,這個輸入首先會被用于判斷是否是對窗口進(jìn)行的操作,比如移動窗口,改變窗口尺寸等。

  2. 好,上一步結(jié)束后,如果有一個窗口正處于 focused 狀態(tài)。那么事件會被送往那個窗口,然后在該窗口中處理。如果沒有窗口處于 focused 狀態(tài),那就送往當(dāng)前 viewport 里的節(jié)點們,繼續(xù)下一步。

  3. 首先,輸入會被送往?Node._input()?函數(shù)(如果重寫了, 且沒有被 Node.set_process_input() 禁止的話)??梢栽诖送ㄟ^ Viewport.set_input_as_handled() 來阻止事件進(jìn)一步傳播。這一步能夠幫助你在進(jìn)行 GUI 操作前處理輸入。

  4. 接下來,這個輸入會被送往 GUI 進(jìn)行處理,叫做?Control._gui_input(),此外,gui 還會觸發(fā)一個信號 gui_input。gui 里面通過 Control.accept_event() 來阻止事件進(jìn)一步傳播。gui 里還可以用 Control.mouse_filter 來控制 gui 是否接受鼠標(biāo)信號以及是否進(jìn)一步傳播。

  5. 好的,如果到現(xiàn)在還沒有被阻止。那么繼續(xù)前往?Node._shortcut_input(),其他表現(xiàn)和?_input?基本一樣。不過這個只會處理 InputEventKey,InputEventShortcut,InputEventJoypadButton 這三種事件。(像是鼠標(biāo)事件這種就不行了)。正如名字所示,這個事件一般用于快捷鍵。

  6. 如果仍然沒有處理完,那么就進(jìn)入?_unhandled_input(),一般 gameplay 的輸入會放在這里。

  7. 還沒有處理完?接下來還有?Node._unhandled_key_input(),如名字所示,只處理鍵盤輸入。

  8. 最終,事件到達(dá)最后。會觸發(fā) Object Picking。該功能可以在項目設(shè)置中配置。會在鼠標(biāo)指著的對象上(如果有碰撞體)執(zhí)行?_input_event()。這個如名字所示,用于鼠標(biāo)事件。所以如果有這樣的需求,就要給這些對象上都加上碰撞體。

那么,節(jié)點之間有沒有順序呢?有。輸入事件在也會在節(jié)點上按照順序傳播。如下圖所示:

也就是說從最下面開始,一直到上面,或者說反向深度優(yōu)先(其實很合理,因為從 canvas 的視角來看,最下面的節(jié)點會覆蓋上層的節(jié)點)。windows,viewport 不管這些 order(如前所示,它們最先跑)。此外,_gui_input?也不滿足這些 order。gui 遵循一套自己的方式。

gui 的控制一般要求會很精確,所以只有事件目標(biāo)的直接祖先會接受該事件??聪聢D(名字不是順序,只是我自己測試用的):

如果在 5 上觸發(fā)事件,只會接著觸發(fā) 4,不會再傳播到 1,2,3 上,因為這些都不是 4 的祖先。也就是說 gui 的更像是前端的冒泡。

需要注意的是,因為 Viewport 不會發(fā)送輸入到別的 Subviewport,所以要用下面兩種方法之一:

  1. 使用 SubViewportContainer。

  2. 根據(jù)需求,自己實現(xiàn)事件傳播。

1.2 輸入事件的剖析

輸入事件代表的是一個事件,不是游戲場景中具體存在的某個東西。

1.3 action

action 可以把一個或者多個輸入事件分組為一個容易理解的標(biāo)題。這有利于你把輸入抽象化。

這樣可以:

  • 匹配不同的設(shè)備:鍵盤,手柄

  • 鍵位更換

  • 手動觸發(fā)操作

如果要手動觸發(fā)操作,可以這么弄:

js

var ev = InputEventAction.new()ev.action = "jump"ev.pressed = trueInput.parse_input_event(ev)

1.4 輸入映射

InputMap,就是你在項目設(shè)置里設(shè)置的那個。

2. 處理輸入的案例

最佳實踐來了,太棒了。

2.1 事件與輪詢

有時候你希望游戲響應(yīng)某個特定的時間,比如跳躍。有些時候你希望任何時候只要按下按鍵就會發(fā)生什么,比如移動。第一種情況可以用?_input?函數(shù),第二種情況則可以使用 godot 專門提供的 Input 單例(Input.is_action_pressed() 這種)。我們會重點討論第一種情況。

2.2 Input Event

注意是 Input Event 不是 InputEvent。Input Event 是 InputEvent 類型的對象(我覺得 Godot 起名真的需要改一下了,真的很混亂各種雙關(guān))。這個對象包含事件相關(guān)的信息。比如:

js

extends Nodefunc _input(event): ? ?print(event.as_text())

我們用 InputEventMouseButton 為例,它繼承了這么多類:

  • InputEvent:基類,不用說了。

  • InputEventWithModifiers: 添加了檢查修飾符的功能,如 shift。

  • InputEventMouse: 添加了鼠標(biāo)事件的屬性與功能。

  • InputEventMouseButton:包含鼠標(biāo)點擊的功能與屬性。

在使用 Input 的時候,最好文檔就放在旁邊。檢查一下可用屬性和方法,總會有驚喜。

如果你嘗試使用不存在的屬性,比如鼠標(biāo)事件檢查鍵盤屬性。所以最好先測試一下事件的類型:

js

func _input(event): ? ?if event is InputEventMouseButton: ? ? ? ?print("mouse button event at ", event.postion)

2.3 輸入映射

Godot 提供了很多內(nèi)置的 action,可以在項目設(shè)置->InputMap 里右上角打開顯示內(nèi)置類型。

2.4 捕捉 Action

定義操作后,可以用 is_action_pressed() 等來處理。

js

func _input(event): ? ?if event.is_action_pressed("my_action"): ? ? ? ?print("my_action occurred!")

2.5 鍵盤事件

雖然建議使用 action,但偶爾還是會有檢查鍵盤的時候,比如:

js

func _input(event): ? ?if event is InputEventKey and event.pressed: ? ? ? ?if event.keycode == KEY_T: ? ? ? ? ? ?print("T was pressed")

2.6 鍵盤重影

鍵盤上一次性輸入太多鍵會導(dǎo)致一些鍵位不會被電腦接收到。這個無法解決畢竟是硬件問題。除了高級玩家自己會去買防重影的游戲?qū)S面I盤以外沒啥辦法。當(dāng)然,因為 wasd,space,enter,方向鍵這些用的太多了,大部分鍵盤會刻意在這些鍵上做防重影。所以把你的鍵位設(shè)置在這幾個上面是有很大意義的。

2.7 修飾符

像是 InputEventKey 這種事件也都繼承了 InputEventWithModifiers,所以可以用其屬性檢查修飾符是否按下。

2.8 鼠標(biāo)事件

鼠標(biāo)事件分為 InputEventMouseButton 和 InputEventMouseMotion。名字就能看出來區(qū)別。

2.8.1 鼠標(biāo)按鈕

鼠標(biāo)按鈕和按鍵處理很像,可以處理鼠標(biāo)左右鍵中鍵以及額外鼠標(biāo)按鍵(一些高級鼠標(biāo))。需要注意滾動也算按鈕事件。

2.8.2 鼠標(biāo)運動

鼠標(biāo)移動就會觸發(fā)運動事件。

js

extends Nodevar dragging = falsevar click_radius = 32 # Size of the sprite.func _input(event): if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: if (event.position - $Sprite2D.position).length() < click_radius: # Start dragging if the click is on the sprite. if not dragging and event.pressed: dragging = true # Stop dragging if the button is released. if dragging and not event.pressed: dragging = false if event is InputEventMouseMotion and dragging: # While dragging, move the sprite with the mouse. $Sprite2D.position = event.position

3. 鼠標(biāo)和輸入坐標(biāo)

這一節(jié)是為了糾正關(guān)于輸入坐標(biāo),獲取鼠標(biāo)位置,屏幕分辨率等常見錯誤。

3.1 硬件顯示坐標(biāo)

如果 UI 非常復(fù)雜(編輯器,工具等),才會用到。除此之外沒有意義。

3.2 視口 Viewport 顯示坐標(biāo)

Godot 使用 Viewport 中顯示內(nèi)容,并且可以縮放視口。
鼠標(biāo)事件中的 position 指的就是視口的坐標(biāo)?;蛘呤褂?Viewport.get_mouse_position() 直接獲取鼠標(biāo)位置。

4. 自定義鼠標(biāo)指針

當(dāng)然,切換鼠標(biāo)指針是一個很常見的事。

切換鼠標(biāo)指針兩種方式,項目設(shè)置或者腳本,前者更方便但限制較多。

理論上來說,也可以用一個圖片跟隨鼠標(biāo)位置切隱藏鼠標(biāo)光標(biāo)來做到,但這樣會有延遲。還是用標(biāo)準(zhǔn)方法比較好。

項目設(shè)置就不說了,很簡單。

4.1 腳本設(shè)置

腳本除了可以設(shè)置一個總體鼠標(biāo)指針以外,還能設(shè)置指針的不同樣子(比如鏈接上變成手型,等等)。如下:

js

extends Node# Load the custom images for the mouse cursor.var arrow = load("res://arrow.png")var beam = load("res://beam.png")func _ready(): # Changes only the arrow shape of the cursor. # This is similar to changing it in the project settings. Input.set_custom_mouse_cursor(arrow) # Changes a specific shape of the cursor (here, the I-beam shape). Input.set_custom_mouse_cursor(beam, Input.CURSOR_IBEAM)

5. 手柄,等各種游戲外設(shè)

因為有著 SDL ,所以 Godot 支持上百種不同的游戲外設(shè)。

不過其中方向盤,踏板等特別專業(yè)的設(shè)備測試比較少,可能會有 bug。

使用 action 來簡化這一切。

5.1 振動

Input.start_joy_vibration 等函數(shù)可以控制振動效果。

5.2 deadzone

像是手柄這種,因為其物理特性,其實靜止?fàn)顟B(tài)的位置并不是 0,0 ,而是有一點點偏離,如果再嚴(yán)重一點這個就叫做手柄飄移。項目設(shè)置內(nèi)默認(rèn)死區(qū)是 0.5,低于這個值不會被讀取。

5.3 回聲

按住控制器上的某個按鈕并不會持續(xù)發(fā)送輸入。鍵盤才會。要想達(dá)成這一效果,要通過代碼生成輸入事件 + pares_input_event() 來達(dá)成。

5.4 窗口焦點

控制器不像鍵盤,控制器沒有窗口焦點,而是會發(fā)送到所有窗口。要想解決這個問題,godot 上文檔有一串代碼可以來控制焦點。。。(為啥不直接做進(jìn)引擎?不懂。)

5.5 預(yù)防進(jìn)入睡眠模式,屏保等

鍵盤持續(xù)輸入會讓電腦不進(jìn)入休眠狀態(tài),但是手柄不會,玩著玩著進(jìn)入睡眠模式或者屏保也太傷了。所以 Godot 默認(rèn)啟動了節(jié)能預(yù)防。項目設(shè)置中能看到。

5.6 其他問題

手柄問題確實很多,godot 上關(guān)于這個的 issue 有幾十條。慢慢踩坑吧。。。

6. 處理退出

在 pc 端點叉叉會關(guān)閉程序。默認(rèn)操作是退出游戲。
如果你想做點啥事(比如彈出提示框,或者保存啥的),可以按照如下代碼:

js

func _notification(what): if what == NOTIFICATION_WM_CLOSE_REQUEST: print(1233)

不過這并不會阻止其退出游戲,要想阻止退出游戲,使用?get_tree().set_auto_accept_quit(false)。

6.1 發(fā)送自己的退出通知

雖然可以用?get_tree().quit()?來退出,但這樣不會執(zhí)行上面 notifiction 里的操作。我們希望發(fā)送?NOTIFICATION_WM_CLOSE_REQUEST。所以我們應(yīng)該這樣子寫:

js

get_tree().root.propogate_notification(NOTIFICATION_WM_CLOSE_REQUEST)

但是注意,4.0 版本開始,這個僅僅只會發(fā)送這個信息,但不會關(guān)閉程序,你還要再手動調(diào)用 get_tree().quit() 來關(guān)閉應(yīng)用


從零開始獨立游戲開發(fā)學(xué)習(xí)筆記(七十一)--Godot 學(xué)習(xí)筆記(四)--指南(二)-輸入的評論 (共 條)

分享到微博請遵守國家法律
哈巴河县| 阿拉善盟| 闽清县| 南开区| 顺义区| 通州区| 中山市| 栾城县| 廉江市| 肇庆市| 平南县| 剑川县| 平和县| 遂溪县| 库尔勒市| 咸宁市| 永兴县| 阿城市| 霍林郭勒市| 阜南县| 盱眙县| 陆良县| 辽阳县| 凌源市| 茌平县| 鲁甸县| 措美县| 时尚| 南投市| 洛川县| 安乡县| 颍上县| 正蓝旗| 县级市| 沧州市| 波密县| 康乐县| 黑龙江省| 河东区| 儋州市| 汉川市|