CodeGym /בלוג Java /Random-HE /מדור משחקים על CodeGym: תיאוריה שימושית
John Squirrels
רָמָה
San Francisco

מדור משחקים על CodeGym: תיאוריה שימושית

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

1. ירושה

עבודה עם מנוע המשחק CodeGym כרוכה בשימוש בירושה. אבל מה אם אתה לא יודע מה זה? מצד אחד, אתה צריך להבין את הנושא הזה: הוא נלמד ברמה 11 . מצד שני, המנוע תוכנן במיוחד להיות פשוט מאוד, כך שתוכלו לברוח עם ידע שטחי על ירושה. אז מהי ירושה? בפשטות רבה, ירושה היא מערכת יחסים בין שני מחלקות. אחד מהם הופך להורה, והשני הופך לילד (צאצא). יתרה מכך, ייתכן שכיתת ההורים אפילו לא יודעת שיש לה צאצאים. במילים אחרות, זה לא משיג יתרון מסוים בכך שיש לו צאצאים. אבל הירושה מעניקה לצאצא יתרונות רבים. והחשוב ביותר הוא שכל המשתנים והשיטות של מחלקת האב מופיעים בצאצא כאילו הקוד של מחלקת האב הועתק למחלקה הצאצאית. זה לא תיאור מדויק לחלוטין, אבל זה יספיק להבנה פשוטה של ​​ירושה. הנה כמה דוגמאות כדי להבין טוב יותר את הירושה. דוגמה 1: הירושה הפשוטה ביותר.
public class Parent {

}
כיתת Child יורשת את כיתת האב באמצעות מילת המפתח extends .
public class Child extends Parent {

}
דוגמה 2: שימוש במשתנים של מחלקת האב.
public class Parent {

   public int age;
   public String name;
}
כיתת הילד יכולה להשתמש במשתני הגיל והשמות של כיתת ההורה כאילו הוכרזו בכיתת ההורה .
public class Child extends Parent {

   public void printInfo() {

     System.out.println(name+" "+age);
   }
}
דוגמה 3: שימוש בשיטות של כיתת האב.
public class Parent {

   public int age;
   public String name;

   public getName() {
      return name;
  }
}
המחלקה Child יכולה להשתמש במשתנים ובשיטות של מחלקה אב כאילו הם הוכרזו במחלקה Child. בדוגמה זו, אנו משתמשים בשיטת getName() .
public class Child extends Parent {

   public void printInfo() {

     System.out.println(getName()+" "+age);
   }
}
כך נראית המחלקה Child עבור המהדר:
public class Child extends Parent{

   public int age;  // Inherited variable
   public String name;  // Inherited variable

   public getName() {  // Inherited method.
      return name;
  }
   public void printInfo() {

     System.out.println(getName()+" "+age);
   }
}

2. שיטות עקיפה

לפעמים יש מצבים שבהם אנחנו גורמים לכיתה Child שלנו לרשת איזו מחלקה אב שימושית מאוד, יחד עם כל המשתנים והשיטות שלה, אבל חלק מהשיטות לא עובדות בדיוק כמו שאנחנו רוצים. או בכלל לא כמו שאנחנו רוצים שיעשו. מה אנחנו יכולים לעשות במצב הזה? אנחנו יכולים לעקוף את השיטה שאנחנו לא אוהבים. זה מאוד קל לעשות: בכיתה Child שלנו, אנחנו פשוט מכריזים על שיטה עם חתימה זהה לשיטה בכיתה Parent, ואז אנחנו כותבים בה קוד משלנו. דוגמה 1: עקיפה של שיטה.
public class Parent {

   public String name;

   public void setName(String nameNew) {
       name = nameNew;
  }

   public getName() {
      return name;
  }
}
השיטה printInfo() תציג "לוק, לא!!!"
public class Child extends Parent{

   public void setName(String nameNew) {
       name = nameNew + ", No!!!";
  }

   public void printInfo() {
      setName("Luke");
      System.out.println(getName());
   }
}
כך נראית המחלקה Child עבור המהדר:
public Child extends Parent {

   public String name;  // Inherited variable

   public void setName(String nameNew)  // Overridden method instead of the inherited method {

       name = nameNew + ", No!!!";
   }
   public getName() {  // Inherited method.

      return name;
   }
   public void printInfo() {

     setName("Luke");
     System.out.println( getName());
   }
}
דוגמה 2: קצת קסם בירושה (והחלפת השיטה).
public class Parent {

