אנדישלום חברים!

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

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

אנדי, יש לי שאלה! שיחקתי במשחק הזה שאתם בונים (לא בזמן השיעור כמובן!), אבל הוא לא מגיב לשום דבר שאני עושה!!

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

אבל אז אתה אף פעם לא נותן לי לדבר!

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

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

אנדי?

אנדינו,
מה עכשיו?

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

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

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

קדימה לעבודה

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

להלן הנושאים אשר ילמדו במהלך שיעור זה:

1. בתיכנות מונחה עצמים – מחלקה אבסטרקטית.
2. בתיכנות מונחה עצמים – קבועים (Final).
3. תיכנות בסיסי – ארועים (או Events).
4. תיכנות באנדרואיד – ארוע לחיצה על המסך בעזרת onTouchEvent.
5. תיכנות Java מתקדם – הפקודה Synchronize ב- Threads

אכן, הרבה חומר.

מחלקה אבסטרקטית

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

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

מחלקה אבסטרקטית (Abstract Class) – מחלקה שלא ניתן ליצור מופע אובייקט, והיא המהווה אך ורק בסיס לירושת מחלקות אחרות, החולקות את אותה פונקציונליות בסיסית.

בואו נראה את הגדרת מחלקת ה- MovableObject שלנו:

package iAndroid.pingPong;

/*
 * This base class defines an abstract object which has central location (x,y),
 * some dimensions which can be determined by calling the methods
 * getLeft(), getRight(), getTop(),getBottom()
 */
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

	// Return the center horizontal position of the object
	public int getX()
	{
		return x;
	}

	// Return the center vertical position of the object
	public int getY()
	{
		return y;
	}

	//should return the left border of the object
	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();
}

בואו נעבור על הקוד:
בשורה 8 אנו מגדירים את המחלקה כמחלקה MovableObject כאבסטרקטית ע"י מילת המפתח abstract.
בשורות 10-23 הגדרה של משתנים השומרים את המיקום של מרכז האובייקט שלנו, ומתודות המאפשרות לקרוא ערכים אלו.
בשורות 25-38 מופיע משהו מעניין – הגדרה של מתודות אבסטרקטיות.

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

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

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

package iAndroid.pingPong;

/*
 * This class represents a ball object which has some radius and can move on the screen
 */
public class Ball extends MovableObject
{
	private final int RESTART_X,RESTART_Y; //the location at which the game should be restarted

	//the borders of balls' movement:
	private int maxWidth;
	private int maxHeight;

	private boolean movingRight; //The ball is moving to the right of the screen
	private boolean movingBottom; //The ball is moving to the bottom of the screen
	public final int RADIUS = 10; //Ball radius from center

	// Receive the start position of the ball and screen dimensions
	public Ball (int startX, int startY, int screenWidth, int screenHight)
	{
		maxWidth = screenWidth;
		maxHeight = screenHight;

		//When game is restarted, the ball is placed at the start position
		RESTART_X = startX;
		RESTART_Y = startY;

		 restart();
	}

	//Start a new game by placing the ball at the original position
	public void restart()
	{
		x = RESTART_X;
		y = RESTART_Y;
		movingBottom = false;
		movingRight = false;
	}

	//Return balls' left border
	@Override
	public int getLeft()
	{
		return x - RADIUS;
	}

	//Return balls' right border
	@Override
	public int getRight()
	{
		return x + RADIUS;
	}

	//Return balls' top most vertical position
	@Override
	public int getTop()
	{
		return y - RADIUS;
	}

	//Return balls' lowest most vertical position
	@Override
	public int getBottom()
	{
		return y + RADIUS;
	}

	// Moving the ball one step.
	@Override
	public void move()
	{
		// Moving the ball
		if (movingRight)
			x++;
		else
			x--;

		if (movingBottom)
			y++;
		else
			y--;

		// Check if we need to change the ball direction horizontally
		if (x >= maxWidth)
			movingRight = false;
		else if (x <= 1)
			movingRight = true;

		// Check if we need to change the ball direction vertically
		if (y >= maxHeight)
			movingBottom = false;
		else if (y <= 1)
			movingBottom = true;
	}

	//If the ball had hit the paddle, it should change direction upwards
	public void bounceUp()
	{
		movingBottom = false;
	}
}

בואו נעבור על הנקודות המעניינות בקוד שהשתנו מהמימוש הקודם שלנו בשיעור ו':
בשורה 6 אנחנו משנים את המחלקה Ball לרשת ממחלקת הבסיס שלנו MovableObject
בשורות 8-9 אנחנו מגדירים סוג מיוחד של משתנים – קבועים (final).

