Wednesday, March 30, 2011

AndEngine from Scratch (V)

Previously on AndEngine from Scratch



Today Goals

  • Finish the CatapultDetector
  • Make the player react to the detector's events.


Let's go with the Detector


I have the Detector stable, here is the complete code:


CatapultDetector.java
  1. package com.pruebas.andengine;
  2.  
  3. import org.anddev.andengine.input.touch.TouchEvent;
  4. import org.anddev.andengine.input.touch.detector.BaseDetector;
  5.  
  6. import android.view.MotionEvent;
  7.  
  8. public class CatapultDetector extends BaseDetector {
  9. // ===========================================================
  10. // Constants
  11. // ===========================================================
  12. private static final float ANGLE_CONSTANT = 90;
  13. private static final float DEFAULT_MAX_DISTANCE = 80;
  14. // ===========================================================
  15. // Fields
  16. // ===========================================================
  17.  
  18. // Listener for the Detector
  19. private final ICatapultDetectorListener mCatapultDetectorListener;
  20. private float mMaxDistance;
  21.  
  22. // First Touch
  23. private float mFirstX;
  24. private float mFirstY;
  25.  
  26. // ===========================================================
  27. // Constructors
  28. // ===========================================================
  29.  
  30. public CatapultDetector(
  31. final ICatapultDetectorListener pCatapultDetectorListener) {
  32. this(DEFAULT_MAX_DISTANCE, pCatapultDetectorListener);
  33. }
  34.  
  35. public CatapultDetector(final float pMaxDistance,
  36. final ICatapultDetectorListener pCatapultDetectorListener) {
  37. this.setMaxDistance(pMaxDistance);
  38. this.mCatapultDetectorListener = pCatapultDetectorListener;
  39. }
  40.  
  41. // ===========================================================
  42. // Methods for/from SuperClass/Interfaces
  43. // ===========================================================
  44.  
  45. @Override
  46. protected boolean onManagedTouchEvent(TouchEvent pSceneTouchEvent) {
  47. final float touchX = this.getX(pSceneTouchEvent);
  48. final float touchY = this.getY(pSceneTouchEvent);
  49. final int action = pSceneTouchEvent.getAction();
  50.  
  51. switch (action) {
  52. case MotionEvent.ACTION_DOWN:
  53. this.mFirstX = touchX;
  54. this.mFirstY = touchY;
  55. return true;
  56. case MotionEvent.ACTION_MOVE:
  57. case MotionEvent.ACTION_UP:
  58. // case MotionEvent.ACTION_CANCEL:
  59. final float distanceX = Math.abs(touchX - this.mFirstX);
  60. final float distanceY = Math.abs(touchY - this.mFirstY);
  61. final float distance = Math.min((float) Math.hypot(
  62. (double) distanceX, (double) distanceY), mMaxDistance);
  63. final double angleX = touchX - this.mFirstX;
  64. final double angleY = touchY - this.mFirstY;
  65. final float angle = (float) Math.toDegrees(Math.atan2(angleY,
  66. angleX))
  67. + ANGLE_CONSTANT;
  68. if (action == MotionEvent.ACTION_MOVE) {
  69. this.mCatapultDetectorListener.onCharge(this, pSceneTouchEvent,
  70. distance, angle);
  71. } else {
  72. this.mCatapultDetectorListener.onShoot(this, pSceneTouchEvent,
  73. distance, angle);
  74. }
  75. return true;
  76. default:
  77. return false;
  78. }
  79. }
  80.  
  81. // ===========================================================
  82. // Getter & Setter
  83. // ===========================================================
  84.  
  85. public void setMaxDistance(float mMaxDistance) {
  86. this.mMaxDistance = mMaxDistance;
  87. }
  88.  
  89. public float getMaxDistance() {
  90. return mMaxDistance;
  91. }
  92.  
  93. // ===========================================================
  94. // Methods
  95. // ===========================================================
  96.  
  97. protected float getX(final TouchEvent pTouchEvent) {
  98. return pTouchEvent.getX();
  99. }
  100.  
  101. protected float getY(final TouchEvent pTouchEvent) {
  102. return pTouchEvent.getY();
  103. }
  104.  
  105. // ===========================================================
  106. // Inner and Anonymous Classes
  107. // ===========================================================
  108.  
  109. public static interface ICatapultDetectorListener {
  110. // ===========================================================
  111. // Constants
  112. // ===========================================================
  113.  
  114. // ===========================================================
  115. // Methods
  116. // ===========================================================
  117.  
  118. public void onCharge(final CatapultDetector pCatapultDetector,
  119. final TouchEvent pTouchEvent, final float pDistance,
  120. final float pAngle);
  121.  
  122. public void onShoot(final CatapultDetector pCatapultDetector,
  123. final TouchEvent pTouchEvent, final float pDistance,
  124. final float pAngle);
  125. }
  126.  
  127. }
  128.  
