Cheatsheet

Unity Physics từ cơ bản đến nâng cao

Rigidbody, collider, va chạm, raycast, joint, CCD, tối ưu vật lý và các use case thường gặp theo API Unity 6.

  • Unity
  • Physics
  • C#

1. Nền tảng: Rigidbody & Collider

Một object tham gia vật lý khi có Collider; nó chịu lực và trọng lực khi có thêm Rigidbody. Cheatsheet dùng tên API của Unity 6 (bản cũ hơn: velocity, drag, angularDrag).

Ba loại body

LoạiCấu hìnhDùng choGhi chú
StaticChỉ Collider, không RigidbodyTường, sàn, địa hìnhKhông được di chuyển bằng Transform khi chạy
DynamicCollider + Rigidbody (isKinematic = false)Thùng, bóng, ragdollChịu lực, trọng lực, va chạm
KinematicCollider + Rigidbody (isKinematic = true)Thang máy, cửa, platform di độngDi chuyển bằng MovePosition, đẩy được Dynamic nhưng không bị đẩy lại

Thuộc tính Rigidbody hay dùng

Thuộc tínhÝ nghĩaGiá trị gợi ý
massKhối lượng (kg)Giữ tỉ lệ giữa các vật ≤ 1:100 để solver ổn định
linearDampingCản chuyển động thẳng0 cho đạn, 1–5 cho vật “nặng nề”
angularDampingCản xoayMặc định 0.05
useGravityBật/tắt trọng lựcTắt cho vật bay, đạn thẳng
constraintsKhóa trục vị trí/xoayFreezeRotation cho nhân vật
interpolationLàm mượt hình ảnh giữa các bước vật lýInterpolate cho vật camera bám theo
collisionDetectionModeKiểu phát hiện va chạmDiscrete mặc định; CCD cho vật nhanh (mục 6)
centerOfMassTâm khốiHạ thấp cho xe để không lật

Collider: từ rẻ đến đắt

  1. SphereCollider — rẻ nhất.
  2. CapsuleCollider — chuẩn cho nhân vật.
  3. BoxCollider — đa số vật thể.
  4. MeshCollider convex — tối đa 255 tam giác, dùng được với Dynamic.
  5. MeshCollider non-convex — đắt nhất, chỉ nên cho Static hoặc Kinematic.

Compound collider: nhiều collider con dưới một Rigidbody cha. Thường rẻ và ổn định hơn một MeshCollider.

2. Va chạm & Trigger

Callback chỉ bắn khi ít nhất một trong hai object có Rigidbody, và hai layer được bật trong Layer Collision Matrix.

Callback

CallbackKhi nàoTham số
OnCollisionEnter / Stay / ExitHai collider thường chạm nhauCollision (contacts, impulse, relativeVelocity)
OnTriggerEnter / Stay / ExitMột trong hai có isTrigger = trueCollider
OnControllerColliderHitCharacterController va vào collider khi gọi MoveControllerColliderHit

Bản 2D dùng hậu tố 2D: OnCollisionEnter2D(Collision2D c), OnTriggerEnter2D(Collider2D c).

Ai nhận được sự kiện

A \ BStaticDynamicKinematic
StaticKhôngCollisionKhông
DynamicCollisionCollisionCollision
KinematicKhôngCollisionKhông (trừ khi bật Kinematic Contacts)

Trigger: cần ít nhất một Rigidbody (Dynamic hoặc Kinematic) trong cặp; Static trigger × Static không bắn.

Code mẫu

void OnCollisionEnter(Collision c)
{
    float impact = c.relativeVelocity.magnitude;
    if (impact > 5f) PlayHitSound(impact);
    ContactPoint p = c.GetContact(0); // tránh c.contacts (cấp phát mảng)
    SpawnFx(p.point, p.normal);
}

void OnTriggerEnter(Collider other)
{
    if (other.CompareTag("Player")) // nhanh hơn other.tag == "Player"
        Collect();
}