קבוע Final

קבוע (final) הוא משתנה שערכו לא משתנה במהלך התוכנית.

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

אנדיתודה על ההרחבה בנושא פרופסור!
אז בואו נמשיך.

בשורות 24-38 אנחנו הרחבנו את הפונקציונליות של הכדור, והוספנו תמיכה באפשרות לאתחל את המשחק, כאשר המשתמש נפסל.
במצב זה, כאשר אנחנו רוצים להתחיל את המשחק מחדש, מבחינת הכדור אנחנו בסה"כ צריכים להחזיר אותו למקום ההתחלתי שלו,ולתת לו את כיוון התנועה הראשוני שלו.
בשורות 41-67
מימשנו את המתודות האבסטרקטיות שהגדרנו במחלקת הבסיס MovableObject המחזירות את גבולות הכדור,
ובשורה 71 שינינו את שמה של המתודה moveBall ל- move על מנת שתממש את המתודה האבסטרקטית move במחלקת האב.

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

אנדיהערה חשובה מאד פרופסור!
תודה רבה.

לבסוף בשורות 97-101 הגדרנו מתודה חדשה, bounceUp, המשנה את כיוון תנועתו של הכדור כלפי מעלה, למקרה והוא נתקל במחבט שלנו.

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

כעת אנחנו מוכנים להוסיף מחלקה חדשה בשם Paddle שתממש את אובייקט המחבט:

package iAndroid.pingPong;

/*
 * This class represents a paddle object which has some width and height and can move on the screen
 */
public class Paddle extends MovableObject
{

	private final int screenWidth; //the borders of paddles' movement
	public final int HEIGHT = 10; //paddles' height
	private final int WIDTH = 50; //paddles' width
	private float paddleDestination; //holds to where the paddle should be moved

	//receive the start position of the paddle and screen width
	public Paddle(int startX, int startY, int newScreenWidth)
	{
		x = startX;
		y = startY;
		screenWidth = newScreenWidth;
		paddleDestination = screenWidth / 2;
	}

	//return the width of the paddle
	public synchronized int getPaddleWidth()
	{
	return WIDTH;
	}

	//return the center of the paddle
	public synchronized int getMiddle()
	{
		return x + WIDTH/2;
	}

	//return the left border of the paddle
	@Override
	public synchronized int getLeft()
	{
		return x;
	}

	//return the right border of the paddle
	@Override
	public synchronized int getRight()
	{
		return x + WIDTH;
	}

	//return the top border of the paddle
	@Override
	public synchronized int getTop()
	{
		return y;
	 }

	//return the bottom border of the paddle
	@Override
	public synchronized int getBottom()
	{
		return y + HEIGHT;
	}

	//returns the  destination of the paddle,
	//synchronizing the access to the paddleDestination variable
	public synchronized float getPaddleDestination()
	{
		return paddleDestination;
	}

	//sets the new destination of the paddle, in response to user input,
	//synchronizing the access to the paddleDestination variable
	public synchronized void setPaddleDestination(float newDestination)
	{
		paddleDestination = newDestination;
	}

	//move the paddle to the right by single point
	public synchronized void moveRight()
	{
		//check if the movement exceeds screen limits:
		if (screenWidth > (getRight()+1))
			x++;
		else
			//place paddle at the rightmost corner
			x = screenWidth - WIDTH;
	}

	//move the paddle to the left by single point
	public synchronized void moveLeft()
	{
		//check if the movement exceeds screen limits:
		if ((getLeft()-1) > 0)
			x--;
		else
			//place paddle at the leftmost corner
			x = 0;
	}

	//move the paddle in the correct direction, determined by paddleDestination
	@Override
	public synchronized void move()
	{
		float destination;

		destination = getPaddleDestination();
		//is the destination is to the right of the paddle?
		if (getMiddle() < destination)
		{
			//move the paddle to the right
			moveRight();
		}
		//is the destination is to the left of the paddle?
		else if (getMiddle() > destination)
		{
			//move the paddle to the left
			moveLeft();
		}
	 }
}