Parsed in 0.130 seconds at 31.87 KB/s, using GeSHi 1.0.8.10


Let's take a look on the Listener, is the starting point of the listener.


CatapultDetector Listener
  1. public static interface ICatapultDetectorListener {
  2. // ===========================================================
  3. // Constants
  4. // ===========================================================
  5.  
  6. // ===========================================================
  7. // Methods
  8. // ===========================================================
  9.  
  10. public void onCharge(final CatapultDetector pCatapultDetector,
  11. final TouchEvent pTouchEvent, final float pDistance,
  12. final float pAngle);
  13.  
  14. public void onShoot(final CatapultDetector pCatapultDetector,
  15. final TouchEvent pTouchEvent, final float pDistance,
  16. final float pAngle);
  17. }
Parsed in 0.091 seconds at 7.05 KB/s, using GeSHi 1.0.8.10

This listener has to respond two kind of events, when we are "charging" and when we shoot. The onCharge() method is for the first one and the onShoot() for the second one. The parameters to this methods are the same.

  • pCatapultDetector: The detector itself.
  • pTouchEvent: The current TouchScreen Event.
  • pDistance: Distance from the starting point of the gesture to the current point.
  • pAngle: The angle formed by the first touch point and the current.
The onShoot() parameters and the onCharge() are the same. Let's take a look at the variables.


CatapultDetector variables
  1. // ===========================================================
  2. // Constants
  3. // ===========================================================
  4. private static final float ANGLE_CONSTANT = 90;
  5. private static final float DEFAULT_MAX_DISTANCE = 80;
  6. // ===========================================================
  7. // Fields
  8. // ===========================================================
  9.  
  10. // Listener for the Detector
  11. private final ICatapultDetectorListener mCatapultDetectorListener;
  12. private float mMaxDistance;
  13.  
  14. // First Touch
  15. private float mFirstX;
  16. private float mFirstY;
Parsed in 0.086 seconds at 6.68 KB/s, using GeSHi 1.0.8.10

We have the constants

  • ANGLE_CONSTANT: In the early tests, i had to sum 90 to the angle for the player to rotate the correct angle.
  • DEFAULT_MAX_DISTANCE: Default max distance of charge, if not indicated in the constructor, this is the max distance of charge, if you try to charge a number larger than the max distance, the detector will trigger the listener with the max distance.


Let's view the variables.

  • mCatapultDetectorListener: Is a ICatapultDetectorListener that we have seen before. Is mandatary to have one Listener.
  • mMaxDistance: In this variable we hold the max distance.
  • mFirstX,mFirstY: First point of touch in X,Y coordinates.


Let's go to the class constructors


CatapultDetector's constructors
  1. // ===========================================================
  2. // Constructors
  3. // ===========================================================
  4.  
  5. public CatapultDetector(
  6. final ICatapultDetectorListener pCatapultDetectorListener) {
  7. this(DEFAULT_MAX_DISTANCE, pCatapultDetectorListener);
  8. }
  9.  
  10. public CatapultDetector(final float pMaxDistance,
  11. final ICatapultDetectorListener pCatapultDetectorListener) {
  12. this.setMaxDistance(pMaxDistance);
  13. this.mCatapultDetectorListener = pCatapultDetectorListener;
  14. }
  15.  
