|
|
4 weeks ago | |
|---|---|---|
| .. | ||
| README.md | 4 weeks ago | |
| assignment.md | 1 month ago | |
README.md
בניית משחק חלל חלק 3: הוספת תנועה
journey
title Your Game Animation Journey
section Movement Basics
Understand motion principles: 3: Student
Learn coordinate updates: 4: Student
Implement basic movement: 4: Student
section Player Controls
Handle keyboard events: 4: Student
Prevent default behaviors: 5: Student
Create responsive controls: 5: Student
section Game Systems
Build game loop: 5: Student
Manage object lifecycle: 5: Student
Implement pub/sub pattern: 5: Student
חשבו על המשחקים האהובים עליכם – מה שהופך אותם למרתקים זה לא רק הגרפיקה היפה, אלא גם האופן שבו הכל זז ומגיב לפעולות שלכם. כרגע, משחק החלל שלכם הוא כמו ציור יפה, אבל אנחנו עומדים להוסיף תנועה שתביא אותו לחיים.
כשמהנדסי נאס"א תכנתו את מחשב ההנחיה למשימות אפולו, הם עמדו בפני אתגר דומה: איך לגרום לחללית להגיב לפקודות הטייס תוך שמירה אוטומטית על תיקוני מסלול? העקרונות שנלמד היום מהדהדים את אותם רעיונות – ניהול תנועה בשליטת שחקן לצד התנהגויות מערכת אוטומטיות.
בשיעור הזה תלמדו איך לגרום לחלליות להחליק על המסך, להגיב לפקודות שחקן וליצור דפוסי תנועה חלקים. נפרק הכל למושגים שניתן להבין בקלות ושבונים אחד על השני באופן טבעי.
בסוף, השחקנים יטיסו את ספינת הגיבור שלהם על המסך בזמן שספינות אויב יסיירו מעל. חשוב יותר, תבינו את העקרונות המרכזיים שמניעים מערכות תנועה במשחקים.
mindmap
root((Game Animation))
Movement Types
Player Controlled
Automatic Motion
Physics Based
Scripted Paths
Event Handling
Keyboard Input
Mouse Events
Touch Controls
Default Prevention
Game Loop
Update Logic
Render Frame
Clear Canvas
Frame Rate Control
Object Management
Position Updates
Collision Detection
Lifecycle Management
State Tracking
Communication
Pub/Sub Pattern
Event Emitters
Message Passing
Loose Coupling
שאלון לפני השיעור
הבנת תנועה במשחקים
משחקים מתעוררים לחיים כשהדברים מתחילים לזוז, ויש שתי דרכים בסיסיות שבהן זה קורה:
- תנועה בשליטת שחקן: כשאתם לוחצים על מקש או מקליקים בעכבר, משהו זז. זה החיבור הישיר ביניכם לבין עולם המשחק.
- תנועה אוטומטית: כשהמשחק עצמו מחליט להזיז דברים – כמו ספינות אויב שצריכות לסייר על המסך בלי קשר למה שאתם עושים.
להזיז אובייקטים על מסך מחשב זה פשוט יותר ממה שאתם חושבים. זוכרים את הקואורדינטות x ו-y משיעורי מתמטיקה? בדיוק עם זה אנחנו עובדים כאן. כשגלילאו עקב אחרי ירחי צדק בשנת 1610, הוא למעשה עשה את אותו הדבר – תיעד מיקומים לאורך זמן כדי להבין דפוסי תנועה.
להזיז דברים על המסך זה כמו ליצור אנימציה בספרון דפדוף – צריך לעקוב אחרי שלושה שלבים פשוטים:
flowchart LR
A["Frame N"] --> B["Update Positions"]
B --> C["Clear Canvas"]
C --> D["Draw Objects"]
D --> E["Frame N+1"]
E --> F{Continue?}
F -->|Yes| B
F -->|No| G["Game Over"]
subgraph "Animation Cycle"
H["1. Calculate new positions"]
I["2. Erase previous frame"]
J["3. Render new frame"]
end
style B fill:#e1f5fe
style C fill:#ffebee
style D fill:#e8f5e8
- עדכון המיקום – לשנות איפה האובייקט צריך להיות (אולי להזיז אותו 5 פיקסלים ימינה)
- מחיקת הפריים הישן – לנקות את המסך כדי שלא יופיעו שאריות רפאים בכל מקום
- ציור הפריים החדש – למקם את האובייקט במקום החדש שלו
עשו זאת מספיק מהר, ובום! יש לכם תנועה חלקה שמרגישה טבעית לשחקנים.
כך זה יכול להיראות בקוד:
// Set the hero's location
hero.x += 5;
// Clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
מה הקוד הזה עושה:
- מעדכן את קואורדינטת ה-x של הגיבור ב-5 פיקסלים כדי להזיז אותו אופקית
- מנקה את כל אזור הקנבס כדי להסיר את הפריים הקודם
- ממלא את הקנבס בצבע רקע שחור
- מצייר מחדש את תמונת הגיבור במיקומו החדש
✅ האם אתם יכולים לחשוב על סיבה מדוע ציור מחדש של הגיבור בפריימים רבים בשנייה עשוי לגרום לעלויות ביצועים? קראו על חלופות לדפוס הזה.
טיפול באירועי מקלדת
כאן אנחנו מחברים את קלט השחקן לפעולת המשחק. כשמישהו לוחץ על מקש הרווח כדי לירות לייזר או מקיש על מקש חץ כדי להתחמק מאסטרואיד, המשחק שלכם צריך לזהות ולהגיב לקלט הזה.
אירועי מקלדת מתרחשים ברמת החלון, כלומר כל חלון הדפדפן שלכם מקשיב ללחיצות המקלדת האלה. לחיצות עכבר, לעומת זאת, יכולות להיות קשורות לאלמנטים ספציפיים (כמו לחיצה על כפתור). עבור משחק החלל שלנו, נתמקד בשליטת מקלדת מכיוון שזה מה שנותן לשחקנים את התחושה הקלאסית של משחק ארקייד.
זה מזכיר לי איך מפעילי טלגרף במאה ה-19 היו צריכים לתרגם קלט קוד מורס להודעות משמעותיות – אנחנו עושים משהו דומה, מתרגמים לחיצות מקשים לפקודות משחק.
כדי לטפל באירוע, עליכם להשתמש בשיטת addEventListener() של החלון ולספק לה שני פרמטרים. הפרמטר הראשון הוא שם האירוע, למשל keyup. הפרמטר השני הוא הפונקציה שצריכה להיות מופעלת כתוצאה מהתרחשות האירוע.
הנה דוגמה:
window.addEventListener('keyup', (evt) => {
// evt.key = string representation of the key
if (evt.key === 'ArrowUp') {
// do something
}
});
פירוק מה שקורה כאן:
- מקשיב לאירועי מקלדת על כל החלון
- לוכד את אובייקט האירוע שמכיל מידע על איזה מקש נלחץ
- בודק אם המקש שנלחץ תואם למקש ספציפי (במקרה הזה, חץ למעלה)
- מבצע קוד כאשר התנאי מתקיים
עבור אירועי מקשים יש שתי תכונות על האירוע שניתן להשתמש בהן כדי לראות איזה מקש נלחץ:
key- זהו ייצוג מחרוזת של המקש שנלחץ, למשל'ArrowUp'keyCode- זהו ייצוג מספרי, למשל37, שמתאים ל-ArrowLeft
✅ מניפולציה של אירועי מקשים שימושית גם מחוץ לפיתוח משחקים. אילו שימושים נוספים אתם יכולים לחשוב עליהם עבור הטכניקה הזו?
sequenceDiagram
participant User
participant Browser
participant EventSystem
participant GameLogic
participant Hero
User->>Browser: Presses ArrowUp key
Browser->>EventSystem: keydown event
EventSystem->>EventSystem: preventDefault()
EventSystem->>GameLogic: emit('KEY_EVENT_UP')
GameLogic->>Hero: hero.y -= 5
Hero->>Hero: Update position
Note over Browser,GameLogic: Event flow prevents browser defaults
Note over GameLogic,Hero: Pub/sub pattern enables clean communication
מקשים מיוחדים: שימו לב!
לחלק מהמפתחות יש התנהגויות מובנות בדפדפן שיכולות להפריע למשחק שלכם. מקשי החצים גוללים את הדף ומקש הרווח מקפיץ למטה – התנהגויות שאתם לא רוצים כשמישהו מנסה להטיס את החללית שלו.
אנחנו יכולים למנוע את ההתנהגויות המובנות האלה ולתת למשחק שלנו לטפל בקלט במקום. זה דומה לאיך שמתכנתים מוקדמים היו צריכים לעקוף הפרעות מערכת כדי ליצור התנהגויות מותאמות אישית – אנחנו פשוט עושים את זה ברמת הדפדפן. הנה איך:
const onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Arrow keys
case 32:
e.preventDefault();
break; // Space
default:
break; // do not block other keys
}
};
window.addEventListener('keydown', onKeyDown);
הבנת קוד המניעה הזה:
- בודק קודי מקשים ספציפיים שעשויים לגרום להתנהגות לא רצויה בדפדפן
- מונע את הפעולה המובנית של הדפדפן עבור מקשי החצים ומקש הרווח
- מאפשר למקשים אחרים לפעול כרגיל
- משתמש ב-
e.preventDefault()כדי לעצור את ההתנהגות המובנית של הדפדפן
🔄 בדיקה פדגוגית
הבנת טיפול באירועים: לפני המעבר לתנועה אוטומטית, ודאו שאתם יכולים:
- ✅ להסביר את ההבדל בין אירועי
keydownו-keyup - ✅ להבין מדוע אנחנו מונעים התנהגויות מובנות של הדפדפן
- ✅ לתאר איך מאזיני אירועים מחברים קלט משתמש ללוגיקת המשחק
- ✅ לזהות אילו מקשים עשויים להפריע לשליטת המשחק
מבחן עצמי מהיר: מה היה קורה אם לא הייתם מונעים את ההתנהגות המובנית עבור מקשי החצים? תשובה: הדפדפן היה גולל את הדף, מה שהיה מפריע לתנועת המשחק
ארכיטקטורת מערכת האירועים: עכשיו אתם מבינים:
- האזנה ברמת החלון: לכידת אירועים ברמת הדפדפן
- תכונות אובייקט האירוע: מחרוזות
keyמול מספריkeyCode - מניעת ברירת מחדל: עצירת התנהגויות לא רצויות של הדפדפן
- לוגיקה מותנית: תגובה לשילובי מקשים ספציפיים
תנועה שמונעת על ידי המשחק
עכשיו נדבר על אובייקטים שזזים ללא קלט שחקן. חשבו על ספינות אויב שמפליגות על המסך, כדורים שעפים בקווים ישרים או עננים שמרחפים ברקע. התנועה האוטונומית הזו גורמת לעולם המשחק שלכם להרגיש חי גם כשאף אחד לא נוגע בשליטה.
אנחנו משתמשים בטיימרים מובנים של JavaScript כדי לעדכן מיקומים במרווחים קבועים. הרעיון הזה דומה לאיך שעונים עם מטוטלת עובדים – מנגנון קבוע שמפעיל פעולות מתוזמנות באופן עקבי. הנה כמה זה פשוט:
const id = setInterval(() => {
// Move the enemy on the y axis
enemy.y += 10;
}, 100);
מה הקוד הזה עושה:
- יוצר טיימר שרץ כל 100 מילישניות
- מעדכן את קואורדינטת ה-y של האויב ב-10 פיקסלים בכל פעם
- שומר את מזהה המרווח כדי שנוכל לעצור אותו מאוחר יותר אם צריך
- מזיז את האויב כלפי מטה על המסך באופן אוטומטי
לולאת המשחק
הנה הרעיון שמחבר הכל יחד – לולאת המשחק. אם המשחק שלכם היה סרט, לולאת המשחק הייתה מקרן הסרטים, שמראה פריים אחרי פריים כל כך מהר שהכל נראה כאילו זז בצורה חלקה.
לכל משחק יש אחת מהלולאות האלה שרצה מאחורי הקלעים. זו פונקציה שמעדכנת את כל אובייקטי המשחק, מציירת מחדש את המסך, וחוזרת על התהליך הזה ברציפות. זה עוקב אחרי הגיבור שלכם, כל האויבים, כל הלייזרים שעפים – כל מצב המשחק.
הרעיון הזה מזכיר לי איך אנימטורים מוקדמים כמו וולט דיסני היו צריכים לצייר מחדש דמויות פריים אחרי פריים כדי ליצור את אשליית התנועה. אנחנו עושים את אותו הדבר, רק עם קוד במקום עפרונות.
כך לולאת משחק יכולה להיראות בדרך כלל, בקוד:
flowchart TD
A["Start Game Loop"] --> B["Clear Canvas"]
B --> C["Fill Background"]
C --> D["Update Game Objects"]
D --> E["Draw Hero"]
E --> F["Draw Enemies"]
F --> G["Draw UI Elements"]
G --> H["Wait for Next Frame"]
H --> I{Game Running?}
I -->|Yes| B
I -->|No| J["End Game"]
subgraph "Frame Rate Control"
K["60 FPS = 16.67ms"]
L["30 FPS = 33.33ms"]
M["10 FPS = 100ms"]
end
style B fill:#ffebee
style D fill:#e1f5fe
style E fill:#e8f5e8
style F fill:#e8f5e8
const gameLoopId = setInterval(() => {
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}
gameLoop();
}, 200);
הבנת מבנה לולאת המשחק:
- מנקה את כל הקנבס כדי להסיר את הפריים הקודם
- ממלא את הרקע בצבע אחיד
- מצייר את כל אובייקטי המשחק במיקומם הנוכחי
- חוזר על התהליך הזה כל 200 מילישניות כדי ליצור אנימציה חלקה
- מנהל את קצב הפריימים על ידי שליטה בתזמון המרווחים
המשך משחק החלל
עכשיו נוסיף תנועה לסצנה הסטטית שבניתם קודם. אנחנו עומדים להפוך אותה מצילום מסך לחוויה אינטראקטיבית. נעבוד על זה שלב אחר שלב כדי להבטיח שכל חלק נבנה על הקודם.
קחו את הקוד מהמקום שבו הפסקנו בשיעור הקודם (או התחילו עם הקוד בתיקיית Part II- starter אם אתם צריכים התחלה חדשה).
הנה מה שאנחנו בונים היום:
- שליטת גיבור: מקשי החצים יטיסו את החללית שלכם על המסך
- תנועת אויבים: ספינות החייזרים יתחילו את ההתקדמות שלהן
בואו נתחיל ליישם את התכונות האלה.
שלבים מומלצים
אתרו את הקבצים שנוצרו עבורכם בתיקיית your-work. היא אמורה להכיל את הדברים הבאים:
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
תתחילו את הפרויקט שלכם בתיקיית your-work על ידי הקלדת:
cd your-work
npm start
מה הפקודה הזו עושה:
- מנווטת לתיקיית הפרויקט שלכם
- מתחילה שרת HTTP בכתובת
http://localhost:5000 - מגישה את קבצי המשחק שלכם כדי שתוכלו לבדוק אותם בדפדפן
הפקודה הזו תתחיל שרת HTTP בכתובת http://localhost:5000. פתחו דפדפן והכניסו את הכתובת הזו, כרגע זה אמור להציג את הגיבור ואת כל האויבים; שום דבר עדיין לא זז!
הוסיפו קוד
-
הוסיפו אובייקטים ייעודיים עבור
hero,enemyו-game object, הם צריכים לכלול תכונותxו-y. (זכרו את החלק על Inheritance or composition).רמז:
game objectצריך להיות זה עםxו-yוהיכולת לצייר את עצמו על קנבס.טיפ: התחילו בהוספת מחלקת
GameObjectחדשה עם הבנאי שלה כפי שמוצג למטה, ואז ציירו אותה על הקנבס:class GameObject { constructor(x, y) { this.x = x; this.y = y; this.dead = false; this.type = ""; this.width = 0; this.height = 0; this.img = undefined; } draw(ctx) { ctx.drawImage(this.img, this.x, this.y, this.width, this.height); } }הבנת מחלקת הבסיס הזו:
- מגדירה תכונות משותפות שכל אובייקטי המשחק חולקים (מיקום, גודל, תמונה)
- כוללת דגל
deadכדי לעקוב אם האובייקט צריך להיות מוסר - מספקת שיטת
draw()שמציירת את האובייקט על הקנבס - מגדירה ערכי ברירת מחדל לכל התכונות שהמחלקות הילדות יכולות להחליף
classDiagram class GameObject { +x: number +y: number +dead: boolean +type: string +width: number +height: number +img: Image +draw(ctx) } class Hero { +speed: number +type: "Hero" +width: 98 +height: 75 } class Enemy { +type: "Enemy" +width: 98 +height: 50 +setInterval() } GameObject <|-- Hero GameObject <|-- Enemy class EventEmitter { +listeners: object +on(message, listener) +emit(message, payload) }עכשיו, הרחיבו את
GameObjectכדי ליצור אתHeroו-Enemy:class Hero extends GameObject { constructor(x, y) { super(x, y); this.width = 98; this.height = 75; this.type = "Hero"; this.speed = 5; } }class Enemy extends GameObject { constructor(x, y) { super(x, y); this.width = 98; this.height = 50; this.type = "Enemy"; const id = setInterval(() => { if (this.y < canvas.height - this.height) { this.y += 5; } else { console.log('Stopped at', this.y); clearInterval(id); } }, 300); } }מושגים מרכזיים במחלקות האלה:
- יורש מ-
GameObjectבאמצעות מילת המפתחextends - קורא לבנאי ההורה עם
super(x, y) - מגדיר מידות ותכונות ספציפיות לכל סוג אובייקט
- מיישם תנועה אוטומטית עבור אויבים באמצעות
setInterval()
-
הוסיפו מאזיני אירועים למקשים כדי לטפל בניווט מקשים (להזיז את הגיבור למעלה/למטה שמאלה/ימינה)
זכרו שזה מערכת קרטזית, פינה שמאלית עליונה היא
0,0. גם זכרו להוסיף קוד לעצירת התנהגות ברירת מחדלטיפ: צרו את פונקציית
onKeyDownשלכם וצרפו אותה לחלון:const onKeyDown = function (e) { console.log(e.keyCode); // Add the code from the lesson above to stop default behavior switch (e.keyCode) { case 37: case 39: case 38: case 40: // Arrow keys case 32: e.preventDefault(); break; // Space default: break; // do not block other keys } }; window.addEventListener("keydown", onKeyDown);מה עושה מאזין האירועים הזה:
- מקשיב לאירועי keydown על כל החלון
- מתעד את קוד המקש כדי לעזור לכם לבדוק אילו מקשים נלחצים
- מונע התנהגות ברירת מחדל של הדפדפן עבור מקשי החצים ומקש הרווח
- מאפשר למקשים אחרים לפעול כרגיל
בדקו את קונסולת הדפדפן שלכם בשלב הזה, וצפו בלחיצות המקלדת שמתועדות.
-
יישמו את דפוס Pub sub, זה ישמור על הקוד שלכם נקי ככל שתמשיכו בחלקים הבאים.
דפוס Publish-Subscribe עוזר לארגן את הקוד שלכם על ידי הפרדת זיהוי אירועים מטיפול באירועים. זה הופך את הקוד שלכם ליותר מודולרי וקל לתחזוקה.
כדי לבצע את החלק האחרון הזה, אתם יכולים:
-
להוסיף מאזין אירועים על החלון:
window.addEventListener("keyup", (evt) => { if (evt.key === "ArrowUp") { eventEmitter.emit(Messages.KEY_EVENT_UP); } else if (evt.key === "ArrowDown") { eventEmitter.emit(Messages.KEY_EVENT_DOWN); } else if (evt.key === "ArrowLeft") { eventEmitter.emit(Messages.KEY_EVENT_LEFT); } else if (evt.key === "ArrowRight") { eventEmitter.emit(Messages.KEY_EVENT_RIGHT); } });
מה עושה מערכת האירועים הזו:
- מזהה קלט מקלדת וממיר אותו לאירועי משחק מותאמים אישית
- מפריד זיהוי קלט מלוגיקת המשחק
- מקלה על שינוי שליטה מאוחר יותר מבלי להשפיע על קוד המשחק
- מאפשרת למערכות מרובות להגיב לאותו קלט
flowchart TD A["Keyboard Input"] --> B["Window Event Listener"] B --> C["Event Emitter"] C --> D["KEY_EVENT_UP"] C --> E["KEY_EVENT_DOWN"] C --> F["KEY_EVENT_LEFT"] C --> G["KEY_EVENT_RIGHT"] D --> H["Hero Movement"] D --> I["Sound System"] D --> J["Visual Effects"] E --> H F --> H G --> H style A fill:#e1f5fe style C fill:#e8f5e8 style H fill:#fff3e0-
צרו מחלקת EventEmitter כדי לפרסם ולהירשם להודעות:
class EventEmitter { constructor() { this.listeners = {}; } on(message, listener) { if (!this.listeners[message]) { this.listeners[message] = []; } this.listeners[message].push(listener); } -
הוסיפו קבועים והגדירו את EventEmitter:
const Messages = { KEY_EVENT_UP: "KEY_EVENT_UP", KEY_EVENT_DOWN: "KEY_EVENT_DOWN", KEY_EVENT_LEFT: "KEY_EVENT_LEFT", KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT", }; let heroImg, enemyImg, laserImg, canvas, ctx, gameObjects = [], hero, eventEmitter = new EventEmitter();
הבנת ההגדרה:
- מגדירה קבועי הודעות כדי להימנע משגיאות ולהקל על שינוי קוד
- מצהירה על משתנים עבור תמונות, הקשר קנבס ומצב משחק
- יוצרת EventEmitter גלובלי עבור מערכת pub-sub
- מאתחל מערך לשמירת כל אובייקטי המשחק
-
אתחול המשחק
function initGame() { gameObjects = []; createEnemies(); createHero(); eventEmitter.on(Messages.KEY_EVENT_UP, () => { hero.y -= 5; }); eventEmitter.on(Messages.KEY_EVENT_DOWN, () => { hero.y += 5; }); eventEmitter.on(Messages.KEY_EVENT_LEFT, () => { hero.x -= 5; });
-
-
הגדרת לולאת המשחק
בצע שינוי בפונקציה
window.onloadכדי לאתחל את המשחק ולהגדיר לולאת משחק במרווח זמן מתאים. בנוסף, תוסיף קרן לייזר:window.onload = async () => { canvas = document.getElementById("canvas"); ctx = canvas.getContext("2d"); heroImg = await loadTexture("assets/player.png"); enemyImg = await loadTexture("assets/enemyShip.png"); laserImg = await loadTexture("assets/laserRed.png"); initGame(); const gameLoopId = setInterval(() => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); drawGameObjects(ctx); }, 100); };הבנת הגדרת המשחק:
- ממתין לטעינת הדף במלואה לפני התחלה
- מקבל את אלמנט הקנבס ואת הקונטקסט של הרינדור ב-2D
- טוען את כל נכסי התמונות באופן אסינכרוני באמצעות
await - מתחיל את לולאת המשחק במרווחים של 100ms (10 FPS)
- מנקה ומצייר מחדש את כל המסך בכל פריים
-
הוסף קוד להזזת אויבים במרווח זמן מסוים
בצע שינוי בפונקציה
createEnemies()כדי ליצור את האויבים ולהוסיף אותם למחלקת gameObjects החדשה:function createEnemies() { const MONSTER_TOTAL = 5; const MONSTER_WIDTH = MONSTER_TOTAL * 98; const START_X = (canvas.width - MONSTER_WIDTH) / 2; const STOP_X = START_X + MONSTER_WIDTH; for (let x = START_X; x < STOP_X; x += 98) { for (let y = 0; y < 50 * 5; y += 50) { const enemy = new Enemy(x, y); enemy.img = enemyImg; gameObjects.push(enemy); } } }מה עושה יצירת האויבים:
- מחשב מיקומים למרכז האויבים על המסך
- יוצר גריד של אויבים באמצעות לולאות מקוננות
- משייך את תמונת האויב לכל אובייקט אויב
- מוסיף כל אויב למערך אובייקטי המשחק הגלובלי
והוסף פונקציה
createHero()כדי לבצע תהליך דומה עבור הגיבור.function createHero() { hero = new Hero( canvas.width / 2 - 45, canvas.height - canvas.height / 4 ); hero.img = heroImg; gameObjects.push(hero); }מה עושה יצירת הגיבור:
- ממקם את הגיבור בתחתית מרכז המסך
- משייך את תמונת הגיבור לאובייקט הגיבור
- מוסיף את הגיבור למערך אובייקטי המשחק לצורך רינדור
ולבסוף, הוסף פונקציה
drawGameObjects()כדי להתחיל את הציור:function drawGameObjects(ctx) { gameObjects.forEach(go => go.draw(ctx)); }הבנת פונקציית הציור:
- עובר על כל אובייקטי המשחק במערך
- קורא לפונקציה
draw()על כל אובייקט - מעביר את קונטקסט הקנבס כדי שהאובייקטים יוכלו לרנדר את עצמם
🔄 בדיקה פדגוגית
הבנת מערכת המשחק המלאה: בדוק את שליטתך בכל הארכיטקטורה:
- ✅ איך הירושה מאפשרת לגיבור ולאויבים לשתף תכונות משותפות של GameObject?
- ✅ למה תבנית pub/sub הופכת את הקוד שלך ליותר ניתן לתחזוקה?
- ✅ מה תפקידה של לולאת המשחק ביצירת אנימציה חלקה?
- ✅ איך מאזיני אירועים מחברים קלט משתמש להתנהגות אובייקטי המשחק?
אינטגרציית מערכת: המשחק שלך עכשיו מדגים:
- עיצוב מונחה עצמים: מחלקות בסיס עם הירושה מותאמת
- ארכיטקטורה מונעת אירועים: תבנית pub/sub לחיבור רופף
- מסגרת אנימציה: לולאת משחק עם עדכוני פריימים עקביים
- טיפול בקלט: אירועי מקלדת עם מניעת ברירת מחדל
- ניהול נכסים: טעינת תמונות ורינדור ספרייטים
תבניות מקצועיות: יישמת:
- הפרדת תחומים: קלט, לוגיקה ורינדור מופרדים
- פולימורפיזם: כל אובייקטי המשחק חולקים ממשק ציור משותף
- העברת הודעות: תקשורת נקייה בין רכיבים
- ניהול משאבים: טיפול יעיל בספרייטים ואנימציות
האויבים שלך צריכים להתחיל להתקדם לעבר חללית הגיבור שלך! } }
and add a `createHero()` function to do a similar process for the hero. ```javascript function createHero() { hero = new Hero( canvas.width / 2 - 45, canvas.height - canvas.height / 4 ); hero.img = heroImg; gameObjects.push(hero); }ולבסוף, הוסף פונקציה
drawGameObjects()כדי להתחיל את הציור:function drawGameObjects(ctx) { gameObjects.forEach(go => go.draw(ctx)); }האויבים שלך צריכים להתחיל להתקדם לעבר חללית הגיבור שלך!
אתגר סוכן GitHub Copilot 🚀
הנה אתגר שישפר את הפוליש של המשחק שלך: הוספת גבולות ושליטה חלקה. כרגע, הגיבור שלך יכול לעוף מחוץ למסך, והתנועה עשויה להרגיש מקוטעת.
המשימה שלך: הפוך את החללית שלך ליותר מציאותית על ידי יישום גבולות מסך ותנועה חלקה. זה דומה לאופן שבו מערכות בקרת טיסה של נאס"א מונעות מחלליות לחרוג מפרמטרי פעולה בטוחים.
מה לבנות: צור מערכת ששומרת את חללית הגיבור שלך על המסך, ותגרום לשליטה להרגיש חלקה. כאשר שחקנים מחזיקים מקש חץ, החללית צריכה להחליק ברציפות במקום לזוז בצעדים נפרדים. שקול להוסיף משוב חזותי כאשר החללית מגיעה לגבולות המסך – אולי אפקט עדין שמציין את קצה אזור המשחק.
למד עוד על מצב סוכן כאן.
🚀 אתגר
ארגון קוד הופך לחשוב יותר ככל שהפרויקטים גדלים. אולי שמתם לב שהקובץ שלכם מתמלא בפונקציות, משתנים ומחלקות מעורבבים יחד. זה מזכיר לי איך המהנדסים שארגנו את קוד משימת אפולו היו צריכים ליצור מערכות ברורות וניתנות לתחזוקה שצוותים מרובים יוכלו לעבוד עליהן בו זמנית.
המשימה שלך: חשוב כמו ארכיטקט תוכנה. איך היית מארגן את הקוד שלך כך שישה חודשים מעכשיו, אתה (או חבר צוות) תוכל להבין מה קורה? גם אם הכל נשאר בקובץ אחד כרגע, אתה יכול ליצור ארגון טוב יותר:
- קיבוץ פונקציות קשורות יחד עם כותרות הערות ברורות
- הפרדת תחומים - שמור את לוגיקת המשחק נפרדת מהרינדור
- שימוש בשמות עקביים למשתנים ולפונקציות
- יצירת מודולים או מרחבי שמות לארגון היבטים שונים של המשחק שלך
- הוספת תיעוד שמסביר את מטרת כל חלק מרכזי
שאלות למחשבה:
- אילו חלקים בקוד שלך הכי קשה להבין כשאתה חוזר אליהם?
- איך היית מארגן את הקוד שלך כדי להקל על מישהו אחר לתרום?
- מה יקרה אם תרצה להוסיף תכונות חדשות כמו בונוסים או סוגי אויבים שונים?
חידון לאחר ההרצאה
סקירה ולימוד עצמי
בנינו הכל מאפס, שזה נהדר ללמידה, אבל הנה סוד קטן – יש כמה מסגרות JavaScript מדהימות שיכולות לטפל בהרבה מהעבודה הקשה עבורך. ברגע שתרגיש בנוח עם היסודות שכיסינו, כדאי לחקור מה זמין.
חשוב על מסגרות כמו שיש לך ארגז כלים מצויד היטב במקום ליצור כל כלי בעצמך. הן יכולות לפתור רבות מאותן אתגרי ארגון קוד שדיברנו עליהם, בנוסף להציע תכונות שייקח שבועות לבנות בעצמך.
דברים שכדאי לחקור:
- איך מנועי משחק מארגנים קוד – תתפלא מהתבניות החכמות שהם משתמשים בהן
- טריקים לביצועים כדי לגרום למשחקי קנבס לרוץ בצורה חלקה
- תכונות JavaScript מודרניות שיכולות להפוך את הקוד שלך לנקי וניתן לתחזוקה
- גישות שונות לניהול אובייקטי משחק והיחסים ביניהם
🎯 ציר הזמן לשליטה באנימציית משחקים שלך
timeline
title Game Animation & Interaction Learning Progression
section Movement Fundamentals (20 minutes)
Animation Principles: Frame-based animation
: Position updates
: Coordinate systems
: Smooth movement
section Event Systems (25 minutes)
User Input: Keyboard event handling
: Default behavior prevention
: Event object properties
: Window-level listening
section Game Architecture (30 minutes)
Object Design: Inheritance patterns
: Base class creation
: Specialized behaviors
: Polymorphic interfaces
section Communication Patterns (35 minutes)
Pub/Sub Implementation: Event emitters
: Message constants
: Loose coupling
: System integration
section Game Loop Mastery (40 minutes)
Real-time Systems: Frame rate control
: Update/render cycle
: State management
: Performance optimization
section Advanced Techniques (45 minutes)
Professional Features: Collision detection
: Physics simulation
: State machines
: Component systems
section Game Engine Concepts (1 week)
Framework Understanding: Entity-component systems
: Scene graphs
: Asset pipelines
: Performance profiling
section Production Skills (1 month)
Professional Development: Code organization
: Team collaboration
: Testing strategies
: Deployment optimization
🛠️ סיכום ערכת הכלים לפיתוח משחקים שלך
לאחר השלמת השיעור הזה, עכשיו שלטת ב:
- עקרונות אנימציה: תנועה מבוססת פריימים ומעברים חלקים
- תכנות מונע אירועים: טיפול בקלט מקלדת עם ניהול אירועים נכון
- עיצוב מונחה עצמים: היררכיות ירושה וממשקים פולימורפיים
- תבניות תקשורת: ארכיטקטורת pub/sub לקוד ניתן לתחזוקה
- ארכיטקטורת לולאת משחק: עדכון בזמן אמת ומחזורי רינדור
- מערכות קלט: מיפוי שליטה משתמש עם מניעת התנהגות ברירת מחדל
- ניהול נכסים: טעינת ספרייטים וטכניקות רינדור יעילות
⚡ מה אתה יכול לעשות ב-5 הדקות הבאות
- פתח את קונסולת הדפדפן ונסה
addEventListener('keydown', console.log)כדי לראות אירועי מקלדת - צור אלמנט div פשוט והזז אותו באמצעות מקשי החצים
- נסה את
setIntervalכדי ליצור תנועה רציפה - נסה למנוע התנהגות ברירת מחדל עם
event.preventDefault()
🎯 מה אתה יכול להשיג בשעה הקרובה
- השלם את החידון לאחר השיעור והבנת תכנות מונע אירועים
- בנה את חללית הגיבור הנעה עם שליטה מלאה במקלדת
- יישם דפוסי תנועה חלקים לאויבים
- הוסף גבולות כדי למנוע מאובייקטי משחק לצאת מהמסך
- צור זיהוי התנגשות בסיסי בין אובייקטי משחק
📅 מסע האנימציה שלך לשבוע
- השלם את משחק החלל המלא עם תנועה ואינטראקציות מלוטשות
- הוסף דפוסי תנועה מתקדמים כמו עקומות, תאוצה ופיזיקה
- יישם מעברים חלקים ופונקציות הקלה
- צור אפקטים חלקיקים ומערכות משוב חזותיות
- בצע אופטימיזציה לביצועי המשחק למשחק חלק ב-60fps
- הוסף שליטה במגע לנייד ועיצוב רספונסיבי
🌟 פיתוח אינטראקטיבי שלך לחודש
- בנה יישומים אינטראקטיביים מורכבים עם מערכות אנימציה מתקדמות
- למד ספריות אנימציה כמו GSAP או צור מנוע אנימציה משלך
- תרום לפרויקטים פתוחים של פיתוח משחקים ואנימציה
- שלוט באופטימיזציית ביצועים ליישומים גרפיים אינטנסיביים
- צור תוכן חינוכי על פיתוח משחקים ואנימציה
- בנה תיק עבודות המציג מיומנויות תכנות אינטראקטיביות מתקדמות
יישומים בעולם האמיתי: מיומנויות האנימציה שלך במשחקים חלות ישירות על:
- יישומי אינטרנט אינטראקטיביים: לוחות מחוונים דינמיים וממשקים בזמן אמת
- הדמיית נתונים: גרפים מונפשים וגרפיקה אינטראקטיבית
- תוכנה חינוכית: סימולציות אינטראקטיביות וכלי למידה
- פיתוח לנייד: משחקים מבוססי מגע וטיפול במחוות
- יישומי שולחן עבודה: אפליקציות Electron עם אנימציות חלקות
- אנימציות אינטרנט: ספריות אנימציה ב-CSS וב-JavaScript
מיומנויות מקצועיות שנרכשו: עכשיו אתה יכול:
- לתכנן מערכות מונעות אירועים שמתרחבות עם מורכבות
- ליישם אנימציות חלקות באמצעות עקרונות מתמטיים
- לפתור בעיות במערכות אינטראקציה מורכבות באמצעות כלי פיתוח דפדפן
- לייעל ביצועי משחק למכשירים ודפדפנים שונים
- לעצב מבני קוד ניתנים לתחזוקה באמצעות תבניות מוכחות
מושגים בפיתוח משחקים שנרכשו:
- ניהול קצב פריימים: הבנת FPS ובקרות תזמון
- טיפול בקלט: מערכות מקלדת ואירועים חוצות פלטפורמות
- מחזור חיים של אובייקט: תבניות יצירה, עדכון והשמדה
- סנכרון מצב: שמירה על מצב משחק עקבי בין פריימים
- ארכיטקטורת אירועים: תקשורת מנותקת בין מערכות משחק
הרמה הבאה: אתה מוכן להוסיף זיהוי התנגשות, מערכות ניקוד, אפקטים קוליים, או לחקור מסגרות משחק מודרניות כמו Phaser או Three.js!
🌟 הישג נפתח: בנית מערכת משחק אינטראקטיבית מלאה עם תבניות ארכיטקטורה מקצועיות!
משימה
כתב ויתור:
מסמך זה תורגם באמצעות שירות תרגום AI Co-op Translator. למרות שאנו שואפים לדיוק, יש לקחת בחשבון שתרגומים אוטומטיים עשויים להכיל שגיאות או אי דיוקים. המסמך המקורי בשפתו המקורית צריך להיחשב כמקור הסמכותי. עבור מידע קריטי, מומלץ להשתמש בתרגום מקצועי אנושי. איננו אחראים לאי הבנות או לפרשנויות שגויות הנובעות משימוש בתרגום זה.