נתאר בקצרה את המחלקה:
המשתנה paddleDestination מחזיק את יעד התנועה שלנו, כפי שקלטנו אותו מהמשתמש. כלומר אם המחבט נמצא בנקודה 100 והמשתמש לחץ לנקודה 200, אז paddleDestination יהיה שווה ל 200 והמחבט יזוז ימינה.
את ערכו של paddleDestination אנו קוראים ומעדכנים באמצעות המתודות getPaddleDestination ו- setPaddleDestination בשורות 63-75,
בשורות 23-61 אנחנו ממשים את המתודות האבסטרקטיות שירשנו ממחלקת MovableObject, המחזירות את גבולות המחבט,
ובשורות 77-119 אנו מטפלים בתנועת הכדור – המתודות moveRight ו- moveLeft מזיזות את המחבט ימינה או שמאלה בפיקסל אחד,
תוך כדי שהן מוודאות לא לחרוג מגבולות המסך,
והמתודה move הממשת את המתודה האבסטרקטית move במחלקת MovableObject,
מחליטה האם צריך להזיז את הכדור – ואם כן האם ימינה או שמאלה, ע"י השוואת מרכז המחבט ליעד שקבע המשתמש.

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

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

כעת אנחנו יכולים להציג את המחבט שלנו על המסך, ע"י הוספת מחלקה חדשה – PaddleView:

package iAndroid.pingPong;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;

/*
 * This class is responsible for drawing a paddle
 */
public class PaddleView extends View
{

	private Paddle paddle; //our paddle to be drawn
	private Paint padlePaint; //the paint to paint the paddle with

	//initialize our view
	public PaddleView(Context context, Paddle newPaddle)
	{
		super(context);

		this.paddle = newPaddle;
	}

	//returns the paint that should be used to paint the paddle
	private Paint getPaddlePaint()
	{
		if (padlePaint == null)
		{
			padlePaint = new Paint();
			padlePaint.setStrokeWidth(2);
			padlePaint.setColor(Color.BLUE);
		}

		return padlePaint;
	}

	//do the actual drawing itself
	@Override
	public void onDraw(Canvas canvas)
	{
		int left,right,top,bottom;

		left = paddle.getLeft();
		right = paddle.getRight();
		top = paddle.getTop();
		bottom = paddle.getBottom();

		// Drawing the paddle
		canvas.drawRect(left, top, right, bottom, getPaddlePaint());
	 }
}

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

כעת אנחנו צריכים להרחיב את ה- View הראשי שלנו – PingPongView – שיכיל את ה- PaddleView החדש שהוספנו, נוסף על ה- BallView:

package iAndroid.pingPong;

import android.content.Context;
import android.graphics.Canvas;
import android.view.View;

public class PingPongView extends View
{
	private BallView ballView; //ball view
	private PaddleView paddleView; // paddle view

	//initialize our view
	public PingPongView(Context context)
	{
		super(context);
	}

	//bind views
	public void setViews(BallView ballView,PaddleView paddleView)
	{
		this.ballView = ballView;
		this.paddleView = paddleView;
	}

	//draw the views
	@Override
	public void onDraw(Canvas canvas)
	{
		// Drawing the ball
		this.ballView.onDraw(canvas);

		// Drawing the paddle
		this.paddleView.onDraw(canvas);
	}
}

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

package iAndroid.pingPong;

/*
 * PaddleMover is responsible for moving the paddle in response to user input,
 * in a separate thread
 */
public class PaddleMover extends Thread
{
	private Paddle gamePaddle; //holds a reference to the paddle
	private PingPongView gameView; //holds a reference to the main view

	//initialize class variables
	public PaddleMover(Paddle thePaddle, PingPongView mainView)
	{
		gamePaddle = thePaddle;
		gameView = mainView;
	}