   public getName() {
      return "Luke";
  }
   public void printInfo() {

     System.out.println(getName());
   }
}
public class Child extends Parent {

   public getName() {
      return "Luke, I am your father";
  }
}
בדוגמה זו, אם printInfoהשיטה (ממחלקת Parent) אינה מועברת במחלקה Child, כאשר מתודה זו נקראת על אובייקט Child, getName()השיטה שלה תיקרא במקום getName()השיטה של ​​מחלקת Parent.
Parent parent = new Parent ();
parent.printnInfo();
קוד זה מציג "לוק" על המסך.
Child child = new Child ();
child.printnInfo();
קוד זה מציג "לוק, אני אביך" על המסך.
כך נראית המחלקה Child עבור המהדר:
public class Child extends Parent {

   public getName() {
      return "Luke, I am your father";
   }
   public void printInfo() {

     System.out.println(getName());
   }
}

3. רשימות

אם עדיין לא פגשתם רשימות (רשימה), הנה סקירה קצרה. אתה יכול למצוא מידע מלא ברמות 6-7 של קורס CodeGym . לרשימות יש הרבה במשותף עם מערכים:
  • אתה יכול לאחסן הרבה נתונים מסוג מסוים;
  • הם מאפשרים לך לקבל פריטים לפי האינדקס שלהם;
  • מדדי אלמנט מתחילים מ-0.
יתרונות של רשימות: בניגוד למערכים, רשימות יכולות לשנות את הגודל באופן דינמי. כאשר רשימה נוצרת, הגודל שלה הוא 0. כאשר אתה מוסיף פריטים לרשימה, הגודל שלה גדל. הנה דוגמה ליצירת רשימה:
ArrayList<String> myList = new ArrayList<String>(); // Create a new ArrayList
הערך בסוגריים של הזווית מציין את סוג הנתונים שהרשימה יכולה לאחסן. הנה כמה שיטות לעבודה עם הרשימה:
קוד תיאור קצר של מה הקוד עושה
ArrayList<String> list = new ArrayList<String>(); צור רשימה חדשה של מחרוזות
list.add("name"); הוסף אלמנט לסוף הרשימה
list.add(0, "name"); הוסף אלמנט לתחילת הרשימה
String name = list.get(5); קבל אלמנט לפי האינדקס שלו
list.set(5, "new name"); שנה אלמנט לפי האינדקס שלו
int count = list.size(); קבל את מספר האלמנטים ברשימה
list.remove(4); מחק רכיב מהרשימה
תוכל ללמוד עוד על רשימות מהמאמרים הבאים:
  1. מחלקה ArrayList
  2. ArrayList בתמונות
  3. מחיקת אלמנט מ-ArrayList

4. מערכים

