‏הצגת רשומות עם תוויות XNA. הצג את כל הרשומות
‏הצגת רשומות עם תוויות XNA. הצג את כל הרשומות

יום ראשון, 5 ביוני 2011

Particle System


היי,

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

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


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

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

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

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



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

עכשיו יש לנו חלקיק עם מאפיינים, והרבה. אבל הוא בעצם לא עושה כלום מעבר לאחסון נתונים, לכן צריך להוסיף לו פונקציות שיבצעו את כל הפעולות שחלקיק עושה.
נתחיל מ-Ctor (פונקציית בנאי, Constructor), אין הרבה מה לעשות מלבד קליטת מאפיינים (תסלחו לי על השימוש ב-keyword this):
Particle Constructor

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

Update Particle

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

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


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


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

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

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

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

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

פונקציות עדכון וציור החלקיקים

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

הערה: לקבלת האפקט המרשים ביותר, בדרך כלל תצטרכו להשתמש ב-SpriteBlendMode.Additive בפקודה Begin של ה-SpriteBatch עליו אתם מציירים.

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

לעוד פרטים והורדה של ה-Particle System שלי:
http://www.fxp.co.il/showthread.php?t=6745484

סרטון הדגמה:


ועידכון קטן בנוגע ל-Minecraft שלי ב-XNA, הוספתי יום עם שמש ולילה עם ירח:

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

ומוזיקה! להקה מצויינת שלא אכזבה באף אלבום שהיא הוציאה:



לילה טוב :)
ד"א אם מישהו מעוניין שאכתוב על נושא מסויים, תגיבו על הפוסט עם הנושא ובמידה והוא יתאים לבלוג אני אדאג לכתוב עליו פוסט.



יום חמישי, 26 במאי 2011

לברוא את העולם ב-C#

היי,


לפני מספר חודשים שיחקתי במשחק MineCraft, למי שלא מכיר זה משחק שבו המטרה היא ליצור ולבנות ביום ולהגן על עצמך מפני זומבים בלילה.
מה שמיוחד במשחק הזה הוא שהדגש לא על הגרפיקה אלא על המשחקיות.
העולם של מיינקראפט בנוי מקוביות ונוצר באופן דינאמי, כלומר תוך כדי המשחק וברנדומליות.
לאחר תקופה מסויימת שבה שיחקתי רציתי ללמוד איך יוצרים עולם כמו של מינקראפט.
בפוסט זה אסביר על יצירת האדמה וקצת מעבר.
MineCraft
הכלי שנראה לי הכי מתאים לפרוייקט הוא XNA, ממספר סיבות:
  • השפה האהובה עלי היא C#.
  • אני מכיר את אופן העבודה עם XNA ויש לי ניסיון עם סביבת העבודה הזאת (בדו מימד בעיקר).
  • XNA נותן גישה ישירה לורטקסים (vertices) שירונדרו. כלומר אני אוכל לשלוט על כל נקודה ונקודה בעולם התלת מימדי באופן ישיר.
לאחר שהחלטתי על XNA גיליתי שאני צריך לרענן את הידע שלי בעבודה עם 3D, לשם כך נעזרתי בסדרת מדריכים שאני ממליץ עליה מאוד:
http://www.riemers.net/ 
(בצד ימין למטה תוכלו לראות את רשימת המדריכים,  3D series 1 ו-3D series 2 ילמדו את הידע הבסיסי הנדרש בעבודה ב-3D עם XNA).

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

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

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


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


התחלתי בניסיונות:


ניסיון 1:


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


הקוד:
לינק

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


על מנת להמחיש את כל התהליך הכנתי אנימציה:



המשבצות האדומות הן השורות הראשונות בציר ה-Z וציר ה-X.
לאחר מכן מתחיל תהליך בו כל משבצת צהובה נוצרת על סמך ממוצע הגבהים בין כל 2 משבצות בצבע תכלת.

התוצאה:


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

התוצאה נחמדה ביותר, יש בעיה עם יצירת מישורים אבל ההרים נראים מצויין.
בעיה די מציקה עם האלגוריתם היא שהוא נוטה להנמיך את המפה ככל שמתרחקים מנק' ההתחלה.
כלומר אם בנקודה (0, 0) היה גובה של 10 קוביות, בנקודה (30, 30) בדרך כלל יהיה גובה של 2 קוביות ומטה.

הבעיות האלה די הפריעו ולכן ניסיתי לחשוב על פתרון אחר.


ניסיון 2:

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


הקוד:
לינק


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


התוצאה:
ניסיון 2. עובד טוב לרוחב אבל לא לאורך

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


עברתי לניסיון הבא.




ניסיון 3:

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

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

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

והתוצאה:



ניסיון 3. אין "רעש" ואין בעיות אורך רוחב.
אם יש משהו שעושה אותי מאושר בחיים האלה זה הפתרון הזה... XD

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



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


לילה (או מה שנשאר ממנו) טוב :)

יום חמישי, 3 בפברואר 2011

מי אמר שמתמטיקה לא יכולה להיות יפה?

היי,

זה הפוסט הראשון בבלוג שלי שיכיל תוכן.
תכננתי לכתוב על Global Game Jam 2011 בישראל אבל ברגע האחרון החלטתי להחליף נושא.

היום דיברתי עם חבר שלי במסן על שההורים שלי החליטו להעיר אותי כשנרדמתי אחרי שחזרתי מבית הספר ואז אמרתי לו שלא הצלחתי לחזור להרדם.
הוא אמר שזה מזכיר לו את אפקט הפרפר ותורת הכאוס (אל תשאלו אותי איזה ספרים הילד קורא...), ואז הוא שלח לי לינק לויקיפדיה על כאוס ולאחר מכן על קבוצת מנדלברוט.
קבוצת מנדלברוט היא אוסף של נקודות המהוות פרקטל (פרקטל - צורה החוזרת על עצמה בקני מידה שונים, בחלק מהפרקטלים משתנה מעט הצורה).
 הגדרת הקבוצה היא כל הנקודות שעבורן הסדרה: zn+1 = zn2 + c חסומה (כלומר - יש ערך כלשהו שהיא לעולם לא תעבור אותו). z הוא מספר מרוכב, כלומר הוא כולל חלק ממשי וחלק לא ממשי וכך גם C. ממש לא בא לי להכנס לכל ההסבר המתמטי של מספרים מרוכבים, אז מי שלא הבין שיעבור למתמטיקה 5 יחידות או שייאלץ ללמוד לבד. :)
הדבר שכל כך יפה בקבוצה הזאת הוא שאם משחקים טיפה עם הפונקציה ניתן לקבל תמונות מדהימות.

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

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

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

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

אז קוד הפסאודו למי שמעוניין:

והקוד שכתבתי ב-XNA (מכיל את יצירת התמונה בלבד, ללא הציור למסך):

כאן תם הפוסט השני בבלוג, מקווה שנהנתם.
בפוסט הבא אני מקווה שיצא לי לכתוב על Global Game Jam כמו שתכננתי.

ואיך אפשר לסיים בלי מוזיקה טובה?
אחד השירים הכי טובים של Linkin Park, אני כל כך רוצה שהם יחזרו ליצור מוזיקה טובה כמו פעם :(


 
אז שיהיה לכולנו לילה טוב! :)