Parsed in 0.086 seconds at 5.98 KB/s, using GeSHi 1.0.8.10

We have two different constructors. The first one is invoked with only one parameter, the ICatapultDetectorListener that responds to the events. In the second one we can specify the value of Max Charge distance.


The real work in the detector is in the onManagedTouchEvent() method. It has only one parameter, the TouchEvent.


  1. final float touchX = this.getX(pSceneTouchEvent);
  2. final float touchY = this.getY(pSceneTouchEvent);
  3. final int action = pSceneTouchEvent.getAction();
Parsed in 0.081 seconds at 1.88 KB/s, using GeSHi 1.0.8.10

Here we use final variables to hold data usefull later in the method. In the first line we hold the X coordinate of the event, in the second the Y coordinate and in the third we have the action performed, in our case three posible values.

  • ACTION_DOWN : The user touch the screen for first time.
  • ACTION_MOVE : The user has the finger on the screen and move it.
  • ACTION_UP : The take the finger away from the screen.


When the event is ACTION_DOWN the only thing we need to do is keep the coordinates where the user put the finger, that we will use them to calculate angles and distance.


To the ACTION_MOVE and ACTION_UP we use the same code for both.


ACTION_MOVE and ACTION_UP
  1. final float distanceX = Math.abs(touchX - this.mFirstX);
  2. final float distanceY = Math.abs(touchY - this.mFirstY);
  3. final float distance = Math.min((float) Math.hypot(
  4. (double) distanceX, (double) distanceY), mMaxDistance);
  5. final double angleX = touchX - this.mFirstX;
  6. final double angleY = touchY - this.mFirstY;
  7. final float angle = (float) Math.toDegrees(Math.atan2(angleY,
  8. angleX))
  9. + ANGLE_CONSTANT;
  10. if (action == MotionEvent.ACTION_MOVE) {
  11. this.mCatapultDetectorListener.onCharge(this, pSceneTouchEvent,
  12. distance, angle);
  13. } else {
  14. this.mCatapultDetectorListener.onShoot(this, pSceneTouchEvent,
  15. distance, angle);
  16. }
  17. return true;
Parsed in 0.078 seconds at 8.91 KB/s, using GeSHi 1.0.8.10

First and second line, we grab the distance between the first and last touch coordinates in pixels. The third line calculates with hypot the real distance between the two points. In this third line we apply the Max distance of charge. Let's go to the angle.


The best way i found to figure th e angle between two point is the next lines method.We have in the angle variable the angle.


If the action is ACTION_MOVE we trigger an onCharge() event, if the action is ACTION_UP we trigger an onShoot() event.


With that we have finished the first version of our Detector. Sure there will be better and more efficiently ways to do this. If you have one better solution, please post it in the comments and i change the tutorial. I will send you a virtual beer ;)


Adapting the Player Class


First of all, the Player is a descendant of the AnimatedSprite class.



We have here the diferent frames of the animation. Let's go to work. Define a constant to hold the max frame of the charging state, if you look well, the las two frames are for the movement when the user releases the player.


  1. private static final int PLAYER_CHARGE_ANIMATIONS = 5;
Parsed in 0.073 seconds at 737 B/s, using GeSHi 1.0.8.10

Let's add one method to the class.


Add this procedure to your Player class.
  1. public void setCharge(final float angle, final float distance)
  2. {
  3. final int step = Math.round(distance * PLAYER_CHARGE_ANIMATIONS / Main.MAX_CHARGE_DISTANCE);  
  4. this.stopAnimation(step);
  5. this.setRotation(angle);  
  6. }
Parsed in 0.067 seconds at 3.32 KB/s, using GeSHi 1.0.8.10