	//main method of the current thread
	@Override
	public void run()
	{
		//infinitely loop, and move the paddle if necessary
		while (1 < 2)
		{
			//check whether the paddle should be moved
			if (gamePaddle.getMiddle() != gamePaddle.getPaddleDestination())
			{
				//move the paddle
				gamePaddle.move();

				//send a request to refresh the display
				gameView.postInvalidate();
			}
			try
			{
				PaddleMover.sleep(3);
			}
			catch (InterruptedException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
	}

}

בואו נעבור על הקוד:
המתודה run בשורות 22-47 אחראית על הרצת התהליכון. עם תחילתה אנו נכנסים ללולאה אינסופית,
שבה אנו בודקים האם המרכז של המחבט שלנו נמצא במקום שהמשתמש קבע.
במידה ולא, אנו מזיזים את המחבט פיקסל אחד בכיוון היעד ע"י קריאה למתודה move של האובייקט gamePaddle בשורה 31,
ולאחר מכן אנו מודיעים בשורה 34 ל- View המרכזי שלנו שיש לעדכן את התצוגה.
בשורות 36-44 אנו מרדימים את התהליכון לפרק שמן של 3 מילי-שניות, כדי להאט במקצת את תנועת המחבט.

אז איך מזהים לחיצה מהמשתמש?

Event-ים (ארועים)

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

הארוע onTouchEvent

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

אנו נממש את onTouchEvent בתוך ה- Activity שלנו, בכך ש"נדרוס" אותה ונגדיר לה מימוש משלנו:

package iAndroid.pingPong;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;

/*
 * The main activity class of the Ping Pong game
 */
public class PingPong extends Activity
{
	/*
	 * The following class variables hold references to the objects the game is composed of -
	 * PingPongView - main view,
	 * gameBall - the ball in the game,
	 * gamePaddle - the paddle in the game,
	 * gameThread - main thread updating the position of the ball,
	 * paddleMoverThread - another thread which is responsible for moving the paddle.
	 */
	private PingPongView gameView;
	private Ball gameBall;
	private Paddle gamePaddle;
	private PingPongGame gameThread;
	private PaddleMover paddleMoverThread;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        int screenWidth,screenHeight;
        BallView ballView;
        PaddleView paddleView;

        // Getting the screen width & height
        screenWidth = this.getWindowManager().getDefaultDisplay().getWidth();
        screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();

        // Creating the ball
	gameBall = new Ball(screenWidth / 2, screenHeight / 2, screenWidth, screenHeight);

	// Creating the ball view, and giving it the gameBall as a parameter
        ballView = new BallView(this, gameBall);

        // Creating the paddle
        gamePaddle = new Paddle(screenWidth / 2, (int)(screenHeight * 0.85), screenWidth);

	// Creating the paddle view, and giving it the gameBall as a parameter
        paddleView = new PaddleView(this, gamePaddle);

        // Creating the game view
        this.gameView = new PingPongView(this);

        // Give the gameView our ballView & paddleView.
        gameView.setViews(ballView,paddleView);

        // Setting the gameView as the main view for the PingPong activity.
        setContentView(gameView);

        //create the main thread
        gameThread = new PingPongGame(gameBall, gamePaddle, gameView);

        // Start the thread!
        gameThread.start();

        //create the thread responsible for moving the paddle
        paddleMoverThread = new PaddleMover(gamePaddle, gameView);

        // Start the thread!
        paddleMoverThread.start();

    }

    //This method is automatically called when the user touches the screen
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
    	float destination;

    	//get the x coordinate of users' press
    	destination = event.getX();

    	//notify the paddle mover thread regarding the new destination
    	gamePaddle.setPaddleDestination(destination);

	    return true;
    }
}

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

בנוסף לכך עדכנו את המתודה onCreate ליצור ולהפעיל את התהליכון של המחבט בשורות 68-72.

כעת כל מה שנותר לנו הוא לעדכן את התהליכון הראשי של המשחק המזיז את הכדור – PingPongGame -
להתייחס למחבט:

package iAndroid.pingPong;

/*
 * PingPongGame is the main thread responsible for moving the ball,
 * in a separate thread
 */
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

	//initialize class variables
	public PingPongGame(Ball theBall, Paddle thePaddle, PingPongView mainView)
	{
		gameBall = theBall;
		gamePaddle = thePaddle;
		gameView = mainView;
	}

	//main method of the current thread
	@Override
	public void run()
	{
		//infinitely loop, and move the ball accordingly
		while (1 < 2)
		{
			//move the ball one step
			gameBall.move();

			//check if the ball has reached the paddle
			checkHitPaddle();

			//send a request to refresh the display
			gameView.postInvalidate();

			try
			{
				PingPongGame.sleep(5);
			}
			catch (InterruptedException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
	}

	//This method is responsible for handling the situation in which the ball has reached the height of the paddle
	private void checkHitPaddle()
	{
		// Checking if the ball had hit the paddle
		if (gameBall.getBottom() >= gamePaddle.getTop())
		{
			// check if the paddle is in the right place
			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();
			}
		}
	}

}

לאחר שהזזנו את הכדור בשורה 29, אנחנו צריכים לבדוק את מיקומו ביחס למחבט במתודה checkHitPaddle.
במידה והתחתית של הכדור הגיעה לתקרת המחבט (שורה 54), אנחנו צריכים לבדוק האם הכדור נמצא בגבולות המחבט (שורות 57-58), ובמידה וכן – להחליף את כיוונו כלפי מעלה.
אחרת, אם המשתמש פיספס את הכדור נהייה סלחניים כלפיו וניתן לו צ'אנס נוסף ע"י אתחול המשחק מחדש (שורה 66).

זהו חברים, כל מה שנותר זה להריץ!

הגיע הזמן להסביר על הוראת ה- synchronized המוזרה שהשתמשנו בה במחלקת Paddle!

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

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

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

להית'!