Layer & Collision Matrix

  • Project Settings → Physics → Layer Collision Matrix: tắt mọi cặp layer không cần va chạm (ví dụ Bullet × Bullet, Pickup × Enemy).
  • Bỏ qua một cặp cụ thể: Physics.IgnoreCollision(colA, colB); cả hai layer: Physics.IgnoreLayerCollision(8, 9).
  • Unity 2022.2+: Collider.includeLayers / excludeLayers override matrix theo từng collider.

3. Di chuyển & lực

Mọi thay đổi lên Rigidbody làm trong FixedUpdate (mặc định 50 lần/giây, Time.fixedDeltaTime = 0.02); đọc input trong Update rồi lưu lại.

Cách di chuyển: chọn cái nào

CáchBodyTôn trọng va chạm?Dùng khi
AddForce / AddTorqueDynamicCóXe, tàu, đẩy nổ, chuyển động tự nhiên
Gán linearVelocityDynamicCóNhân vật cần phản hồi chính xác, nhảy
MovePosition / MoveRotationKinematicĐẩy vật khác, bản thân không bị chặnPlatform, cửa, vật theo đường ray
transform.positionBất kỳKhông — teleportChỉ để dịch chuyển tức thời; tránh mỗi frame

ForceMode

ModeTính khối lượng?Tức thời?Ví dụ
Force (mặc định)CóKhông, × fixedDeltaTimeĐộng cơ, gió
AccelerationKhôngKhôngTrọng lực tùy biến
ImpulseCóCóNhảy, bắn, nổ
VelocityChangeKhôngCóDash, đổi hướng tức thì

Code mẫu

[SerializeField] float speed = 6f, jumpForce = 5f;
Rigidbody rb; Vector3 input; bool jumpQueued;

void Awake() => rb = GetComponent<Rigidbody>();

void Update()
{
    input = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;
    if (Input.GetButtonDown("Jump")) jumpQueued = true; // đọc ở Update để không mất phím
}

void FixedUpdate()
{
    Vector3 v = input * speed;
    v.y = rb.linearVelocity.y;          // giữ vận tốc rơi
    rb.linearVelocity = v;

    if (jumpQueued) { rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); jumpQueued = false; }
}

Ghi nhớ nhanh

  • Không nhân Time.deltaTime khi dùng AddForce — Unity đã tính.
  • AddForceAtPosition tạo cả lực lẫn mô-men xoay (đạn trúng mép thùng).
  • AddExplosionForce(force, center, radius, upwardsModifier) cho vụ nổ.
  • AddRelativeForce dùng trục cục bộ (tàu, máy bay).
  • rb.maxAngularVelocity mặc định 7 rad/s; tăng lên nếu bánh xe/con quay xoay chậm bất thường.

4. Truy vấn vật lý (Raycast, Cast, Overlap)

Luôn truyền LayerMask và maxDistance; trong code chạy mỗi frame, dùng bản NonAlloc để không tạo rác GC.

HàmTrả vềDùng cho
Physics.RaycastHit đầu tiênBắn hitscan, check mặt đất, click chọn object
Physics.RaycastNonAllocNhiều hit vào mảng có sẵnĐạn xuyên, LOS nhiều lớp
SphereCast / CapsuleCast / BoxCastHit đầu tiên của hình khối quétGround check đáng tin, dự đoán va chạm, camera chống xuyên tường
OverlapSphereNonAlloc / OverlapBoxNonAllocCollider trong vùngAoE, phát hiện kẻ địch gần, nổ
CheckSphere / CheckBoxboolChỉ cần biết có/không (rẻ nhất)
Physics.LinecastHit giữa 2 điểmLine of sight
Collider.RaycastHit trên một collider duy nhấtRaycast lên đúng một object, bỏ qua scene
RaycastCommand (Jobs)Hàng nghìn ray song songAI cảm biến, đạn số lượng lớn

Code mẫu

[SerializeField] LayerMask groundMask;   // chọn layer trong Inspector
readonly Collider[] hits = new Collider[32];

bool IsGrounded() =>
    Physics.CheckSphere(feet.position, 0.2f, groundMask, QueryTriggerInteraction.Ignore);

void Explode(Vector3 center)
{
    int n = Physics.OverlapSphereNonAlloc(center, 5f, hits, enemyMask);
    for (int i = 0; i < n; i++)
        if (hits[i].attachedRigidbody is { } rb)
            rb.AddExplosionForce(800f, center, 5f, 1f);
}

if (Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out RaycastHit hit, 100f, clickMask))
    Select(hit.collider);

Lưu ý

  • LayerMask: 1 << layer cho một layer, ~mask để đảo; dùng LayerMask.GetMask("Ground","Wall") một lần rồi cache.
  • QueryTriggerInteraction.Ignore để ray không trúng trigger (mặc định theo setting Queries Hit Triggers).
  • Kết quả RaycastNonAlloc không sắp xếp theo khoảng cách — tự sort nếu cần.
  • Cast bắt đầu bên trong collider sẽ không trúng collider đó; lùi điểm bắt đầu ra một chút.
  • Debug: Debug.DrawRay(origin, dir * dist, Color.red).

5. Joints, Physics Material, CharacterController

Joints

JointRàng buộcUse case
FixedJointKhóa cứng hai vậtDính vật vào nhau, vật vỡ khi lực lớn (breakForce)
HingeJointXoay quanh 1 trụcCửa, bản lề, bánh xe đơn giản, con lắc
SpringJointGiữ khoảng cách đàn hồiDây thun, kéo vật bằng chuột
CharacterJointXoay có giới hạn như khớpRagdoll
ConfigurableJointTùy chỉnh cả 6 bậc tự do + driveActive ragdoll, cần cẩu, grab VR
ArticulationBodyChuỗi khớp giải chính xác (Featherstone)Robot, cánh tay máy, chuỗi dài không bị giãn

Mẹo joint: bật enablePreprocessing = false nếu rung giật; tăng solverIterations cho chuỗi dài (xích, dây); giữ tỉ lệ mass giữa hai vật nối ≤ 1:10.

Physics Material

Thuộc tínhÝ nghĩaVí dụ
dynamicFrictionMa sát khi đang trượtBăng 0.05, cao su 0.8
staticFrictionMa sát khi đứng yênThường ≥ dynamic
bouncinessĐộ nảy 0–1Bóng cao su 0.8
frictionCombine / bounceCombineCách gộp giữa 2 vậtMinimum cho tường trơn

Mẹo kinh điển: gán material ma sát 0 + Minimum cho collider nhân vật để không bị dính vào tường khi nhảy.

Rigidbody hay CharacterController?

Tiêu chíRigidbodyCharacterController
Điều khiểnQua lực/vận tốc, cảm giác “có quán tính”Chính xác từng frame qua Move()
Bậc thang, dốcTự xử lý bằng codeCó sẵn stepOffset, slopeLimit
Trọng lựcTự độngTự cộng vào vận tốc
Bị đẩy bởi vật khácCóKhông (tự code)
Hợp vớiGame vật lý, xe, ragdoll, phản lựcFPS/TPS cổ điển, platformer cần điều khiển “chặt”

6. Nâng cao

Continuous Collision Detection (chống xuyên tường)

ModeChống xuyên vớiChi phíDùng cho
Discrete—Thấp nhấtĐa số vật
ContinuousStaticTrung bìnhVật nhanh va vào tường
ContinuousDynamicStatic + Dynamic có CCDCaoĐạn thật, bóng golf
ContinuousSpeculativeTất cả, kể cả xoay nhanhTrung bìnhKinematic, vật xoay nhanh; có thể gây “va chạm ma”

Quy tắc ngón tay: vật đi quá nửa bề dày của nó trong một bước vật lý thì cần CCD. Đạn rất nhanh: dùng Raycast từ vị trí cũ đến vị trí mới thay vì CCD.

Interpolation

  • Interpolate: hiển thị trễ một bước, mượt và ổn định — mặc định nên dùng cho player.
  • Extrapolate: đoán trước, không trễ nhưng có thể lố vào tường.
  • Chỉ bật cho vật camera theo dõi hoặc cận cảnh; camera follow phải chạy trong LateUpdate.
  • Khi có interpolation, đọc rb.position (giá trị vật lý) khác với transform.position (giá trị hiển thị).

Solver & thông số toàn cục (Project Settings → Physics)

Thông sốMặc địnhKhi nào đổi
Default Solver Iterations6Tăng khi joint/chồng vật rung; có thể đặt riêng rb.solverIterations
Default Solver Velocity Iterations1Tăng khi vật nảy sai, mất năng lượng
Default Contact Offset0.01Giảm với vật rất nhỏ
Sleep Threshold0.005Tăng để vật ngủ sớm hơn
Solver TypeProjected Gauss SeidelTemporal Gauss Seidel cho joint/ragdoll ổn định hơn
Friction TypePatchOne Directional rẻ hơn

Tự điều khiển bước vật lý

// Project Settings → Physics → Simulation Mode = Script
void FixedUpdate() => Physics.Simulate(Time.fixedDeltaTime);

// Dự đoán quỹ đạo trên scene vật lý riêng
Scene sim = SceneManager.CreateScene("Sim", new CreateSceneParameters(LocalPhysicsMode.Physics3D));
PhysicsScene ps = sim.GetPhysicsScene();
// ... SceneManager.MoveGameObjectToScene(ghost, sim);
for (int i = 0; i < 60; i++) { ps.Simulate(Time.fixedDeltaTime); line.SetPosition(i, ghost.position); }

Dùng cho: vẽ quỹ đạo (Angry Birds, golf), rollback netcode, replay, chạy mô phỏng nhanh hơn thời gian thực.

Các API chuyên sâu

  • Contact Modification (Physics.ContactModifyEvent, bật collider.hasModifiableContacts): sửa điểm tiếp xúc trước khi giải — one-way platform 3D, bánh xe trơn, bỏ va chạm có điều kiện.
  • Physics.ContactEvent: nhận toàn bộ contact của frame theo batch, thay cho hàng nghìn OnCollisionEnter.
  • Physics.BakeMesh (job): bake MeshCollider ở background thread, tránh giật khi spawn mesh procedural.
  • Physics.ComputePenetration / ClosestPoint: tự viết character controller kinematic, đẩy vật ra khỏi tường.
  • Unity Physics / Havok (DOTS): stateless, định định (deterministic), phù hợp hàng chục nghìn vật hoặc netcode.

Khác biệt 2D vs 3D

3D (PhysX)2D (Box2D)
ComponentRigidbody, BoxColliderRigidbody2D, BoxCollider2D
KinematicisKinematicbodyType = Kinematic
Trọng lực riêngKhônggravityScale
One-way platformTự code / Contact ModificationPlatformEffector2D có sẵn
Collider đặc biệtTerrain, WheelCompositeCollider2D, EdgeCollider2D, TilemapCollider2D

Hai hệ không tương tác với nhau: collider 2D không va với collider 3D.

7. Tips tối ưu hiệu năng

Hai đòn bẩy lớn nhất thường là: giảm số cặp cần kiểm tra (layer matrix) và giảm tần suất bước vật lý (fixed timestep). Đo bằng Profiler trước khi tối ưu.

Cấu hình dự án

  • Fixed Timestep: 0.02 (50 Hz) là mặc định; mobile có thể 0.0333 (30 Hz) nếu gameplay cho phép.
  • Maximum Allowed Timestep: đặt ~0.1 để tránh “spiral of death” (FPS tụt → chạy nhiều FixedUpdate hơn → tụt thêm).
  • Layer Collision Matrix: tắt mọi cặp không cần — thường là tối ưu rẻ và hiệu quả nhất.
  • Auto Sync Transforms: để tắt (mặc định); gọi Physics.SyncTransforms() thủ công khi cần.
  • Reuse Collision Callbacks: bật (mặc định từ 2019+) để không cấp phát Collision mỗi lần.
  • Broadphase Type: Automatic Box Pruning cho thế giới lớn nhiều vật.
  • Simulation Mode = Script cho scene trình diễn/menu không cần vật lý.

Collider & Rigidbody

  • Ưu tiên primitive (Sphere > Capsule > Box) và compound collider thay cho MeshCollider.
  • MeshCollider: dùng mesh collision riêng đã giảm tam giác; bật Cooking Options phù hợp; bake trước bằng Physics.BakeMesh.
  • Không scale không đều (non-uniform) lên MeshCollider/Sphere — tốn chi phí và dễ sai.
  • Object có collider mà di chuyển → phải có Rigidbody (Kinematic). Di chuyển Static collider buộc PhysX rebuild cấu trúc.
  • Để vật ngủ: không gán velocity/position mỗi frame khi không cần; kiểm tra rb.IsSleeping().
  • Gom collider trên một Rigidbody thay vì nhiều Rigidbody nối FixedJoint.
  • Tắt GameObject (hoặc Rigidbody) ở xa người chơi; dùng culling/LOD cho vật lý.

Code

  • Dùng NonAlloc + mảng cache cho Raycast/Overlap; hoặc RaycastCommand / OverlapSphereCommand cho batch lớn.
  • Cache GetComponent<Rigidbody>() trong Awake; dùng TryGetComponent thay GetComponent + null check.
  • CompareTag thay cho so sánh tag; c.GetContact(i) / c.GetContacts(buffer) thay c.contacts.
  • Tránh OnCollisionStay / OnTriggerStay nếu chỉ cần Enter/Exit — chúng chạy mỗi bước.
  • Không raycast mỗi frame cho AI — giãn ra 5–10 lần/giây và so le giữa các agent.
  • Pool đạn/hiệu ứng thay vì Instantiate/Destroy; khi lấy từ pool, reset linearVelocity, angularVelocity.
  • Số lượng cực lớn (hàng nghìn vật): cân nhắc DOTS Physics hoặc tự mô phỏng đơn giản không dùng Rigidbody.

Đo đạc

  • Profiler → module Physics: số Active Dynamic/Kinematic bodies, contacts, trigger overlaps.
  • CPU timeline: Physics.Simulate, Physics.Processing, Physics.FetchResults, Physics.ProcessReports.
  • Window → Analysis → Physics Debugger: xem collider, contact, vật đang ngủ/thức.

8. Use case thường gặp

Ground check đáng tin cậy

bool Grounded(out RaycastHit hit) =>
    Physics.SphereCast(transform.position + Vector3.up * 0.5f, 0.3f, Vector3.down,
                       out hit, 0.6f, groundMask, QueryTriggerInteraction.Ignore);
// hit.normal để biết độ dốc: Vector3.Angle(hit.normal, Vector3.up) > maxSlope → trượt

Nhảy cảm giác tốt (coyote time + jump buffer + rơi nhanh)

float coyote, buffer;
void Update() { if (Input.GetButtonDown("Jump")) buffer = 0.15f; }
void FixedUpdate()
{
    coyote = Grounded(out _) ? 0.1f : coyote - Time.fixedDeltaTime;
    buffer -= Time.fixedDeltaTime;
    if (buffer > 0 && coyote > 0) {
        rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpSpeed, rb.linearVelocity.z);
        buffer = coyote = 0;
    }
    if (rb.linearVelocity.y < 0) rb.AddForce(Physics.gravity * 1.5f, ForceMode.Acceleration); // rơi nhanh hơn
}

Đứng trên platform di động

Platform: Rigidbody Kinematic + MovePosition trong FixedUpdate. Player Rigidbody: cộng vận tốc platform (platformRb.GetPointVelocity(hit.point)) vào vận tốc của mình. Tránh SetParent player vào platform khi player là Dynamic.

Nhặt/kéo vật (grab)

void FixedUpdate()
{
    Vector3 toTarget = holdPoint.position - held.position;
    held.linearVelocity = toTarget / Time.fixedDeltaTime * 0.5f; // bám theo, vẫn va tường
    held.angularVelocity *= 0.8f;
}

Phương án khác: SpringJoint hoặc ConfigurableJoint với drive — mềm hơn, hợp VR.

