運動學角色(2D)
前言
是的,這個名字聽起來很奇怪。「運動學角色」到底是什麼?會有這個名稱,是因為早期物理引擎問世時,被稱為「動力學(Dynamics)」引擎(因為它們主要處理碰撞回應)。當時有許多嘗試想用動力學引擎做角色控制器,但實際上沒那麼簡單。Godot 擁有目前最好的動力學角色控制器實作之一(可以在 2D 平台範例中看到),但要使用它需要對物理引擎有相當深的理解與技巧(或者你要有很多耐心來摸索)。
有些物理引擎(像 Havok)認為動力學角色控制器是最佳選擇,但也有其他(例如 PhysX)則更偏好推廣運動學角色控制器。
那麼,兩者到底有什麼區別?:
動力學角色控制器 是用一個具有無限慣性張量的剛體(RigidBody),這種剛體是不能旋轉的。物理引擎會讓物體移動與碰撞,再一起解決碰撞反應。這讓動力學角色控制器可以像平台遊戲範例那樣,和其他物理物件無縫互動。不過,這些互動並非總是可預測的。有時碰撞會需要超過一個影格才能解決,因此碰撞時可能會有輕微的偏移。這些問題雖然能解決,但需要一定程度的技巧。
運動學角色控制器 則假設一開始總是在非碰撞狀態,並且移動時也會保持非碰撞狀態。如果它一開始就處於碰撞狀態,會像剛體一樣嘗試自我脫離,但這是例外狀況。這讓它的控制和移動更加可預測,也更容易寫程式。不過缺點是:不能直接和其他物理物件互動,除非你在程式裡手動處理。
這個簡短教學主要聚焦在運動學角色控制器。它採用傳統的碰撞處理方式(底層未必比較簡單,只是都被很好地包裝在 API 裡了)。
物理處理流程
要管理 Kinematic 角色或物體的邏輯,建議都放在物理處理流程(_physics_process)裡,因為它會在物理步驟前呼叫,並且和物理伺服器同步,而且每秒呼叫次數固定。這樣做,物理和移動的計算會比用一般的 process 更可預測,不會因為影格率太高或太低而產生誤差或抖動。
extends CharacterBody2D
func _physics_process(delta):
pass
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
public override void _PhysicsProcess(double delta)
{
}
}
場景設定
要方便測試,這裡有一個場景(來自 TileMap 教學):kinematic_character_2d_starter.zip。我們會為角色建立一個新場景。請使用機器人角色圖,並建立如下圖的場景:
你會注意到 CollisionShape2D 節點旁邊有個警告圖示,因為還沒設定形狀。請在 CollisionShape2D 的 shape 屬性裡建立一個新的 CircleShape2D,然後點擊 <CircleShape2D> 進入選項,把半徑(radius)設為 30:
注意:如同之前物理教學提到,物理引擎無法正確處理大多數形狀的縮放(只有碰撞多邊形、平面和線段除外),所以請直接修改形狀的參數(例如半徑),而不是去縮放它。對於 Kinematic/Rigid/Static 這些物理節點本身也是一樣,因為它們的 scale 會影響碰撞形狀。
現在,請為這個角色建立腳本,可以用上面範例作為基礎。
最後,將角色場景實例化到 TileMap 場景中,並把地圖場景設為主場景,這樣按下執行時就會直接運作。
移動運動學角色
回到角色場景並打開腳本,魔法即將開始!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
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
public override void _PhysicsProcess(double delta)
{
// Move down 1 pixel per physics frame
MoveAndCollide(new Vector2(0, 1));
}
}
結果會發現角色會移動,但在碰到地板時就會停下來。很酷吧?
下一步是加上重力,這樣角色行為會更像一般遊戲角色:
extends CharacterBody2D
const GRAVITY = 200.0
func _physics_process(delta):
velocity.y += delta * GRAVITY
var motion = velocity * delta
move_and_collide(motion)
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
private const float Gravity = 200.0f;
public override void _PhysicsProcess(double delta)
{
var velocity = Velocity;
velocity.Y += (float)delta * Gravity;
Velocity = velocity;
var motion = velocity * (float)delta;
MoveAndCollide(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()
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
private const float Gravity = 200.0f;
private const int WalkSpeed = 200;
public override void _PhysicsProcess(double delta)
{
var velocity = Velocity;
velocity.Y += (float)delta * Gravity;
if (Input.IsActionPressed("ui_left"))
{
velocity.X = -WalkSpeed;
}
else if (Input.IsActionPressed("ui_right"))
{
velocity.X = WalkSpeed;
}
else
{
velocity.X = 0;
}
Velocity = velocity;
// "MoveAndSlide" already takes delta time into account.
MoveAndSlide();
}
}
可以試試看效果。
這就是平台遊戲設計的好起點。更完整的範例可以在 Godot 官方提供的 demo zip 或 https://github.com/godotengine/godot-demo-projects/tree/master/2d/kinematic_character 找到。