# Δημιουργία Παιχνιδιού Διαστήματος Μέρος 4: Προσθήκη Λέιζερ και Ανίχνευση Συγκρούσεων ## Κουίζ Πριν το Μάθημα [Κουίζ πριν το μάθημα](https://ff-quizzes.netlify.app/web/quiz/35) Σε αυτό το μάθημα θα μάθετε πώς να πυροβολείτε λέιζερ με JavaScript! Θα προσθέσουμε δύο πράγματα στο παιχνίδι μας: - **Ένα λέιζερ**: αυτό το λέιζερ εκτοξεύεται από το διαστημόπλοιο του ήρωά σας και κινείται κάθετα προς τα πάνω. - **Ανίχνευση συγκρούσεων**, ως μέρος της υλοποίησης της δυνατότητας να *πυροβολείτε*, θα προσθέσουμε και κάποιους ωραίους κανόνες παιχνιδιού: - **Το λέιζερ χτυπάει εχθρό**: Ο εχθρός πεθαίνει αν χτυπηθεί από λέιζερ. - **Το λέιζερ χτυπάει την κορυφή της οθόνης**: Το λέιζερ καταστρέφεται αν φτάσει στην κορυφή της οθόνης. - **Σύγκρουση εχθρού και ήρωα**: Εχθρός και ήρωας καταστρέφονται αν συγκρουστούν. - **Ο εχθρός φτάνει στο κάτω μέρος της οθόνης**: Εχθρός και ήρωας καταστρέφονται αν ο εχθρός φτάσει στο κάτω μέρος της οθόνης. Με λίγα λόγια, εσείς -- *ο ήρωας* -- πρέπει να χτυπήσετε όλους τους εχθρούς με λέιζερ πριν καταφέρουν να φτάσουν στο κάτω μέρος της οθόνης. ✅ Κάντε μια μικρή έρευνα για το πρώτο παιχνίδι υπολογιστή που δημιουργήθηκε ποτέ. Ποια ήταν η λειτουργικότητά του; Ας γίνουμε ηρωικοί μαζί! ## Ανίχνευση Συγκρούσεων Πώς κάνουμε ανίχνευση συγκρούσεων; Πρέπει να σκεφτούμε τα αντικείμενα του παιχνιδιού μας ως ορθογώνια που κινούνται. Γιατί αυτό, μπορεί να ρωτήσετε; Λοιπόν, η εικόνα που χρησιμοποιείται για να σχεδιάσει ένα αντικείμενο παιχνιδιού είναι ένα ορθογώνιο: έχει `x`, `y`, `πλάτος` και `ύψος`. Αν δύο ορθογώνια, δηλαδή ένας ήρωας και ένας εχθρός, *τέμνονται*, έχουμε σύγκρουση. Τι πρέπει να συμβεί τότε εξαρτάται από τους κανόνες του παιχνιδιού. Για να υλοποιήσετε την ανίχνευση συγκρούσεων, χρειάζεστε τα εξής: 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. **Δημιουργία ενός αντικειμένου παιχνιδιού που μοιάζει με λέιζερ** όταν πατηθεί το πλήκτρο. ## Χρονικό διάστημα για το λέιζερ Το λέιζερ πρέπει να πυροβολείται κάθε φορά που πατάτε ένα πλήκτρο, όπως το *space* για παράδειγμα. Για να αποτρέψουμε το παιχνίδι από το να δημιουργεί πάρα πολλά λέιζερ σε σύντομο χρονικό διάστημα, πρέπει να διορθώσουμε αυτό το πρόβλημα. Η λύση είναι να υλοποιήσουμε ένα λεγόμενο *χρονικό διάστημα*, έναν χρονοδιακόπτη, που διασφαλίζει ότι ένα λέιζερ μπορεί να πυροβοληθεί μόνο κάθε τόσο. Μπορείτε να το υλοποιήσετε με τον εξής τρόπο: ```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. } } } ``` ✅ Ανατρέξτε στο μάθημα 1 της σειράς παιχνιδιών διαστήματος για να θυμηθείτε τα *χρονικά διαστήματα*. ## Τι να δημιουργήσετε Θα πάρετε τον υπάρχοντα κώδικα (τον οποίο θα πρέπει να έχετε καθαρίσει και αναδιαρθρώσει) από το προηγούμενο μάθημα και θα τον επεκτείνετε. Ξεκινήστε είτε με τον κώδικα από το μέρος 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 Server στη διεύθυνση `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. **Προσθέστε μήνυμα γεγονότος πλήκτρου**. Το πλήκτρο *space* θα πρέπει να δημιουργεί ένα λέιζερ ακριβώς πάνω από το διαστημόπλοιο του ήρωα. Προσθέστε τρεις σταθερές στο αντικείμενο Messages: ```javascript KEY_EVENT_SPACE: "KEY_EVENT_SPACE", COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER", COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO", ``` 1. **Χειριστείτε το πλήκτρο space**. Επεξεργαστείτε τη συνάρτηση `window.addEventListener` keyup για να χειριστείτε το πλήκτρο space: ```javascript } else if(evt.keyCode === 32) { eventEmitter.emit(Messages.KEY_EVENT_SPACE); } ``` 1. **Προσθέστε listeners**. Επεξεργαστείτε τη συνάρτηση `initGame()` για να διασφαλίσετε ότι ο ήρωας μπορεί να πυροβολήσει όταν πατηθεί το πλήκτρο space: ```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; } } ``` Σε αυτό το σημείο, το παιχνίδι σας έχει κάποια λειτουργικότητα! Μπορείτε να πλοηγηθείτε με τα πλήκτρα βελών, να πυροβολήσετε λέιζερ με το πλήκτρο space και οι εχθροί εξαφανίζονται όταν τους χτυπάτε. Μπράβο! --- ## 🚀 Πρόκληση Προσθέστε μια έκρηξη! Ρίξτε μια ματιά στα γραφικά του παιχνιδιού στον [φάκελο Space Art](../../../../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) --- **Αποποίηση Ευθύνης**: Αυτό το έγγραφο έχει μεταφραστεί χρησιμοποιώντας την υπηρεσία αυτόματης μετάφρασης AI [Co-op Translator](https://github.com/Azure/co-op-translator). Παρόλο που καταβάλλουμε προσπάθειες για ακρίβεια, παρακαλούμε να έχετε υπόψη ότι οι αυτόματες μεταφράσεις ενδέχεται να περιέχουν σφάλματα ή ανακρίβειες. Το πρωτότυπο έγγραφο στη μητρική του γλώσσα θα πρέπει να θεωρείται η αυθεντική πηγή. Για κρίσιμες πληροφορίες, συνιστάται επαγγελματική ανθρώπινη μετάφραση. Δεν φέρουμε ευθύνη για τυχόν παρεξηγήσεις ή εσφαλμένες ερμηνείες που προκύπτουν από τη χρήση αυτής της μετάφρασης.