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

Friday, March 25, 2011

AndEngine from Scratch (IV)

Previously on AndEngine from Scratch



Today goals

  • Create the player sprite.
  • Create a Detector to the catapult effect.



Create the player


To create the player i decided to use an AnimatedSprite, but we subclass the AnimatedSprite to be able to personalize it.


To crate the class, go to the folder with the sources in Eclipse. File New -> Class and the Name is Player and the SuperClass is AnimatedSprite. We add a debug TAG and for now is ok.


  1. package com.pruebas.andengine;
  2.  
  3. import org.anddev.andengine.entity.sprite.AnimatedSprite;
  4. import org.anddev.andengine.input.touch.detector.HoldDetector;
  5. import org.anddev.andengine.input.touch.detector.HoldDetector.IHoldDetectorListener;
  6. import org.anddev.andengine.opengl.texture.region.TiledTextureRegion;
  7.  
  8. import android.util.Log;
  9.  
  10. public class Player extends AnimatedSprite  {
  11.  
  12. private static final String TAG = "Player"; 
  13.  
  14. public Player(float pX, float pY, TiledTextureRegion pTiledTextureRegion) {
  15. super(pX, pY, pTiledTextureRegion);  
  16. }
  17.  
  18.  
  19. }
  20.  
Parsed in 0.077 seconds at 7.25 KB/s, using GeSHi 1.0.8.10

Get back to the Main.java and let's add a variable to hold the active Player object. Let's change the mFaceTextureRegion to mBallTextureRegion to make it pretty and change the type to TiledTextureRegion. My variables looks like this:


  1. // ===========================================================
  2. // Fields
  3. // ===========================================================
  4.  
  5. private ZoomCamera mCamera;
  6. private Texture mTexture;
  7. private TiledTextureRegion mBallTextureRegion;
  8. private TiledTextureRegion mPlayerTextureRegion;
  9. private SurfaceScrollDetector mScrollDetector;
  10. private TMXTiledMap mTMXTiledMap;
  11. private Player mActivePlayer;
Parsed in 0.106 seconds at 3.86 KB/s, using GeSHi 1.0.8.10

Here is the player sprite, download to your assets/gfx folder as usual.



Here is some experimental ball sprite (i need a real ball to make photos), to the same site, assets/gfx



Now we must change the way to load the textures to load this new two textures. All the textures go to the mTexture Object. The mTexture object need a power of two in width and height in pixels. To hold the textures recently downloaded we need exactly 256X256 pixels. The texture in memory would look like this:




Let's change en onLoadResources() method to load all this new stuff. First of all, the size of the mTexture needs to be 256X256 and now to load the ball texture we use the createTiledFromAsset() function with the parameters:

  • this.mTexture: The first is the mTexture object where all the textures are going
  • this: Actual class.
  • "gfx/ui_ball.png": Path to the image containing the tiled sprites.
  • 0,0: The next two integer parameters is where are going yo put the image inside mTexture. In this case, in the top left corner (0,0). The player in the top middle (128,0).
  • 2,4: Number of rows an columns in the tile. 2,4 in our case.
The code looks like this:


  1. @Override
  2. public void onLoadResources() {
  3. this.mTexture = new Texture(256, 256,
  4. TextureOptions.BILINEAR_PREMULTIPLYALPHA);
  5. this.mBallTextureRegion = TextureRegionFactory.createTiledFromAsset(
  6. this.mTexture, this, "gfx/ui_ball.png", 0, 0, 2, 4);
  7. this.mPlayerTextureRegion = TextureRegionFactory.createTiledFromAsset(
  8. this.mTexture, this, "gfx/ui_player.png", 128, 0, 2, 4);
  9.  
  10. this.mEngine.getTextureManager().loadTexture(this.mTexture);
  11.  
  12. }
Parsed in 0.074 seconds at 6.24 KB/s, using GeSHi 1.0.8.10

