|
|
<!--
|
|
|
CO_OP_TRANSLATOR_METADATA:
|
|
|
{
|
|
|
"original_hash": "a6ce295ff03bb49df7a3e17e6e7100a0",
|
|
|
"translation_date": "2025-08-29T01:14:20+00:00",
|
|
|
"source_file": "6-space-game/4-collision-detection/README.md",
|
|
|
"language_code": "he"
|
|
|
}
|
|
|
-->
|
|
|
# בניית משחק חלל חלק 4: הוספת לייזר וזיהוי התנגשויות
|
|
|
|
|
|
## שאלון לפני השיעור
|
|
|
|
|
|
[שאלון לפני השיעור](https://ff-quizzes.netlify.app/web/quiz/35)
|
|
|
|
|
|
בשיעור הזה תלמדו איך לירות לייזרים באמצעות JavaScript! נוסיף שני דברים למשחק שלנו:
|
|
|
|
|
|
- **לייזר**: הלייזר נורה מהחללית של הגיבור שלכם כלפי מעלה.
|
|
|
- **זיהוי התנגשויות**: כחלק מיישום היכולת *לירות*, נוסיף גם כמה כללי משחק נחמדים:
|
|
|
- **לייזר פוגע באויב**: אויב מת אם לייזר פוגע בו.
|
|
|
- **לייזר פוגע בחלק העליון של המסך**: לייזר נהרס אם הוא פוגע בחלק העליון של המסך.
|
|
|
- **התנגשות בין אויב לגיבור**: אויב וגיבור נהרסים אם הם מתנגשים זה בזה.
|
|
|
- **אויב פוגע בתחתית המסך**: אויב וגיבור נהרסים אם האויב מגיע לתחתית המסך.
|
|
|
|
|
|
בקיצור, אתם -- *הגיבור* -- צריכים לפגוע בכל האויבים עם לייזר לפני שהם מצליחים להגיע לתחתית המסך.
|
|
|
|
|
|
✅ בצעו מחקר קטן על משחק המחשב הראשון שנכתב אי פעם. מה הייתה הפונקציונליות שלו?
|
|
|
|
|
|
בואו נהיה גיבורים יחד!
|
|
|
|
|
|
## זיהוי התנגשויות
|
|
|
|
|
|
איך מבצעים זיהוי התנגשויות? אנחנו צריכים לחשוב על אובייקטי המשחק שלנו כמלבנים שנעים. למה, אתם שואלים? ובכן, התמונה שמשמשת לציור אובייקט משחק היא מלבן: יש לה `x`, `y`, `width` ו-`height`.
|
|
|
|
|
|
אם שני מלבנים, כלומר גיבור ואויב, *מצטלבים*, יש לכם התנגשות. מה שצריך לקרות אז תלוי בכללי המשחק. כדי ליישם זיהוי התנגשויות, תצטרכו את הדברים הבאים:
|
|
|
|
|
|
1. דרך לקבל ייצוג מלבני של אובייקט משחק, משהו כזה:
|
|
|
|
|
|
```javascript
|
|
|
rectFromGameObject() {
|
|
|
return {
|
|
|
top: this.y,
|
|
|
left: this.x,
|
|
|
bottom: this.y + this.height,
|
|
|
right: this.x + this.width
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
2. פונקציית השוואה, הפונקציה יכולה להיראות כך:
|
|
|
|
|
|
```javascript
|
|
|
function intersectRect(r1, r2) {
|
|
|
return !(r2.left > r1.right ||
|
|
|
r2.right < r1.left ||
|
|
|
r2.top > r1.bottom ||
|
|
|
r2.bottom < r1.top);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
## איך הורסים דברים
|
|
|
|
|
|
כדי להרוס דברים במשחק, צריך להודיע למשחק שלא לצייר את הפריט הזה יותר בלולאת המשחק שמופעלת במרווחים מסוימים. דרך לעשות זאת היא לסמן אובייקט משחק כ-*מת* כאשר משהו קורה, כך:
|
|
|
|
|
|
```javascript
|
|
|
// collision happened
|
|
|
enemy.dead = true
|
|
|
```
|
|
|
|
|
|
לאחר מכן ניתן להמשיך ולסנן אובייקטים *מתים* לפני ציור המסך מחדש, כך:
|
|
|
|
|
|
```javascript
|
|
|
gameObjects = gameObject.filter(go => !go.dead);
|
|
|
```
|
|
|
|
|
|
## איך יורים לייזר
|
|
|
|
|
|
ירי לייזר מתורגם לתגובה לאירוע מקש וליצירת אובייקט שנע בכיוון מסוים. לכן, נצטרך לבצע את השלבים הבאים:
|
|
|
|
|
|
1. **יצירת אובייקט לייזר**: מהחלק העליון של החללית של הגיבור, שמתחיל לנוע כלפי מעלה לכיוון החלק העליון של המסך מיד עם יצירתו.
|
|
|
2. **חיבור קוד לאירוע מקש**: נצטרך לבחור מקש במקלדת שמייצג את פעולת הירי של הלייזר.
|
|
|
3. **יצירת אובייקט משחק שנראה כמו לייזר** כאשר המקש נלחץ.
|
|
|
|
|
|
## זמן המתנה ללייזר
|
|
|
|
|
|
הלייזר צריך להיירות בכל פעם שאתם לוחצים על מקש, למשל *רווח*. כדי למנוע מהמשחק לייצר יותר מדי לייזרים בזמן קצר, נצטרך לתקן זאת. התיקון הוא באמצעות יישום מה שנקרא *זמן המתנה*, טיימר, שמבטיח שלייזר יוכל להיירות רק בתדירות מסוימת. ניתן ליישם זאת כך:
|
|
|
|
|
|
```javascript
|
|
|
class Cooldown {
|
|
|
constructor(time) {
|
|
|
this.cool = false;
|
|
|
setTimeout(() => {
|
|
|
this.cool = true;
|
|
|
}, time)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
class Weapon {
|
|
|
constructor {
|
|
|
}
|
|
|
fire() {
|
|
|
if (!this.cooldown || this.cooldown.cool) {
|
|
|
// produce a laser
|
|
|
this.cooldown = new Cooldown(500);
|
|
|
} else {
|
|
|
// do nothing - it hasn't cooled down yet.
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
✅ חזרו לשיעור הראשון בסדרת משחק החלל כדי להזכיר לעצמכם את נושא *זמן ההמתנה*.
|
|
|
|
|
|
## מה לבנות
|
|
|
|
|
|
תיקחו את הקוד הקיים (שכבר ניקיתם ושיפרתם) מהשיעור הקודם, ותאריכו אותו. תוכלו להתחיל עם הקוד מחלק II או להשתמש בקוד ב-[חלק III - התחלה](../../../../../../../../../your-work).
|
|
|
|
|
|
> טיפ: הלייזר שתעבדו איתו כבר נמצא בתיקיית הנכסים שלכם ומוזכר בקוד שלכם.
|
|
|
|
|
|
- **הוסיפו זיהוי התנגשויות**, כאשר לייזר מתנגש במשהו, הכללים הבאים צריכים לחול:
|
|
|
1. **לייזר פוגע באויב**: אויב מת אם לייזר פוגע בו.
|
|
|
2. **לייזר פוגע בחלק העליון של המסך**: לייזר נהרס אם הוא פוגע בחלק העליון של המסך.
|
|
|
3. **התנגשות בין אויב לגיבור**: אויב וגיבור נהרסים אם הם מתנגשים זה בזה.
|
|
|
4. **אויב פוגע בתחתית המסך**: אויב וגיבור נהרסים אם האויב מגיע לתחתית המסך.
|
|
|
|
|
|
## צעדים מומלצים
|
|
|
|
|
|
אתרו את הקבצים שנוצרו עבורכם בתיקיית `your-work`. היא אמורה להכיל את הדברים הבאים:
|
|
|
|
|
|
```bash
|
|
|
-| assets
|
|
|
-| enemyShip.png
|
|
|
-| player.png
|
|
|
-| laserRed.png
|
|
|
-| index.html
|
|
|
-| app.js
|
|
|
-| package.json
|
|
|
```
|
|
|
|
|
|
תתחילו את הפרויקט שלכם בתיקיית `your_work` על ידי הקלדת:
|
|
|
|
|
|
```bash
|
|
|
cd your-work
|
|
|
npm start
|
|
|
```
|
|
|
|
|
|
הפעולה הזו תתחיל שרת HTTP בכתובת `http://localhost:5000`. פתחו דפדפן והזינו את הכתובת הזו, כרגע הוא אמור להציג את הגיבור ואת כל האויבים, שום דבר עדיין לא זז :).
|
|
|
|
|
|
### הוספת קוד
|
|
|
|
|
|
1. **הגדירו ייצוג מלבני של אובייקט המשחק, כדי לטפל בהתנגשויות** הקוד הבא מאפשר לכם לקבל ייצוג מלבני של `GameObject`. ערכו את מחלקת GameObject כדי להרחיב אותה:
|
|
|
|
|
|
```javascript
|
|
|
rectFromGameObject() {
|
|
|
return {
|
|
|
top: this.y,
|
|
|
left: this.x,
|
|
|
bottom: this.y + this.height,
|
|
|
right: this.x + this.width,
|
|
|
};
|
|
|
}
|
|
|
```
|
|
|
|
|
|
2. **הוסיפו קוד שבודק התנגשויות** זו תהיה פונקציה חדשה שבודקת אם שני מלבנים מצטלבים:
|
|
|
|
|
|
```javascript
|
|
|
function intersectRect(r1, r2) {
|
|
|
return !(
|
|
|
r2.left > r1.right ||
|
|
|
r2.right < r1.left ||
|
|
|
r2.top > r1.bottom ||
|
|
|
r2.bottom < r1.top
|
|
|
);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
3. **הוסיפו יכולת ירי לייזר**
|
|
|
1. **הוסיפו הודעת אירוע מקש**. מקש *רווח* צריך ליצור לייזר ממש מעל החללית של הגיבור. הוסיפו שלוש קבועות לאובייקט Messages:
|
|
|
|
|
|
```javascript
|
|
|
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
|
|
|
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
|
|
|
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
|
|
|
```
|
|
|
|
|
|
1. **טפלו במקש רווח**. ערכו את פונקציית `window.addEventListener` keyup כדי לטפל במקש רווח:
|
|
|
|
|
|
```javascript
|
|
|
} else if(evt.keyCode === 32) {
|
|
|
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
1. **הוסיפו מאזינים**. ערכו את פונקציית `initGame()` כדי לוודא שהגיבור יכול לירות כאשר מקש הרווח נלחץ:
|
|
|
|
|
|
```javascript
|
|
|
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
|
|
|
if (hero.canFire()) {
|
|
|
hero.fire();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
והוסיפו פונקציה חדשה `eventEmitter.on()` כדי להבטיח התנהגות כאשר אויב מתנגש בלייזר:
|
|
|
|
|
|
```javascript
|
|
|
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
|
|
|
first.dead = true;
|
|
|
second.dead = true;
|
|
|
})
|
|
|
```
|
|
|
|
|
|
1. **הזיזו אובייקט**, ודאו שהלייזר נע בהדרגה לחלק העליון של המסך. תיצרו מחלקת Laser חדשה שמרחיבה את `GameObject`, כפי שעשיתם קודם:
|
|
|
|
|
|
```javascript
|
|
|
class Laser extends GameObject {
|
|
|
constructor(x, y) {
|
|
|
super(x,y);
|
|
|
(this.width = 9), (this.height = 33);
|
|
|
this.type = 'Laser';
|
|
|
this.img = laserImg;
|
|
|
let id = setInterval(() => {
|
|
|
if (this.y > 0) {
|
|
|
this.y -= 15;
|
|
|
} else {
|
|
|
this.dead = true;
|
|
|
clearInterval(id);
|
|
|
}
|
|
|
}, 100)
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
1. **טפלו בהתנגשויות**, יישמו כללי התנגשות עבור הלייזר. הוסיפו פונקציה `updateGameObjects()` שבודקת אובייקטים מתנגשים:
|
|
|
|
|
|
```javascript
|
|
|
function updateGameObjects() {
|
|
|
const enemies = gameObjects.filter(go => go.type === 'Enemy');
|
|
|
const lasers = gameObjects.filter((go) => go.type === "Laser");
|
|
|
// laser hit something
|
|
|
lasers.forEach((l) => {
|
|
|
enemies.forEach((m) => {
|
|
|
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
|
|
|
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
|
|
|
first: l,
|
|
|
second: m,
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
gameObjects = gameObjects.filter(go => !go.dead);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
ודאו להוסיף את `updateGameObjects()` ללולאת המשחק שלכם ב-`window.onload`.
|
|
|
|
|
|
4. **יישמו זמן המתנה** ללייזר, כך שהוא יוכל להיירות רק בתדירות מסוימת.
|
|
|
|
|
|
לבסוף, ערכו את מחלקת Hero כך שתוכל לטפל בזמן המתנה:
|
|
|
|
|
|
```javascript
|
|
|
class Hero extends GameObject {
|
|
|
constructor(x, y) {
|
|
|
super(x, y);
|
|
|
(this.width = 99), (this.height = 75);
|
|
|
this.type = "Hero";
|
|
|
this.speed = { x: 0, y: 0 };
|
|
|
this.cooldown = 0;
|
|
|
}
|
|
|
fire() {
|
|
|
gameObjects.push(new Laser(this.x + 45, this.y - 10));
|
|
|
this.cooldown = 500;
|
|
|
|
|
|
let id = setInterval(() => {
|
|
|
if (this.cooldown > 0) {
|
|
|
this.cooldown -= 100;
|
|
|
} else {
|
|
|
clearInterval(id);
|
|
|
}
|
|
|
}, 200);
|
|
|
}
|
|
|
canFire() {
|
|
|
return this.cooldown === 0;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
בשלב הזה, למשחק שלכם יש קצת פונקציונליות! אתם יכולים לנווט עם מקשי החצים, לירות לייזר עם מקש הרווח, ואויבים נעלמים כשאתם פוגעים בהם. כל הכבוד!
|
|
|
|
|
|
---
|
|
|
|
|
|
## 🚀 אתגר
|
|
|
|
|
|
הוסיפו פיצוץ! הסתכלו על נכסי המשחק ב-[מאגר האמנות של החלל](../../../../6-space-game/solution/spaceArt/readme.txt) ונסו להוסיף פיצוץ כאשר לייזר פוגע בחייזר.
|
|
|
|
|
|
## שאלון אחרי השיעור
|
|
|
|
|
|
[שאלון אחרי השיעור](https://ff-quizzes.netlify.app/web/quiz/36)
|
|
|
|
|
|
## סקירה ולימוד עצמי
|
|
|
|
|
|
נסו לשחק עם המרווחים במשחק שלכם עד כה. מה קורה כשאתם משנים אותם? קראו עוד על [אירועי תזמון ב-JavaScript](https://www.freecodecamp.org/news/javascript-timing-events-settimeout-and-setinterval/).
|
|
|
|
|
|
## משימה
|
|
|
|
|
|
[חקירת התנגשויות](assignment.md)
|
|
|
|
|
|
---
|
|
|
|
|
|
**כתב ויתור**:
|
|
|
מסמך זה תורגם באמצעות שירות תרגום מבוסס בינה מלאכותית [Co-op Translator](https://github.com/Azure/co-op-translator). למרות שאנו שואפים לדיוק, יש לקחת בחשבון שתרגומים אוטומטיים עשויים להכיל שגיאות או אי-דיוקים. המסמך המקורי בשפתו המקורית נחשב למקור הסמכותי. למידע קריטי, מומלץ להשתמש בתרגום מקצועי על ידי בני אדם. איננו נושאים באחריות לכל אי-הבנה או פרשנות שגויה הנובעת משימוש בתרגום זה. |