ping pong activity
Download
Report
Transcript ping pong activity
משחק הפינג פונג
הסבר מקוצר לפרויקט הדוגמא
ע"י עירן דנן
פעילות היא בסיס ממשקי המשתמש הויזואליים שלנו.
כל חלק באפליקציה שנראה על המסך ,יהיה בתוך פעילות
(.)ACTIVITY
למעשה ,כמו שנראה ,רוב ממשקי המשתמש שלנו יהיו בעצם
"בנים" של פעילות ,כלומר מחלקות היורשות את אופי
הפעילות הכללי ומוסיפות לה.
ברירת המחדל כאשר יוצרים פרוייקט חדש היא בד"כ
פעילות שכותבת "שלום" על המסך .
בואו ניצור את הפרויקט החדש שלנו:
באקליפס נבחר
File -> New -> Project
ונקבל את המסך הבא:
נבחר באפשרות
Android project
כמו שמסומן
נגיע למסך הבא,
שם נבחר את ה
Android 1.6
כגרסה המינימלית
של מערכת ההפעלה
עליה התוכנית שלנו
תעבוד.
נשים לב למלא גם את
השם בתא העליון ,את שם
החבילה ושם האפליקציה.
אם עשינו
הכל נכון ,יפתח
לנו פרויקט
בצד השמאלי
של המסך תחת
הכותרת
Package explorer
נפתח את הפרויקט ,נכנס לתיקייה העליונה
ונפתח את החבילה היחידה שלנו .הקובץ הראשון שם
הוא הפעילות שלנו.
הסבר קטן על
שורות הקוד
המופיעות כאן:
) - Public void onCreate(Bundle bפונקצית ה"לידה" של הפעילות ,כאן מגדירים איך
.1
ניצור את הפעילות שלנו .
- Super.onCreateלפעילות שלנו יש אב ,שבדמותו היא נוצרה ,ואליו ניגש בעזרת המילה
.2
.super
הבן נוצר בדמותו של האב ולכן יורש את כל הפונקציונילות שלו .במקרה הזה הקריאה לבנאי*
האב תבצע את הקישור למסך ,רישום במערכת וכו'.
) - setContentView(R.layout.mainקביעת התוכן הויזואלי של הפעילות ע"י LAYOUT
,שהוא כמו שרטוט חסר פונקציונליות של המסך ,שרטוט "יבש".
הפעילות שלנו מוכנה ,החלון הגדול מוכן .אבל מה נציג בתוכו?
אפשרות אחת היא פריסת התוכן בלייאאוט ,כמו ברירת
המחדל.
במקרה שלנו דרושה קצת גמישות גרפית ,אז ניצור
בעצמנו (דרך הקוד) את התוכן שלנו ,ה VIEWשלנו.
ה VIEWהיא בעצם מחלקת האב לכל הרכיבים הגרפיים של
האפליקציות שלנו.
דוגמאות להרחבה של VIEWהן תיבות טקסט ,כפתורים,
חלונות להצגת תמונות ועוד.
למה צריך אב?
במחלקת VIEWמוגדרות כל השיטות – הפונקציות – שנצטרך בכל
תוכן גרפי שהוא ,כמו מיקום על הציר ,גודל החלון והפונקציות
המנהלות אותם.
נניח שאני רוצה ליצור משחק פינג פונג ,וגם משחק כדורגל.
לשניהם אני צריך להגדיר היכן אני רוצה למקמם במסך ,מה צבע
הרקע ,פונקציות המרשות לי לצייר עליהן וכו' .כלומר יהיה לי קל
אם אוכל לרשת את כל התכונות האלה "במכה" ,ולהתרכז רק
באיפיון היחודי של התוכן שלי.
לצורך כך נוצרה הורשה בכלל ,וב VIEWבפרט.
עוד קצת על המחלקה :VIEW
http://developer.android.com/reference/android/vi
ew/View.html
ככלל ,נרצה לחלק כל
תוכנית מחשב שאנו בונים
לכמה שיותר חלקים
עצמאיים .בפרט בגרפיקה,
ובפרט באנימציות שבהן המשתמש משפיע.
למשל ,במקרה שלנו ,יהיה נכון ליצור VIEWשל המחבט ,וVIEW
של הכדור ,ואת שניהם ב VIEWראשי שבו ינועו שניהם.
כך ,אם נחליט בחודש הבא להוציא את PingPong2לאור שבו
הכדור יהיה כחול ,או המחבט יהיה של נייק ,כל מה שנצטרך לעשות
זה לשנות את ה VIEWשל הכדור ,ולא לצלול לתוך התוכנית.
מה שגם ,תמיד קל יותר לפתור אוסף של בעיות קטנות מאשר בעיה
אחת גדולה..
מסך המכשירים הניידים שלנו ,בדומה למסך המחשב ,מורכב
מפיקסלים ,כאשר כל פיקסל יכול להצבע בצבע אחר.
מיקום הפיקסלים במסך מיוצג ע"י Xו ,Yכאשר הנקודה
השמאלית העליונה היא (.)0,0
תמונה מסוימת היא בעצם הגדרה מדוייקת של כל הפיקסלים
במסך .למשל פס לבן באמצע מסך ברוחב 400פיקסלים יצוייר
ע"י צביעת כל הפיקסלים שערך ה Xשלהם הוא 100 ,99או 101
,בלבן ,ואת כל השאר בשחור.
איך זה רלוונטי לנו?
נראה רגע איך מתרחש משחק הפינג פונג.
הכדור נע על המסך בין מחבט למחבט ,וכאשר הכדור נוגע
במחבט הוא הופך כיוון ומשנה זווית ,בהתאם לזווית הפגיעה.
מה זה אומר שהכדור נע בין כתלי המסך?
או בכלל מנקודה Aל?B
זה אומר שהכדור היה בכל נקודה על הקו הישר בין Aל ,B
ומכאן שאנו צריכים לצייר אותו בכל נקודה על הקו בין Aל ,B
ולמחוק ולצייר בנקודה הבאה וכך הלאה עד שנגיע ל.B
תחילה ,ניצור את ה VIEWהראשי שלנו .PingPongView ,ניצור
מחלקה חדשה שתירש מ ,VIEWכך:
נלחץ על המקש הימני בעכבר כשאנחנו על
החבילה שלנו( .תזכורת ,החבילה היחידה
שלנו נמצאת בתיקייה .)SRC
בתפריט שיפתח נלך ל New -> Class
ונקבל את המסך הזה >-
בתיבת השם נקליד ,PingPongView
בתיבה superclassנמצא את
Android.view.viewשזוהי ההגדרה של
המחלקה ,VIEWממנה אנו יורשים.
נלחץ ,FINISHונראה שנוספה לנו מחלקה
בתיקייה.
כעת נקבל את המסך
הבא ,ובו שגיאה.
כדי לתקן את השגיאה
נלחץ (כמו בפעמים
רבות אחרות) על הX
המסומן באדום,
ונבחר את האפשרות הראשונה ,שתוסיף לנו בנאי שמקבל פרמטר
אחד בלבד.
הסיבה לשגיאה היא שלמחלקת האב VIEWיש רק בנאי שמקבל
פרמטר ,כלומר כדי ליצור מופע של המחלקה שלנו צריך גם כן בנאי
שמקבל פרמטר .עוד על הורשה ובנאים אפשר למצוא בשקפים באתר
הקורס בנושא הורשה.
כעת יצרנו את ה VIEWהראשי שלנו ,ובו בנאי.
איך עובד עניין הציור על המסך?
המחלקה VIEWמציירת את התוכן שלה בעזרת השיטה
onDrawבאופן אוטומטי כאשר התוכנית שלנו עולה.
onDrawמקבל ,Canvasשהוא כשמו כמו בלוק ציור .בכל
מסך גרפי ישנו Canvasכלשהו .ה Canvasיודע לצייר בתוכו
צורות שונות בעזרת שיטות כמו drawCircleלמשל ,המציירת
מעגל ,ועוד רבות .נוסיף את השיטה onDrawלמחלקה שלנו,
ריקה בינתיים ונקבל את המחלקה הבאה:
import android.content.Context;
import android.graphics.Canvas;
import android.view.View;
public class PingPongView extends View
{
public PingPongView(Context context) {
super(context);
}
@Override
public void onDraw(Canvas canvas) {
כאן נצייר את המשחק//
}}
}
את הכדור נחלק לשתי מחלקות:
- Ball .1מחלקה שתהווה את החלק הלוגי של הכדור ,התכונות וההתנהגות
שלו.
– BallView .2מחלקה היורשת מ VIEWבדומה ל,PingPongView
שיודעת לצייר את הכדור שלנו.
האם אפשרי לממש את שני החלקים באותה מחלקה? כמובן .בעקרון אפשר
לעשות כמעט כל תוכנית במחלקה גדולה אחת .אז למה לא?
הפרדה בין חלקים שונים של התוכנית חשובה מכמה סיבות ,החשובה מבניהן
היא שאם נרצה לחזור ולשנות משהו בתוכנית ,יהיה לנו הרבה יותר ברור איפה
השינוי צריך להתבצע כאשר יש הפרדה ברורה בין כל חלק של התוכנית.
הפרדה בין חלקי התוכנית מביאה לאי תלות בין חלקי התוכנית ,וזה מביא
לחסכון בזמן ובמאמץ.
נחשוב על רכב שמיוצר כולו כחלק אחד "קומפלט" .אם חלק אחד יתקלקל ,או
שנרצה לשדרג חלק כלשהו ,נצטרך להחליף את כל הרכב..
יצירת המחלקה Ballתתבצע בדיוק כמו יצירת המחלקה ,PingPongViewרק שבשדה superclassבתפריט לא
נשנה את ברירת המחדל.
המחלקה Ballתראה כך:
{Public class Ball
private int x,y; //משתני המיקום של הכדור
{)public Ball (int x,int y
this.x = x; //הבנאי מציב את המיקום ההתחלתי של הכדור באובייקט החדש שנוצר
;this.y = y
}
{)(public int getX
return x; //שיטות גישה למיקום הכדור ,כדי שנוכל לגשת למיקומו מבחוץ
}
{)(public int getY
;return y
}
: כך, למשל. שהוא יהיה הכדור שלנו, נצייר עיגולonDraw ובשיטה, שובVIEW נירש מ, כאמור. שהיא תהיה אחראית על ציור הכדור,BallView כעת ניצור את המחלקה
import android.graphics.Canvas;
import android.graphics.Color;
import android.content.Context;
import android.graphics.Paint;
import android.view.View;
public class BallView extends View {
private Ball theBall;
private Paint ballPaint;
public BallView(Context context, Ball newBall) {
super(context);
this.theBall = newBall;
{
private Paint getBallPaint(){
למעשה את המברשת המציירת את הכדור,ניצור צבע חדשif (ballPaint == null)
{
ballPaint = new Paint();
ballPaint.setStrokeWidth(2);
ballPaint.setColor(Color.WHITE);
}
return ballPaint;
}
@Override
public void onDraw(Canvas canvas) {
// Drawing the ball
canvas.drawCircle(theBall.getX(), theBall.getY(), 15, getBallPaint());
}
}
//
נחזור כעת ל.PingPongView
אנו צריכים לשנות את השיטה onDrawשלו כדי שתייצר אובייקט כדור .נוסיף מתשנה
מסוג BallViewכך:
;Private BallView ballView
ונוסיף לו שיטת גישה כדי שנוכל לאתחל אותו ,כך:
{)Public void setBallView(BallView b
;ballView = b
}
כעת ,כשיש לנו כדור ב ,PingPongViewאפשר לשנות את onDrawכך שתצייר אותו,
כך:
{)Public void onDraw(Canvas canvas
;)ballView.onDraw(canvas
}
נשים לב שכדי לצייר את הכדור כל מה שאנו צריכים לעשות זה לקרוא ל onDrawשל
.BallView
: בואו נראה מה יצא.BallView - של הכדורVIEW ואת הPingPongView - של השולחןVIEWעד כה יצרנו את ה
- את ה, של המשחקView - להגדיר את ה, מה שאנחנו צריכים זה להגדיר את הכדור.נחבר את כל החלקים יחדיו
. הראשי שלהView - שלנו את הActivity - לתת ל,של הכדור ולבסוףView
: תראה כךPingPong המחלקה
public class PingPong extends Activity {
private PingPongView gameView;
private Ball gameBall;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gameBall = new Ball(100, 100); // ) (עדיין לא מציור100 100 אנחנו יוצרים כדור חדש במיקום
gameView = new PingPongView(this); // הראשי שלנוVIEW מאתחלים את ה
BallView ballView = new BallView(this, gameBall); // מאתחלים את מחלקת הציור של
הכדור
gameView.setBallView(ballView); // נקבע את כדור המשחק ככדור שיצרנו שורה למעלה
setContentView(gameView); // נקבע את תוכן הפעילות שלנו כשולחן המשחק
}
}
אם עשינו הכל
נכון ,נקבל את
המסך הבא
באימולטור.
כמו שאנחנו
רואים ,יש לנו
כדור!
לפני שנמשיך ,בואו נדבר קצת על הרצת שני קטעי קוד במקביל.
בתכנות בד"כ ,וב JAVAבפרט ,שורות הקוד של תהליך מסוים
מתבצעות באופן סידרתי ,כלומר ,עד ששורה 1לא מתבצעת במלואה,
לא תתבצע שורה ,2וכך הלאה.
אך היינו רוצים לבצע כמה פעולות במקביל בכל זאת ,בכדי שתהליך
מסוים יתנהל "חלק" בלי לחכות לתהליכים אחרים.
ב ,JAVAנעשה זאת בעזרת נימים (.)THREADS
באופן כללי ,בכל מערכת הפעלה מודרנית רצות כל הזמן הרבה
תוכניות שלכל אחת מוקצה זכרון משלה וכל אחת למעשה מתפקדת
כתוכנית עצמאית .כל תוכנית כזאת נקראת .PROCESS
PROCESSמסוים יכול להריץ כמה THREADים ,ש"מתחלקים"
בזכרון של ה PROCESSשיצר אותם.
)Processאו תהליך) – כל תוכנית מחשב שמופעלת על גבי המחשב שלנו מתבצעת ב Process -נפרדProcess .מכיל
את קוד התוכנית המהודר ,ומועלה לזיכרון המחשב ע"י מערכת ההפעלה ברגע שתוכנית המחשב מופעלת ,על מנת
שהמעבד יוכל לבצע את ההוראות המופיעות בו .רוב מערכות ההפעלה היום מסוגלות להפעיל מספר רב של תהליכים
במקביל .למשל הדפדפן שהפעלתם כדי לקרוא את השיעור שלנו רץ בתוך תהליך משלו ,במקביל להרבה תהליכים
אחרים ,כמו לדוגמא מעבד תמלילים ,מחשבון ,תהליכי רקע של מערכת ההפעלה או כל תוכנה אחרת שפתוחה לכם
כעת.
- Threadזהו קטע קוד הבסיסי ביותר שמערכת ההפעלה מסוגלת להריץ כיחידת קוד עצמאית .כל Processיכול
להכיל Threadאחד או יותר.
ההבדל המשמעותי בין Processל Thread -הוא שכברירת מחדל ,לכל תהליך יש זיכרון נפרד ,עם משתנים משלו,
שתהליכים אחרים לא רואים ולא יכולים לגשת אליהם (ללא שיתוף מיוחד) ,בעוד שכל הThread- -ים שרצים בתוך
תהליך מסויים חולקים כברירת מחדל את אותו הזיכרון והמשתנים ,ויכולים לגשת ולשנות ערכים של משתנים של
Threadים אחרים.נושא ריבוי התהליכים והThread- -ים הוא אחד מהנושאים המורכבים ביותר בתחום פיתוח התוכנה ,בשל המורכבות
הנובעת מגישה במקביל לאותו משאב ,כתיבה וקריאה מאותה כתובת זיכרון ,והתקלות העלולות לנבוע בשל
ההתרחשות המקרית של שני ארועים בו זמנית ,שקורות לא תמיד ,כמו באגים רגילים ,אלא לעיתים רחוקות – דבר
המקשה משמעותית על איתור ותיקון הבעיה.
אנחנו נשתמש ב THREADאחד בלבד ,ובאופן פשוט ,לצורך ביצוע האנימציה.
שני שקפים אחורה ,עצרנו כאשר יש לנו כדור בנקודה ()100,100
על המסך.
לפני שניצור את התהליך שרץ ברקע ומזיז לנו את הכדור,
נצטרך להרחיב את המחלקה הלוגית של הכדור שלנו )(Ball
ולהוסיף לה מתודה שמזיזה את הכדור על המסך.
המתודה תזיז את הכדור בכל פעם בפיקסל אחד בציר הx -ובפיקסל אחד
בציר הy, -בהתאם לכיוון התנועה של הכדור.
כלומר:
אם הכדור זז לכיוון ימין מטה ,המתודה תוסיף למיקום האופקי
(משתנה ה )xשל הכדור +1ולמיקום האנכי (משתנה ה (yשל הכדור .+1
אם הכדור זז לכיוון שמאל מטה ,המתודה תוסיף למיקום האופקי
(משתנה ה (xשל הכדור -1ולמיקום האנכי (משתנה ה )yשל הכדור .+1
אם הכדור זז לכיוון ימין מעלה ,המתודה תוסיף למיקום האופקי
(משתנה ה )xשל הכדור +1ולמיקום האנכי (משתנה ה (yשל הכדור .-1
אם הכדור זז לכיוון שמאל מעלה ,המתודה תוסיף למיקום האופקי
(משתנה ה (xשל הכדור -1ולמיקום האנכי (משתנה ה )yשל הכדור .-1
בנוסף לכל אלה ,אנחנו צריכים גם לבדוק האם הכדור פגע בגבולות המסך
ואם כן ,אז לשנות את כיוון הכדור בהתאם לאיפה שהוא פגע.
איור המחשה לתזוזה של הכדור:
private int x;
private int y;
private int maxWidth;
private int maxHeight;
private boolean movingRight; //משתנים שיגידו לנו לאיזה כיוון אנו זזים
private boolean movingBottom;
// Receive the start position of the ball and screen dimensions
public Ball (int startX, int startY, int screenWidth, int screenHight)
{
//constructor with arguments
this.x = startX;
this.y = startY;
this.maxWidth = screenWidth;
this.maxHeight = screenHight;
this.movingBottom = false;
this.movingRight = false;
}
: עם מימוש התנועה יראה כךBall קוד המחלקה
:המשך קוד המחלקה
public int getX(){
return this.x;
}
public int getY(){
return this.y;
}
// Moving the ball one step.
public void moveBall(){
// Moving the ball
if (movingRight)
this.x++;
else
this.x--;
if (movingBottom)
this.y++;
else
this.y--;
// Check if we need to change the ball direction horizontally
if (this.x >= this.maxWidth)
this.movingRight = false;
else if(this.x <= 1)
this.movingRight = true;
// Check if we need to change the ball direction vertically
if (this.y >= this.maxHeight)
this.movingBottom = false;
else if (this.y <= 1)
this.movingBottom = true;
}}
הסבר קצר על הקוד:
המשתנים MaxWidthו MaxHeightיחזיקו את הרוחב
והגובה של מסך המשחק ,כלומר הגבולות ,כך שנדע מתי הכדור
צריך לשנות את כיוון תנועתו.
בפונקצייה moveBallאנחנו מזיזים את הכדור בצורה
מבוקרת .בחלק הראשון של הפונקציה אנחנו זזים בהתאם
למשתנים movingRightו ,movingBottomובחלק השני
בודקים אם הגענו לגבול המסך שלנו כדי לשנות כיוון אם צריך
(אם היינו מגדילים את Xמעבר לגבול המסך הכדור היה
נעלם.)..
כעת ניצור את הנים ( )THREADשיזיז את הכדור .למה
לעשות את זה ב?THREAD
כאשר תהליך התזוזה של הכדור תלוי ב THREADהראשי,
יווצרו "לאגים" ,כלומר תזוזת הכדור תהיה לא חלקה ,מכיוון
שה THREADהראשי מבצע עוד פעולות.
ה THREADהחדש שניצור יוקדש כולו לתנועת הכדור.
בגדול ,מה שה THREADשלנו צריך לעשות הוא "להתעורר" כל
פרק זמן מסויים ,להזיז את הכדור ואז לעדכן הView -שלנו
שמשהו השתנה בלוגיקה ושצריך לצייר את המסך מחדש.
ניצור מחלקה חדשה בשם PingPongGameהיורשת מהמחלקה
.THREAD
ישנן שתי דרכים ליצור נים .דרך אחת היא עם אובייקט מסוג
,Runnableשבו נממש את הפונקציה )(.run
בדרך שלנו ,שבה הרחבנו את ,THREADנדרוס את הפונקציה
)( runבתוך המחלקה שלנו .קוד המחלקה יראה כך:
public class PingPongGame extends Thread {
private Ball gameBall;
private PingPongView gameView;
public PingPongGame(Ball theBall, PingPongView mainView){
this.gameBall = theBall;
this.gameView = mainView;
}
@Override
public void run(){
while (true){
this.gameBall.moveBall();
this.gameView.postInvalidate();
{
try
PingPongGame.sleep(5);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
הסבר קצר על הקוד:
;)(this.gameBall.moveBall
;)(this.gameView.postInvalidate
השורה הראשונה מזיזה את הכדור (באופן לוגי ,לא על המסך),
והשורה השנייה אומרת ל VIEWשיש שינוי בתצוגה ויש צורך לעדכן .אם לא
היינו בתוך THREADהיינו משתמשים בשיטה )(.invalidate
{Try
;)PingPongGame.sleep(5
}…{)…)}catch
השורה הזאת אומרת לנים "לישון" ל 5מילי שניות .הסיבה ל TRYוהCATCH
היא שה"שינה" המאולצת הזאת לא תמיד מצליחה ,אז אנו רוצים לטפל במקרים
שהיא לא ,ע"י תפיסת החריגה שהיא זורקת .חריגות – בשקפים על .JAVA
אם לא היינו אומרים לנים לישון אחרי כל תזוזה ,הכדור היה זז כל כך מהר שלא
היינו מבחינים בו כלל.
. ונראה מה קיבלנו, שוב נחבר את כל מה שיצרנו עד כה,כעת
: (הפעילות שלנו) שתראה כךPingPong נשכתב את המחלקה
public class PingPong extends Activity {
private PingPongView gameView;
private Ball gameBall;
private PingPongGame gameThread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Getting the screen width & height
int screenWidth = this.getWindowManager().getDefaultDisplay().getWidth();
int screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();
gameBall = new Ball(screenWidth / 2, screenHeight / 2, screenWidth,
screenHeight);
BallView ballView = new BallView(this, gameBall);
this.gameView = new PingPongView(this);
gameView.setBallView(ballView);
setContentView(gameView);
gameThread = new PingPongGame(gameBall, gameView);
gameThread.start();
}
{
:הסבר קצר על הקוד
השורות האלו
int screenWidth = this.getWindowManager().getDefaultDisplay().getWidth();
int screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();
כדי שנוכל לדעת מתי,נותנות לנו את הגובה והרוחב של המסך
.לשנות כיוון לכדור
מתחילה את ריצתgameThread.start() השורה
.(הנים)במקביל לפעילות שרצה
אוקי ..אז מה יש לנו עד כה?
יש לנו כדור ,שזז לו על המסך בנחת ..איפה נכנס כאן
המשתמש?
אנחנו צריכים מנגנון כלשהו שידע מתי המשתמש מבצע פעולה
כלשהי (למשל לחיצה על המסך) ,ושאותו מנגנון יאפשר לנו
להגיב על אותן פעולות.
אותן פעולות של המשתמש נקראות אירועים (,)EVENTS
וניהולן נקרא טיפול באירועים (.)EVENT HANDLING
להלן כמה דוגמאות לאירועים ב:ANDROID
אירועי מגע ( – )TOUCHכאשר ישנה נגיעה במסך.
אירועי לחיצה ( – )CLICKכאשר ישנה לחיצה על כפתור או
רכיב אחר אשר לחיצה מוגדרת עבורו.
אירועי שינוי פוקוס ( – )FOCUS CHANGEכאשר פוקוס
התוכנית עובר מרכיב אחד לאחר במסך ,או ממסך אחד לאחר.
אירועי מקש ( – )KEYכאשר ישנה לחיצה על מקש מקלדת או
כפתור חומרה (כמו כפתור הכיבוי למשל).
בכדי "לתפוס" את האירועים האלו כאשר הם מתרחשים ,אנו
נשתמש במאזינים (.)LISTENERS
מאזין הוא אובייקט ש"מחכה" לאירוע מסוים ,וכאשר האירוע
הזה מתרחש הוא מפעיל שיטה מסוימת.
לכל אירוע יש מאזין ספציפי לו .למשל ,בכדי להאזין ללחיצה על
כפתור הכיבוי ,נשתמש במאזין מסוג .OnKeyListener
המאזין יקלוט כל אירוע מהסוג הזה ויריץ את השיטה onKey
עבור כל אירוע כזה שהוא קולט .ע"י מימוש השיטה onKey
אנחנו בעצם מגיבים לאירוע איך שנבחר .למשל ,נוכל להגיב על
לחיצת כפתור הכיבוי בהודעה על המסך.
את המאזין נרשום אצלנו בתוכנית כדי שיתחיל להאזין ,בד"כ ע"י
שיטת .setלמשל ,אם נרצה להוסיף לכפתור bמסוים מאזין ללחיצה
זה יראה כך:
{)(b.setOnClickListener(new OnClickListener
{)onClick(..
// implementation
}
;)}
כמו שרואים ,ניתן לממש את אובייקט המאזין אנונימית ולא
במחלקה משלו ,למרות שגם זה אפשרי.
השיטה שנממש תקבל בד"כ ארגומנטים שיכילו מידע על האירוע,
כמו נקודות הלחיצה ,עוצמת הלחיצה ,משך זמן הלחיצה וכדומה.
טוב ,כעת שאנחנו יודעים מהו אירוע ,אפשר להמשיך לפתח את
המשחק.
ראשית ,נרחיב את המחלקות Ballו Paddleכך שירשו
ממחלקה אחת שתהווה בסיס לכל אובייקט שיכול לזוז אצלנו.
נראה למחלקת האב הזאת ,MoveableObjectומימושה יהיה
כזה שיכריח כל מחלקת בת שלה לממש את שיטות המחזירות
את גבולות האובייקט שאותו היא מממשת .נעשה זאת בעזרת
שיטות אבסטרקטיות.
עוד על מחלקות אבסטרקטיות תמצאו בשקפים בנושא הורשה
ופולימורפיזם ב...JAVA
public abstract class MovableObject
{
protected int x; //current horizontal position of the center of the object
protected int y; //current vertical position of the center of the object
public int getX() { return x; }
public int getY() { return y; }
public abstract int getLeft();
//should return the right border of the object
public abstract int getRight();
//should return the top border of the object
public abstract int getTop();
//should return the bottom border of the object
public abstract int getBottom();
//This method should move the object
public abstract void move();
}
Ball נשכתב שוב (בפעם האחרונה) את המחלקה,MovableObject אחרי שהוספנו את המחלקה
:Paddle ואת המחלקה
public class Ball extends MovableObject
{
private final int RESTART_X,RESTART_Y; //נקודת ההתחלה של המשחק
private int maxWidth,maxHeight;//גבולות המסך
private boolean movingRight,movingBottom; //כיוון התנועה
public final int RADIUS = 10; //רדיוס הכדור
}
public Ball (int startX, int startY, int screenWidth, int screenHight){
maxWidth = screenWidth;
maxHeight = screenHight;
RESTART_X = startX;
RESTART_Y = startY;
restart();
public void restart(){
x = RESTART_X;
y = RESTART_Y;
movingBottom = false;
movingRight = false;
}
public int getLeft(){ return x - RADIUS; }
public int getRight(){ return x + RADIUS; }// MovableObject מימוש השיטות האבסטרקטיות שירשנו מ
public int getTop(){ return y - RADIUS;}
public int getBottom(){ return y + RADIUS;{
public void move(){
if (movingRight) x++;
else x--;
if (movingBottom) y++;
else y--;
if (x >= maxWidth) movingRight = false;
else if (x <= 1) movingRight = true;
movingBottom = false;
if (y >= maxHeight)
else if (y <= 1) movingBottom = true;
}
public void bounceUp(){
movingBottom = false;
}
}
: המייצגת את המחבט שלנו תראה כךPaddle המחלקה,בדומה
public class Paddle extends MovableObject
{
private final int screenWidth;
public final int HEIGHT = 10;
private final int WIDTH = 50;
private float paddleDestination;
public Paddle(int startX, int startY, int newScreenWidth){
x = startX;
y = startY;
screenWidth = newScreenWidth;
paddleDestination = screenWidth / 2;
}
. ויש לנו את רוחב המסך בכדי לשלוט בתנועת המחבט, למחבט יש גובה ורוחב,כאמור
:MovableObjectנממש את השיטות שירשנו מ
public synchronized int getLeft(){ return x; }
public synchronized int getRight(){ return x + WIDTH; }
public synchronized int getTop() { return y; }
public synchronized int getBottom() {return y + HEIGHT;}
:ועוד כמה שיטות שיעזרו לנו עוד מעט
public synchronized int getPaddleWidth(){ return WIDTH; }
public synchronized int getMiddle(){ return x + WIDTH/2; }
public synchronized float getPaddleDestination(){ return paddleDestination; }
public synchronized void setPaddleDestination(float newDestination){
paddleDestination = newDestination;
}
: נטפל בתנועת המחבט,וכמובן
public synchronized void moveRight(){
if (screenWidth > (getRight()+1)) x++;
else x = screenWidth - WIDTH;
}
public synchronized void moveLeft(){
if ((getLeft()-1) > 0) x--;
else x = 0;
}
public synchronized void move(){
float destination;
destination = getPaddleDestination();
moveRight();
if (getMiddle() < destination)
moveLeft();
else if (getMiddle() > destination)
}
}
: בדומה לכדור,PaddleView בעזרת המחלקה,כעת אפשר להציג את המחבט שלנו על המסך
public class PaddleView extends View
{
private Paddle paddle; //our paddle to be drawn
private Paint padlePaint; //the paint to paint the paddle with
public PaddleView(Context context, Paddle newPaddle){
super(context);
this.paddle = newPaddle;
}
private Paint getPaddlePaint(){
{
if (padlePaint == null)
padlePaint = new Paint();
padlePaint.setStrokeWidth(2);
padlePaint.setColor(Color.BLUE);
}
return padlePaint;
}
@Override
public void onDraw(Canvas canvas){
int left,right,top,bottom;
left = paddle.getLeft();
right = paddle.getRight();
top = paddle.getTop();
bottom = paddle.getBottom();
canvas.drawRect(left, top, right, bottom, getPaddlePaint());
}
}
public class PingPongView extends View
{
private BallView ballView; //ball view
private PaddleView paddleView; // paddle view
: כך, הראשי שלנו את המחבטView נוסיף ל,כעת
public PingPongView(Context context){
super(context);
}
public void setViews(BallView ballView,PaddleView paddleView){
this.ballView = ballView;
this.paddleView = paddleView;
}
@Override
public void onDraw(Canvas canvas){
this.ballView.onDraw(canvas);
this.paddleView.onDraw(canvas);
}
}
! הזזת המחבט שלנו ממש:ועכשיו הגענו לקטע המעניין
, נפרדThread -אנחנו נבצע זאת ב
– כדי לאפשר את התזוזה של המחבט באמת במקביל לארועים אחרים שקורים במשחק שלנו
.כמו תנועת הכדור
:Thread - היורשת מPaddleMover – לצורך כך נוסיף מחלקה חדשה
public class PaddleMover extends Thread
{
private Paddle gamePaddle; //holds a reference to the paddle
private PingPongView gameView; //holds a reference to the
main view
public PaddleMover(Paddle thePaddle, PingPongView
mainView){
gamePaddle = thePaddle;
gameView = mainView;
}
@Override
{ public void run()
while (1 < 2){
if (gamePaddle.getMiddle() !=
gamePaddle.getPaddleDestination()){
gamePaddle.move();
gameView.postInvalidate();
}
try{
PaddleMover.sleep(3);
{
}catch (InterruptedException e)
e.printStackTrace();
}
}
}
}
במקרה שלנו אנו נרצה להאזין לארועים של נגיעת המשתמש במסך .באנדרואיד ,המחלקה
Viewמממשת את onTouchEvent,כלומר נוכל לזהות לחיצה על כל רכיב שלנו המממש
את ) Viewכמו המחבט או הכדור).
גם המחלקה Activityמממשת את onTouchEventכאשר המשמעות היא האזנה
ללחיצת בכל מקום באפליקציה שלנו ,ולא על רכיב מסויים.
אנו נממש את onTouchEventבתוך הActivity -שלנו ,בכך ש"נדרוס" אותה ונגדיר לה
מימוש משלנו:
public class PingPong extends Activity
{
;private PingPongView gameView
;private Ball gameBall
;private Paddle gamePaddle
;private PingPongGame gameThread
;private PaddleMover paddleMoverThread
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
int screenWidth,screenHeight;
BallView ballView;
PaddleView paddleView;
screenWidth = this.getWindowManager().getDefaultDisplay().getWidth();
screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();
gameBall = new Ball(screenWidth / 2, screenHeight / 2, screenWidth, screenHeight);
ballView = new BallView(this, gameBall);
gamePaddle = new Paddle(screenWidth / 2, (int)(screenHeight * 0.85), screenWidth);
paddleView = new PaddleView(this, gamePaddle);
this.gameView = new PingPongView(this);
gameView.setViews(ballView,paddleView);
setContentView(gameView);
gameThread = new PingPongGame(gameBall, gamePaddle, gameView);
gameThread.start();
paddleMoverThread = new PaddleMover(gamePaddle, gameView);
paddleMoverThread.start();
}
@Override
public boolean onTouchEvent(MotionEvent event){
float destination;
destination = event.getX();
gamePaddle.setPaddleDestination(destination);
return true;
}
}
כעת כל מה שנותר לנו הוא לעדכן את התהליכון הראשי של המשחק המזיז את הכדור ולהפעיל
:גם את המחבט
public class PingPongGame extends Thread
{
private Ball gameBall; //holds a reference to the ball
private Paddle gamePaddle; //holds a reference to the paddle
private PingPongView gameView; //holds a reference to the
main view
public PingPongGame(Ball theBall, Paddle thePaddle,
PingPongView mainView){
gameBall = theBall;
gamePaddle = thePaddle;
gameView = mainView;
}
@Override
public void run(){
while (1 < 2)
{
gameBall.move();
checkHitPaddle();
gameView.postInvalidate();
{
try
PingPongGame.sleep(5);
{ }catch (InterruptedException e)
e.printStackTrace();
}
}
}
private void checkHitPaddle(){
if (gameBall.getBottom() >= gamePaddle.getTop())
{
if ((gameBall.getRight() > gamePaddle.getLeft()) &&
(gameBall.getLeft() < gamePaddle.getRight())){
//The ball had hit the paddle - so it should be bounced
gameBall.bounceUp();
}else{
//The ball had missed the paddle - player looses - restart the game
gameBall.restart();
}
}
}
}
זהו!
כל מה שנותר זה להריץ ..מקווה שנהנתם ,ולמדתם
משהו.
עירן.