Now, some code on the onLoadScene() method. First we delete the source creating the ball and deactivate the ScrollDetector. The procedure should look like this:


  1. scene.setOnAreaTouchTraversalFrontToBack();
  2.  
  3. this.mScrollDetector = new SurfaceScrollDetector(this);
  4. this.mScrollDetector.setEnabled(false); //Hemos puesto esto a false
  5.  
  6. /*
  7.   final int centerX = (CAMERA_WIDTH - this.mFaceTextureRegion.getWidth()) / 2;
  8.   final int centerY = (CAMERA_HEIGHT - this.mFaceTextureRegion
  9.     .getHeight()) / 2;
  10.  
  11.   final Sprite ball = new Sprite(centerX, centerY,
  12.     this.mFaceTextureRegion);
  13.   scene.getLastChild().attachChild(ball);
  14.      */
  15. scene.setOnSceneTouchListener(this);
  16. scene.setTouchAreaBindingEnabled(true);
  17.  
  18. return scene;
  19. }
Parsed in 0.077 seconds at 7.57 KB/s, using GeSHi 1.0.8.10

Let's create a method to create a player to test the animation.


  1. // ===========================================================
  2. // Methods
  3. // ===========================================================
  4.  
  5. private void createPlayer() {
  6. final Scene scene = this.mEngine.getScene();
  7.  
  8. final Player sprite = new Player(200, 100, this.mPlayerTextureRegion);
  9. // scene.registerTouchArea(sprite);
  10.  
  11. scene.getLastChild().attachChild(sprite);
  12. this.mActivePlayer = sprite;
  13. }
Parsed in 0.069 seconds at 5.99 KB/s, using GeSHi 1.0.8.10

To test, we are gonna put a player in the 200X100 position. Call this new function from onLoadComplete().


  1. @Override
  2. public void onLoadComplete() { // scene.set 
  3. createPlayer();
  4. }
Parsed in 0.072 seconds at 1.08 KB/s, using GeSHi 1.0.8.10

In this point, we can debug our program to see what's happen. We can see the background grass and the player. It doesn't respond to scroll because we deactivated it. Let's create the detector.


Creating a Detector


Looking around in the AndEngine sources, the ScrollDetector is a very easy way to create Detectors.


We create a new class in our project. Name: CatapultDetector and SuperClass BaseDetector
(org.anddev.andengine.input.touch.detector.BaseDetector)


  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. public class CatapultDetector extends BaseDetector {
  7.  
  8. @Override
  9. protected boolean onManagedTouchEvent(TouchEvent pSceneTouchEvent) {
  10. // TODO Auto-generated method stub
  11. return false;
  12. }
  13.  
  14. }
  15.  
Parsed in 0.091 seconds at 3.82 KB/s, using GeSHi 1.0.8.10

We are going to try to make a Detector that works like the ScrollDetector, we need a listener interface.


  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. }
  18.  
Parsed in 0.102 seconds at 6.30 KB/s, using GeSHi 1.0.8.10


This is a basic listener to test the working of the Detector. The listener object recieves the angle and distance of the soot. We define this class inside the CatapultDetector class like the other detector in AndEngine.


Let's put some variables to the detector.


  1. // ===========================================================
  2. // Constants
  3. // ===========================================================
  4. private static final float TRIGGER_SCROLL_MINIMUM_DISTANCE_DEFAULT = 10;
  5. private static final float ANGLE_CONSTANT = 10;
  6. private static final int DEFAULT_STEPS = 6;
  7. private float DEFAULT_MAX_DISTANCE = 100;
  8. // ===========================================================
  9. // Fields
  10. // ===========================================================
  11.  
  12. //Minimum distance to execute 
  13. private float mTriggerScrollMinimumDistance;
  14.  
  15. //Listener for the Detector
  16. private final ICatapultDetectorListener mCatapultDetectorListener;
  17.  
  18. private boolean mTriggered;
  19.  
  20. //First Touch
  21. private float mFirstX;
  22. private float mFirstY;
  23.  
  24. //Last Touch
  25. private float mLastX;
  26. private float mLastY;
  27.  
  28. private int mSteps;
  29. private float mMaxDistance;
Parsed in 0.088 seconds at 10.05 KB/s, using GeSHi 1.0.8.10