מהי מטריצה? מטריצה ​​היא לא יותר מטבלה מלבנית שניתן למלא בנתונים. במילים אחרות, זה מערך דו מימדי. כפי שאתה בוודאי יודע, מערכים ב-Java הם אובייקטים. מערך חד מימדי סטנדרטי intנראה כך:
int [] array = {12, 32, 43, 54, 15, 36, 67, 28};
אנחנו יכולים לדמיין את זה כך:
0 1 2 3 4 5 6 7
12 32 43 54 15 36 67 28
השורה העליונה מציינת את הכתובות של התאים. במילים אחרות, כדי לקבל את המספר 67, עליך לגשת לרכיב המערך עם אינדקס 6:
int number = array[6];
הכל מאוד פשוט. מערך דו מימדי הוא מערך של מערכים חד מימדיים. אם אתה שומע על זה בפעם הראשונה, עצור ודמיין את זה בראש שלך. מערך דו מימדי נראה כך:
0 מערך חד מימדי מערך חד מימדי
1 מערך חד מימדי
2 מערך חד מימדי
3 מערך חד מימדי
4 מערך חד מימדי
5 מערך חד מימדי
6 מערך חד מימדי
7 מערך חד מימדי
בקוד:
int [][] matrix = {
{65, 99, 87, 90, 156, 75, 98, 78}, {76, 15, 76, 91, 66, 90, 15, 77}, {65, 96, 17, 25, 36, 75, 54, 78}, {59, 45, 68, 14, 57, 1, 9, 63}, {81, 74, 47, 52, 42, 785, 56, 96}, {66, 74, 58, 16, 98, 140, 55, 77}, {120, 99, 13, 90, 78, 98, 14, 78}, {20, 18, 74, 91, 96, 104, 105, 77} }
0 0 1 2 3 4 5 6 7
65 99 87 90 156 75 98 78
1 0 1 2 3 4 5 6 7
76 15 76 91 66 90 15 77
2 0 1 2 3 4 5 6 7
65 96 17 25 36 75 54 78
3 0 1 2 3 4 5 6 7
59 45 68 14 57 1 9 63
4 0 1 2 3 4 5 6 7
81 74 47 52 42 785 56 96
5 0 1 2 3 4 5 6 7
66 74 58 16 98 140 55 77
6 0 1 2 3 4 5 6 7
120 99 13 90 78 98 14 78
7 0 1 2 3 4 5 6 7
20 18 74 91 96 104 105 77
כדי לקבל את הערך 47, עליך להתייחס לאלמנט המטריצה ​​ב-[4][2].
int number = matrix[4][2];
אולי שמתם לב שקואורדינטות המטריצה ​​שונות ממערכת הקואורדינטות המלבנית הקלאסית (מערכת הקואורדינטות הקרטזית). כאשר אתה ניגש למטריצה, אתה מציין תחילה את קואורדינטת y ולאחר מכן את קואורדינטת x. במתמטיקה נהוג לציין קודם את קואורדינטת x, כלומר (x, y). אולי אתה תוהה: "ובכן, למה לא לסובב את הייצוג שלך של המטריצה ​​ואז לגשת לאלמנטים בדרך הרגילה באמצעות (x, y)? פעולה זו לא תשנה את תוכן המטריצה". כן, שום דבר לא ישתנה. אבל בעולם התכנות, הנוהג המקובל הוא לגשת למטריצות "קודם כל לפי y, ואז לפי x". אתה צריך לקבל את זה כדרך הנכונה. עכשיו בואו נדבר על הקרנת המטריצה ​​למנוע שלנו ( Gameמחלקה). כידוע, למנוע יש שיטות רבות שמשנות את התאים של שדה המשחק בקואורדינטות ספציפיות. למשל setCellValue(int x, int y, String value)השיטה. הוא מגדיר תא ספציפי עם קואורדינטות (x, y) שווה לפרמטר הערך. אולי שמתם לב ששיטה זו לוקחת את x ראשון, בדיוק כמו במערכת הקואורדינטות הקלאסית. השיטות האחרות של המנוע פועלות בצורה דומה. בעת פיתוח משחקים, לרוב יהיה צורך לשחזר את מצב המטריצה ​​על המסך. איך אנחנו עושים את זה? ראשית, אתה צריך לחזור על כל רכיבי המטריצה ​​בלולאה. שנית, קרא לשיטת התצוגה עבור כל אחד מהם, תוך שימוש בקואורדינטות REVERSED. לדוגמה:
private void drawScene() {
    for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length; j++) {
            setCellValue(j, i, String.valueOf(matrix[i][j]));
        }
    }
}
מטבע הדברים, ההיפוך פועל בשני הכיוונים. אתה יכול להעביר (i, j) לשיטה setCellValueובו זמנית לקחת את האלמנט [j][i] מהמטריצה. היפוך הקואורדינטות אולי נראה קצת קשה, אבל אתה צריך לזכור את זה. ותמיד, אם אתה נתקל בבעיות כלשהן, אתה צריך לתפוס פיסת נייר ועט, לצייר את המטריצה ​​ולשחזר את התהליכים הכוללים את המטריצה.

5. מספרים אקראיים

