יום שישי, 9 במרץ 2012

קשיי שפה - חוזרים מ casual connect בהמבורג



Casual Connect הוא כנס בנושא משחקי הקזואל. הוא מתרחש 4 פעמים בשנה, אם אני זוכר נכון, כל פעם בעיר אחרת בעולם. אנחנו (אני ואייל, שהוא גם הבוס שלי וגם חבר טוב) נסענו הפעם לכנס בהמבורג.

זו הפעם הראשונה שאני בכנס פיתוח משחקים בחול. ואומנם נסעתי עם הבוס שלי מהעבודה, אבל זו היתה קודם כל נסיעה בשביל עצמי, רציתי לראות פעם אחת אייך נראה כנס כזה בחול, ולא התאכזבתי.

אני מניח שהחוויה שלי היתה שונה משל הרבה אנשים שנסעו לכנס במסגרת העבודה. זה לא שלא עבדנו שם, עבדנו די הרבה, אבל בניגוד להרבה אנשים שנסעו פשוט כי החברה שלחה אותם, והגיעו לשם עמוסים בחבילות כרטיסי ביקור, ועם פגישות קבועות מראש, ולוח זמנים צפוף וקשוח. אנחנו באנו באמת ב casual. בלי תוכניות מראש, בלי שורה של יעדים, ובכל זאת הכנס היה מאוד מוצלח מבחינתנו, גם מבחינה אישית, וגם מבחינה עיסקית.

אני מציין את הנקודה הזאת, כי האמת שמנקודת המבט שלי זה נראה קצת שונה. אני רגיל להסתכל על המקצוע שלנו, דרך הנקודת מבט, שפיתוח משחקים צריך להיות קודם כל כייף. ברורה לי החשיבות העיסקית שלו, והצורך להרוויח כסף כדי להתקיים, אבל מבחינת פיתוח המשחקים, אז גורם ההנאה הוא בעיני הפאקטור הכי חשוב, כי אחרת אפשר ללכת לעבוד, בחברות יותר יציבות, ובתעשיות יותר יציבות.



ובכל זאת, בכנס אי אפשר היה להתעלם מהצד העיסקי החזק מאוד של התעשיה הזאת. היו הרבה אנשים, שרצו מפגישה לפגישה, ועשו connections גם על משקאות בבר, במסיבה בשעות הערב. מעבר לזה, היו המון הרצאות שהתעסקו בנושאים שהיו בעיקר כלכליים. ביום האחרון היתה אפילו  הרצאה של מישהי מזינגה, שדיברה איתנו על התהליך של הקשבה ללקוחות, והפקת מסכנות לשיפור המשחקיות. לא היה לנו נעים להגיד לה, שעם כל הכבוד (וואלה, אין אפילו טיפה של כבוד) זינגה הצליחה, ועדיין מצליחה, בעיקר על ידי זה שהיא מעתיקה 1 ל-1 משחקים מוצלחים של מפתחים אחרים, ואז דורסת את אותם מפתחים על ידי שיווק אגרסיבי.
ל"שמחתנו" לא הינו צריכים להרגיש לא נעים, כי למרות שהיא סיימה רבע שעה לפני הזמן, היא לא נתנה זמן לשאלות מן הקהל.

אז היו שם הרבה הרצאות, שדיברו בין השאר על, כמה עולה להביא שחקן חדש למשחק, ובמספרים הגדולים כמה זה אומר שצריך להרוויח מהמשחק על מנת שהמשחק יהיה ריווחי. והאמת - אני לא מבין את זה.
 דיברו שם עלויות של דולר וחצי, 5 דולר, 15 דולר, ואפילו סכומים שהגיעו למעל 100$. אני מבין את המתמטיקה הפשוטה שאם מביאים שחקן למשחק, ובסופו של דבר הסכום הממוצע ששחקן מוציא על משחק הוא גדול יותר מהסכום שעלה להביא אותו, אז יש בזה הגיון כלכלי כלשהו. אבל בעולם שיש בו אומנם יותר ויותר שחקנים כל הזמן, שמוציאים יותר ויותר כסף, ומצד שני יש כל הזמן יותר ויותר מפתחי משחקים שנכנסים לתמונה, אז באיזשהו שלב, מישהו מפסיד כסף.
אם במשחקים בהם השחקן משלם 50-60$ למשחק בקופסה, יש משחקים שלא מרוויחים מספיק, ויש הרבה כאלה. אז גם בתחום הקזואל שבו ההכנסות הרבה יותר קטנות, ובדרך כלל השחקן קודם כל עולה כסף לחברה כי הוא מופץ חינם, אין לי ספק שיהיו הרבה חברות שיפסידו הרבה כסף, אם הם ידבקו במתכונת הזאת של "לקנות שחקנים".
יש אומנם את ההגיון הזה שאומר, שאם הבאת שחקן והוא נשאר, אז אחרי העלות הראשונית, הוא ימשיך להכניס לך כסף. אבל אני שואל אתכם ברצינות עכשיו. נניח שאדם ממשיך לשחק ולהוציא כסף, כמה משחקים הוא ישחק במקביל. ולכמה זמן? האם באמת אפשר לסמוך על זה שכל שחקן למשל ישחק ב 3 משחקים במקביל, ויוציא בממוצע על כל אחד מהם מספר דולארים כל חודש?

אולי יש כאלה שיגידו שכן. אבל אני חושב שבשורה התחתונה, זה יהיה כמו שזה תמיד היה שאלה של כמה המשחק טוב. גם אם מדובר במשחק של חברה שמוציאה מליונים על שיווק, בסופו של יום, המשחקים שעושים לה את הרווחים הם המשחקים שהם פשוט טובים, שמתחילים לעבור מפה לאוזן, ושאנשים מוציאים עליהם כי הם באמת נהנים מהם.

ואם זה נכון בגדול זה נכון בקטן. תאמינו לי שאני יודע כמה קשה להוציא משחק מוצלח ולהצליח איתו. וגם שחקניות גדולות בשוק הזה, הוציאו הרבה פעמים כמה עשרות משחקים לפני שהם הוציאו את המשחק שעשה את המכה. תוך כדי, לומדים לעשות משחקים יותר טובים, לשווק יותר טוב, לתמחר יותר טוב, ובסופו של דבר, כמו בכל תחום, העיקביות, ההתמדה והשאיפה לעשות דברים יותר טוב, היא זאת שמביאה בסופו של דבר לתוצאות.

לכן אני עומד מאחורי הדעה שלי, שקודם כל חשוב לעבוד על משחקים, ולשאוף לעשות משחקים טובים, ובאופן שיהיה כייף גם לכם לפתח אותם, וגם לאנשים שישחקו אותם לשחק אותם.

יש לי עוד הרבה מה לומר על דברים שלמדתי בכנס הזה, ואני אמשיך לכתוב עליו, ועל כמה משחקים שראיתי שם בפוסטים הבאים.

מי שתוהה למה קוראים לפוסט "קשיי שפה - חוזרים מ casual connect בהמבורג", אז זה לא שהיה לי קשה עם הגרמנית (שאני לא מדבר בכלל), או עם האנגלית (שאני מדבר די טוב). זה שמרוב היובש, וכנראה גם קצת בגלל כוויות קור, שנע בין מינוס 15 למינוס 5, אז חזרתי לארץ, עם שפה עליונה בגודל שפה של לוייתן (ללוויתן יש בכלל שפתים?... לא משנה זה דימוי...).

אני ואייל צחקנו על זה, שהכנס הזה הפך אותנו לבחורות. מרוב שהיה קר ויבש, אנחנו שתינו כל  הזמן, והלכנו בגלל זה לשירותים כל חצי שעה. (ובגלל התדירות גם יצא שהיינו רוב הזמן הולכים לשירותים יחד). כמו כן שמנו על עצמנו כרמים בלי סוף, ווזלין לשפתים, וכל דבר אחר שחשבנו שיוכל לעזור. הדבר שהכי עזר, היה לחזר לארץ, ועדיין אני סובל מבעיות שפה לא קלות :) 

יום רביעי, 1 בפברואר 2012

מחשבות על GGJ2012 בירושלים


בדיוק כמו המעבר של הומר סימפסון בין 5 שלבי האבל:





...הגלובל גיים ג'אם (2012 בירושלים), נותן לנו הזדמנות יחודית, מרוכזת ואינטנסיבית, להבין מה בדיוק כרוך בתהליך של פיתוח משחק.

הוא עושה את זה ב 48 שעות, וחוסך  לנו לפחות חצי שנה של ניסיון בפיתוח משחקים, בזה שהוא דוחק את כל החוויות פנימה.

מצד שני - הלחץ לקראת הסוף קצת הרגיש לי, כאילו ירדה לי שנה או שתיים מהחיים, אז אני לא בטוח אם יצאתי עם ידי על העליונה :-)

אז מה אפשר ללמוד מה GGJ?


1.  brainstorming - חושבים על רעיון למשחק. 


תוצאת תמונה עבור ‪brainstorm‬‏
השלב שחושבים על המשחק. בשלב הזה לכולם יש רעיונות, והרבה פעמים גם מגלים כמה קשה זה לגבש רעיון אחד שלם. 

גם אם גיבשתם משהו, יכול להיות שכל אחד בקבוצה תאר את זה לעצמו קצת אחרת.

אחד המקורות לבעיות יכול להיות שהמתכנת חשב משהו אחד, והמעצב Art חשב משהו אחר. 

אלה בעיות שקימות גם כשמפתחים משחק בחברה, ההבדל הגדול הוא שבחברה יש בדר"כ היררכיה יותר ברורה, ויש מישהו שיכול לקחת החלטה.  ב GGJ כולם שווים, מי שיש לו יותר ניסיון ומי שפחות, וכמובן שכולם רוצים לתרום את הרעיונות שלהם למוצר המוגמר. 

בנוסף, התחושה שאני קיבלתי היא, שככל שהקבוצה יותר גדולה, ופחות ברור מי עומד בראשה, יותר קשה להגיע לרעיון ולהסכים עליו. מצד שני, יותר אנשים זה יותר כוח עבודה. אז מה עדיף?

ובכן, בכל הנוגע לכמות אנשים.הניסיון מוכיח, שבפרויקטים באופן כללי, אם מוסיפים אנשים באמצע פרויקט כדי לגמור אותו יותר מהר, בדר"כ גומרים אותו יותר לאט, כי האנשים שכבר עבדו עליו מבזבזים יותר זמן בלהכניס את החדשים לעניינים, מאשר הזמן שבסופו של דבר נחסך בעבודה, כתוצאה מהמהלך. 
במקרה שלנו, של GGJ, אותה תופעה של בזבוז זמן, תקרה כשיש פשוט צוות מאוד גדול, וצריך לדאוג להגיע להבנה של הפרויקט על ידי כל הצוות, ולתיאום ביניהם תוך כדי עבודה  על הפרויקט. 

אז מה המחשבות שלי בנושא.

בנוגע לתהליך של בחירת רעיון. כדאי אולי שבהתחלה כל אחד מחברי הקבוצה יציע רעיון משלו, ואחרי שדנים קצת על כל רעיון, ומגיעים לעמק השווה, צריך להצביע על איזה מהם רוצים לעבוד.
מהרגע שבוחרים, זה אומר שהצוות התחייב לפרויקט, וזה שהציע אותו, אחראי על החזון שלו, ועל כל החלטה שנוגעת לעיצוב שלו. 

 אח"כ כדאי לבחור בוס, או מפיק אם תרצו, ולתת לו פחות או יותר את הכוח להחליט את כל ההחלטות שנוגעות לצדדים הפרקטיים של בנית המשחק.  שווה לשקול לבחור אדם עם הרבה ניסיון, שמלבד לנהל ולהפיק את המשחק לא יעשה כלום, יעמוד עם ידים בכיסים ורק יפקח. המטרה שלו היא לדאוג שהמשחק יצא שלם ובזמן. 
יכול להיות שה"בוס" יהיה זה שהעלה את הרעיון, אבל זה לא מחייב. גם בתעשייה, שני התפקידים האלה, הרבה פעמים לא שייכים לאותו אדם.  



הערה: כמו שהכותרת מרמזת, אלו הן המחשבות שלי, ויכול להיות והגיוני שאתם אולי יודעים יותר ולא מסכימים איתן. אם כן, אני אשמח אם תשתפו אותי בתגובות.


לגבי התהליך שאני עברתי בחלק הזה. 
אני לא עשיתי את תהליך ה brainstorming עם קבוצה של אנשים. פשוט רצתי על רעיון בראש, ידעתי שאני אוכל לתכנת אותו, וסמכתי על זה, שאו שיצטרפו אלי אנשים בהמשך, או שאני אעשה גרפיקה שלי.

אני לא בדיוק מעצב, כך  שאם הייתי עושה גרפיקה, היא מן הסתם לא היתה יוצאת משהו, אבל היא היתה עושה את העבודה.

בכל מקרה. כיוון שבשלב שהצגנו את הרעיונות,  (אחרי ה brainstorming) לאף אחד מהאנשים עוד לא ממש היתה קבוצת עבודה  מגובשת. כך שזה שאני עוד הייתי לבדי על הפרויקט, זה לא ממש הטריד אותי. 
כך או כך לקח זמן עד שבאמת הצטרפו אלי אנשים, ואני לא כל כך מתפלא. הרעיון שלי היה כל כך אמורפי במיוחד בהתחלה, וכל כך ממוקד משחקיות. שאני מבין למה היה קשה לאנשים שלא היו חלק מתהליך  brainstorming  על הרעיון, להתחבר אליו.

במיוחד שהתיאור שלו נשמע משהו כזה: "קצת כמו פגל, עם כדור שנופל למטה וחוזר מלמעלה, בלי אפשרות להפסיד, ניקוד לפי זמן ממוצע, ואיסוף זמן..." 


אחרי שסיימנו את שלב הצגת הרעיונות, התחלתי להתארגן לעבודה. אני יודע שבקבוצות אחרות, ה brainstorming נמשך, ואני חושב שזו בעיה.
 צריך לדעת מתי לעצור brainstorming, ומתי להתחיל לעבוד במיוחד אם יש כל כך מעט זמן בסה"כ.

2. concept art ן - פרוטוטייפ.
תמונה קשורה

השלב הבא הוא להתחיל ממש בפיתוח, המעצבים יתחילו לעבוד על ארט, והמתכנתים או מתכנת יתחילו להכין משהו שיוכל לעבוד. 

במקרה שלי אני עבדתי על פרויקט בפלאש, עם box2d. והתחלתי לארגן סביבה עם box2d שתעבוד עם מה שאני צריך.

למשחק שעשינו בסופו של דבר היו הרבה פיצ'רים.

ביניהם סידור השלב הבא באופן רנדומאלי, בדיקה שאני לא שם דברים אחד על השני, ואפשרות לעשות scale לאוביקטים גם בפיזיקה וגם בגרפיקה.
להכניס את כל החלקים האלה, לקח זמן נכבד בסופו של דבר. אבל הפרוטוטיפ הראשוני עבד די מהר, וסיימתי אותו ביום הראשון. 


שני המעצבים שהצטרפו אלי, אורן, ואייל מבצלאל,  עבדו על הקונספט, תכננו מה לעשות ואייך להציג אותו.
בדיעבד אני חושב בשלב הזה, הייתי צריך כן לעשות איתם סוג של brainstorming ולהסביר להם יותר על התכנון למשחק.

זה שחשבתי שהם יעשו גרפיקה, ואחרי זה נחליט אייך לשלב אותה למשחק, גרמה גם לזה שדברים מסויימים שהם עבדו עליהם לא נכנסו. כי למרות שהם נראו מאוד יפה, אז או שהם לא התאימו לפיזיקה, או שפשוט לא היה לזה מספיק זמן.