We create some methods to use later and getter and setter to mTriggerScrollMinimumDistance


  1. // ===========================================================
  2. // Getter & Setter
  3. // ===========================================================
  4.  
  5. public void setTriggerScrollMinimumDistance(
  6. float mTriggerScrollMinimumDistance) {
  7. this.mTriggerScrollMinimumDistance = mTriggerScrollMinimumDistance;
  8. }
  9.  
  10. public float getTriggerScrollMinimumDistance() {
  11. return mTriggerScrollMinimumDistance;
  12. }
  13.  
  14. // ===========================================================
  15. // Methods
  16. // ===========================================================
  17.  
  18. protected float getX(final TouchEvent pTouchEvent) {
  19. return pTouchEvent.getX();
  20. }
  21.  
  22. protected float getY(final TouchEvent pTouchEvent) {
  23. return pTouchEvent.getY();
  24. }
Parsed in 0.098 seconds at 7.41 KB/s, using GeSHi 1.0.8.10

Code a little in the class constructors.


  1. // ===========================================================
  2. // Constructors
  3. // ===========================================================
  4.  
  5. public CatapultDetector(
  6. final ICatapultDetectorListener pCatapultDetectorListener) {
  7. this(TRIGGER_SCROLL_MINIMUM_DISTANCE_DEFAULT, pCatapultDetectorListener);
  8. }
  9.  
  10. public CatapultDetector(final float pTriggerScrollMinimumDistance,
  11. final ICatapultDetectorListener pCatapultDetectorListener) {
  12. this.setTriggerScrollMinimumDistance(pTriggerScrollMinimumDistance);
  13. this.mCatapultDetectorListener = pCatapultDetectorListener;
  14. }
Parsed in 0.077 seconds at 7.53 KB/s, using GeSHi 1.0.8.10

And now the main method of the class, where all happens.


  1. // ===========================================================
  2. // Methods for/from SuperClass/Interfaces
  3. // ===========================================================
  4.  
  5. @Override
  6. protected boolean onManagedTouchEvent(TouchEvent pSceneTouchEvent) {
  7. final float touchX = this.getX(pSceneTouchEvent);
  8. final float touchY = this.getY(pSceneTouchEvent);
  9.  
  10. switch (pSceneTouchEvent.getAction()) {
  11. case MotionEvent.ACTION_DOWN:
  12. this.mFirstX = touchX;
  13. this.mFirstY = touchY;
  14. this.mLastX = touchX;
  15. this.mLastY = touchY;
  16. this.mTriggered = false;
  17. return true;
  18. case MotionEvent.ACTION_MOVE:
  19. case MotionEvent.ACTION_UP:
  20. case MotionEvent.ACTION_CANCEL:
  21. final float distanceX = touchX - this.mLastX;
  22. final float distanceY = touchY - this.mLastY;
  23. if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_MOVE) {
  24. final float triggerScrollMinimumDistance = this.mTriggerScrollMinimumDistance;
  25. if (this.mTriggered
  26. || Math.abs(distanceX) > triggerScrollMinimumDistance
  27. || Math.abs(distanceY) > triggerScrollMinimumDistance) {
  28. final float distance = (float)Math.hypot((double)distanceX,(double)distanceY);     
  29. final double angleX = touchX - this.mFirstX;
  30. final double angleY = touchY - this.mFirstY;
  31. final float angle = (float)Math.toDegrees(Math.atan2(angleY, angleX))+ANGLE_CONSTANT;
  32. this.mCatapultDetectorListener.onCharge(this, pSceneTouchEvent, distance, angle);
  33. this.mLastX = touchX;
  34. this.mLastY = touchY;
  35. this.mTriggered = true;
  36. }
  37. else
  38. {
  39.  
  40.  
  41. }
  42. }
  43. return true;
  44. default:
  45. return false;
  46. }
  47. }
  48.  
Parsed in 0.097 seconds at 16.70 KB/s, using GeSHi 1.0.8.10

In The next article we are going to explain more carefully the Detector, but looking the source you can see how it works. Let's back to the Main.java. Create a private variable to hold the Detector Object.


  1. // ===========================================================
  2. // Fields
  3. // ===========================================================
  4.  
  5. private ZoomCamera mCamera;
  6. private Texture mTexture;
  7. private TiledTextureRegion mBallTextureRegion;
  8. private TiledTextureRegion mPlayerTextureRegion;
  9. private SurfaceScrollDetector mScrollDetector; 
  10. private TMXTiledMap mTMXTiledMap;
  11. private Player mActivePlayer;
  12. private CatapultDetector mCatapultDetector; //Nueva variable Creada
