Tampilan
Bab 46 — Collision Detection
Sampai sekarang objek di gamemu bergerak menembus satu sama lain seperti hantu. Bab ini mengubahnya: peluru mengenai musuh, pemain memungut koin, dan tidak ada yang bisa keluar dari batas layar.
Deteksi tabrakan adalah salah satu bagian yang paling memengaruhi performa, jadi kita juga membahas cara membuatnya tetap murah.
Bagaimana Flame mendeteksi tabrakan
Flame memakai sistem hitbox — bentuk sederhana yang mewakili area tabrakan sebuah komponen.
Sprite yang rumit Hitbox yang sederhana
┌─────────────────┐ ┌─────────────────┐
│ ╱╲ │ │ ╭───────────╮ │
│ ╱ ╲ │ │ │ │ │
│ ╱ ▲▲ ╲ │ │ │ │ │
│ │ ████ │ │ │ │ │ │
│ ╰──────╯ │ │ ╰───────────╯ │
└─────────────────┘ └─────────────────┘
Yang dilihat pemain Yang dihitung mesinMemeriksa tabrakan piksel-per-piksel pada gambar rumit terlalu mahal untuk dilakukan 60 kali per detik. Bentuk sederhana — lingkaran, persegi, poligon — cukup akurat dan jauh lebih murah.
Menyiapkan
Dua langkah yang keduanya wajib.
dart
// 1. Game harus punya mixin HasCollisionDetection
class GameSaya extends FlameGame with HasCollisionDetection {
// ...
}dart
// 2. Setiap komponen yang ikut tabrakan butuh mixin + hitbox
class Pemain extends PositionComponent with CollisionCallbacks {
@override
Future<void> onLoad() async {
await super.onLoad();
add(RectangleHitbox()); // hitbox mengikuti size komponen
}
}Melewatkan salah satunya membuat tabrakan tidak terdeteksi
Kalau kamu lupa HasCollisionDetection pada game, atau lupa menambahkan hitbox pada komponen, tidak ada error apa pun — tabrakan hanya tidak pernah terjadi.
Ini termasuk bug Flame yang paling sering menghabiskan waktu, karena tidak ada petunjuk sama sekali. Kalau tabrakanmu tidak bekerja, periksa dua hal itu terlebih dahulu.
Jenis hitbox
dart
// Persegi — paling umum dan paling murah
add(RectangleHitbox());
// Persegi yang lebih kecil dari komponen
add(
RectangleHitbox(
size: Vector2(30, 40),
position: Vector2(5, 10),
),
);
// Persegi relatif terhadap ukuran komponen
add(
RectangleHitbox.relative(
Vector2(0.7, 0.9), // 70% lebar, 90% tinggi
parentSize: size,
anchor: Anchor.center,
),
);
// Lingkaran — cocok untuk objek bulat
add(CircleHitbox());
add(CircleHitbox(radius: 20, position: Vector2(10, 10)));
// Poligon — untuk bentuk tidak beraturan
add(
PolygonHitbox([
Vector2(0, 20),
Vector2(20, 0),
Vector2(40, 20),
Vector2(20, 40),
]),
);Buat hitbox sedikit lebih kecil dari sprite
Ini trik desain game yang penting. Hitbox pemain yang sedikit lebih kecil dari gambarnya membuat permainan terasa lebih adil — pemain merasa "hampir kena" padahal sebenarnya tidak.
Sebaliknya, hitbox musuh yang sedikit lebih besar membuat menembak terasa lebih memuaskan.
Ini bukan kecurangan; ini kompensasi untuk fakta bahwa mata manusia dan waktu reaksi tidak sempurna.
Menangani tabrakan
dart
class Pemain extends PositionComponent with CollisionCallbacks {
@override
Future<void> onLoad() async {
await super.onLoad();
add(RectangleHitbox());
}
/// Dipanggil SEKALI, saat tabrakan MULAI.
@override
void onCollisionStart(
Set<Vector2> titikSentuh,
PositionComponent lawan,
) {
super.onCollisionStart(titikSentuh, lawan);
if (lawan is Musuh) {
terkenaSerangan();
} else if (lawan is Koin) {
lawan.dipungut();
}
}
/// Dipanggil SETIAP FRAME selama masih bersentuhan.
@override
void onCollision(Set<Vector2> titikSentuh, PositionComponent lawan) {
super.onCollision(titikSentuh, lawan);
if (lawan is Api) {
// Kerusakan berkelanjutan — pakai onCollision, bukan onCollisionStart
nyawa -= 10 * (findGame()!.currentTime());
}
}
/// Dipanggil saat tabrakan BERAKHIR.
@override
void onCollisionEnd(PositionComponent lawan) {
super.onCollisionEnd(lawan);
if (lawan is ZonaAman) {
keluarZonaAman();
}
}
}Perbedaan ketiganya penting:
| Metode | Kapan dipanggil | Untuk |
|---|---|---|
onCollisionStart | Sekali, saat mulai bersentuhan | Kerusakan, memungut item, memantul |
onCollision | Setiap frame selama bersentuhan | Kerusakan berkelanjutan, dorongan |
onCollisionEnd | Sekali, saat berpisah | Keluar zona, mengakhiri efek |
Jangan pakai onCollision untuk hal yang terjadi sekali
dart
// ❌ Pemain kehilangan 10 nyawa PER FRAME — mati dalam sekejap
@override
void onCollision(Set<Vector2> titik, PositionComponent lawan) {
if (lawan is Musuh) nyawa -= 10;
}
// ✅ Kehilangan 10 nyawa satu kali
@override
void onCollisionStart(Set<Vector2> titik, PositionComponent lawan) {
if (lawan is Musuh) nyawa -= 10;
}Pada 60 FPS, onCollision dipanggil 60 kali per detik. Bug ini menghasilkan kerusakan 600 per detik alih-alih 10.
Batas layar
Flame menyediakan komponen khusus untuk mendeteksi tepi layar.
dart
class GameSaya extends FlameGame with HasCollisionDetection {
@override
Future<void> onLoad() async {
await super.onLoad();
add(ScreenHitbox()); // membuat batas di keempat tepi
}
}Lalu komponen bisa bereaksi terhadapnya:
dart
class BolaMemantul extends PositionComponent with CollisionCallbacks {
final Vector2 kecepatan;
@override
void onCollisionStart(Set<Vector2> titik, PositionComponent lawan) {
super.onCollisionStart(titik, lawan);
if (lawan is ScreenHitbox) {
// Tentukan tepi mana yang tersentuh dari titik sentuhnya
final titikSentuh = titik.first;
final batas = findGame()!.size;
if (titikSentuh.x <= 0 || titikSentuh.x >= batas.x) {
kecepatan.x = -kecepatan.x;
}
if (titikSentuh.y <= 0 || titikSentuh.y >= batas.y) {
kecepatan.y = -kecepatan.y;
}
PengaturanAudio.instance.putarSfx('pantul.wav');
}
}
}Sering kali clamp lebih sederhana
Untuk sekadar menjaga pemain di dalam layar, clamp dari Bab 44 lebih sederhana dan lebih andal daripada ScreenHitbox:
dart
position.x = position.x.clamp(setengah, batas.x - setengah);
position.y = position.y.clamp(setengah, batas.y - setengah);ScreenHitbox berguna ketika kamu butuh bereaksi terhadap sentuhan tepi — memantul, memutar, atau memainkan suara.
Contoh lengkap: pemain, musuh, dan peluru
Mari gabungkan semuanya menjadi sistem yang berfungsi.
dart
// lib/game_tembak.dart
import 'dart:math';
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'komponen/pemain.dart';
import 'komponen/musuh.dart';
class GameTembak extends FlameGame with HasCollisionDetection {
late final Pemain pemain;
late final JoystickComponent joystick;
int skor = 0;
int nyawa = 3;
final _acak = Random();
double _sisaWaktuSpawn = 1.5;
@override
Color backgroundColor() => const Color(0xFF0A0A1A);
@override
Future<void> onLoad() async {
await super.onLoad();
add(ScreenHitbox());
joystick = JoystickComponent(
knob: CircleComponent(radius: 20, paint: Paint()..color = Colors.white54),
background:
CircleComponent(radius: 50, paint: Paint()..color = Colors.white24),
margin: const EdgeInsets.only(left: 36, bottom: 36),
);
pemain = Pemain(joystick: joystick)..position = size / 2;
addAll([pemain, joystick]);
}
@override
void update(double dt) {
super.update(dt);
_sisaWaktuSpawn -= dt;
if (_sisaWaktuSpawn <= 0) {
_sisaWaktuSpawn = 1.5;
_munculkanMusuh();
}
}
void _munculkanMusuh() {
// Muncul dari tepi acak
final sisi = _acak.nextInt(4);
final posisi = switch (sisi) {
0 => Vector2(_acak.nextDouble() * size.x, -30), // atas
1 => Vector2(size.x + 30, _acak.nextDouble() * size.y), // kanan
2 => Vector2(_acak.nextDouble() * size.x, size.y + 30), // bawah
_ => Vector2(-30, _acak.nextDouble() * size.y), // kiri
};
add(Musuh(posisi: posisi));
}
void tambahSkor(int nilai) {
skor += nilai;
}
void kurangiNyawa() {
nyawa--;
if (nyawa <= 0) {
pauseEngine();
overlays.add('kalah');
}
}
}Musuhnya:
dart
// lib/komponen/musuh.dart
import 'dart:ui';
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import '../game_tembak.dart';
import 'peluru.dart';
class Musuh extends PositionComponent
with CollisionCallbacks, HasGameReference<GameTembak> {
Musuh({required Vector2 posisi})
: super(
position: posisi,
size: Vector2.all(36),
anchor: Anchor.center,
);
static const double _kecepatan = 90;
double _nyawa = 2;
final _cat = Paint()..color = Colors.redAccent;
@override
Future<void> onLoad() async {
await super.onLoad();
// Lingkaran sedikit lebih besar dari sprite —
// membuat menembak terasa lebih memuaskan
add(CircleHitbox(radius: size.x / 2 + 2, anchor: Anchor.center)
..position = size / 2);
}
@override
void update(double dt) {
super.update(dt);
// Kejar pemain — pola dari Bab 43
final arah = (game.pemain.position - position)..normalize();
position += arah * _kecepatan * dt;
angle = arah.screenAngle();
}
@override
void onCollisionStart(Set<Vector2> titik, PositionComponent lawan) {
super.onCollisionStart(titik, lawan);
if (lawan is Peluru) {
lawan.removeFromParent();
_terkena();
}
// Tabrakan dengan pemain ditangani DI SISI PEMAIN,
// agar tidak diproses dua kali
}
void _terkena() {
_nyawa--;
if (_nyawa <= 0) {
game.tambahSkor(10);
PengaturanAudio.instance.putarSfx('ledakan.wav');
removeFromParent();
return;
}
// Berkedip putih sesaat sebagai umpan balik
_cat.color = Colors.white;
add(
TimerComponent(
period: 0.08,
removeOnFinish: true,
onTick: () => _cat.color = Colors.redAccent,
),
);
}
@override
void render(Canvas canvas) {
super.render(canvas);
canvas.drawCircle(
Offset(size.x / 2, size.y / 2),
size.x / 2,
_cat,
);
}
}Tentukan siapa yang menangani setiap pasangan tabrakan
Ketika pemain dan musuh bertabrakan, kedua komponen menerima onCollisionStart. Kalau keduanya mengurangi nyawa, kerusakannya jadi dua kali lipat.
Aturan yang jelas: tetapkan satu sisi sebagai penanggung jawab untuk setiap pasangan. Konvensi yang umum:
- Pemain menangani tabrakan dengan musuh dan item.
- Musuh menangani tabrakan dengan peluru.
- Peluru tidak menangani apa pun — ia hanya dihapus oleh yang lain.
Tulis konvensi ini di komentar kode. Tanpa itu, akan ada saat di mana kamu menambahkan penanganan di sisi kedua tanpa sadar.
Pemainnya:
dart
// lib/komponen/pemain.dart — bagian tabrakan
class Pemain extends PositionComponent
with CollisionCallbacks, HasGameReference<GameTembak> {
bool _kebal = false;
@override
Future<void> onLoad() async {
await super.onLoad();
// Hitbox sedikit LEBIH KECIL — membuat permainan terasa adil
add(
RectangleHitbox.relative(
Vector2(0.8, 0.8),
parentSize: size,
anchor: Anchor.center,
)..position = size / 2,
);
}
@override
void onCollisionStart(Set<Vector2> titik, PositionComponent lawan) {
super.onCollisionStart(titik, lawan);
if (lawan is Musuh && !_kebal) {
_terkena();
lawan.removeFromParent();
}
}
void _terkena() {
game.kurangiNyawa();
PengaturanAudio.instance.putarSfx('terkena.wav');
// Kebal sesaat agar tidak langsung mati beruntun
_kebal = true;
add(
OpacityEffect.fadeOut(
EffectController(
duration: 0.12,
reverseDuration: 0.12,
repeatCount: 6,
),
onComplete: () => _kebal = false,
),
);
}
}Kekebalan sesaat setelah terkena adalah pola desain game yang penting. Tanpa itu, pemain yang tertabrak di tengah kerumunan musuh akan kehilangan seluruh nyawanya dalam sepersekian detik — pengalaman yang terasa tidak adil.
Menjaga performa
Deteksi tabrakan naik secara kuadrat: 100 objek berarti hampir 5.000 pasangan yang harus diperiksa setiap frame.
Broad phase
Flame sudah memakai algoritma penyaringan awal untuk menghindari memeriksa semua pasangan. Kamu bisa memilih strategi yang lebih optimal:
dart
class GameSaya extends FlameGame with HasCollisionDetection {
@override
Future<void> onLoad() async {
await super.onLoad();
// Lebih efisien untuk banyak objek yang tersebar
collisionDetection = StandardCollisionDetection(
broadphase: QuadTreeBroadphase(),
);
}
}Hitbox pasif
Objek yang tidak pernah bergerak — dinding, rintangan — sebaiknya ditandai pasif. Flame tidak akan memeriksa tabrakan antar sesama objek pasif.
dart
class Dinding extends PositionComponent with CollisionCallbacks {
@override
Future<void> onLoad() async {
await super.onLoad();
add(RectangleHitbox(collisionType: CollisionType.passive));
}
}| Tipe | Perilaku |
|---|---|
active | Diperiksa terhadap semua — bawaan |
passive | Hanya diperiksa terhadap yang active |
inactive | Tidak diperiksa sama sekali |
Untuk game dengan 50 dinding dan 5 objek bergerak, menandai dinding sebagai pasif mengurangi pemeriksaan dari 1.485 pasangan menjadi 260.
Menonaktifkan sementara
dart
// Saat pemain kebal, matikan hitbox-nya
hitbox.collisionType = CollisionType.inactive;
// Aktifkan lagi
hitbox.collisionType = CollisionType.active;Bentuk yang lebih murah
Termurah ──────────────────────────► Termahal
Lingkaran Persegi Poligon
(satu (empat (banyak
perbandingan perbandingan perbandingan)
jarak)Untuk objek yang jumlahnya banyak — peluru, partikel — pakai lingkaran.
Deteksi manual
Kadang kamu tidak butuh sistem tabrakan penuh. Untuk kasus sederhana, memeriksanya sendiri lebih murah.
dart
/// Dua lingkaran bertabrakan kalau jarak pusatnya
/// lebih kecil dari jumlah jari-jarinya.
bool lingkaranBertabrakan(
Vector2 pusatA, double jariA,
Vector2 pusatB, double jariB,
) {
final jarakKuadrat = pusatA.distanceToSquared(pusatB);
final jumlahJari = jariA + jariB;
return jarakKuadrat <= jumlahJari * jumlahJari;
}Perhatikan distanceToSquared alih-alih distanceTo. Menghitung akar kuadrat relatif mahal; membandingkan kuadratnya memberi hasil yang sama tanpa akar.
dart
/// Dua persegi bertabrakan kalau keduanya tumpang tindih
/// pada SUMBU X dan SUMBU Y sekaligus.
bool persegiBertabrakan(
Vector2 posA, Vector2 ukA,
Vector2 posB, Vector2 ukB,
) {
return posA.x < posB.x + ukB.x &&
posA.x + ukA.x > posB.x &&
posA.y < posB.y + ukB.y &&
posA.y + ukA.y > posB.y;
}Memakainya untuk memeriksa jarak sebelum tabrakan sesungguhnya:
dart
@override
void update(double dt) {
super.update(dt);
// Deteksi "hampir kena" untuk efek dramatis,
// tanpa membebani sistem tabrakan
for (final musuh in game.children.whereType<Musuh>()) {
if (position.distanceToSquared(musuh.position) < 80 * 80) {
game.tampilkanPeringatanBahaya();
break;
}
}
}Melihat hitbox saat debug
dart
class GameSaya extends FlameGame with HasCollisionDetection {
@override
bool get debugMode => true;
}Ini menggambar garis di sekeliling setiap hitbox, sehingga kamu bisa melihat langsung apakah ukuran dan posisinya benar.
Hitbox yang tidak sesuai sprite
Masalah paling umum yang terlihat lewat mode debug: hitbox berada di posisi yang salah karena anchor komponen dan hitbox tidak cocok.
dart
// Komponen memakai Anchor.center
class Musuh extends PositionComponent {
Musuh() : super(anchor: Anchor.center, size: Vector2.all(40));
@override
Future<void> onLoad() async {
// ❌ Hitbox di pojok, sprite di tengah
add(CircleHitbox(radius: 20));
// ✅ Hitbox ikut di tengah
add(CircleHitbox(radius: 20, anchor: Anchor.center)
..position = size / 2);
}
}Alur deteksi tabrakan
Latihan Mandiri
Kerjakan salah satu, beberapa, atau semuanya secara berurutan untuk melatih pemahamanmu sampai benar-benar lekat.
Variasi 1: Nyawa dan Kekebalan — ⭐⭐ · 45–60 menit
Tantangan: Buat pemain dengan tiga nyawa yang berhadapan dengan beberapa musuh yang bergerak. Setiap sentuhan mengurangi satu nyawa, diikuti kekebalan sesaat dengan tanda visual. Tambahkan item yang bisa dipungut yang menambah nyawa, dan pastikan memungutnya tidak pernah menghasilkan dua tambahan.
Bayangan tampilan:
text
┌─────────────────────────────────┐
│ ❤️❤️❤️ (nyawa: 3) │
│ │
│ 👾 → 🙂✨ (kekebalan │
│ sesaat, berkedip) │
│ 💊 (item nyawa) │
└─────────────────────────────────┘Kriteria selesai:
- Game memakai
HasCollisionDetection, dan setiap objek yang bertabrakan punyaCollisionCallbacksbeserta hitbox. - Kerusakan dan pungutan terjadi di
onCollisionStart, bukanonCollision. - Untuk setiap pasangan, satu sisi saja yang bertanggung jawab atas efeknya — kamu bisa menunjuk sisi mana dan kenapa.
- Kekebalan sesaat berjalan setelah terkena, dan tandanya jelas terlihat.
- Hitbox pemain sedikit lebih kecil dari sprite-nya, dan kamu sudah merasakan bedanya dengan mencoba versi yang pas.
- Penggambaran hitbox dinyalakan setidaknya sekali selama pengerjaan.
Petunjuk: Kalau tabrakan tidak terdeteksi sama sekali dan tidak ada pesan apa pun, periksa kedua mixin — melewatkan salah satunya adalah kegagalan senyap paling umum di seluruh Bagian VI. Kalau justru sebaliknya, nyawa habis seketika saat tersentuh, itu tanda kamu memakai onCollision: ia dipanggil setiap frame selama keduanya masih bersentuhan, jadi satu sentuhan setengah detik berarti tiga puluh kali kerusakan. Dan kalau nyawa berkurang dua padahal hanya satu sentuhan, kemungkinan besar kedua komponen sama-sama memproses efeknya — keduanya memang menerima callback, dan kamu harus memilih satu penanggung jawab. Nyalakan penggambaran hitbox sebelum menebak apa pun; ia menjawab sebagian besar pertanyaan dalam hitungan detik.
Variasi 2: Meja Biliar — ⭐⭐ · 40–60 menit
Tantangan: Bangun meja dengan beberapa bola yang memantul dari dinding dan dari satu sama lain, melambat karena gesekan, dan berhenti sepenuhnya di bawah kecepatan tertentu. Bola yang masuk ke lubang di sudut dihapus.
Bayangan tampilan:
text
┌─────────────────────────────────┐
│ ○ ↘ │
│ ●────→ ○ (bertabrakan, │
│ arah berubah) │
│ ○ (melambat, lalu berhenti) ⚫│
│ (lubang)│
└─────────────────────────────────┘Kriteria selesai:
- Pantulan dinding memakai
ScreenHitboxatau pemeriksaan tepi, dan bekerja benar di keempat sisi termasuk sudut. - Tabrakan antar bola menukar arah dengan cara yang terlihat masuk akal.
- Bola berhenti sepenuhnya, tidak bergetar selamanya dengan kecepatan sangat kecil.
- Dua bola tidak pernah tersangkut saling menempel.
Petunjuk: Bola yang bergetar selamanya adalah gejala gesekan yang mengalikan kecepatan tanpa pernah mencapai nol — tetapkan ambang di bawahnya kecepatan dipaksa nol. Bola yang tersangkut biasanya karena keduanya sudah saling menembus saat tabrakan terdeteksi, sehingga frame berikutnya mendeteksi tabrakan lagi dan membalik arah kembali; pisahkan keduanya sedikit saat tabrakan pertama terdeteksi.
Variasi 3: Ribuan Objek — ⭐⭐⭐ · 45–60 menit
Tantangan: Isi layar dengan ratusan objek yang saling bertabrakan, lalu optimalkan sampai tetap mulus. Ukur frame rate di mode profile sebelum dan sesudah setiap perubahan.
Bayangan perbandingan:
text
Sebelum optimasi Sesudah optimasi
●●●●●●●●●●●●●●●●● ●●●●●●●●●●●●●●●●●
●●●●●●●●●●●●●●●●● ●●●●●●●●●●●●●●●●●
FPS (mode profile): ~22 FPS (mode profile): ~58
(objek diam ikut diperiksa) (objek diam ditandai pasif,
dilewati pemeriksaannya)Kriteria selesai:
- Objek yang diam ditandai sebagai pasif, dan kamu bisa menyebut berapa banyak pemeriksaan yang dihemat.
- Bentuk hitbox dipilih sadar — lingkaran untuk yang bulat, karena pemeriksaannya jauh lebih murah.
- Objek di luar layar tidak ikut diperiksa.
- Angka frame rate sebelum dan sesudah tercatat, diukur di mode profile.
Petunjuk: Deteksi tabrakan berbiaya kuadratik kalau setiap objek diperiksa terhadap setiap objek lain — dua ratus objek berarti sekitar dua puluh ribu pemeriksaan per frame. Menandai objek diam sebagai pasif menghilangkan seluruh pemeriksaan pasif-lawan-pasif sekaligus, dan biasanya itulah mayoritasnya. Ingat juga aturan dari Bab 13: angka frame rate di mode debug tidak berarti apa-apa.
Variasi 4: Bentuk yang Tidak Biasa — ⭐⭐⭐⭐ · 60–90 menit
Tantangan: Bangun deteksi untuk kasus yang tidak tertangani hitbox standar: peluru sangat cepat yang bisa menembus dinding tipis dalam satu frame, dan area berbentuk kerucut untuk jangkauan pandang musuh.
Bayangan tampilan:
text
┌─────────────────────────────────┐
│ ▓ (dinding tipis) │
│ ●──────────────▶ (peluru cepat, │
│ lintasan penuh diperiksa, │
│ bukan cuma titik akhirnya) │
│ │
│ 👾 │
│ ╱ ╲ (kerucut pandang) │
│ ╱ ╲ │
│ 🙂 di dalam → terdeteksi │
└─────────────────────────────────┘
🙂 di belakang musuh → diabaikan (di luar kerucut)Kriteria selesai:
- Peluru cepat tidak pernah menembus dinding, berapa pun kecepatannya.
- Kerucut pandang memakai pemeriksaan manual berbasis jarak dan sudut, bukan hitbox.
- Musuh hanya bereaksi pada pemain yang berada di dalam kerucut, bukan di belakangnya.
- Kedua mekanisme digambar saat mode debug supaya bisa diperiksa mata.
Petunjuk: Peluru yang menembus dinding adalah masalah "tunneling": pada kecepatan tinggi, posisi peluru melompat dari satu sisi dinding ke sisi lain tanpa pernah berada di dalamnya pada frame mana pun. Solusinya adalah memeriksa seluruh lintasan antara posisi lama dan baru, bukan hanya posisi barunya — sebuah pemeriksaan garis terhadap kotak. Untuk kerucut, dua pemeriksaan sederhana sudah cukup: jarak di bawah jangkauan, dan sudut antara arah hadap dengan arah ke pemain di bawah setengah lebar kerucut.
Ikhtisar
- Butuh dua hal: mixin
HasCollisionDetectionpada game, dan mixinCollisionCallbacks+ hitbox pada komponen. Melewatkan salah satunya membuat tabrakan tidak terdeteksi tanpa error apa pun. onCollisionStartdipanggil sekali (kerusakan, memungut item);onCollisionsetiap frame (kerusakan berkelanjutan);onCollisionEndsaat berpisah.- Memakai
onCollisionuntuk kerusakan sekali menghasilkan 60 kali kerusakan per detik. - Kedua komponen menerima callback. Tetapkan satu sisi sebagai penanggung jawab untuk setiap pasangan, atau efeknya berlipat.
- Hitbox pemain sedikit lebih kecil dari sprite membuat permainan terasa adil; hitbox musuh sedikit lebih besar membuat menembak terasa memuaskan.
- Kekebalan sesaat setelah terkena mencegah pemain kehilangan seluruh nyawa dalam sepersekian detik.
ScreenHitboxuntuk bereaksi terhadap tepi layar;clamplebih sederhana kalau kamu hanya ingin menahan objek di dalam.- Tandai objek diam sebagai
CollisionType.passive— mengurangi jumlah pemeriksaan secara drastis. - Lingkaran lebih murah daripada persegi, persegi lebih murah daripada poligon. Pakai lingkaran untuk objek yang jumlahnya banyak.
- Pakai
distanceToSquaredalih-alihdistanceTosaat hanya membandingkan jarak — menghindari akar kuadrat. debugMode = truemenggambar hitbox. Masalah paling umum yang terungkap:anchorhitbox tidak cocok dengananchorkomponen.
Berikutnya: Bab 47 — Timer.
Transkrip asli
Disintesis dari 5_flutter_flame/8_case-study-4-collision-detection.md (7 video: Collision Detection in games, Screen Boundary Detection and HitBox Rendering, Collision Detection UML Diagram, Coding Collision Detection, dan latihan-latihannya). Lihat PDF Flame Engine.