מעבר לזה, כשחשתבי על הרעיון היה לי קונספט מסויים, והם משכו בסופו של דבר לדבר קצת אחר. וכאמור במקום שנחשוב על הקונספט ב brainstorming, הם חשבו על קונספט מאוד מעניין ויפה עיצובית, אבל שונה מהתפיסה  הפשטנית שלי. אני מצידי, קברתי את עצמי בקוד עד שהפרוטוטיפ היה מוכן. זה אומר שאמרתי "יהיה בסדר" ולא נתתי להם משוב בשלב מספיק מוקדם, וכיוון שהם עצמם עוד היו עסוקים בתוכן, וסמכו על זה שבכל מקרה נוכל לעשות התאמות בסוף, נראה היה שבמהלך כל היום הראשון, אף אחד מאיתנו לא באמת רצה להכנס לאייך נעשה באמת את זה.

 ובכל זאת, כיוון שלי יש יותר ניסיון בפיתוח משחקים, אני לגמרי חושב שהאחריות על החלק הזה היא שלי, ושבמקרה הזה יכולתי אולי לנהוג בדרך אחרת. 

כאמור אני קברתי את עצמי בקוד עד שהפרוטוטיפ היה מוכן... בערך.
בתחלס כדי שהמשחק ממש יתפקד הייתי פחות או יותר צריך לסיים אותו, וזה אומר שסיימתי את רובו ולא כולו רק ביום השני בצהרים - 6 שעות לפני הדד ליין. - וזה הרבה יותר מידי זמן לחלק הזה. 

אז מה המסקנות שלי לגבי השלב הזה של העבודה:

1. פרוטוטיפ תכנותי כמעט  מלא של המשחק, צריך להיות גמור ביום הראשון. יש מספיק דברים אחרי זה לעשות.
וכל עבודה נוספת שנעשית על שלב זה, אחרי היום הראשון, רק מקטינה את הסיכוי להשלים דברים אחרים.

2. לעשות תיאום יותר טוב בין חברי הצוות. זה שהיה לי מראש חסר זמן לסיים את הפרויקט, זה לא אומר שאני לא צריך לוודא שהאנשים שאיתי לא מבינים בדיוק אייך המשחק יעבוד, ושהם בכיוון הנכון. 

3.  הסעיף הזה הוא בעצם שילוב של 1+2 - לעשות משחק פשוט. אין טעם לעשות משהו גרנדיוזי, ולא להספיק אותו. גם אם רוצים להוסיף wish list של דברים נוספים, ללכת על משהו ממש פשוט, שגם קל יחסית לבנות, וקל יחסית להבין. יש 48 שעות, כולל שינה (או שלא), קפה, עוגיות, והפסקות שרותים (תתקלחו אחרי זה!!!) . אז ללכת על משהו פשוט, שעומד בפני עצמו, ויכול להחשב כמשחק בפני עצמו.

3. משלבים ביחד,

בשלב הזה צריך להמשיך את הפרוטוטיפ למשחק המלא, תוך שילוב גרפיקה טיפול בניקוד, וכל מה שצריך להשלים גיימפלי. וגם סאוונד.

זהו שלב כבד, והכרחי. לכן חשוב לתת לו מספיק זמן. בגלל זה חשוב לסיים בגדול את הארט, ואת הפרוטוטיפ ביום הראשון, השלב הזה אמור להתבצע בחצי הראשון של היום השני לדעתי. זה מאוד מפתה לחשוב שיש באמת יומיים לפתח משחק, ואז למשוך את זה וגם דברים אחרים, לקראת החלק האחרון של היום השני. אבל התוצאה של עשות את זה, ולמשוך את הזמן באופן הזה, באמת מעלה את הלחץ לקראת הסוף.

4. לסגור פינות, תפריטים, טיפה גרפיקה, עוד סאוונד. להעלות לאתר, להכין סרטונים, תמונות מסך, קרדיטים לכל מי שעבד קשה.


תוצאת תמונה עבור ‪wrapping up‬‏
הקטע הקשה בשלב הזה כאמור, הוא לדאוג שישאר לו זמן. אני חושב שלא היה משחק אחד שלם לחלוטין שהצלחנו לעשות בירושלים, אני עוד לא בטוח אייך הלך בהרצליה מהבחינה הזאת. 

באמת שיצאו משחקים מדהימים, אבל בכולם יש קצת באגים כנראה, ועוד מעט בעיות, ודברים חסרים.


זה סרטון שמציג מה יצא לנו (עם frame rate לא משהו):





הסיכום 

אם נסכם את כל מה שיש לי לומר, אז בגדול, תכנון זמן אחר היה עושה את החיים שלי, ושל מי שהצטרפו אלי לפרויקט ליותר קלים. בהתחשב בזה שיצא לנו משחק נחמד בסה"כ, ושכולנו עושים את זה פעם ראשונה, ושלהם לא היה ממש ניסיון עם עיצוב למשחקים קודם לכן, אני חושב שהתוצאה מאוד מרשימה (וכן, אני משוחד).




בתחלס כאמור את החלק הרביעי וחלק מהשלישי, לא היה בכלל זמן לעשות, עד הדד ליין של ההגשות שלנו. אבל כיוון שהדד ליין היה ב 19:00 ביום שבת, כשהתחלנו ביום שישי בבוקר, והיה עוד אפשר לשנות ולהעלות את הקבצים.
 החלטתי שאני משנה ומוסיף, אך ורק דברים שיאפשרו לאנשים להנות מהמשחק. כלומר טוטוריאל, ניקוד וסיכום, וקרדיט לאנשים. לא שיניתי שום דבר נוסף בגיים פלי. למרות שהמעצבים היו שמחים אם הייתי מכניס עוד אנימציה, ואולי דברים נוספים.
הייתי מעדיף לא לעבוד על המשחק עוד שעה בבית אחרי שכבר הצגנו את המשחק. וגם לא את העוד חצי שעה לטפל בהעלאה לאתר.  
אני רואה את זה כשיעור, מה שלא הספקנו להכניס, וגם מה שכן הוספתי, אלה דברים שאני יכול ללמוד מהם על מנת לדעת  מה לעשות יותר טוב בפעם הבאה. 

בכל מקרה הכל נעשה בטווח של 48 שעות, ונטו  עבדנו על המשחק פחות מ 24 שעות.

באופן כללי ההשגים של האירוע היו מאוד יפים 8 (או 9) משחקים יצאו מירושלים. היתה אומנם קבוצה אחת שהתפרקה במהלך היום הראשון, אבל זה צפוי. זה בגדול קרה כי ה brainstorming לא ממש נגמר (שוב - הגבלת זמן, ומנהיגות זה דבר חשוב).

היו שם משחקים עם רעיונות מדהימים, וביצועים מרשימים, אבל אף אחד לא היה ממש שלם כשסיימנו ב 19:00. השאלה האם שווה לעשות משהו יותר פשוט, ואז להיות בטוחים שניתן לסיים אותו בזמן,  זה הדבר הנכון לעשות... 
אין לי ממש תשובה טובה לזה. אני יודע שאנשים השקיעו הכל ברעיונות קצת גדולים מידי, שאולי לא היה מצב לסיים בזמן, אבל אני ממש אהבתי את הרעיונות ואת הביצוע, אז נראה לי שלצורך האפשרות פשוט לעשות משהו אחר, מקורי חדשני ומעניין, זה בסדר גמור אם זה אומר שהמשחק יצא לא לגמרי מושלם.  

אני אשמח לדעת מה דעתכם.




בכל מקרה אני רוצה מאוד להודות למי שעמל וטרח על האירוע הזה, וכל הכבוד לחבר'ה מבצלאל, שתרמו את המקום (והשתתפו בהמוניהם) ולצוות במקום שעזר ודאג, ולמנחים, ולאנשים שטיפלו בלוגיסטיקה, ובווידאו, ושתלו בריסטולים על החלונות, למי שתיזז מהרצליה לירושלים, (כן יובל וגולדי, זה אתם) וכל האנשים שבאו והשתתפו, אפילו אם לא היה להם ניסיון קודם, וכמובן לאישתי, שבילתה סוף שבוע בבית, כמעט בלי לראות אותי, ובלי שאני אעזור בכלום. 

בנימה היותר אישית, אני רוצה להודות לאורן  ואייל מהצוות שלי, שהיה להם אומץ ללכת עם הרעיון שלי, שעשו עבודת עיצוב כל כך טובה, (במשחק עצמו, אני אחראי לגרפיקת תפריטים ה"מוצלחת") וגם סבלו אותי, שזה לא תמיד הכי פשוט. אני מקווה שאתם מרוצים מהתוצאה הסופית, ושיצא לנו לעבוד אולי שוב בהזדמנות אחרת, ולעשות משהו יותר טוב ויותר יצירתי.
תודה ליונתן לוריה, שהכי לנו סאוונד מגניב (ואפילו קצת מטריד) למשחק שהוא אפילו לא ראה. 

וח"ח  מיוחד לעמיחי, שאירגן ועשה וניהל את האירוע הנפלא בירושלים.

אני אשמח לשמוע את המחשבות שלכם, ואת הרעיונות שלכם בתגובות.

פה אתם יכולים למצוא את המשחק באתר של GGJ

הערה חשובה: המשחק פותח עם פלאש ( CS5.5 ) ו box2d זו הזדמנות מצוינת למי שרצה לראות פרויקט שלם עם box2d בפלאש, לראות וללמוד קצת על אייך אני עובד עם זה. 

מפאת קוצר זמן הפיתוח אין שם המון תיעוד, אבל זה בהחלט פרויקט טוב ללימוד. 



יום שני, 2 בינואר 2012

הטוב, הכייף והמכוער

אחד הדברים שאנחנו בתור מפתחי משחקים, נוהגים להגיד למישהו שרוצה להכנס לתחום, הוא פשוט - להתחיל ליצור משחקים.


כמו בהרבה תחומים אחרים, גם בבנית משחקים אין כמו לקפוץ למים העמוקים כדי ללמוד לשחות. ובעוד שאם תקפוץ לבריכה עמוקה בלי לדעת לשחות, יש סיכוי שתתבע, אם תתחיל לעבוד על בניית משחק, לפני שאתה בדיוק יודע מה אתה עושה. אז יהיו הרבה בעיות, תסכולים ובאגים, אבל גם במקרה הגרוע, רק האגו שלך יהיה זה שיפגע.


יצא לי להזכיר בעבר כלים שאפשר להתחיל מהם, כמו GameMaker ו GameSalad. את הראשון אני מכיר לא רע בכלל, אל השני פחות התחברתי, אבל אם אתם מעוניינים, יש סדנה שאפשר ללמוד בה לתכנת משחקים פשוטים עם  GameSalad . הסדנה תהיה בשנקר, על ידי ארז שהוא אחד המרצים בקורס לעיצוב ופיתוח משחקי מחשב. ארז הוא מתכנת משחקים מוכשר ביותר, וגם יש לו ניסיון בכל הנושא של מאיפה מתחילים לתכנת משחקים. ניסיון כזה שלא לכל מרצה לתיכנות יש. אז הסדנה בהחלט מומלצת.


"לכותב התמים, זו אולי נראית כמו הצעה לגיטימית."... 








עכשיו נחזור לנושא העיקרי שלנו. אותו משחק או משחקים, שניצור בהתחלה כדי ללמוד את הדרך. לא פעם אנחנו רואים בפורומים ובקבוצות השונות, שבהם מסתובבים מפתחי משחקים, הצעות מעולות בסגנון, "אני X, יש לי רעיון מדהים למשחק, ואני מחפש Y ו   Z כדי שנבנה אותו ביחד (ונתחלק במיליונים אחרי זה)". 
לכותב התמים, זו אולי נראית כמו הצעה לגיטימית. אבל זה פחות או יותר כמו לגשת לאנשים זרים ברחוב, ולהציע להם לבנות ביחד בית עם כמה דירות. במילים אחרות, גם אם אנשים יסכימו להצעה שלך, רוב הסיכויים שאתה צריך להיות ממש מודאג מסיכויי הפרויקט הזה להסתיים. 


"עבודה של מספר אנשים, היא תמיד יותר מסובכת"










עבודה בצוות על פרויקט והשלמתו, היא מהכישורים החשובים ביותר שמפתח משחקים יכול לרכוש, אבל אין זה אומר שזה משהו שחייבים לעשות על ההתחלה. מצד שני, הפיתוי לחבור לאנשים אחרים, על מנת שיעזרו לנו להרים את החזון שלנו בצורה הטובה ביותר, הוא גדול. אבל לדעתי האישית, זה אינו בהכרח הדבר הנכון לעשות.


זה נכון שזה לא הגיוני שאדם אחד יעשה פרויקט מהתחלה עד הסוף. גם אם אתם מיחידי הסגולה שמסוגלים לעשות זאת, זה כרוח בהרבה עבודה. אז גם אם אתם מסוגלים זה לא אומר שאתם צריכים לעשות את זה. מצד שני, עבודה של מספר אנשים, היא תמיד יותר מסובכת, מבחינת ציפיות, תיאום זמן, ותיקשור הרצונות השונים. ואם מוסיפים לזה את הענין שאותם אנשים הם או כולם חסרי ניסיון, או בעלי רמת ניסיון שונה, אז אפשר להתחיל להבין כמה בעיות עלולות לצוץ במהלך הפרויקט.


"תתחילו לבד - במטרה להשלים ביחד"






אז אייך אפשר לגשת לזה בצורה אחרת?
ההצעה שלי היא כזאת, תתחילו לבד - במטרה להשלים ביחד. מה זה אומר - זה אומר שתקדמו כמה שאתם יכולים מהפרויקט בעצמכם, ואז (במידה ועדיין תרצו בכך) תכניסו אנשים נוספים


אם למשל אתם מפתחים, שזו אותה פוזיציה שאני נמצא בה. בשביל לעשות את המשחק ולבדוק אם הוא כיפי, אתם יכולים להתחיל לעשות אותו בלי סאוונד יותר מידי, ובלי גרפיקה יותר מידי. זו לא פעם ראשונה שאני מזכיר את הגישה הזאת בבלוג, אבל אם קודם לכן הזכרתי אותה כגישה שתאפשר לכם מלכתחילה ללמוד לפתח משחקים, הפעם אני דווקא מתיחס אליה כגישה מתאימה גם לבניה של משחק. 


למה הגישה הזאת רלוונטית גם לבניה של משחק? מפני שאם בנינו את המשחק, עם מרובעים ועיגולים, וסאוונדים זמנים שהורדנו מהאינטנט. והמשחק עובד ושלם באופן כללי, אז המעבר מהגירסה הזאת לגירסה שתכלול גרפיקה טובה, וסאוונד מושקע, היא הרבה יותר קצרה.


מכאן בא השם של הפוסט. בניית גירסה של משחק שהוא טוב, והוא כיפי, אבל הוא מכוער.
נכון שזה משהו פחות טוב ממשחק מושלם ומהוקצע, אבל הוא הרבה יותר בר השגה, ואפשר להתחיל לעשות אותו לבד.


"ברור מאליו, שעם כל המידע הזה, יהיה קל יותר..."










אם אחרי שפיתחתם את הגירסה הזאת של המשחק, שנקרא לה אלפא. אתם רואים שהמשחק טוב, ומהנה ושאתם רוצים להמשיך אותו למוצר מוגמר, אז המצב כבר הרבה יותר טוב, כיוון שה Design של המשחק, נובע מהמשחק ברמת האלפא. ואם עשיתם למשל אנימציה של איש קווים הולך של 20 פריימים, ומלבן מסויים שיהיה רקע, ריבוע למכשול, וכדומה, ונתתם להם שמות מתאימים בשלב באלפא, אתם יכולים לתת אותם למעצב, עם המשחק, ויהיה לו הרבה יותר קל להבין להחליט וליצור את האסטים הגרפיים למשחק.
אותו דבר נכון בשביל הסאוונד. יכול להיות שהייתם מתחילים לעבוד מהתחלת הפרויקט עם אשת סאוונד, ואומרים לה שאתם צריכים צליל ליריה במשחק. היא היתה מיצרת לכם משהו, וכשהייתם משלבים אותו במשחק, הייתם רואים שהוא לא מתאים לאופי של היריה במשחק. יכול להיות למשל שהיריה במשחק איטית, והצליל מהיר.
בשיטה שעובדים עם גירסת אלפא, ורק אז מכניסים את אשת הסאוונד, אז בגירסת האלפא יש כבר אולי איזה צליל שהורדתם מהאינטרנט, שהוא אולי לא מתאים פרפקט, אבל הוא בכיוון הנכון. ויש את הרפרנס של המשחק, שמראה את אופי היריה. ברור מאליו, שעם כל המידע הזה, יהיה קל יותר לאשת הסאוונד, למצוא בדיוק את הסאוונד שמתאים למשחק שלכם. 


 "קיבלתם את "שרביט" ההנהגה..."








אז מובן לנו למה זה יותר קל לעשות את הפיתוח של המשחק לבד, ואז להכניס בהמשך את האנשים האחרים. אבל יש לזה עוד יתרון נוסף. כי הבניה של האלפא, לא רק מקלה את התהליך של הכנסת אנשים נוספים, היא עושה עוד שני דברים. היא קובעת את ה Design ועובדות אחרות בשטח, והיא גם מראה מה היכולת שלכם בתור מפתחים. מהרגע שבניתם את האלפא, אתם מובילים את הפרויקט, כל מי שנכנס אחרי זה, מתאים את עצמו אליכם. די בעבדה הזאת לבדה כדי לעוזר לשמור על החזון שלכם למשחק. באותה הזדמנות, קיבלתם את "שרביט" ההנהגה. עכשיו אם אתם עדיין רוצים לסיים את הפרויקט עם מתנדבים על חשבון רווחים עתידיים, אז מה יכול לתת יותר מוטיבציה ממשחק עם אלפא גמורה, שאפשר לשחק בו ולהרגיש כמה הוא כייף.
כמובן שאם הוא לא כייף... לא נורא - ניסיתם, אפשר לנסות שוב וללמוד עוד. בכל מקרה, זה אולי החיסרון הכי גדול לשיטה הזאת. אם חסרה לכם היכולת לעשות משחק טוב, מכל סיבה שהיא, כמו חוסר ניסיון, מחסור בידע תכנותי וכדומה. אז אתם אולי תלמדו הרבה מהעבודה על האלפא, אבל יש הרבה סיכוי, שלא יהיה מי שירצה לעזור לכם להמשיך אותה. בכל מקרה, עדיף להבין שמשהו לא מספיק טוב, וללמוד מזה, מאשר להיות במצב שאתם עוד לא מבינים למה זה לא מספיק טוב. תופעה שאפשר לראות אצל אנשים שבטוחים שהם יכולים לעשות משחק מצויין וכיפי, שכולם ירצו לשחק. למרות שלהם עצמם לא יצא לשחק בכל כך הרבה משחקים.


"אבל מה אם אתם בכלל גרפיקאים?"






אז הזכרנו את היתרונות  הלא מעטים, בבנית משחק בשיטה הזאת של בניית גרסת אלפא, אבל הצגנו אותה מכיוון המפתח - המתכנת. אבל מה אם אתם בכלל גרפיקאים? האמת - אני חושב שבמקרה של גרפיקאים, אפשר להגיד שפחות או יותר אותם כללים תקפים. רק שהכוח של הגרפיקאים נובע יותר בהצגת קונספט, מאשר משחקיות. אם גרפיקאי היה בא אלי עם רעיון טוב למשחק, כשכל הקונספט ארט מוכן, ויש אנימציות, רקעים ואסטים נוספים, הייתי מתיחס להצעה שלו ברצינות. במיוחד אם זה היה מוצא חן בעיני. הרי אפשר לעשות אנימציות לא רעות בפלאש שיראו את החווית משתמש של שחקן שמשחק, שממנה ינבע הרבה מ design והתכנות של המשחק.


כיוון אחר שגרפיקאים (עם קצת חוש טכני) יכולים לעשות, הוא אולי לקחת דוגמה למשחק מוכן, ולהחליף את האסטים לדברים שהם יצרו, וליצור לו לוק אחר.


מבחינת אנשי סאוונד - המצב כבר אחר










גישה אחרת היא לנסות את מזלם עם מנועי משחקים שהזכרנו בתחילת הבלוג, שאולי לא יאפשרו להם לבנות משחק מורכב במיוחד, אבל  כן משהו התחלתי ופשוט יותר.


מבחינת אנשי סאוונד - המצב כבר אחר. לאיש סאוונד הייתי מציע אולי את האופציה של להחליף סאוונדים למשחק קיים בדומה לגרפיקאים, ואולי גם לעבוד עם אחד המנועים. אבל במקרה שלהם, להראות תיקיה של סאוונדים למשחק, אולי עם כמה ציורים פשוטים, בסגנון story board, זה משהו שיכול לקדם איש סאוונד, לקראת בניית סאוונדים למשחקים. 


אז זו ההצעה שלי, לגבי אייך להתחיל לבנות משחק, גם אם אין לכם צוות ואתם מתקשים או חוששים מלגייס מתנדבים (ובצדק).
לגישת הזאב הבודד במצב זה יש את היתרונות שלה. יש לה גם את החסרונות שלה. לבד זה פחות כייף, ויכול להיות שהיה לוקח פחות זמן לבנות את הפרויקט, אם כולם היו מתחילים באותה נקודה. זה כמובן תקף שבעתיים למשחקים גדולים, ככל שהמשחק יותר גדול ומורכב, לבנות אותו לבד זה... פחות כייף. אני אשאיר לכם לעשות את השיקולים שלכם, אבל אני בהחלט מציע לכם להמנע מהודעות בסגנון "אני X, יש לי רעיון מדהים למשחק, ואני מחפש Y ו   Z כדי שנבנה אותו ביחד (ונתחלק במיליונים אחרי זה)".  (<- כפל קוד) 


זה פשוט לא כזה מקצועי ולא כזה רציני.


לעומת זאת אחד הדברים שיעבדו הכי טוב, זה לשלב את גישת הזאב הבודד, עם גירסת האלפא ביחד עם... כסף. אם בניתם אלפא שאתם מרוצים ממנה, ויש לכם כסף לשלם לאנשים שהזמן והנסיון שלהם שווה את הכסף, אז הייתי אומר ששיחקתם אותה. כסף תמיד עושה את העבודה יותר טוב, ובצורה יותר קלה.  יש לו יכולת מדהימה ליצור מוטיבציה, ולפנות זמן אצל אנשים עסוקים.


...ונסיים בנימה קפייטליסטית זאת.


ותכתבו משהו בתגובות. אם אתם עושים לייק לחתול שמנגן על פסנתר ביו טיוב, או מגיבים בפורום על מה יותר טוב - פליסטשן או אקס בוקס, אז אתם יכולים לשקיע גם פה כמה ביטים וירטואליים. 



יום ראשון, 25 בדצמבר 2011

ללמוד מחיפושיות



"באגים הם חשובים"








כשלמדתי מדעי המחשב, לפני... כמה וכמה שנים, היה לי מורה לתכנות בשם אורי מוסנזון. הוא היה מורה מצוין מסיבות רבות, ובכלל אחלה בן אדם באופן כללי.


והוא לימד אותי דבר חשוב מאוד על באגים. הוא לימד אותי שבאגים הם חשובים. 


בתור מתכנתים אנחנו עושים הכל כדי להמנע מבאגים, לא בשונה הרבה מג'וקים, הינו שמחים לגלות כלפיהם אלימות פיזית חסרת רחמים אם רק יכולנו. הם תמיד מפריעים לעבודה, משגעים אותנו, מבזבזים לנו את הזמן, ומעלים אצלנו שאלות קיומיות בסגנון "למה לא הלכתי להיות אומן...". 


הרבה אנשים עוזבים את מקצוע התכנות, בגלל התסכול חסר הרחמים, שהם חוו בעיקבות באגים.   באגים גורמים לאנשים לחשוב שהמחשב שונא אותם. לפעמים הם אפילו משכנעים אותנו שוודו זה דבר אמיתי, שהמחשב מקולל. 




"יש דרכים טובות מאוד להתמודד עם באגים. ולא - לשבור את המסך עם המקלדת היא לא אחת מהן."




הייתי רוצה להגיד לכם שזה לא נכון, שבאגים הם תמיד בסופו של דבר הגיוניים, ושתמיד אפשר לפתור אותם, עם קצת רצון ומאמץ. אבל האמת שאני עוד לא לגמרי משוכנע בזה, והייתי מעדיף לא לעצבן את אלוהי הביטים אם אני לא חייב.


ובכול זאת, יש דרכים טובות מאוד להתמודד עם באגים. ולא - לשבור את המסך עם המקלדת היא לא אחת מהן. הדרך להתמודד בצורה טובה עם באגים, מחזירה אותנו למשפט שהזכרנו קודם "באגים הם חשובים"




באגים הם סימן מאלוהי הביטים, שיש לנו משהו ללמוד.












אז מה הכוונה בזה שבאגים הם חשובים. הכוונה היא, שבאגים הם לא איזה תוצר לוואי סתמי של טעות בתכנות, שכדאי לדלג עליו כמה שיותר מהר ולהמשיך הלאה. וזאת ברגע שמצאנו פתרון.
זה הרבה פעמים נראה ככה, שטעות כתיב, משתנה לא נכון, תנאי שלא נכתב כמו שצריך, הם סתם איזה משהו שעשינו בחוסר תשומת לב, ושברגע שעלינו עליהם (או מיד אחרי שקראנו לעצמנו מטומטם או אדיוט) אז זה הזמן להמשיך הלאה ו"להתקדם בעבודה". 


באגים הם סימן מאלוהי הביטים, שיש לנו משהו ללמוד.
כמו בהרבה מקומות אחרים בחיים, ניסיון לעשות משהו שלא הצליח הוא שיעור. הוא לא כישלון. הוא הופך להיות כישלון רק אם התיאשנו והפסקנו. לפי זה אולי תטעו לחשוב, שזה שאתם פותרים באג, זה אומר שניסיתם וניסיתם, ואז הצלחתם - אז הכל בסדר. אבל ההצלחה האמיתית מגיעה, (בתקווה שהיא באמת מגיעה) רק בפעם הבאה שתתקלו באותו באג או בבאג דומה.


מה זה אומר? ניתן דוגמה פשוטה. 


יש שני סוגי באגים עיקריים: באגים של סינטק (טעויות בכתיבת הקוד שהקומפיילר מוצא בקלות) ובאגים לוגיים, שהתוכנית מתקמפלת ואולי אפילו עובדת, עד שמגיעים לנקודה שמשהו לא מתרחש כמו שצריך. אז בדוגמה שלנו, נסתכל על טעות מסוג כתיבה. נגיד שכתבנו שורה בסגנון:


echo "Hello World \n;

"מה יקרה בפעם הבאה שאני שתקרה לי אותה טעות?"



גם בלי קומפילר, יש מצב שכבר שמתם לב שחברים לי גרשיים בסוף הפקודה " ושבעצם לא סיימתי את הסטרינג. זו טעות שדי קל לגלות, וקרוב לודאי שהקומפיילר יצעק עליה. אז סבבה, אני אקבל הודעת שגיאה, אלך למקום הזה ואתקן- וזהו. זה מהיר זה קל, וזה לא מבזבז זמן יותר מידי. מה יקרה בפעם הבאה שאני שתקרה לי אותה טעות? קרוב לודאי שאותו תהליך, ושוב זה לא נורא, כי זה לא ממש מבזבז זמן. פשוט רואים מה הטעות, ויש יודעים אייך לתקן.

הבעיה היא, מה קורה אם אין לנו את המידע הזה. ננייח שזו טעות, שהקומפילר לא אומר עליה כלום, הוא פשוט לא ידפיס את השורה או משהו. אז יכול להיות שנצטרך לעשות איזהו שהוא תהליך מחשבתי, ומעבר בקוד כדי למצוא את הטעות הזאת. ויכול להיות שז במקום שתי דקות יקח לנו חצי שעה למצוא את הבעיה. 
וכאן הגענו לנקודה החשובה. בפעם הראשונה לא היתה לנו ברירה, היינו צריכים לחפש ולמצוא את הבאג. את החצי שעה הזאת לא נוכל להחזיר לנו, אבל אם הצלחנו ללמוד משהו מהפעם הראשונה, שיעזור לנו לבזבז פחות זמן בפעם השנייה או השלישית וכן הלאה, אז למדנו וחסכנו זמן יקר.



תבדקו את ההנחות שלכם



זו בדיוק הסיבה למה עלינו להתיחס לבאגים כחשובים. בעיקבות מה שאורי לימד אותי, גם בעבודה שלי, וגם כשאני מכשיר או מיעץ למתכנתים מתחילים, אני תמיד מדגיש ומתיחס ברצינות לנקודה הזאת. צריך ללמוד את הבאגים שלנו. צריך להבין אייך הם קרו, למה עשינו את הטעות שגרמה להם מלכתחילה, מה עזר לנו לפתור אותם, ואייך נוכל לזהות ולפתור אותם יותר מהר בעתיד. 

מצורת הלמידה הזאת של באגים, לומדים אחר כך עוד כמה כללי אצבע שעוזרים למצוא באגים. אחד מהם הוא למשל 

תבדקו את ההנחות שלכם

הרבה פעמים אנחנו עושים הנחות לגבי קוד שלנו, ואז אנחנו מחפשים, מחפשים, ומחפשים ובסוף מגלים שעשינו - במקום + או משהו. לפעמים אולי נחשוב שפונקציה לא עובדת, ויסתבר שלמרות שהיינו בטוחים שקראנו לה, פשוט לא קראנו לה. אז פשוט - תבדקו את ההנחות שלכם.

כלל נוסף:

הקוד האחרון ששינינו הוא בדרך כלל מקור הטעות.

 יש מקרים אחרים כמובן, של שילובי קוד שיצרו תופעה חדשה, או משהו שפיספסנו. אבל בחלק גדול מאוד של המקרים, הדבר האחרון שנגענו בו מאז הקוד שעבד טוב, הוא זה שגורם את הבעיה.
 למה זה חשוב? במיוחד בבאגים לוגים, ובמיוחד במערכות מרכבות, או משחקים מורכבים, יש מצבים ששינוי קטן, טעות קטנה, יגרמו לתופעות, שלא הייתם מנחשים אפילו. דברים שלא יכולתם לדמיין. הם יכולים לגרום לכם לקפוץ לכל מיני חלקים בקוד, להתחיל לעשות שינויים דראסטיים, או כל מיני walkaround שרק יוסיפו בעיות במקום לעזור לכם להפתר מהבעיות הקימות. זה יכול להגיע למצב, שפתאום תגלו את הבאג, אבל אז תצטרכו להתמודד עם כל מיני שינויים שעשיתם. 
אז רגע, תעצרו את הסוסים, תחשבו על מה עשיתם מאז הפעם האחרונה שהקוד עבד, במה נגעתם. ואייך משהו שעשיתם שם, יכל להשפיע בצורה דראסטית גם על חלקים אחרים. 

זה לא משהו שאני עשיתי בקוד. (וזה כבר טוב :) ) 





מקרה שקרה. מתכנת שעבדתי על פרויקט שלו כשהוא היה בחופש, התקשר אלי כעבור כמה זמן, ואמר לי שהוא לא מצליח לעשות publish. הוא לא היה בטוח אם זה משהו שהוא עשה בקוד, שינוי גירסה של התוכנה שהוא עבד עליה, או שזה  היו שינויים שאני עשיתי בקוד. חשבתי על זה קצת, ואז אמרתי - "רגע, מאז שחזרת, עשית איזשהו publish שהצליח?". התשובה היתה חיובית, אז כבר צימצמנו את הבעיה לזה, שזה לא משהו שאני עשיתי בקוד. (זה כבר טוב :) ) 

עכשיו נותרה השאלה, האם זה משהו שהוא עשה בקוד, או שינוי גירסה. אז אמרתי לו לנסות לעשות publish  לגירסה ישנה של הקוד, שהוא יודע שהוא הצליח לעשות לה publish בעבר. ברגע שהוא ניסה ולא הצליח, אז כבר היה ברור שזה משהו שנגרם מהעידכון תוכנה, ומשם הדרך לפתרון היתה יותר קלה ומהירה. 

בואו נתן דוגמה לבאג, ונראה אייך אפשר ללמוד ממנו, על מנת למנע ממנו.

var  enemyA:Enemy = new Enemy();
enemyA.x = enemyPositions[1].x;
enemyA.y = enemyPositions[1].y;
this.addChild(enemyA);

var  enemyB:Enemy = new Enemy();
enemyB.x = enemyPositions[1].x;
enemyB.y = enemyPositions[1].y;
this.addChild(enemyB);

זה לא הקוד הכי מבריק בעולם, אבל הוא יספיק לנו.
נניח שאני מריץ את המשחק, ורואה שבמקום שני אויבים, אני רואה רק אחד מהם. רק את B - האויב השני. אני חושב שאולי לא הוספתי את הראשון למסך, אז אני בודק ורואה שיש את השורה:

this.addChild(enemyA);

אז אני חושב שאולי לא מיקמתי אותו טוב במסך, אבל אני בודק את המיקום: 

enemyA.x = enemyPositions[1].x;
enemyA.y = enemyPositions[1].y;

וזה נראה טוב. 



זה באמת קשור לשכפול




אז אני חושב אולי כששיכפלתי את הקוד, שכחתי להחליף איפה שהוא את enemyA ל enemyB אבל אני בודק ורואה שהחלפתי בכל המקומות הנכונים.

בסופו של דבר אני מבין שזה באמת קשור לשכפול קוד כייון שאולי שיניתי את כל ה enemyA ל enemyB איפה שצריך, אבל לא החלפתי את האינדקס של המערך מ 1 ל 2, ולכן מה שקרה הוא בעצם שמיקמתי את אויב B בדיוק מעל A ולכן זה נראה ש A לא נמצא.
יכול להיות שגיליתי את זה בגלל שהדפסתי את המיקום של האויבים על המסך, וגיליתי שהם בדיוק באותו מקום (ואולי לא שאחד איכשהו מחוץ למסך כי לא חשדתי).
יכול להיות גם שחשבתי שכשהכנסתי את המיקומים למערך, אז עשיתי copy paste והמיקומים של שני האויבים הם זהים, ורק אחרי שבדקתי גיליתי שהטעות היא לא בנתונים, אלה בזה שאני לוקח את הנתון הלא נכון.

אז קודם כל, אנחנו כבר רואים אייך כללי האצבע עזרו לנו למצוא את הבעיה, לא הנחתי שבטוח מיקמתי את הדברים נכון ובדקתי, והתמקדתי בקוד האחרון ששיניתי. אז זו לא היתה כזו בעיה למצוא את זה יכול להיות שזה לקח לי 10 דקות רבע שעה, ואם הייתי נוהג אחרת זה היה לוקח יותר. 



"מה אני יכול לעשות בעתיד כדי להמנע מאותה טעות."




אבל פה כאמור זה לא נגמר, מה אפשר ללמוד מזה:

1. שכפול קוד - מקור לטעויות, כדאי לשים לב שעדכנו את מה שצריך לעדכן.
2. אם לא רואים משהו, זה לא אומר שהוא לא על המסך או שלא הוספנו אותו. יכול להיות שהוא מוסתר.
3. טעות יכולה להיות או בנתונים המקורים, או שאולי אני לוקח את הנתון הלא נכון
4. הדפסות של מספרים שלא מתנהגים כמו שצריך (במקרה הזה המיקום) יכולות לעזור למצוא את הבעיה.

אז אחרי שהבנתי מה בדיוק מקור הטעות, ושמתי לב מה עזר לי לפתור אותו, וחשבתי על מה אני יכול לעשות בעתיד כדי להמנע ממנה. אז אם יהיה לי מקרה דומה שיחזור על עצמו - שבו הוספתי משהו למסך, ואני לא רואה אותו. אני אוכל לצפות שאולי זו הבעיה, אני אוכל להשתמש בכלים האלה, וברעיונות האלה כדי לבדוק אם זו הבעיה ואם אני פותר אותה. ואם זו תהיה בעיה דומה, אני אחסוך תסכול מיותר, וזמן יקר.

אבל בזה זה לא נגמר, כי אותם כלים ואותה הסתכלות, יוכלו גם לעזור לי במקרים שונים לגמרי. נגיד הדפסתי טקסט ממסמך, וקיבלתי שתי שורות זהות. זו בעיה בסגנון שונה קצת, אבל ההתנהגות היא דומה. כי כמו המקרה שעברנו לב, שגילינו שהמיקומים זהים. אז פה כבר אנחנו יודעים שהנתונים זהים, אז אם נבדוק באופן דומה, נגלה אולי שבמסמך, שיכפלנו בטעות שורה, או שלא תיקנו שורה ששכפלנו, ויש שתי שורות זהות.
וכך פתרון בעיה מסוג אחד עוזר לפתור בעיה מסוג אחר.

בעיה אחרת שנתקלתי בה. יצרתי כלי לעיצוב שלבים, שהשתמשתי בו כדי לסדר מלבנים בצורות שונות. אז סידרתי מלבנים כך שתצא צורה של לב למשל. אבל כשניגשתי למשחק ראיתי שאני רואה פחות מלבנים ממה ששמתי ושזה לא מסודר כמו שצריך. בגלל שנתקלתי במקרה דומה בעבר, היסקתי שהמספרים ממנוע הבניה לא עברו כמו שצריך, כי השתמשתי במקום מסוים ב float ובמקום אחר קיבלתי אותו כ int מה שגרם לכך שהמיספרים עוגלו, והמלבנים "התישרו" למיקומים מסויימים. וכך הידע הקודם, והאינטואיציה שנבעה ממנו חסכו לי זמן רק.

אז המסר של הפוסט הזה הוא פשוט - הבאגים שלכם חשובים, תלמדו אותם, תלמדו מהם, זה יחסוך לכם זמן ותסכול, ויהפוך אתכם למתכנתים הרבה יותר טובים. 



ולסיום - פינת הסחטיין
רציתי לתת ח"ח עצבני לכל מי שהיה קשור ופעל למען האי-כנס של GameIS שהיה בשפיים. היה היה מעולה. אני אציין בפרט את מדיה גיים שהשאילה לנו את הציוד לפינת המשחקים, וכמובן את בנק הפועלים שאירחו אותנו במתחם ההכשרה שלהם. 





יום ראשון, 18 בדצמבר 2011

חזרזיר וציפור כועסת נכנסים לבר... (פיזיקה במשחקים חלק 3)



אני ממשיך את הדיון שהתחלתי בשני הפוסטים הקודמים על פיזיקה במשחקים. 


כפי שאמרתי בסוף הפוסט האחרון, נשאר לנו לעבור קצת על מה קורה ב MyContactListener ובפונקציה CheckAndHandleCollision שמטפלת בהתנגשויות


נתחיל ב MyContactListener

public class MyContactListener extends b2ContactListener{

var _contacts:Array;

public function MyContactListener() {
// constructor code
_contacts = [];
}
override public function BeginContact(contact:b2Contact):void{
_contacts.push(contact);
}
override public function EndContact(contact:b2Contact):void {
if(_contacts.indexOf(contact) >=0){
// removing contact
_contacts.splice(_contacts.indexOf(contact),1);
}
}
override public function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void{ 
var fixtureA:b2Fixture=contact.GetFixtureA();
var fixtureB:b2Fixture=contact.GetFixtureB();
var nameA:String = MovieClip(fixtureA.GetUserData()).name;
var nameB:String = MovieClip(fixtureB.GetUserData()).name;
if((nameA == "DragonBlinking" &&
                                     nameB.length >=5 && nameB.substr(0,5) == "enemy") ||
  (nameB == "DragonBlinking"&& 
                                    nameA.length >=5 && nameA.substr(0,5) == "enemy")){
//trace("disablingContact");
contact.SetEnabled(false);
}

}

}

כפי שאתם רואים הקוד הוא די קצר, ואפילו אפשר להגיד שיש בו דברים מיותרים.

הדבר הכי חשוב להבין הוא ש MyContactListener יורש מ b2ContactListener וזאת בגלל ש b2ContactListener בעצם כבר עושה את כל הטיפולים בהתנגשות מבחינת העולם הפיזיקלי.

מה זה אומר - בעצם כלום, כי b2ContactListener באמת לא עושה כלום. b2ContactListener היא רק מחלקה שמגדירה 4 פונקציות ולא מממשת אותן, כל זאת בשביל שאנחנו נוכל להגדיר אותן לשימוש שלנו. הפונקציות הן:

 BeginContact - נקראת כאשר נוצר מגע בן שני אוביקטים
EndContact - נקראת כאשר נפסק מדע בין שני אוביקטים
PreSolve - נקראת רגע לפני שנוצא המגע (ובעצם מאפשרת לנו לבטל אותו, כולל השפעותיו הפיזיקאליות
PostSolve - נקראת רגע אחרי שנוצר המגע (לכן מאפשרת שימוש בידע על הכוחות שפועלים על האוביקטים בעקבות המגע.)

את הפונקציות האלה, אנחנו לא חייבים להגדיר מחדש, אבל מן הסתם אם אנחנו רוצים להשתמש במנגנונים של box2d כדי לדעת מתי יש מגע בין אוביקטים ולטפל בהם, אנחנו צריכים להגדיר אותן.
אז בואו נראה את ההגדרה:

var _contacts:Array;

public function MyContactListener() {
// constructor code
_contacts = [];
}
override public function BeginContact(contact:b2Contact):void{
_contacts.push(contact);
}
override public function EndContact(contact:b2Contact):void {
if(_contacts.indexOf(contact) >=0){
// removing contact
_contacts.splice(_contacts.indexOf(contact),1);
}
}

אני עושה כאן דבר פשוט, כל פעם ש box2d מודיע לי על קשר חדש (על מגע חדש) שנוצר, אני מוסיף אותו למערך משלי, וכל פעם שהוא מסיר אותו אז אני מסיר אותו אצלי. 

למה? - כי למערך הזה יש לי גישה ברורה, ובכל רגע במשחק יש לי אפשרות לדעת בדיוק מה נוגע במה, ולהשתמש במידע הזה לצרכים שלי (נראה את זה בהמשך, כשנסתכל על הטיפול שלנו בהתנגשויות) 

נמשיך לפונקציה הבאה:

override public function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void{ 
var fixtureA:b2Fixture=contact.GetFixtureA();
var fixtureB:b2Fixture=contact.GetFixtureB();
var nameA:String = MovieClip(fixtureA.GetUserData()).name;
var nameB:String = MovieClip(fixtureB.GetUserData()).name;
if((nameA == "PlayerBlinking" &&
                                     nameB.length >=5 && nameB.substr(0,5) == "enemy") ||
   (nameB == "PlayerBlinking"&& 
                                    nameA.length >=5 && nameA.substr(0,5) == "enemy")){
//trace("disablingContact");
contact.SetEnabled(false);
}

}

בתחלס ברוב המקרים לא הייתי עושה מימוש לפונקציה הזאת. הסיבה היא פשוטה. הפונקציה הזאת בעיקר משמשת אותי, כשאני רוצה לבדוק ממש לפני המגע אם הוא מתקיים, ולהחליט אם אני רוצה להשאיר אותו או לא. 

זה לא שימושי כמו שזה נשמע, כיוון שאנחנו כבר יודעים שיש לנו דרכים לעשות שבאוביקט מסוים, שום דבר לא יוכל לפגוע, על ידי הגדרתו כ sensor. או שהוא לא יתקל באוביקטים מסוימים, על ידי הגדרות של category bits ו mask bits). כיוון שאת הפרמטרים האלה ניתן לשנות בכל זמן, זה כבר נותן לנו שליטה רבה על מנגנון ההתנגשויות.

אבל כשחשוב לנו בזמן מסויים, ובנקודה מסוימת להחליט אם להתעלם ממגע שנוצר, אז זה המקום לעשות את זה. במקרה שמוצג בקוד למעלה PlayerBlinking זה שם שאני נותן ל Sprite של השחקן, אחרי שהוא נפגע, איבד פסילה, ואני לא רוצה שהוא יהיה פגיע מאויבים בזמן הקרוב. אז אני בודק שזה באמת PlayerBlinking שנוגע ב enemy מסוג כלשהו לפני שאני "מבטל" את המגע הזה.

מה שחשוב לשים לב אליו, הוא אייך לוקחים את המידע מה Fixtures של ה contact על מנת לבדוק מה נגע במה, אנחנו נראה שימוש דומה, בטיפול שנעה בהתנגשויות, שזה החלק הבא.

אם כן סיימנו עם ה MyContactListener בואו נסתכל על ה checkAndHandleCollisions. להזכירכם זוהי פונקציה שנקראת בכל צעד של המשחק, ומטרתה להוציא מידע מהמנוע של box2d ולעשות החלטות בהתאם. הינה היא בצורה מצומצמת שלה עם המידע הרלוונטי לנו:



// checkAndHandleCollisions  -------------------------
 
public function checkAndHandleCollisions():void{
// going over all the contacts
for each(var contact:b2Contact in PhysicsVars._contactListener._contacts){
if(contact.IsEnabled()==false){
continue;
}
var fixA:b2Fixture = contact.GetFixtureA();
var fixB:b2Fixture = contact.GetFixtureB();
var nameA:String = fixA.GetUserData().name;
var nameB:String = fixB.GetUserData().name;
var sprA:MovieClip = fixA.GetUserData();
var sprB:MovieClip = fixB.GetUserData();

...

// 20.8 fire touches enemies body
 if((isFireName(nameA) && isEnemyBodyName(nameB)) ||
(isFireName(nameB) && isEnemyBodyName(nameA))){
var eName2:String;
if(isFireName(nameA)){
eName2 = nameB;
}
else{
eName2 = nameA;
}
var tmpEnemy2:EnemyActor = _enemiesManager.getEnemyByName(eName2);
if(tmpEnemy2 && tmpEnemy2._isObstical == false){
tmpEnemy2.handleGotHit(true);
}
 
}
}
...
}

כיוון שמרבית הקוד בפונקציה הזאת, הוא ספציפי מידי מצד אחד, ובכל מקרה חוזר על עצמו מצד שני, השארתי רק את הדוגמה לתנאי if אחד, משורה די ארוכה של תנאים. אבל למעשה, באמת שבכל מקרה כולם מתנהגים אותו דבר.

נתחיל בקוד שמשתמש במידע מה ContcactListener

for each(var contact:b2Contact in PhysicsVars._contactListener._contacts){
if(contact.IsEnabled()==false){
continue;
}
var fixA:b2Fixture = contact.GetFixtureA();
var fixB:b2Fixture = contact.GetFixtureB();
var nameA:String = fixA.GetUserData().name;
var nameB:String = fixB.GetUserData().name;
var sprA:MovieClip = fixA.GetUserData();
var sprB:MovieClip = fixB.GetUserData();

אז אנחנו רואים שאנחנו מוציאים את ה fixtures את ה Sprites ואת ה names מכל contact שקיים במערך contacts_ של MyContactListener

מהרגע שיש לנו את המידע הזה על הקשר, אנחנו יכולים להחליט אייך לטפל בו:

// 20.8 fire touches enemies body
 if((isFireName(nameA) && isEnemyBodyName(nameB)) ||
 (isFireName(nameB) && isEnemyBodyName(nameA))){
var eName2:String;
if(isFireName(nameA)){
eName2 = nameB;
}
else{
eName2 = nameA;
}
var tmpEnemy2:EnemyActor = _enemiesManager.getEnemyByName(eName2);
if(tmpEnemy2 && tmpEnemy2._isObstical == false){
tmpEnemy2.handleGotHit(true);
}
  
 }

אז זו הדוגמה של אייך אנחנו בודקים אם אש של השחקן שלנו פגעה באויב, אנחנו בודקים את זה די בקלות באמצעות השמות שהוצאנו, וכיוון שהשם של האויב הוא ספציפי לכל אויב, אפשר להשתמש בו כדי למצוא את האויב שבמערך של ה enemiesManager (מחלקה שאני משתמש בה לניהול כל האויבים במשחק) ואז אפשר לקרוא לפונקציה handleGotHit שמטפלת בכל השינויים הגרפיים והלוגיים הנחוצים לטיפול באויב שנפגע.

וזה בעצם הכל - סיימנו לעבור על הנקודות החשובות והעיקריות בבנית משחק פיזיקאלי עם box2d. יש מן הסתם עוד המון דברים שאפשר לעשות עם box2d אבל זה הבסיס. אם זה מעניין אתכם, אז זה הזמן להתחיל לחפור ולעשות קצת טוטוריאלים.  רק שימוש לב שהטוטוריאלים שאתם עובדים איתם עדכניים מספיק, כי יש דברים כמו השימוש ב fixtures שנכנסו רק מגירסה 2.1 בפלאש. 

מקווה שנהניתם, בכל מקרה אם יש לכם שאלות נוספות, אתם מוזמנים להשאיר תגובה.