Parsed in 0.078 seconds at 6.09 KB/s, using GeSHi 1.0.8.10

Now tell the Main class to implement the new ICatapultDetectorListener interface.


  1. public class Main extends BaseGameActivity implements IScrollDetectorListener,
  2. IOnSceneTouchListener, ICatapultDetectorListener
Parsed in 0.082 seconds at 1.54 KB/s, using GeSHi 1.0.8.10

Now we must implement the missing methods of the interface.


  1. @Override
  2. public void onCharge(CatapultDetector pCatapultDetector,
  3. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  4. Log.d(TAG, "Cargando... {Distancia:" + pDistance + ", angulo: "
  5. + pAngle + "}");
  6. this.mActivePlayer.setRotation(pAngle);
  7.  
  8. }
  9.  
  10. @Override
  11. public void onShoot(CatapultDetector pCatapultDetector,
  12. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  13. Log.d(TAG, "Disparo... {Distancia:" + pDistance + ", angulo: " + pAngle
  14. + "}");
  15. }
Parsed in 0.084 seconds at 5.75 KB/s, using GeSHi 1.0.8.10

In the onCharge() method, we rotate the player sprite according to the calculated angle. In the onShoot() method, only Debug message. The next step is create in the onLoadScene() method the Detector and assign to the Main Object.


  1. this.mScrollDetector = new SurfaceScrollDetector(this);
  2. this.mScrollDetector.setEnabled(false); 
  3.  
  4. //Creamos el detector de catapulta
  5. this.mCatapultDetector = new CatapultDetector(this);
  6. this.mCatapultDetector.setEnabled(true);
Parsed in 0.093 seconds at 2.55 KB/s, using GeSHi 1.0.8.10

And in the onSceneTouchEvent() method we pass the event to the CatapultDetector.



  1. @Override
  2. public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
  3. if (this.mActivePlayer != null) {
  4. this.mCatapultDetector.onTouchEvent(pSceneTouchEvent);
  5. }
  6. return true;
  7. }
Parsed in 0.079 seconds at 2.59 KB/s, using GeSHi 1.0.8.10


We now can debug again the project and see what's going on. F11... we can see the splash, the background, the player... all fine. Let's tap with the finger on the Screen and do some move around. The payer rotates but not in the desired angle. It's a start. Now my files are like this:



Main.java
  1. package com.pruebas.andengine;
  2.  
  3. import org.anddev.andengine.engine.Engine;
  4. import org.anddev.andengine.engine.camera.ZoomCamera;
  5. import org.anddev.andengine.engine.options.EngineOptions;
  6. import org.anddev.andengine.engine.options.EngineOptions.ScreenOrientation;
  7. import org.anddev.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;
  8. import org.anddev.andengine.entity.layer.tiled.tmx.TMXLayer;
  9. import org.anddev.andengine.entity.layer.tiled.tmx.TMXLoader;
  10. import org.anddev.andengine.entity.layer.tiled.tmx.TMXProperties;
  11. import org.anddev.andengine.entity.layer.tiled.tmx.TMXTile;
  12. import org.anddev.andengine.entity.layer.tiled.tmx.TMXTileProperty;
  13. import org.anddev.andengine.entity.layer.tiled.tmx.TMXTiledMap;
  14. import org.anddev.andengine.entity.layer.tiled.tmx.TMXLoader.ITMXTilePropertiesListener;
  15. import org.anddev.andengine.entity.layer.tiled.tmx.util.exception.TMXLoadException;
  16. import org.anddev.andengine.entity.scene.Scene;
  17. import org.anddev.andengine.entity.scene.Scene.IOnSceneTouchListener;
  18. import org.anddev.andengine.entity.sprite.AnimatedSprite;
  19. import org.anddev.andengine.entity.sprite.Sprite;
  20. import org.anddev.andengine.entity.util.FPSLogger;
  21. import org.anddev.andengine.input.touch.TouchEvent;
  22. import org.anddev.andengine.input.touch.detector.ScrollDetector;
  23. import org.anddev.andengine.input.touch.detector.SurfaceScrollDetector;
  24. import org.anddev.andengine.input.touch.detector.ScrollDetector.IScrollDetectorListener;
  25. import org.anddev.andengine.opengl.texture.Texture;
  26. import org.anddev.andengine.opengl.texture.TextureOptions;
  27. import org.anddev.andengine.opengl.texture.region.TextureRegion;
  28. import org.anddev.andengine.opengl.texture.region.TextureRegionFactory;
  29. import org.anddev.andengine.opengl.texture.region.TiledTextureRegion;
  30. import org.anddev.andengine.ui.activity.BaseGameActivity;
  31. import org.anddev.andengine.util.Debug;
  32.  
  33. import android.util.Log;
  34.  
  35. import com.pruebas.andengine.CatapultDetector.ICatapultDetectorListener;
  36.  
  37. public class Main extends BaseGameActivity implements IScrollDetectorListener,
  38. IOnSceneTouchListener, ICatapultDetectorListener {
  39. // ===========================================================
  40. // Constants
  41. // ===========================================================
  42. static final int CAMERA_WIDTH = 480;
  43. static final int CAMERA_HEIGHT = 320;
  44.  
  45. private static final String TAG = "AndEngineTest";
  46.  
  47. // ===========================================================
  48. // Fields
  49. // ===========================================================
  50.  
  51. private ZoomCamera mCamera;
  52. private Texture mTexture;
  53. private TiledTextureRegion mBallTextureRegion;
  54. private TiledTextureRegion mPlayerTextureRegion;
  55. private SurfaceScrollDetector mScrollDetector; 
  56. private TMXTiledMap mTMXTiledMap;
  57. private Player mActivePlayer;
  58. private CatapultDetector mCatapultDetector; //Nueva variable Creada
  59.  
  60. // ===========================================================
  61. // Constructors
  62. // ===========================================================
  63.  
  64. // ===========================================================
  65. // Getter & Setter
  66. // ===========================================================
  67.  
  68. // ===========================================================
  69. // Methods for/from SuperClass/Interfaces
  70. // ===========================================================
  71.  
  72. @Override
  73. public void onLoadComplete() {  
  74. createPlayer();
  75. }
  76.  
  77. @Override
  78. public Engine onLoadEngine() {
  79. this.mCamera = new ZoomCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
  80. final int alturaTotal = CAMERA_HEIGHT * 3;
  81. this.mCamera.setBounds(0, CAMERA_WIDTH, 0, alturaTotal);
  82. this.mCamera.setBoundsEnabled(true);
  83. return new Engine(new EngineOptions(true, ScreenOrientation.LANDSCAPE,
  84. new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT),
  85. this.mCamera));
  86. }
  87.  
  88. @Override
  89. public void onLoadResources() {
  90. this.mTexture = new Texture(256, 256,
  91. TextureOptions.BILINEAR_PREMULTIPLYALPHA);
  92. this.mBallTextureRegion = TextureRegionFactory.createTiledFromAsset(
  93. this.mTexture, this, "gfx/ui_ball.png", 0, 0, 2, 4);
  94. this.mPlayerTextureRegion = TextureRegionFactory.createTiledFromAsset(
  95. this.mTexture, this, "gfx/ui_player.png", 128, 0, 2, 4);
  96.  
  97. this.mEngine.getTextureManager().loadTexture(this.mTexture);
  98.  
  99. }
  100.  
  101. @Override
  102. public Scene onLoadScene() {
  103. this.mEngine.registerUpdateHandler(new FPSLogger());
  104.  
  105. final Scene scene = new Scene(1);
  106.  
  107. try {
  108. final TMXLoader tmxLoader = new TMXLoader(this, this.mEngine
  109. .getTextureManager(), // TextureOptions.BILINEAR_PREMULTIPLYALPHA,
  110. TextureOptions.NEAREST, new ITMXTilePropertiesListener() {
  111.  
  112. @Override
  113. public void onTMXTileWithPropertiesCreated(
  114. final TMXTiledMap pTMXTiledMap,
  115. final TMXLayer pTMXLayer,
  116. final TMXTile pTMXTile,
  117. final TMXProperties<TMXTileProperty> pTMXTileProperties) {
  118.  
  119. }
  120.  
  121. });
  122. this.mTMXTiledMap = tmxLoader.loadFromAsset(this, "tmx/field.tmx");
  123. } catch (final TMXLoadException tmxle) {
  124. Debug.e(tmxle);
  125. }
  126.  
  127. final TMXLayer tmxLayer = this.mTMXTiledMap.getTMXLayers().get(0);
  128. scene.getFirstChild().attachChild(tmxLayer);
  129.  
  130. scene.setOnAreaTouchTraversalFrontToBack();
  131.  
  132. this.mScrollDetector = new SurfaceScrollDetector(this);
  133. this.mScrollDetector.setEnabled(false); 
  134.  
  135. this.mCatapultDetector = new CatapultDetector(this);
  136. this.mCatapultDetector.setEnabled(true);
  137.  
  138. scene.setOnSceneTouchListener(this);
  139. scene.setTouchAreaBindingEnabled(true);
  140.  
  141. return scene;
  142. }
  143.  
  144.  
  145. @Override
  146. public void onCharge(CatapultDetector pCatapultDetector,
  147. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  148. Log.d(TAG, "Cargando... {Distancia:" + pDistance + ", angulo: "
  149. + pAngle + "}");
  150. this.mActivePlayer.setRotation(pAngle);
  151.  
  152. }
  153.  
  154. @Override
  155. public void onShoot(CatapultDetector pCatapultDetector,
  156. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  157. Log.d(TAG, "Disparo... {Distancia:" + pDistance + ", angulo: " + pAngle
  158. + "}");
  159. } 
  160.  
  161.  
  162. @Override
  163. public void onScroll(ScrollDetector pScollDetector, TouchEvent pTouchEvent,
  164. float pDistanceX, float pDistanceY) {
  165. this.mCamera.offsetCenter(-pDistanceX, -pDistanceY);
  166. }
  167.  
  168. @Override
  169. public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
  170. if (this.mActivePlayer != null) {
  171. this.mCatapultDetector.onTouchEvent(pSceneTouchEvent);
  172. }
  173. return true;
  174. }
  175.  
  176. // ===========================================================
  177. // Methods
  178. // ===========================================================
  179.  
  180. private void createPlayer() {
  181. final Scene scene = this.mEngine.getScene();
  182.  
  183. final Player sprite = new Player(200, 100, this.mPlayerTextureRegion);
  184. scene.getLastChild().attachChild(sprite);
  185.  
  186. this.mActivePlayer = sprite;
  187. } 
  188.  
  189.  
  190. // ===========================================================
  191. // Inner and Anonymous Classes
  192. // ===========================================================
  193. }
  194.  
