Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

物理介紹

在遊戲開發領域,你常常會需要在遊戲中兩個物體相交或是接觸時得到提醒。這被稱為 碰撞偵測(collision detection)。當偵測到碰撞,你通常會想要讓某些事情發生。這被稱作**碰撞反應(collision response)**。

Godot在2D和3D中提供了許多碰撞物件, 以提供碰撞偵測和回應. 你可能很難決定哪個適合你的專案. 一旦瞭解了每種方法的工作原理以及它們的優缺點, 你就可以避免出現問題並簡化開發過程.

在本指南中,我們將學到:

  • Godot的四種碰撞對象型別

  • 每個碰撞物件的工作原理

  • 何時以及為何選擇這種型別而不是另一種型別

備註

本文件的範例將使用2D物件. 每個2D物理物件和碰撞形狀在3D中具有直接等價物, 並且在大多數情況下它們以相同的方式工作.

碰撞物體

Godot 提供了四個碰撞對象,它們都擴充了:ref:CollisionObject2D <class_CollisionObject2D>。以下列出的最後三個是物理體,也擴充了 PhysicsBody2D

  • Area2D

    Area2D 節點提供 偵測影響 . 它們可以偵測物體何時重疊, 並在物體進入或離開時發出訊號. Area2D 也可用於覆蓋物理屬性, 例如一定區域內的重力或阻尼.

  • StaticBody2D

    靜態主體是物理引擎不移動的主體. 它參與碰撞偵測, 但不會回應碰撞而移動. 它們通常用於屬於環境的物件或不需要任何動態行為的物件.

  • RigidBody2D

    這是實作類比2D物理的節點. 您不直接控制 RigidBody2D , 而是您對它施加力(重力, 衝動等), 物理引擎計算得到的運動. 閱讀更多關於使用剛體的資訊.

  • Vector2

    提供碰撞偵測的物體, 但沒有物理功能. 所有移動和碰撞回應必須在程式碼中實作.

物理屬性

靜態體和剛性體可以被配置為使用:ref:物理材質 <class_PhysicsMaterial>。這允許調整一個物體的摩擦力和反彈力,並設定它是否具有吸收性、粗糙性。

碰撞形體

物理體可以包含任意數量的 Shape2D 對象作為子物件. 這些形狀用於定義物件的碰撞邊界並偵測與其他物件的接觸.

備註

為了偵測碰撞, 必須至少為物件分配一個 Shape2D .

分配形狀的最常用方法是新增 CollisionShape2DCollisionPolygon2D 作為對象的子項. 這些節點允許您直接在編輯器工作區中繪製形狀.

重要

注意,不要在編輯器中縮放碰撞形狀。“屬性面板”中的“Scale”屬性應保持為 (1, 1)。改變碰撞形狀的大小時,你應該使用尺寸控制柄,而**不是** Node2D 縮放控制柄。縮放形狀可能會導致意外的碰撞行為。

../../_images/player_coll_shape.png

物理過程回呼函式

物理引擎以固定速率運作(預設為每秒 60 次迭代)。此速率通常不同於影格速率,影格速率根據渲染內容和可用資源而波動。

