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

לו רק היה לי חזרזיר לשנוא (פיזיקה במשחקים חלק 2)

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


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


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


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


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


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


Dynamic
Static
Kinematic


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


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


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


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


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


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


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


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

public class Actor extends EventDispatcher {

protected var _body:b2Body
protected var _costume:DisplayObjectContainer;
public function Actor(myBody:b2Body, myCostume:DisplayObjectContainer) {
// constructor code
_body = myBody;
_body.SetUserData(this);
_costume = myCostume;
updateMyLook();
}
public function updateNow():void{
updateMyLook();
childSpecificUpdate();
}

protected function childSpecificUpdate():void{
// function does nothing
// might be called by children
}
public function destroy():void{
//remove actor from world
// remove even listeners - misc cleanup
cleanupBeforeRemoving();
// remove the custom sprite from the display
_costume.parent.removeChild(_costume);
//destroy body
PhysicsVars.world.DestroyBody(_body);
}
protected function cleanupBeforeRemoving():void{
// function does nothing
// might be called by children
}
protected function updateMyLook():void{
_costume.x =  _body.GetPosition().x *PhysicsVars.RATIO;
_costume.y = _body.GetPosition().y *PhysicsVars.RATIO;
_costume.rotation = _body.GetAngle()*(180/Math.PI);
}
public function get body():b2Body { return _body;}
public function get costume():DisplayObject{ return _costume;}
}


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

public function Actor(myBody:b2Body, myCostume:DisplayObjectContainer) {
// constructor code
_body = myBody;
_body.SetUserData(this);
_costume = myCostume;
updateMyLook();
}

הפונקציה מעל היא כמובן ה constructor. היא בנויה באופן כזה, שב Actors השונים, שיורשים מהמחלקה Actor אנחנו נגדיר את מה שצריך להגדיר כדי לבנות את ה body ואת ה costume ואז נעביר אותם לפונקציה הזאת, שהיא ה super constructor (ה constructor של האבא). 

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


בסוף הקונסטרקטור, אנחנו קוראים לפונקציה הבאה:

protected function updateMyLook():void{
_costume.x =  _body.GetPosition().x *PhysicsVars.RATIO;
_costume.y = _body.GetPosition().y *PhysicsVars.RATIO;
_costume.rotation = _body.GetAngle()*(180/Math.PI);
}

הפונציה הזאת דואגת להצמדה במיקום בין ה costume ל body. למעשה העולם הפיזיקאלי סוג של חי בפני עצמו, האוביקטים נעים שם לפי החוקים שלו, ומשפיעים אחד על השני. אבל ה Sprite שלנו (הרי הם ה costumes) הם לא חלק מהעולם הפיזיקאלי. אז אם לא נעשה משהו שיגרום להם לנוע בהתאמה לאוביקטים הפיזיקלים, אז בעצם לא תהיה לנו פיזיקה במשחק.
אז פה אנחנו עושים את זה אנחנו מעדכנים את ה x וה y. להזכירכם ה PhysicsVars.RATIO הוא משתנה שמיצג את היחס בין גודל בנקודות במסך למטר בעולם הפיזיקאלי, ומוכר גם כ PTM RATIO. לכן אנחנו מכפילים את ערכי ה x וה y של ה body שחי בעולם של יחידות של מטרים, ב ration שיתן לנו את הגודל בנקודות שמתאים לעבודה עם spriteים. (ושוב נזכיר, במקרה שלנו נקודות זה כמו פיקסלים -> כל 30 נקודות הם מטר אחד). 

בשורה האחרונה אנחנו מסובבים את ה costume בהתאמה ל sprite גם פה יש לנו איזה חישוב המרה, שבגדול פשוט עושה המרה מרדיאנים למעלות. (לא נכנס לעומק של זה כרגע, אפשר לקחת ולהשתמש בקוד הזה כמו שהוא. ולמידע נוסף לחפש משהו כמו radians to degrees או להיפך, בידידנו הטוב גוגל) 

נקודה חשובה לזכור: לפונקציה הזאת אנחנו נמשיך ונקרא בכל loop של התקדמות במשחק, עבור כל ה Actorsהפעילים. רק על ידי זה, אנחנו יכולים לגרום לכל ה costumes לנוע עם כל ה bodies המתאימים להם, וליצור את האשליה שה costumes הם שעובדים פיזיקאלית.

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

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

החלק הראשון של ה constructor, החלק בו מגדירים את ה costume.

public function FireActor(aParent:DisplayObjectContainer,fireType:int, pos:Point) {
// constructor code
// 20. type, id and costume
_fireType = fireType;
var tmpCostume:MovieClip = new FiresAnimations();
FiresAnimations(tmpCostume).setupFire(_fireType);
tmpCostume.x = pos.x;
tmpCostume.y = pos.y;
aParent.addChild(tmpCostume);


פה אנחנו יוצרים את ה FireActor מחלקה שיורשת מ Fire ונועדה לטפל ביריות.

_fireType = fireType;

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

       var tmpCostume:MovieClip = new FiresAnimations();
      FiresAnimations(tmpCostume).setupFire(_fireType);

ה FiresAnimations זה בעצם מחלקה שמוצמדת ל MovieClip שמכיל את כל האנימציות והתצוגות של סוגי היריות השונים, זה בעצם ה Costume של ה Actor שאנחנו דואגים גם להוסיף לסצינה.

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



//30. vars for body & shape
var aBody:b2Body;
var aBodyDef:b2BodyDef;
var aBodyShape:b2CircleShape;
var aBodyFixtureDef:b2FixtureDef;
נתחיל עם המשתנים שנצטרך, יש את ה body שזה הגוף עצמו. יש BodyDef שזה ההגדרות לפיהם נבנה את הגוף, יש את ה Shape במקרה שלנו צורה מעגלית b2CircleShape.  ויש את ה Fixture שזה אפשר אולי להגדיר כ תצורה של האוביקט, שנובעת מהצורה.

הרבה חלקים לאוביקט אחד...

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

הצורה Shape היא למעשה חלק מה Fixture. היא באה ליצג צורה כלשהי באוביקט. שימו לב - צורה באוביקט, ולא כל האוביקט. למעשה אוביקט יכול להיות מורכב מכמה fixtures כשלכל אחת צורה משלה. בהרבה מקרים זה חייב להיות ככה. בואו נסביר. 

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

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

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

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

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

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

ואוו - זה כבר נהיה די ארוך, בואו נתקדם.




//40. set bodyDef, position && type
aBodyDef = new b2BodyDef();
aBodyDef.position.Set(pos.x/PhysicsVars.RATIO, (pos.y)/PhysicsVars.RATIO);

אז למעלה אנחנו רואים הגדרה של bodyDef עם המיקום (שימו לב שהפעם חילקנו ב Ratio) 

                      if(_fireType == 1){
aBodyDef.type = b2Body.b2_kinematicBody ;
aBodyShape = new b2CircleShape(24/PhysicsVars.RATIO);
}
else if(_fireType == 2){
aBodyDef.type = b2Body.b2_dynamicBody;
aBodyShape = new b2CircleShape(12/PhysicsVars.RATIO);
}



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

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

                       var rBodyShape:b2PolygonShape = new b2PolygonShape();  
if(_fireType == 3){ // lightning
rBodyShape.SetAsBox(340/PhysicsVars.RATIO, 60/PhysicsVars.RATIO);
aBodyFixtureDef.shape = rBodyShape;
}

האמת, שחשבתי בהתחלה לוותר על להראות קוד שקשור ליריה מהסוג הזה, אבל זו דוגמה טובה ליצירה של קופסה. הגדלים הם חצי הרוחב וחצי הגובה, כך שהקופסה הזאת באמת היא בגודל 680 על 120.
והשורה הזאת:
aBodyFixtureDef.shape = rBodyShape;

היא כבר ממש ההגדרה של הצורה כחלק מהתצורה, ה fixture


בכל מקרה, אחרי שיש לנו צורה, אפשר ליצור fixture בואו נסתכל על כל התהליך של יצירת fixture:

// 60. fixture

aBodyFixtureDef = new b2FixtureDef();
        aBodyFixtureDef.shape = aBodyShape;
aBodyFixtureDef.shape = aBodyShape;
aBodyFixtureDef.density = 10.0;
aBodyFixtureDef.filter.categoryBits = 1;
aBodyFixtureDef.filter.maskBits = 65535 - 4;
aBodyFixtureDef.friction =0;
aBodyFixtureDef.restitution=1;
//70 user data
tmpCostume.name = "fireType_"+_fireType+"_ID_"+_id;
aBodyFixtureDef.userData = tmpCostume;


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

יש לנו פה עוד שני דברים חשובים, האחד הוא ה categoryBits , וה maskBits שהם חלק מהפילטר. 
השני זה ה userData של ה fixture. 

ה box2d נותן לנו כלי מצוין להחליט מה אנחנו רוצים שיתנגש במה. ה category bits מגדירים את הקטגוריה של ה fixture, ומשייכים אותו לקבוצה. וה mask bits, מיצגים את כל הקבוצות שה fixture הזה יכול להתנגש איתם. 
לא מדובר רק בהתנגשות פיזיקאלית, למעשה אם נגיד שיש אויב שה category bits שלו הוא 16, וה mask bits של יריה מסוג מסויים הוא 65535-16 אז כל לא ישמר, ולא תהיה שום התיחסות בין כל מגע של היריה עם האויב.  המספרים עצמם, הם חזקות של 2, (למבינים בינארית, יצוג של 16 ביטים, כלומר 16 קבוצות אפשריות) 
אם זה מבלבל אתכם קצת תלכו לפי הכלל הזה. לכל קבוצה תנו ב category bits קוד של חזקה של 2 (1,2,4,8,16,32 וכן הלאה) וב mask bits תנו 65535 פחות כל הקודים שאתם לא רוצים שהקבוצה הזאת תגע בהם.

ה user data של ה fixture עוזר לנו לדעת באיזה חלק של ה body פגענו. הרי body יכול גם להיות מורכב מכמה fixture, למשל אחד בצורת מלבן לראש, ואחד בצורת עיגול לגוף. זה אולי לא נשמע הגיוני ככה, אבל תחשבו על אויב במריו - אפשר לקפוץ לו על הראש ולהרוג אותו, אבל אם פוגעים לו בגוף אז נפגעים. אז אם נבנה גוף עם מלבן לראש, שהוא sensor ועיגול לגוף, ונדע אייך להבחין בינהם, אז נוכל לדעת מתי הדמות של השחקן היא זו שנפגעת ומתי זו של האויב.

בעצם מה שעשיתי זה הכנסתי את ה costume (או כל MovieClip או Sprite יכולים לעבוד) ל userData ודאגתי שהשם שלו יהיה משהו שאני אוכל לזהות בקלות. אני מקווה שבהמשך תהיה לנו הזדמנות לראות, אייך המידע הזה הופך שימושי בהתנגשויות.

עכשיו אחרי שהגדרנו את ה fixture Def נותר לנו ליצור את ה body ואז ליצור לו את ה fixture:

// 80. handling the body
aBody = PhysicsVars.world.CreateBody(aBodyDef);
aBody.CreateFixture(aBodyFixtureDef);
aBody.GetMass();


ולסיום, מה שנותר זה כמו שאמרנו, לשלוח את ה body וה costume ל super constructor

// 150. updating in actor
super(aBody, tmpCostume);

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

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

ונתראה בקרוב עם החלק שלישי (והאחרון?) של הדיון על פיזיקה עם box2d במשחקים.


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


וזה המשחק, Mishu the Dragon



יום רביעי, 30 בנובמבר 2011

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


הרבה מפתחי משחקים ברחבי העולם מקנאים מן הסתם בהצלחה של Angry Birds. מי לא רוצה לפתח משחק שמוכר מליונים של עותקים? (אפילו אם רק בדולר)


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


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


המנוע box2d הוא מנוע פופולארי למדי, הוא נבנה במקור ב C++, אבל יש לו המרות גם ל JAVA, גם ל objective C וגם לפלאש (actionscript). יש מן הסתם גם לשפות נוספות, לי אישית יצא לעבוד איתו בעיקר בפלאש, ובתכנות לאיפון. בתכנות לאיפון הוא מגיע בחבילה עם המנוע של cocos2d (שאולי גם עליו שווה לדבר בהזדמנות).


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


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


העולם הפיזיקאלי


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


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


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


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


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

private function setupPhysicsWorld():void{
var gravity:b2Vec2 = new b2Vec2(0,30);
var allowSleep:Boolean=true;
PhysicsVars.world = new b2World(gravity, allowSleep);
PhysicsVars._contactListener = new MyContactListener()
PhysicsVars.world.SetContactListener(PhysicsVars._contactListener);
// debug draw
var debug_draw:b2DebugDraw = new b2DebugDraw();
var debug_sprite:Sprite = new Sprite();
this.addChild(debug_sprite);
debug_draw.SetSprite(debug_sprite);
debug_draw.SetDrawScale(PhysicsVars.RATIO);
debug_draw.SetFillAlpha(0.5);
debug_draw.SetLineThickness(3);
debug_draw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
PhysicsVars.world.SetDebugDraw(debug_draw);
PhysicsVars._debugDrawSprite = debug_sprite;
}

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




var gravity:b2Vec2 = new b2Vec2(0,30);
var allowSleep:Boolean=true;
PhysicsVars.world = new b2World(gravity, allowSleep);

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

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

PhysicsVars._contactListener = new MyContactListener()
PhysicsVars.world.SetContactListener(PhysicsVars._contactListener);

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

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

                       // debug draw
var debug_draw:b2DebugDraw = new b2DebugDraw();
var debug_sprite:Sprite = new Sprite();
this.addChild(debug_sprite);
debug_draw.SetSprite(debug_sprite);
debug_draw.SetDrawScale(PhysicsVars.RATIO);
debug_draw.SetFillAlpha(0.5);
debug_draw.SetLineThickness(3);
debug_draw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
PhysicsVars.world.SetDebugDraw(debug_draw);
PhysicsVars._debugDrawSprite = debug_sprite;

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

אנחנו מגדירים פה מה יצוייר, אייך יצוייר ואיפה. אני לא אכנס לעומק של הדברים, רק אני אנצל את ההזדמנות להסביר מושג ה RATIO. אצלי אני מחזיק אותו בתוך מחלקה PhysicsVars במקום שהוא נגיש בקלות לכל מחלקה בפרויקט. נהוג לכנות אותו PTM_RATIO כשהכוונה ל Points to Meter Ratio, יחס בין נקודות למטרים.

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

העולם של המשחק שלנו הוא קצת שונה מהעולם האמיתי יש לנו פיקסלים, שאנו נתיחס אליהם כנקודות, ויש את היחידות של העולם הפיזיקאלי. עכשיו נניח שגובה הדמות שאנחנו משחקים במשחק היא64 פיקסלים, אם נגדיר את ה PTM_RATIO כשווה ל 1, זה אומר שהפיזיקה תתיחס אל הדמות כאילו הגובה שלה הוא 64 מטר, והבהתאמה, לכל שאר האוביקטים במשחק. זה יכול לגרום לכל הפיזיקה במשחק להתנהג באופן מוזר מאוד. אבל אם ה PTM_RATIO שווה ל 30 למשל, אז גובה הדמות שלי הוא קצת מעל 2 מטר, זה אולי נשמע לכם עדיין גבוה, אבל ברמה פיזיקאלית, ההבדל בין זה לבין 1.80, או אפילו 1.50 מטר אינו כזה גדול. אז אפשר לצפות שהפיזיקה תתנהג פחות או יותר כפי שאנחנו נצפה.

במקרה של פלאש אני משתמש באמת ב PTM_RATIO ששווה ל 30. כשאני מתכנת לאיפון אני משתמש ב 32, מסיבות טכניות של נוחות  (וגם  כי זה ה default ב template של cocos2d עם box2d)


לולאת המשחק הפיזיקאלי

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


private function enterFrameHandler(e:Event):void{
. . .
// 10. handling the step and all the basics
var timeStep:Number = 1/60;
var velocityIter:int=4;
var positionIter:int=4;
// 20. doing step
PhysicsVars.world.Step(timeStep, velocityIter, positionIter);
// 30. update actors
for each (var tmpActore:Actor in _allActors){
tmpActore.updateNow();
}
PhysicsVars.world.ClearForces();
if(GameData.kSHOW_DEBUG_DATA){
PhysicsVars.world.DrawDebugData();
}
// 50.check and handle collisions
checkAndHandleCollisions();  
       

                           .  .  .

}

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

                        // 10. handling the step and all the basics
var timeStep:Number = 1/60;
var velocityIter:int=4;
var positionIter:int=4;
// 20. doing step
PhysicsVars.world.Step(timeStep, velocityIter, positionIter);


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

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

הזמן של הרגע הוא 1/60 למעשה 1/60 שניה, זאת בגלל שהמשחק שלי שואף לרוץ בקצב של 60 פריימים לשניה (FPS). 

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

                      // 30. update actors
for each (var tmpActore:Actor in _allActors){
tmpActore.updateNow();
}

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

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

                        PhysicsVars.world.ClearForces();
if(GameData.kSHOW_DEBUG_DATA){
PhysicsVars.world.DrawDebugData();
}

אני לא אכנס למה השורה הזאת חשובה: 


 PhysicsVars.world.ClearForces();

אתם יכולים ברוב המקרים להתייחס אליה כמשהו נתון.

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

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

                        // 50.check and handle collisions
checkAndHandleCollisions();  

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

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

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

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

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


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




יום שלישי, 29 בנובמבר 2011

בעיות גדולות, פתרונות קטנים



בעיות גדולות, פתרונות קטנים




"פיצה ובירה הם רכיבים חיוניים בדיאטה המומלצת לרעיונות יצירתיים"








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


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


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




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


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


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


"הוא יכול לתת לכם לראות, אם הרעיון שהרגיש לכם הכי כיפי בעולם ברמת התיאוריה שווה משהו בפרקטיקה"


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


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


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



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






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


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




"...מה שהפך את הבחירה ללא משמעותית, ולמעשה הרס את הרעיון של מורכבות טקטית במשחק."


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


"היה לנו רעיון למשחק פלטפורמה שמבוסס על המשחקיות של The Lost Vikings, משחק שיצא מהחברה Silicon & Synapse, שלימים הפכה ל Blizzard".


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


לדוגמה, לפני כמה וכמה שנים הייתי חבר בצוות קטן של מפתחים בחברה בשם Koplay. למעשה היינו קבוצת חברים שכולם למדו מדעי המחשב בזמנו. היה לנו רעיון למשחק פלטפורמה שמבוסס על המשחקיות של The Lost Vikings, משחק שיצא מהחברה Silicon & Synapse, שלימים הפכה ל Blizzard. וחשבנו שזה יהיה נחמד לבנות את המשחק הזה כמשחק פרסומי, וכך לגייס תקציב לפיתוח שלו. אז אני יצרתי בזמנו אנימציות לדמות התינוק של במבה, ולדמויות של ביסלי, ויצרנו דגם קטן של המשחקיות בפלאש, ואיתו ניגשנו לאסם.
בסופו של דבר מסיבות שונות (בעיקר חוסר הצלחה של משחק פירסומי קודם של במבה) לא קיבלנו מהם מימון, אבל ראש הצוות לקח על עצמו אחריות גייס כסף, ובסופו של דבר הרמנו את המשחק The Dudes, שאפילו שווק בזמנו על ידי אטרי בארץ. (לצערנו ההשקעה לא חזרה, אבל אפשר להגיד שכולנו עשינו שם את הצעד הראשון והמשמעותי שלנו בפיתוח משחקים).


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


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






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


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


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


"עדיין יש מצב שעוד שבוע, שבועיים או חודש, תצטרכו לחזור על התהליך"




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


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


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


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


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


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



יום שישי, 18 בנובמבר 2011

עוד משחק?



עוד משחק?










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


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






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










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


"אנחנו רוצים לשמר את תחושת ההנאה מהמשחק"








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


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


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


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


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


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




"סצינת מעבר, שמצד אחד נותנת לו לנוח קצת, אבל מצד שני מעוררת מחדש את העניין שלו במשחק"


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


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




"אפשר לעשות פשוט restart"






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


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


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


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






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


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


"מה שקורה במעבר משאיר את השחקן בתחושה שהוא היה בה בעת ששיחק"




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


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


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


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


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


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


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


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


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


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

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


היו לי עוד כמה דוגמאות בנושא, רעות וטובות. אבל בין Angry Birds, ל  Cut the Rope ל Fruit Ninja - נראה לי שמצינו. הנושא הזה הוא רק חלק קטן מהמכלול של הנושאים שקשורים לחווית משתמש טובה במשחק. אבל אני רואה אותו כמאוד חשוב, כיוון שלפעמים אנחנו כל כך מתאמצים לתת לשחקן חוויה טובה צבעונית וכיפית בתוך הגיים פלי, שאנחנו שוכחים כמה חשוב זה פשוט לדאוג שהוא גם ישאר עם ההרגשה הזאת..



יום שני, 14 בנובמבר 2011

שיווק - its a bitch



"זה המקום שלי לדבר,  כמו כל ישראלי טוב, על משהו שאני לא מבין בו"




שיווק - its a bitch...


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


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


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




"אני לא מישהו שמפחד מללמוד שפה חדשה. כבר קשה להפתיע אותי"










רק שלשום הייתי בכנס של גוגל ואנדרואיד (תודה ליובל ספיר על הכניסה, ומצטער שלא מצאתי לך אפור, והמבין יבין) יצא לי לפגוש כמה מחברי, איתם יצא לי לעבוד בעבר בהייטק. [ הכנס כאן shoutout לרנן, ולמייק] .  מן הסתם חלק גדול מהכנס היה ממוקד אנדרואיד, ובין השאר שמעתי כמה הצעות לעבור לפיתוח לאנדרואיד, או לגרסאות מוביל עתידיות של windows. עכשיו, זה לא שאני לא מבין, שהשווקים האלה טומנים בתוכם אפשרויות חדשות ליצור ולהתפתח, אבל להגיד לכם את האמת, אחרי שכבר למדתי די טוב, ליצור משחקים שאני אוהב עם objective c ו cocos2d. ממש אבל ממש לא בא לי להחליף כיוון כל כך מהר. 


ללמוד לתכנת לאייפון עם  objective c  ו cocos2d זה היה לא פשוט. זה היה ללמוד סינטקט שונה, זה היה ללמוד מחדש להתמודד עם ניהול זיכרון (מה שלא ממש עשיתי מאז שתכנתתי ב C בלימודים) ולהתמודד עם זה, שזה לא תכנות עם מנוע, ושצריך לבנות כלים, מסויימים, רק כדי שתוכל לדעת לפחות חלקית אייך דברים נראים על המסך. באמת, לתכנות הזה יש קשיים, ואני לא מישהו שמפחד מללמוד שפה חדשה. כבר קשה להפתיע אותי אחרי בייסיק, C++, ג'אווה, פיטון, C#,  שלא לדבר על actionscript 2 ו- 3 ואפילו קצת XNA ושפת התכנות לתוכנת התלת מימד מאיה. אבל אני מודה, להכנס ל אובג'קטיב  C היה קצת קשה, אחרי הכל זה כיוון התפתחות שונה של שפות, מהצד של העץ של C++, ג'אווה ו actionscript. אבל למדתי לאהוב את השפה הזאת, והיום אני כבר מסוגל לעשות משחקים פיזיקאליים די ממגניבים תוך זמן קצר.


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




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


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


"משחק טוב + כסף לשיווק != המון כסף"














ובואו אני אספר לכם עוד אמת קטנה שאתה מכירים:


משחק טוב + כסף לשיווק != המון כסף


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






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












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






" - Marketing is underrated."










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


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


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


שפי