Parsed in 0.129 seconds at 53.47 KB/s, using GeSHi 1.0.8.10


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 TRIGGER_SCROLL_MINIMUM_DISTANCE_DEFAULT = 10;
  13. private static final float ANGLE_CONSTANT = 10;
  14. private static final int DEFAULT_STEPS = 6;
  15. private float DEFAULT_MAX_DISTANCE = 100;
  16. // ===========================================================
  17. // Fields
  18. // ===========================================================
  19.  
  20. //Minimum distance to execute 
  21. private float mTriggerScrollMinimumDistance;
  22.  
  23. //Listener for the Detector
  24. private final ICatapultDetectorListener mCatapultDetectorListener;
  25.  
  26. private boolean mTriggered;
  27.  
  28. //First Touch
  29. private float mFirstX;
  30. private float mFirstY;
  31.  
  32. //Last Touch
  33. private float mLastX;
  34. private float mLastY;
  35.  
  36. private int mSteps;
  37. private float mMaxDistance;
  38.  
  39. // ===========================================================
  40. // Constructors
  41. // ===========================================================
  42.  
  43. public CatapultDetector(
  44. final ICatapultDetectorListener pCatapultDetectorListener) {
  45. this(TRIGGER_SCROLL_MINIMUM_DISTANCE_DEFAULT, pCatapultDetectorListener);
  46. }
  47.  
  48. public CatapultDetector(final float pTriggerScrollMinimumDistance,
  49. final ICatapultDetectorListener pCatapultDetectorListener) {
  50. this.setTriggerScrollMinimumDistance(pTriggerScrollMinimumDistance);
  51. this.mCatapultDetectorListener = pCatapultDetectorListener;
  52. }
  53.  
  54.  
  55.  
  56. // ===========================================================
  57. // Methods for/from SuperClass/Interfaces
  58. // =========================================================== 
  59.  
  60. @Override
  61. protected boolean onManagedTouchEvent(TouchEvent pSceneTouchEvent) {
  62. final float touchX = this.getX(pSceneTouchEvent);
  63. final float touchY = this.getY(pSceneTouchEvent);
  64.  
  65. switch (pSceneTouchEvent.getAction()) {
  66. case MotionEvent.ACTION_DOWN:
  67. this.mFirstX = touchX;
  68. this.mFirstY = touchY;
  69. this.mLastX = touchX;
  70. this.mLastY = touchY;
  71. this.mTriggered = false;
  72. return true;
  73. case MotionEvent.ACTION_MOVE:
  74. case MotionEvent.ACTION_UP:
  75. case MotionEvent.ACTION_CANCEL:
  76. final float distanceX = touchX - this.mLastX;
  77. final float distanceY = touchY - this.mLastY;
  78. if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_MOVE) {
  79. final float triggerScrollMinimumDistance = this.mTriggerScrollMinimumDistance;
  80. if (this.mTriggered
  81. || Math.abs(distanceX) > triggerScrollMinimumDistance
  82. || Math.abs(distanceY) > triggerScrollMinimumDistance) {
  83. final float distance = (float)Math.hypot((double)distanceX,(double)distanceY);     
  84. final double angleX = touchX - this.mFirstX;
  85. final double angleY = touchY - this.mFirstY;
  86. final float angle = (float)Math.toDegrees(Math.atan2(angleY, angleX));
  87. this.mCatapultDetectorListener.onCharge(this, pSceneTouchEvent, distance, angle);
  88. this.mLastX = touchX;
  89. this.mLastY = touchY;
  90. this.mTriggered = true;
  91. }
  92. else
  93. {
  94.  
  95.  
  96. }
  97. }
  98. return true;
  99. default:
  100. return false;
  101. }
  102. }
  103.  
  104. // ===========================================================
  105. // Getter & Setter
  106. // ===========================================================
  107.  
  108. public void setTriggerScrollMinimumDistance(
  109. float mTriggerScrollMinimumDistance) {
  110. this.mTriggerScrollMinimumDistance = mTriggerScrollMinimumDistance;
  111. }
  112.  
  113. public float getTriggerScrollMinimumDistance() {
  114. return mTriggerScrollMinimumDistance;
  115. }
  116.  
  117. // ===========================================================
  118. // Methods
  119. // ===========================================================
  120.  
  121. protected float getX(final TouchEvent pTouchEvent) {
  122. return pTouchEvent.getX();
  123. }
  124.  
  125. protected float getY(final TouchEvent pTouchEvent) {
  126. return pTouchEvent.getY();
  127. } 
  128.  
  129.  
  130. // ===========================================================
  131. // Inner and Anonymous Classes
  132. // =========================================================== 
  133.  
  134. public static interface ICatapultDetectorListener {
  135. // ===========================================================
  136. // Constants
  137. // ===========================================================
  138.  
  139. // ===========================================================
  140. // Methods
  141. // ===========================================================
  142.  
  143. public void onCharge(final CatapultDetector pCatapultDetector,
  144. final TouchEvent pTouchEvent, final float pDistance,
  145. final float pAngle);
  146.  
  147. public void onShoot(final CatapultDetector pCatapultDetector,
  148. final TouchEvent pTouchEvent, final float pDistance,
  149. final float pAngle);
  150. }
  151.  
  152. }
  153.  
Parsed in 0.126 seconds at 38.60 KB/s, using GeSHi 1.0.8.10

Good work today. I'm working on this Detector. The next chapter in the same subject.



Source Code

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


Index