التصادمات وحالة اللعبة
أضف القواعد وشروط الفوز
التصادمات وحالة اللعبة درس مجاني في Learn Rust Coding على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Learn Rust Coding، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.
أساسيات تصادم AABB
أبسط اختبار للتصادم هو تداخل صندوقي الإحاطة المحاذيين للمحاور (AABB). يتصادم مستطيلان عندما يتداخلان على محوري x وy معًا.
بالنسبة إلى Sprites، تتميز هذه الطريقة بالسرعة وتكفي لكثير من الألعاب ثنائية الأبعاد قبل اللجوء إلى محرك فيزياء متكامل.
fn overlaps(a_pos: Vec2, a_size: Vec2, b_pos: Vec2, b_size: Vec2) -> bool {
(a_pos.x - b_pos.x).abs() < (a_size.x + b_size.x) * 0.5
&& (a_pos.y - b_pos.y).abs() < (a_size.y + b_size.y) * 0.5
}مكوّن Collider
أضيفوا مكوّن Collider يحتوي على أنصاف أبعاد كل كيان أو حجمه الكامل. ثم تقرأ الأنظمة التحويل وCollider لاختبار التداخلات.
يتيح الاحتفاظ بالحجم في مكوّن مستقل لكيانات مختلفة مشاركة نظام التصادم نفسه.
#[derive(Component)]
struct Collider {
size: Vec2,
}اكتشاف التصادمات في نظام
لمقارنة لاعب بعدة عوائق، استعلموا عن اللاعب مرة واحدة وكرّروا على العوائق. يمنع استخدام استعلامين منفصلين استعارة البيانات نفسها مرتين.
عند حدوث تداخل، تستجيبون له — بإحداث ضرر أو ارتداد أو حذف — عبر Commands أو بتعديل المكوّنات.
fn detect(
player: Query<(&Transform, &Collider), With<Player>>,
walls: Query<(&Transform, &Collider), With<Wall>>,
) {
let (pt, pc) = player.single().unwrap();
for (wt, wc) in &walls {
if overlaps(pt.translation.truncate(), pc.size, wt.translation.truncate(), wc.size) {
info!("hit a wall");
}
}
}الاستجابة باستخدام الأحداث
بدلًا من معالجة النتائج داخل النظام مباشرةً، أرسلوا Event مخصّصًا. يكتشف نظام التصادمات ويصدر الأحداث، بينما يقرأها نظام آخر ويطبّق التأثيرات.
يفصل ذلك الاكتشاف عن الاستجابة ويحافظ على صغر كل نظام.
#[derive(Event)]
struct CollisionEvent { entity: Entity }
fn emit(mut writer: EventWriter<CollisionEvent>, e: Entity) {
writer.send(CollisionEvent { entity: e });
}قراءة الأحداث
يسحب نظام القراءة الأحداث الموجودة في قائمة الانتظار باستخدام EventReader ويتعامل مع كل حدث. تبقى الأحداث لمدة إطارين، لذلك يراها أي قارئ.
يتوسع نمط النشر والاشتراك هذا بسهولة: عدد كبير من المنتجين والمستهلكين من دون ترابط وثيق.
fn on_hit(mut reader: EventReader<CollisionEvent>, mut commands: Commands) {
for ev in reader.read() {
commands.entity(ev.entity).despawn();
}
}تحديث النتيجة
عندما يجمع اللاعب عنصرًا، عدّلوا مورد Score العام باستخدام ResMut.
لأن الموارد فريدة، يمكن لأي نظام قراءة النتيجة أو تحديثها من دون تمريرها يدويًا.
fn collect(mut score: ResMut<Score>, mut reader: EventReader<PickupEvent>) {
for _ in reader.read() {
score.0 += 10;
}
}الحالات: الصورة الكبرى
تنتقل الألعاب بين أوضاع مختلفة — القائمة، واللعب، والإيقاف المؤقت، وانتهاء اللعبة. يصمم Bevy هذه الأوضاع باستخدام تعداد States المسجّل عبر init_state.
تكون حالة واحدة فقط نشطة في كل مرة، ويمكن تقييد تشغيل الأنظمة بحالات محددة.
#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
enum GameState {
#[default]
Menu,
Playing,
GameOver,
}تسجيل الحالة
أضيفوا الحالة إلى التطبيق باستخدام init_state. بعد ذلك يتتبع Bevy القيمة الحالية ويتيح موردي State وNextState.
اقرؤوا الحالة الحالية باستخدام Res<State<GameState>> عندما يحتاج النظام إلى معرفة الوضع.
App::new()
.add_plugins(DefaultPlugins)
.init_state::<GameState>()
.run();تشغيل الأنظمة في حالة
قيّدوا الأنظمة بحالة باستخدام شرط run_if المتمثل في in_state. لا تعمل الحركة والتصادمات إلا أثناء Playing.
هكذا يعمل الإيقاف المؤقت: أوقفوا أنظمة اللعب بمغادرة حالة Playing، وعندها لن تعمل ببساطة.
app.add_systems(
Update,
(drive, detect).run_if(in_state(GameState::Playing)),
);انتقال الحالات
لتغيير الحالة، اضبطوا NextState من أي نظام. يطبّق Bevy الانتقال عند حدود الإطار التالي.
استخدموا جدولي OnEnter وOnExit لإنشاء واجهة المستخدم عند الدخول إلى حالة أو تنظيفها عند مغادرتها.
fn lose(mut next: ResMut<NextState<GameState>>, health: Res<Health>) {
if health.0 == 0 {
next.set(GameState::GameOver);
}
}التنظيف عند الخروج
عند مغادرة Playing، احذفوا كيانات اللعب حتى تبدأ الجولة التالية بحالة نظيفة. أضيفوا إليها وسمًا مميزًا واحذفوا كل كيان يحمله.
سجّلوا عملية التنظيف في OnExit(GameState::Playing) حتى تعمل مرة واحدة بالضبط لكل انتقال.
fn cleanup(mut commands: Commands, q: Query<Entity, With<GameEntity>>) {
for e in &q {
commands.entity(e).despawn();
}
}
// app.add_systems(OnExit(GameState::Playing), cleanup);تحقّق سريع
فكّروا في كيفية إيقاف اللعب مؤقتًا بطريقة سليمة.
مراجعة: التصادمات وحالة اللعبة
تكشف اختبارات AABB مع مكوّن Collider التداخلات؛ وتفصل الأحداث الاكتشاف عن الاستجابة، بينما يتتبع مورد Score التقدم. ينظّم نظام States، مع run_if وNextState وOnEnter/OnExit، القوائم واللعب وانتهاء اللعبة.
أصبح لديكم الآن المسار الكامل: الإنشاء والتحكم والتصادم والانتقال. ابنوا لعبة Bevy متكاملة باستخدام هذه الأجزاء.
الأسئلة الشائعة
هل درس «التصادمات وحالة اللعبة» مجاني؟
نعم — نص درس «التصادمات وحالة اللعبة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Learn Rust Coding، انتقل إلى CoddyKit PRO. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.
ماذا ستتعلم في «التصادمات وحالة اللعبة»؟
أضف القواعد وشروط الفوز تتمرن على Learn Rust Coding مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Learn Rust Coding؟
لا تُشترط خبرة سابقة. Learn Rust Coding على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «التصادمات وحالة اللعبة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Learn Rust Coding هذا؟
نعم. كل درس في Learn Rust Coding يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- عقلية ECS
- إنشاء الكيانات وتحريكها
- التعامل مع الإدخال والوقت
- التصادمات وحالة اللعبة