In the first line, we use a "rule of three" (Is this correct in english?) to calculate the actual frame according to distance the detector send to us. We use the MAX_CHARGE_DISTANCE constant (we will define that later). We use the stopAnimation() method to indicate the sprite not to animate and which frame we want to see in the screen. Then we rotate the Sprite the angle.


Let's go to the Main.java and put that constant. Mine look like this:


  1. // ===========================================================
  2. // Constants
  3. // ===========================================================
  4. private static final int CAMERA_WIDTH = 480;
  5. private static final int CAMERA_HEIGHT = 320;
  6. public static final float MAX_CHARGE_DISTANCE = 80;
  7.  
  8. private static final String TAG = "AndEngineTest";
  9.  
Parsed in 0.069 seconds at 4.94 KB/s, using GeSHi 1.0.8.10

In the onLoadScene() method we touch where the CatapultDetector is created and pass this constant to the constructor.


  1. this.mScrollDetector = new SurfaceScrollDetector(this);
  2. this.mScrollDetector.setEnabled(false); 
  3.  
  4. this.mCatapultDetector = new CatapultDetector(MAX_CHARGE_DISTANCE,this);
  5. this.mCatapultDetector.setEnabled(true);
Parsed in 0.089 seconds at 2.46 KB/s, using GeSHi 1.0.8.10

Change the onCharge() method


  1. @Override
  2. public void onCharge(CatapultDetector pCatapultDetector,
  3. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  4. this.mActivePlayer.setCharge(pAngle,pDistance);  
  5. }
Parsed in 0.076 seconds at 2.42 KB/s, using GeSHi 1.0.8.10

Here we pass the player the level of charge. With these modifications the programs runs correctly. If you experienced any problem in this tutorial, please contact with me or post a comment and i will be pleased to solve it.


In the next chapter we will see how integrate the Box2d phisics engine and some improvements in the game.


Source Code

To get the source code: svn checkout http://ch-soccer.googlecode.com/svn/trunk/ tutorial-read-only -r 8


Index

10 comments:

  1. Felicidades! Excelentes tutoriales, no puedo esperar por la parte VI :D

    ReplyDelete
  2. Okay I got everything working except for the ball sprite is not showing? Are you just not there yet in the tutorial? I saw some code you commented out about the "new Sprite ball" but trying to use that code and cleaning it up a bit always gives me a constructor undefined error... hey at least I got further. Also I'm using a Motorola Droid 2 so my screen orientation looks a little different than yours and so does the player's position.

    My resolution is 854 x 480

    Big Question:
    Where did you write the code for the ball to be created and drawn and attached like the Player has been done? I was thinking this tutorial was complete and should do everything in the shown on tutorial one's page. No?



    Thanks

    ReplyDelete
  3. Another great work, I need urge to buy a Android mobile :)

    gjoutdoors.
    In this Tuto he deleted the ball, try Tutorial II and III.

    ReplyDelete
  4. Excellent so far, I've done Tutorial Part I through IV, and still need to go through Part V, but I wanted to encourage you to get Part VI online.

    Thanks,

    Jay

    ReplyDelete
  5. It was really great stuff until version IV, then you just went off the deep end with the custom detector, difficulty jumped way too high! Its quite difficult to follow after that.

    ReplyDelete
  6. I'm writing similar game but for some reason charging and shooting my ball works kind of "not real". Can you tell something on that topic? Thanks.

    ReplyDelete
  7. Super stuff! Ages since this post, will there still be a next one?

    ReplyDelete
  8. Andengine has suffered a lot of changes since i started the blog. Recoding all is a lot of work, and i dont want to rewrite the series and suffer the same problem again... When i have more free time i would like to do the job... But i cant promise anything.

    ReplyDelete
  9. I recently bought a book called "Learning Android Game Programming" and nearly half the stuff is deprecated! It's really annoying how there's very little documentation on AndEngine as well.

    Regardless, your tutorials are very helpful! Please keep them coming when you have time.

    ReplyDelete
  10. The video shows the player is doing some action with ball. But in code no ball is getting displayed and where is the physics code to move the ball?

    ReplyDelete