איך עובדים עם מחולל מספרים אקראיים? המחלקה Gameמגדירה את getRandomNumber(int)השיטה. מתחת למכסה המנוע, הוא משתמש Randomבכיתה מחבילת java.util, אבל הדרך שבה אתה עובד עם מחולל המספרים האקראיים לא משתנה. getRandomNumber(int)לוקח מספר שלם כטיעון. מספר זה יהיה הגבול העליון של מה שהגנרטור יכול להחזיר. הגבול התחתון הוא 0. חָשׁוּב! המחולל לעולם לא יחזיר את מספר הגבול העליון. לדוגמה, אם תתקשר getRandomNumber(3), הוא יחזיר באופן אקראי 0, 1 או 2. כפי שאתה יכול לראות, הוא לא יכול להחזיר 3. השימוש במחולל בדרך זו הוא די פשוט, אך יעיל ביותר במקרים רבים. נניח שאתה צריך לקבל מספר אקראי בטווח מסוים: תאר לעצמך שאתה צריך מספר תלת ספרתי בטווח [100..999]. כפי שאתה כבר יודע, המספר המינימלי המוחזר הוא 0. אז תצטרך להוסיף 100. אבל במקרה זה, אתה צריך לדאוג לא לחרוג מהגבול העליון. כדי לקבל 999 כערך האקראי המקסימלי, קרא למתודה getRandomNumber(int)עם הארגומנט 1000. אבל עכשיו אנחנו זוכרים שאנחנו מוסיפים 100 לתוצאה: זה אומר שהגבול העליון צריך להיות מופחת ב-100. במילים אחרות, הקוד ל- קבל את המספר האקראי בן שלוש הספרות שלנו ייראה כך:
int number = 100 + getRandomNumber(900);
אבל כדי לפשט את ההליך הזה, המנוע מספק את getRandomNumber(int, int)השיטה שהפרמטר הראשון שלה הוא המספר המינימלי להחזיר. באמצעות שיטה זו, ניתן לשכתב את הדוגמה הקודמת באופן הבא:
int number = getRandomNumber(100, 1000);
ניתן להשתמש במספרים אקראיים כדי לקבל אלמנט של מערך אקראי:
String [] names = {"Sarah", "Val", "Sergey"};
String randomName = names[getRandomNumber(names.length)]
יצירת אירועים מסוימים בהסתברות מסוימת. עבור בני אדם, הבוקר מתחיל עם כמה תרחישים אפשריים: שינה יתר - סיכוי של 50%; התעורר בזמן - סיכוי של 40%; התעוררתי שעה מוקדם - סיכוי של 10%. תאר לעצמך שאתה כותב מחולל תוצאות בוקר. אתה צריך ליצור אירועים עם הסתברות מסוימת. כדי לעשות זאת, אתה שוב צריך להשתמש מחולל מספרים אקראיים. מימושים שונים אפשריים, אך הפשוט ביותר צריך להתבסס על האלגוריתם הבא:
  1. להגדיר את המגבלות המשמשות ליצירת מספר;
  2. ליצור מספר אקראי;
  3. לעבד את המספר שהתקבל.
במקרה זה, המקסימום יהיה 10. קראו לשיטה getRandomNumber(10)ונתחו שהיא נוכל להחזיר. זה יכול להחזיר 10 מספרים (מ-0 עד 9), כל אחד עם אותה הסתברות - 10%. כעת עלינו לשלב את כל התוצאות האפשריות ולמפות אותן לאירועים האפשריים שלנו. הדמיון שלך עשוי לחשוב על הרבה שילובים אפשריים, אבל הנה הדבר הברור ביותר: "אם המספר האקראי נמצא בטווח [0..4], יש לנו את האירוע "Overslept"; אם המספר נמצא בטווח [5] ..8], יש לנו את אירוע "התעורר בזמן"; ואם המספר הוא 9, אז יש לנו את אירוע "התעוררתי שעה מוקדם". הכל פשוט מאוד. יש 5 מספרים בטווח [0] ..4], שכל אחד מהם עשוי להיות מוחזר בהסתברות של 10%, בסך הכל 50%; ישנם 4 מספרים בטווח [5..8], ובכן, ו-9 הוא רק מספר אחד שמופיע עם הסתברות של 10%. כל העיצוב החלקלק הזה נראה אפילו יותר קל בקוד:
int randomNumber = getRandomNumber(10);
if (randomNumber < 5) {
    System.out.println("Overslept");
} else if (randomNumber < 9) {
    System.out.println("Woke up on time");
} else {
    System.out.println("Woke up an hour early");
}
באופן כללי, יש המון דרכים להשתמש במספרים אקראיים. אתה מוגבל רק על ידי הדמיון שלך. אבל הם משמשים בצורה היעילה ביותר אם אתה צריך לקבל שוב ושוב תוצאה כלשהי. אז התוצאה החדשה תהיה שונה מהקודמת. עם סבירות מסוימת, כמובן. זה הכל לעת עתה! אם אתה רוצה ללמוד עוד על הקטע "משחקים", הנה כמה תיעוד שימושי שיכול לעזור:
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION