Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

運動學角色(2D)

前言

是的,這個名字聽起來很奇怪。「運動學角色」到底是什麼?會有這個名稱,是因為早期物理引擎問世時,被稱為「動力學(Dynamics)」引擎(因為它們主要處理碰撞回應)。當時有許多嘗試想用動力學引擎做角色控制器,但實際上沒那麼簡單。Godot 擁有目前最好的動力學角色控制器實作之一(可以在 2D 平台範例中看到),但要使用它需要對物理引擎有相當深的理解與技巧(或者你要有很多耐心來摸索)。

有些物理引擎(像 Havok)認為動力學角色控制器是最佳選擇,但也有其他(例如 PhysX)則更偏好推廣運動學角色控制器。

那麼,兩者到底有什麼區別?:

  • 動力學角色控制器 是用一個具有無限慣性張量的剛體(RigidBody),這種剛體是不能旋轉的。物理引擎會讓物體移動與碰撞,再一起解決碰撞反應。這讓動力學角色控制器可以像平台遊戲範例那樣,和其他物理物件無縫互動。不過,這些互動並非總是可預測的。有時碰撞會需要超過一個影格才能解決,因此碰撞時可能會有輕微的偏移。這些問題雖然能解決,但需要一定程度的技巧。

  • 運動學角色控制器 則假設一開始總是在非碰撞狀態,並且移動時也會保持非碰撞狀態。如果它一開始就處於碰撞狀態,會像剛體一樣嘗試自我脫離,但這是例外狀況。這讓它的控制和移動更加可預測,也更容易寫程式。不過缺點是:不能直接和其他物理物件互動,除非你在程式裡手動處理。

這個簡短教學主要聚焦在運動學角色控制器。它採用傳統的碰撞處理方式(底層未必比較簡單,只是都被很好地包裝在 API 裡了)。

物理處理流程

To manage the logic of a kinematic body or character, it is always advised to use physics process, because it's called before physics step and its execution is in sync with physics server, also it is called the same amount of times per second, always. This makes physics and motion calculation work in a more predictable way than using regular process, which might have spikes or lose precision if the frame rate is too high or too low.

extends CharacterBody2D

func _physics_process(delta):
    pass

場景設定

要方便測試,這裡有一個場景(來自 TileMap 教學):kinematic_character_2d_starter.zip。我們會為角色建立一個新場景。請使用機器人角色圖,並建立如下圖的場景:

../../_images/kbscene.webp

你會注意到 CollisionShape2D 節點旁邊有個警告圖示,因為還沒設定形狀。請在 CollisionShape2D 的 shape 屬性裡建立一個新的 CircleShape2D,然後點擊 <CircleShape2D> 進入選項,把半徑(radius)設為 30:

../../_images/kbradius.webp

注意:如同之前物理教學提到,物理引擎無法正確處理大多數形狀的縮放(只有碰撞多邊形、平面和線段除外),所以請直接修改形狀的參數(例如半徑),而不是去縮放它。對於 Kinematic/Rigid/Static 這些物理節點本身也是一樣,因為它們的 scale 會影響碰撞形狀。

現在,請為這個角色建立腳本,可以用上面範例作為基礎。

最後,將角色場景實例化到 TileMap 場景中,並把地圖場景設為主場景,這樣按下執行時就會直接運作。

../../_images/kbinstance.webp

移動運動學角色

回到角色場景並打開腳本,魔法即將開始!Kinematic 物件預設什麼都不會做,但它有一個很實用的方法 CharacterBody2D.move_and_collide()。這個方法會接收一個 Vector2 參數,並嘗試讓角色移動這個向量的距離。如果過程中發生碰撞,角色就會停在碰撞點。

所以,讓我們把角色往下移動,直到碰到地板為止:

extends CharacterBody2D

func _physics_process(delta):
    move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame

結果會發現角色會移動,但在碰到地板時就會停下來。很酷吧?

下一步是加上重力,這樣角色行為會更像一般遊戲角色:

extends CharacterBody2D

const GRAVITY = 200.0

func _physics_process(delta):
    velocity.y += delta * GRAVITY

    var motion = velocity * delta
    move_and_collide(motion)

現在角色會順暢地落下了。接下來讓它在按下左右方向鍵時往左右移動。記得這裡的速度單位是像素/秒。

這樣就支援按下左右鍵時角色能左右走動了:

extends CharacterBody2D

const GRAVITY = 200.0
const WALK_SPEED = 200

func _physics_process(delta):
    velocity.y += delta * GRAVITY

    if Input.is_action_pressed("ui_left"):
        velocity.x = -WALK_SPEED
    elif Input.is_action_pressed("ui_right"):
        velocity.x =  WALK_SPEED
    else:
        velocity.x = 0

    # "move_and_slide" already takes delta time into account.
    move_and_slide()

可以試試看效果。

這就是平台遊戲設計的好起點。更完整的範例可以在 Godot 官方提供的 demo zip 或 https://github.com/godotengine/godot-demo-projects/tree/master/2d/kinematic_character 找到。