1. บทนำ
Godot 4 ได้นำการเปลี่ยนแปลงครั้งใหญ่มาสู่การเคลื่อนที่ของตัวละคร โหนดเดิม KinematicBody2D และ KinematicBody3D ถูกเปลี่ยนชื่อเป็น CharacterBody2D และ CharacterBody3D และ API ของ move_and_slide() ก็ได้รับการออกแบบใหม่ทั้งหมด ไม่ว่าคุณจะกำลังย้ายมาจาก Godot 3 หรือเริ่มต้นใหม่ คู่มือนี้ครอบคลุมทุกอย่างที่คุณจำเป็นต้องรู้
CharacterBody2D และ CharacterBody3D เป็นฟิสิกส์บอดีที่ออกแบบมาสำหรับตัวละครที่ควบคุมด้วยโค้ด ต่างจาก RigidBody ตรงที่มันไม่ตอบสนองต่อแรงโดยอัตโนมัติ — คุณควบคุมการเคลื่อนที่ของมันทั้งหมดผ่านสคริปต์ ทำให้มันเหมาะอย่างยิ่งสำหรับตัวละครผู้เล่น NPC และทุกสิ่งที่ต้องการการเคลื่อนที่ที่แม่นยำและคาดเดาได้
2. สิ่งที่เปลี่ยนไปจาก Godot 3
ต่อไปนี้คือการเปลี่ยนแปลงที่สำคัญที่สุดเกี่ยวกับการเคลื่อนที่ของตัวละครระหว่าง Godot 3 และ Godot 4:
-
KinematicBody2Dตอนนี้กลายเป็นCharacterBody2D -
KinematicBody3Dตอนนี้กลายเป็นCharacterBody3D -
velocityตอนนี้เป็นพร็อพเพอร์ตีในตัว — คุณไม่ต้องส่งมันเข้าmove_and_slide()อีกต่อไป -
move_and_slide()ไม่รับอาร์กิวเมนต์ — มันอ่านจากพร็อพเพอร์ตีvelocityโดยตรง -
move_and_slide_with_snap()ถูกลบออกแล้ว — ให้ใช้พร็อพเพอร์ตีfloor_snap_lengthแทน -
is_on_floor(),is_on_wall(),is_on_ceiling()ยังคงทำงานเหมือนเดิม -
get_gravity()ถูกเพิ่มเข้ามาใน Godot 4.4 เพื่ออ่านแรงโน้มถ่วงเริ่มต้นของโปรเจกต์ในรูปแบบ Vector — สะดวกมาก
ใน Godot 3 move_and_slide() จะคืนค่าความเร็วที่ได้ ส่วนใน Godot 4 มันคืนค่าเป็น bool (ว่ามีการชนเกิดขึ้นหรือไม่) และความเร็วจะถูกอัปเดตในที่เดิมบนพร็อพเพอร์ตี velocity
3. คอนโทรลเลอร์แพลตฟอร์มเมอร์ 2D พื้นฐาน
นี่คือคอนโทรลเลอร์แพลตฟอร์มเมอร์แบบเลื่อนด้านข้างมาตรฐาน ตัวละครสามารถเคลื่อนที่ซ้าย/ขวา และกระโดดเมื่ออยู่บนพื้น นี่คือเทมเพลตที่ Godot สร้างให้เมื่อคุณสร้างสคริปต์ CharacterBody2D ใหม่
extends CharacterBody2D
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
func _physics_process(delta: float) -> void:
# Apply gravity when not on floor
if not is_on_floor():
velocity += get_gravity() * delta
# Jump when on floor and jump pressed
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Horizontal movement
var direction := Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
คำอธิบายทีละบรรทัด
-
extends CharacterBody2D— สคริปต์สืบทอดจาก CharacterBody2D ทำให้เราเข้าถึงvelocity,move_and_slide(),is_on_floor()ฯลฯ ได้ -
SPEED = 300.0— ความเร็วในการเคลื่อนที่แนวนอนหน่วยเป็นพิกเซลต่อวินาที -
JUMP_VELOCITY = -400.0— เป็นค่าลบเพราะแกน Y ชี้ลงใน 2D ค่าลบทำให้ตัวละครเคลื่อนที่ขึ้น -
get_gravity() * delta— ใช้แรงโน้มถ่วงของโปรเจกต์ในทุกเฟรมget_gravity()คืนค่าVector2ที่ชี้ลง (เช่นVector2(0, 980)) การคูณด้วยdeltaทำให้ไม่ขึ้นกับเฟรมเรต -
Input.get_axis("ui_left", "ui_right")— คืนค่า float ตั้งแต่ -1.0 ถึง 1.0 ตามปุ่มทิศทางที่ถูกกด รองรับอินพุตแบบอนาล็อก -
move_toward(velocity.x, 0, SPEED)— ลดความเร็วลงจนถึงศูนย์อย่างนุ่มนวลเมื่อไม่มีการกดอินพุต อาร์กิวเมนต์ตัวที่สามคือค่าการเปลี่ยนแปลงสูงสุดต่อการเรียกหนึ่งครั้ง -
move_and_slide()— เคลื่อนบอดีโดยใช้velocityปัจจุบัน จัดการการชน เลื่อนไปตามพื้นผิว และอัปเดตvelocityโดยอัตโนมัติ
CharacterBody2D ของคุณต้องมีโหนดลูก CollisionShape2D อย่างน้อยหนึ่งโหนดที่กำหนดรูปทรงไว้ (เช่น RectangleShape2D หรือ CapsuleShape2D) หากไม่มี move_and_slide() จะไม่ตรวจจับการชนใดๆ
4. คอนโทรลเลอร์ท็อปดาวน์ 2D พื้นฐาน
สำหรับเกมท็อปดาวน์ (RPG, ทวินสติกชูตเตอร์ ฯลฯ) คุณเคลื่อนที่ได้ทั้งสี่ทิศทางโดยไม่มีแรงโน้มถ่วง โค้ดจะเรียบง่ายกว่าแพลตฟอร์มเมอร์
extends CharacterBody2D
const SPEED = 200.0
func _physics_process(delta: float) -> void:
var input_direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = input_direction * SPEED
move_and_slide()
วิธีการทำงาน
-
Input.get_vector()คืนค่าVector2ที่ถูกนอร์มัลไลซ์จากอินพุตแอ็กชันสี่ตัว สิ่งนี้จัดการการเคลื่อนที่ทแยงมุมได้อย่างถูกต้อง — ความยาวของเวกเตอร์จะเป็น 1.0 หรือ 0.0 เสมอ ดังนั้นการเคลื่อนที่ทแยงมุมจึงไม่เร็วกว่าการเคลื่อนที่ตามแกน -
ไม่มีการใช้แรงโน้มถ่วงเพราะนี่เป็นมุมมองท็อปดาวน์ ตัวละครจะหยุดทันทีเมื่อไม่มีการกดอินพุต เพราะเรากำหนด
velocityโดยตรง (ไม่มีความเฉื่อย)
การเพิ่มความเร่งและแรงเสียดทาน
เพื่อให้รู้สึกนุ่มนวลขึ้นด้วยการเร่งและการชะลอตัว:
extends CharacterBody2D
const SPEED = 200.0
const ACCELERATION = 1200.0
const FRICTION = 1000.0
func _physics_process(delta: float) -> void:
var input_direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
if input_direction != Vector2.ZERO:
velocity = velocity.move_toward(input_direction * SPEED, ACCELERATION * delta)
else:
velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
move_and_slide()
5. คอนโทรลเลอร์ 3D มุมมองบุคคลที่สาม
เวอร์ชัน 3D ใช้รูปแบบเดียวกัน ความแตกต่างหลักคือการทำงานกับ Vector3 และการใช้ transform.basis เพื่อแปลงอินพุต 2D ให้เป็นการเคลื่อนที่ในพื้นที่โลก 3D
extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
func _physics_process(delta: float) -> void:
# Apply gravity
if not is_on_floor():
velocity += get_gravity() * delta
# Jump
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get input and convert to 3D direction
var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()
ความแตกต่างสำคัญจาก 2D
-
หน่วย: ใน 3D 1 หน่วย = 1 เมตร (ตามธรรมเนียม) ดังนั้น
SPEED = 5.0จึงหมายถึง 5 เมตรต่อวินาที — เป็นตัวเลขที่เล็กกว่าค่าพิกเซลของ 2D มาก -
ความเร็วกระโดดเป็นค่าบวก: ใน 3D แกน Y ชี้ขึ้น (ต่างจาก 2D) ดังนั้น
JUMP_VELOCITY = 4.5จึงเป็นค่าบวก -
การแปลงทิศทาง:
transform.basis * Vector3(input_dir.x, 0, input_dir.y)แปลงอินพุต 2D ให้เป็นทิศทาง 3D ที่สัมพันธ์กับทิศทางที่ตัวละครหันหน้าไป ค่า Y ของอินพุตจะถูกแมปไปยังแกน Z (หน้า/หลังใน 3D) -
ควบคุมเฉพาะ X และ Z: เรากำหนด
velocity.xและvelocity.zแยกกัน โดยเว้นvelocity.yไว้สำหรับแรงโน้มถ่วงและการกระโดด
เมธอด get_gravity() ถูกเพิ่มเข้ามาใน Godot 4.4 สำหรับเวอร์ชันเก่ากว่านี้ ให้ใช้ Vector2(0, ProjectSettings.get_setting("physics/2d/default_gravity")) สำหรับ 2D หรือ Vector3(0, -ProjectSettings.get_setting("physics/3d/default_gravity"), 0) สำหรับ 3D
6. อ้างอิงพร็อพเพอร์ตีสำคัญ
ทั้ง CharacterBody2D และ CharacterBody3D ใช้พร็อพเพอร์ตีสำคัญเหล่านี้ร่วมกัน ปรับได้ในอินสเปกเตอร์หรือผ่านโค้ด
| พร็อพเพอร์ตี | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
velocity |
Vector2 / Vector3 | Vector2.ZERO |
ความเร็วของตัวละคร กำหนดค่านี้ก่อนเรียก move_and_slide() หลังการเรียกจะถูกอัปเดตด้วยความเร็วที่ได้
|
floor_snap_length |
float | 1.0 |
แทนที่ move_and_slide_with_snap() ระยะทางในการสแนปตัวละครติดกับพื้น ตั้งเป็น 0 เพื่อปิดใช้งาน มีประโยชน์สำหรับทางลาดและบันได
|
up_direction |
Vector2 / Vector3 | Vector2.UP |
กำหนดว่าทิศทางใดคือ "ขึ้น" สิ่งนี้เป็นตัวกำหนดว่าอะไรนับเป็นพื้น ผนัง หรือเพดาน ค่าเริ่มต้นคือ (0, -1) ใน 2D และ (0, 1, 0) ใน 3D
|
floor_stop_on_slope |
bool | true |
เมื่อเป็น true ตัวละครจะไม่ไถลลงทางลาดขณะยืนนิ่ง จำเป็นอย่างยิ่งสำหรับแพลตฟอร์มเมอร์ |
floor_max_angle |
float | 0.785 (45°) |
มุมความชันสูงสุดที่เดินได้หน่วยเป็นเรเดียน พื้นผิวที่ชันกว่านี้จะถูกจัดการเป็นผนัง ใช้ deg_to_rad(60) เพื่อตั้งค่า 60 องศา
|
max_slides |
int | 6 |
จำนวนรอบการวนตรวจการชนสูงสุดต่อการเรียก move_and_slide() หนึ่งครั้ง ค่าที่สูงกว่าจะแม่นยำกว่าแต่ช้ากว่า
|
wall_min_slide_angle |
float | 0.262 (15°) |
มุมต่ำสุดสำหรับการไถลไปตามผนัง ป้องกันไม่ให้ตัวละครติดค้างอยู่กับผนังที่เกือบขนานกัน |
platform_on_leave |
PlatformOnLeave | ADD_VELOCITY |
พฤติกรรมเมื่อออกจากแพลตฟอร์มที่เคลื่อนที่ ADD_VELOCITY รักษาโมเมนตัมไว้ ADD_UPWARD_VELOCITY เพิ่มเฉพาะองค์ประกอบทิศขึ้น DO_NOTHING ละเว้นความเร็วของแพลตฟอร์ม
|
slide_on_ceiling |
bool | true |
เมื่อเป็น true จะอนุญาตให้ไถลบนเพดาน เมื่อเป็น false จะหยุดความเร็วแนวนอนเมื่อชนเพดาน |
7. การตรวจจับการชนหลัง move_and_slide()
หลังจากเรียก move_and_slide() คุณสามารถตรวจสอบการชนที่เกิดขึ้นได้ สิ่งนี้มีประโยชน์ในการตอบสนองต่อประเภทคอลไลเดอร์เฉพาะ การเล่นเสียงเอฟเฟกต์เมื่อลงพื้น หรือการสร้างกลไกการกระโดดผนัง
func _physics_process(delta: float) -> void:
# ... set velocity here ...
move_and_slide()
# Check all collisions from this frame
for i in get_slide_collision_count():
var collision := get_slide_collision(i)
var collider := collision.get_collider()
print("Collided with: ", collider.name)
print("Normal: ", collision.get_normal())
print("Position: ", collision.get_position())
# Practical example: bounce off enemies
for i in get_slide_collision_count():
var collision := get_slide_collision(i)
if collision.get_collider().is_in_group("enemies"):
velocity = collision.get_normal() * 300
break
เมธอด KinematicCollision ที่มีประโยชน์
get_collider()— วัตถุที่ถูกชนget_normal()— เวกเตอร์ตั้งฉากของพื้นผิวที่ชน ชี้ออกจากพื้นผิวget_position()— จุดในพื้นที่โลกที่การชนเกิดขึ้นget_travel()— ระยะทางที่บอดีเคลื่อนที่ก่อนจะชนget_remainder()— การเคลื่อนที่ที่เหลือซึ่งไม่ได้ถูกนำไปใช้get_collider_velocity()— ความเร็วของคอลไลเดอร์ (มีประโยชน์สำหรับแพลตฟอร์มที่เคลื่อนที่)
8. ชีตสรุปการย้ายโค้ด
อ้างอิงฉบับย่อสำหรับการแปลงโค้ด Godot 3 เป็น Godot 4:
| Godot 3 | Godot 4 |
|---|---|
KinematicBody2D |
CharacterBody2D |
KinematicBody3D |
CharacterBody3D |
velocity = move_and_slide(velocity, Vector2.UP) |
velocity = ... |
move_and_slide_with_snap(vel, snap, up) |
floor_snap_length = 4.0 |
var vel = move_and_slide(...)คืนค่า velocity |
move_and_slide()พร็อพเพอร์ตี velocity ถูกอัปเดตในที่เดิม |
var gravity = ProjectSettings.get("physics/2d/default_gravity") |
get_gravity() (4.4+) |
move_and_slide(..., up_direction, ...) |
up_direction = Vector2.UP (กำหนดเป็นพร็อพเพอร์ตี) |
move_and_slide(..., stop_on_slope, ...) |
floor_stop_on_slope = true (กำหนดเป็นพร็อพเพอร์ตี) |
เมื่อเปิดโปรเจกต์ Godot 3 ใน Godot 4 เอนจินจะเสนอให้แปลงโปรเจกต์ของคุณ มันจะเปลี่ยนชื่อโหนดและพยายามอัปเดตสคริปต์ แต่ไม่ได้แปลงการเรียก move_and_slide() ให้ถูกต้องเสมอไป คุณอาจต้องแก้ไขโค้ดการเคลื่อนที่ด้วยตนเอง
9. ข้อผิดพลาดที่พบบ่อย
ข้อผิดพลาดที่ 1: ส่งอาร์กิวเมนต์เข้า move_and_slide()
# ERROR: move_and_slide() takes no arguments in Godot 4
velocity = move_and_slide(velocity, Vector2.UP)
# Set velocity, then call move_and_slide() with no arguments
velocity.x = direction * SPEED
move_and_slide()
ข้อผิดพลาดที่ 2: ลืมกำหนด velocity ก่อน move_and_slide()
func _physics_process(delta: float) -> void:
move_and_slide() # velocity is Vector2.ZERO — nothing happens
ข้อผิดพลาดที่ 3: ใช้ _process() แทน _physics_process()
move_and_slide() ต้องถูกเรียกใน _physics_process() ซึ่งทำงานที่อัตราคงที่ (ค่าเริ่มต้น 60 Hz) การใช้ _process() จะผูกฟิสิกส์เข้ากับเฟรมเรตของการเรนเดอร์ ทำให้พฤติกรรมไม่สม่ำเสมอ
ข้อผิดพลาดที่ 4: ไม่ตั้งค่า CollisionShape
CharacterBody2D/CharacterBody3D ที่ไม่มีโหนดลูก CollisionShape จะทะลุผ่านทุกสิ่ง Godot จะแสดงไอคอนเตือนในเอดิเตอร์หากขาดรูปทรงการชน
ข้อผิดพลาดที่ 5: เขียนทับ velocity.y ด้วยแรงโน้มถ่วงทุกเฟรม
# This overwrites any jump velocity!
velocity.y = gravity * delta # should be +=, not =
# Gravity accumulates over time
velocity += get_gravity() * delta
ข้อผิดพลาดที่ 6: ใช้แรงโน้มถ่วงขณะอยู่บนพื้น
ควรครอบแรงโน้มถ่วงด้วย if not is_on_floor() เสมอ หากไม่มีการตรวจสอบนี้ แรงโน้มถ่วงจะสะสมต่อเนื่องขณะอยู่บนพื้น เมื่อตัวละครเดินตกขอบ มันจะดิ่งลงด้วยความเร็วสูงผิดปกติแทนที่จะตกลงอย่างเป็นธรรมชาติ