Vùng sát thương / phát hiện

  • Vùng cố định (bẫy, checkpoint): trigger collider + OnTriggerEnter/Exit.
  • Đòn đánh tức thời (kiếm, AoE): OverlapSphereNonAlloc đúng frame ra đòn — không bật/tắt trigger collider.
  • Tầm nhìn AI: Overlap tìm ứng viên → lọc theo góc (Vector3.Dot) → Linecast kiểm tra bị che.

Đạn

LoạiCách làm
Hitscan (súng trường)Physics.Raycast từ nòng/camera, không cần Rigidbody
Đạn bay nhanhKhông dùng Rigidbody; tự cộng vị trí + Raycast(lastPos → newPos) mỗi bước
Đạn chậm có trọng lực (lựu đạn, mũi tên)Rigidbody + AddForce(Impulse); mũi tên xoay theo linearVelocity

Xe

WheelCollider × 4 + Rigidbody mass ~1000–1500, hạ centerOfMass, đồng bộ mesh bánh bằng GetWorldPose. Xe arcade: tự raycast treo từng bánh + AddForceAtPosition dễ tinh chỉnh hơn.

Ragdoll khi chết

Tạo bằng GameObject → 3D Object → Ragdoll; lúc sống đặt mọi bone isKinematic = true, collider tắt. Khi chết: tắt Animator, bật lại vật lý, chuyển linearVelocity cuối cùng của nhân vật cho các bone, AddForce theo hướng đòn.

Quỹ đạo dự đoán (ném, bắn cung)

Công thức giải tích (không damping), với v0 là vận tốc ban đầu và g là Physics.gravity:

p(t) = p_0 + v_0 t + \tfrac{1}{2} g t^2

Có damping hoặc va chạm: dùng scene vật lý riêng (mục 6).

9. Lỗi thường gặp & cách debug

Triệu chứngNguyên nhân hay gặpCách sửa
OnCollisionEnter / OnTriggerEnter không chạyThiếu Rigidbody; layer bị tắt trong matrix; trộn 2D với 3D; sai chữ ký hàmThêm Rigidbody (Kinematic cũng được), kiểm tra matrix, đúng Collision vs Collision2D
Vật xuyên tườngQuá nhanh; di chuyển bằng transform; collider quá mỏngCCD, raycast bù, dùng linearVelocity/MovePosition, làm tường dày hơn
Nhân vật giật khi camera bámCamera cập nhật lệch nhịp với vật lýBật Interpolate, camera trong LateUpdate
Chồng thùng rung, trôiSolver iteration thấp; mass chênh lệchTăng solverIterations, đưa mass về tỉ lệ hợp lý, thử TGS solver
Nhân vật dính vào tường khi nhảyMa sát giữa capsule và tườngPhysics Material ma sát 0, combine Minimum
Nhân vật bị đổ lănRigidbody tự xoayconstraints = FreezeRotation
Nhảy lúc ăn lúc khôngĐọc GetButtonDown trong FixedUpdateĐọc ở Update, xử lý ở FixedUpdate (mục 3)
Joint giãn, dây xích “bung”Chuỗi dài, mass lệchTăng iterations, dùng ArticulationBody, bật projection trên ConfigurableJoint
Vật nảy mãi hoặc nổ tung khi spawnSpawn chồng lên collider khácKiểm tra CheckSphere/CheckBox trước khi spawn; giảm maxDepenetrationVelocity
Raycast không trúngSai LayerMask (truyền số layer thay vì mask); bắt đầu trong colliderDùng 1 << layer hoặc LayerMask; lùi điểm bắt đầu
FPS tụt theo số vậtQuá nhiều cặp contact, MeshCollider, Stay callbackMục 7; xem Profiler module Physics

Công cụ debug

  • Physics Debugger (Window → Analysis): hiển thị collider, contact, trạng thái ngủ.
  • Debug.DrawRay / DrawLine cho ray; OnDrawGizmos + Gizmos.DrawWireSphere cho Overlap/SphereCast.
  • Time.timeScale = 0.1f để xem chậm va chạm nhanh.
  • Log rb.linearVelocity, rb.IsSleeping() và collision.impulse khi vật hành xử lạ.