重要的是所有與物理相關的程式碼都以這個固定速率運作。因此,Godot 區分了物理處理和空閒處理 <doc_idle_and_physical_processing>`。運作每一影格的程式碼稱為空閒處理,在每個物理滴答上運作的程式碼稱為物理處理。 Godot 提供了兩種不同的回調,每種回調對應一種處理速率。

The physics callback, Node._physics_process(), is called before each physics step. Any code that needs to access a body's properties should be run in here. This method will be passed a delta parameter, which is a floating-point number equal to the time passed in seconds since the last step. When using the default 60 Hz physics update rate, it will typically be equal to 0.01666... (but not always, see below).

備註

建議在物理計算中使用 delta 參數, 以便當您更改物理更新速率或玩家裝置跟不上時, 遊戲能夠正確運作.

碰撞層與遮罩

碰撞層系統是最強大但經常被誤解的碰撞功能之一。該系統允許您在各種物件之間建立複雜的互動。關鍵概念是**層**(Layer)和**遮罩**(Mask)。每個 CollisionObject2D 都有 20 個不同的實體層可以相互作用。

讓我們依次看看每個屬性:

  • collision_layer

    表示該物件**位於**哪些層。預設情況下,所有實體都在圖層 1 上。

  • collision_mask

    表示該物件會對哪些層上的實體進行**掃描**。如果物件不在任何遮罩層中,則該實體將其忽略。預設情況下,所有實體都會掃描圖層 1

可以通過程式碼配置這些屬性,也可以在“屬性面板”中對其進行編輯。

追蹤您正在使用每個圖層的內容可能很困難,因此您可能會發現為您正在使用的圖層指定名稱很有用。可以在“專案設定 -> Layer Names”中指定名稱。

../../_images/physics_layer_names.png

計時器範例

遊戲中有四種節點型別:牆(Wall)、玩家(Player)、敵人(Enemy)、金幣(Coin)。玩家和敵人都應該與牆碰撞。玩家節點應該偵測與敵人和硬幣的碰撞,但敵人和硬幣應該互相忽略。

首先將 1 至 4 層分別命名為“walls”(牆)“player”(玩家)“enemies”(敵人)“coins”(金幣)並使用“Layer”屬性將每個節點型別放在其各自的層中。然後通過選擇它應該與之互動的層來設定每個節點的“Mash”屬性。例如,玩家的設定將看起來像這樣:

../../_images/player_collision_layers.png ../../_images/player_collision_mask.png

計時器範例

在函式呼叫中, 為層指定位元遮罩. 當一個函式預設啟用所有圖層時, 圖層遮罩將被指定為 0x7fffffff. 根據你的喜好, 你的程式碼可以使用二進位, 十六進位或十進位來展示層遮罩.

如果要用程式碼來啟用第 1、3、4 層:

# Example: Setting mask value for enabling layers 1, 3 and 4

# Binary - set the bit corresponding to the layers you want to enable (1, 3, and 4) to 1, set all other bits to 0.
# Note: Layer 32 is the first bit, layer 1 is the last. The mask for layers 4,3 and 1 is therefore
0b00000000_00000000_00000000_00001101
# (This can be shortened to 0b1101)

# Hexadecimal equivalent (1101 binary converted to hexadecimal)
0x000d
# (This value can be shortened to 0xd)

# Decimal - Add the results of 2 to the power of (layer to be enabled - 1).
# (2^(1-1)) + (2^(3-1)) + (2^(4-1)) = 1 + 4 + 8 = 13
pow(2, 1-1) + pow(2, 3-1) + pow(2, 4-1)

Area2D

Area 節點的作用是**偵測**和**影響**。它們可以偵測物體何時重疊,並在物體進入或離開時發出訊號。Area 也可用於覆蓋物理屬性,例如一定區域內的重力或阻尼。

Area2D 的主要用途有三種:

  • 覆蓋給定區域中的物理參數(例如重力)。

  • 偵測其他實體何時進入或退出某個區域或目前哪個實體位於某個區域。

  • 檢查是否與其他區域重疊。

預設情況下,area還會接收滑鼠和觸控式螢幕輸入.

StaticBody2D

靜態主體是物理引擎不移動的主體. 它參與碰撞偵測, 但不會回應碰撞而移動. 然而, 它可以使用它的 constant_linear_velocityconstant_angular_velocity 屬性將運動或旋轉傳遞給碰撞體, 好像 它正在移動一樣.

StaticBody2D 節點最常用於屬於環境的物件或不需要任何動態行為的物件.

StaticBody2D 的範例用法:

  • 平臺(包括可移動的平臺)

  • 輸送帶

  • 牆壁和其他障礙

RigidBody2D

這是實作類比2D物理的節點. 你不能直接控制一個 RigidBody2D. 取而代之的是, 對它施加力, 物理引擎會計算由此產生的運動, 包括與其他物體的碰撞, 以及碰撞回應, 如彈跳, 旋轉等.

你可以通過“Mass”(品質)“Friction”(摩擦)“Bounce”(反彈)等屬性修改剛體的行為,這些都可以在屬性面板中設定。

物體的行為也受到“專案設定 -> Physics”中設定的世界屬性的影響,或者通過輸入覆蓋全域物理屬性的 Area2D

當一個剛體處於靜止狀態, 有一段時間沒有移動, 它就會進入睡眠狀態. 睡眠的物體就像一個靜態的物體, 它的力不會被物理引擎計算. 當力被施加時, 無論是通過碰撞還是通過程式碼, 該物體都會被喚醒.

使用 RigidBody2D

使用剛體的一個好處是,可以“免費”獲得許多行為而無需編寫任何程式碼。例如,如果您正在製作一個帶有下降塊的《憤怒的小鳥》式遊戲,您只需要建立 RigidBody2D 並調整它們的屬性。堆疊、下降、彈跳將由物理引擎自動計算。

However, if you do wish to have some control over the body, you should take care - altering the position, linear_velocity, or other physics properties of a rigid body can result in unexpected behavior. If you need to alter any of the physics-related properties, you should use the _integrate_forces() callback instead of _physics_process(). In this callback, you have access to the body's PhysicsDirectBodyState2D, which allows for safely changing properties and synchronizing them with the physics engine.

例如,以下是《爆破彗星》式太空船的程式碼:

extends RigidBody2D

var thrust = Vector2(0, -250)
var torque = 20000

func _integrate_forces(state):
    if Input.is_action_pressed("ui_up"):
        state.apply_force(thrust.rotated(rotation))
    else:
        state.apply_force(Vector2())
    var rotation_direction = 0
    if Input.is_action_pressed("ui_right"):
        rotation_direction += 1
    if Input.is_action_pressed("ui_left"):
        rotation_direction -= 1
    state.apply_torque(rotation_direction * torque)

請注意, 我們不是直接設定 linear_velocityangular_velocity 屬性, 而是將力( thrusttorque )施加到物體上並讓物理引擎計算出最終的運動.

備註

當一個剛體進入睡眠狀態時, _integrate_forces() 函式將不會被呼叫. 要重寫這一行為, 您需要通過建立碰撞, 對其施加力或禁用 can_sleep 屬性來保持物體的啟動. 請注意, 這可能會對性能產生負面影響.

接觸報告

預設情況下, 剛體不會追蹤接觸點, 因為如果場景中存在許多體, 這可能需要大量的記憶體. 要啟用接觸報告, 請將 contacts_reported 屬性設定為非零值. 然後可以通過 Physics2DDirectBodyState.get_contact_count() 和相關函式獲得聯繫.

通過訊號的接觸監控, 啟用 contact_monitor 屬性. 請參閱 RigidBody2D 的可用訊號列表.

CharacterBody2D

KinematicBody2D 物體偵測與其他物體的碰撞, 但不受重力或摩擦等物理屬性的影響. 相反, 它們必須由使用者通過程式碼控制. 物理引擎不會移動運動體.

移動運動體時, 不應直接設定其 position . 相反, 您使用 move_and_collide()move_and_slide() 方法. 這些方法沿著給定的向量移動物體, 如果與另一個物體偵測到碰撞, 它將立即停止. 在物體發生碰撞後, 必須手動編碼任何碰撞回應.

顯示碰撞區域

碰撞後, 您可能希望物體反彈, 沿著牆壁滑動, 或者改變它所擊中的物體的屬性. 處理碰撞回應的方式取決於您用於移動KinematicBody2D的方法.

move_and_collide

當使用 move_and_collide() 時, 該函式返回一個 KinematicCollision2D 物件, 其中包含有關碰撞和碰撞體的資訊. 您可以使用此資訊來確定回應.

例如, 如果要搜尋發生碰撞的空間點:

extends PhysicsBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        var collision_point = collision_info.get_position()

或者從碰撞物體反彈:

extends PhysicsBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        velocity = velocity.bounce(collision_info.get_normal())

move_and_slide

滑動是一種常見的碰撞回應; 想像一個遊戲角色在上帝視角的遊戲中沿著牆壁移動, 或者在平臺遊戲中上下坡. 雖然可在使用 move_and_collide() 之後自己編寫這個回應, 但 move_and_slide() 提供了一種快捷方法來實作滑動且無需編寫太多程式碼.

警告

move_and_slide() 在計算中自動包含時間步長, 因此您 應將速度向量乘以 delta .

例如, 使用以下程式碼製作一個可以沿著地面(包括斜坡)行走的角色, 並在站在地面時跳躍:

extends CharacterBody2D

var run_speed = 350
var jump_speed = -1000
var gravity = 2500

func get_input():
    velocity.x = 0
    var right = Input.is_action_pressed('ui_right')
    var left = Input.is_action_pressed('ui_left')
    var jump = Input.is_action_just_pressed('ui_select')

    if is_on_floor() and jump:
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    velocity.y += gravity * delta
    get_input()
    move_and_slide()

有關使用 move_and_slide() 的更多詳細資訊, 請參閱 運動學角色(2D) , 包括帶有詳細程式碼的演示專案.