• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.apis.view;
18 
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.graphics.Paint;
22 import android.graphics.Path;
23 import android.graphics.Paint.Style;
24 import android.os.Handler;
25 import android.os.SystemClock;
26 import android.util.AttributeSet;
27 import android.view.InputDevice;
28 import android.view.KeyEvent;
29 import android.view.MotionEvent;
30 import android.view.View;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Random;
35 
36 /**
37  * A trivial joystick based physics game to demonstrate joystick handling.
38  *
39  * @see GameControllerInput
40  */
41 public class GameView extends View {
42     private final long ANIMATION_TIME_STEP = 1000 / 60;
43     private final int MAX_OBSTACLES = 12;
44 
45     private final Random mRandom;
46     private Ship mShip;
47     private final List<Bullet> mBullets;
48     private final List<Obstacle> mObstacles;
49 
50     private long mLastStepTime;
51     private InputDevice mLastInputDevice;
52 
53     private static final int DPAD_STATE_LEFT  = 1 << 0;
54     private static final int DPAD_STATE_RIGHT = 1 << 1;
55     private static final int DPAD_STATE_UP    = 1 << 2;
56     private static final int DPAD_STATE_DOWN  = 1 << 3;
57 
58     private int mDPadState;
59 
60     private float mShipSize;
61     private float mMaxShipThrust;
62     private float mMaxShipSpeed;
63 
64     private float mBulletSize;
65     private float mBulletSpeed;
66 
67     private float mMinObstacleSize;
68     private float mMaxObstacleSize;
69     private float mMinObstacleSpeed;
70     private float mMaxObstacleSpeed;
71 
72     private final Runnable mAnimationRunnable = new Runnable() {
73         public void run() {
74             animateFrame();
75         }
76     };
77 
GameView(Context context, AttributeSet attrs)78     public GameView(Context context, AttributeSet attrs) {
79         super(context, attrs);
80 
81         mRandom = new Random();
82         mBullets = new ArrayList<Bullet>();
83         mObstacles = new ArrayList<Obstacle>();
84 
85         setFocusable(true);
86         setFocusableInTouchMode(true);
87 
88         float baseSize = getContext().getResources().getDisplayMetrics().density * 5f;
89         float baseSpeed = baseSize * 3;
90 
91         mShipSize = baseSize * 3;
92         mMaxShipThrust = baseSpeed * 0.25f;
93         mMaxShipSpeed = baseSpeed * 12;
94 
95         mBulletSize = baseSize;
96         mBulletSpeed = baseSpeed * 12;
97 
98         mMinObstacleSize = baseSize * 2;
99         mMaxObstacleSize = baseSize * 12;
100         mMinObstacleSpeed = baseSpeed;
101         mMaxObstacleSpeed = baseSpeed * 3;
102     }
103 
104     @Override
onSizeChanged(int w, int h, int oldw, int oldh)105     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
106         super.onSizeChanged(w, h, oldw, oldh);
107 
108         // Reset the game when the view changes size.
109         reset();
110     }
111 
112     @Override
onKeyDown(int keyCode, KeyEvent event)113     public boolean onKeyDown(int keyCode, KeyEvent event) {
114         ensureInitialized();
115 
116         // Handle DPad keys and fire button on initial down but not on auto-repeat.
117         boolean handled = false;
118         if (event.getRepeatCount() == 0) {
119             switch (keyCode) {
120                 case KeyEvent.KEYCODE_DPAD_LEFT:
121                     mShip.setHeadingX(-1);
122                     mDPadState |= DPAD_STATE_LEFT;
123                     handled = true;
124                     break;
125                 case KeyEvent.KEYCODE_DPAD_RIGHT:
126                     mShip.setHeadingX(1);
127                     mDPadState |= DPAD_STATE_RIGHT;
128                     handled = true;
129                     break;
130                 case KeyEvent.KEYCODE_DPAD_UP:
131                     mShip.setHeadingY(-1);
132                     mDPadState |= DPAD_STATE_UP;
133                     handled = true;
134                     break;
135                 case KeyEvent.KEYCODE_DPAD_DOWN:
136                     mShip.setHeadingY(1);
137                     mDPadState |= DPAD_STATE_DOWN;
138                     handled = true;
139                     break;
140                 default:
141                     if (isFireKey(keyCode)) {
142                         fire();
143                         handled = true;
144                     }
145                     break;
146             }
147         }
148         if (handled) {
149             step(event.getEventTime());
150             return true;
151         }
152         return super.onKeyDown(keyCode, event);
153     }
154 
155     @Override
onKeyUp(int keyCode, KeyEvent event)156     public boolean onKeyUp(int keyCode, KeyEvent event) {
157         ensureInitialized();
158 
159         // Handle keys going up.
160         boolean handled = false;
161         switch (keyCode) {
162             case KeyEvent.KEYCODE_DPAD_LEFT:
163                 mShip.setHeadingX(0);
164                 mDPadState &= ~DPAD_STATE_LEFT;
165                 handled = true;
166                 break;
167             case KeyEvent.KEYCODE_DPAD_RIGHT:
168                 mShip.setHeadingX(0);
169                 mDPadState &= ~DPAD_STATE_RIGHT;
170                 handled = true;
171                 break;
172             case KeyEvent.KEYCODE_DPAD_UP:
173                 mShip.setHeadingY(0);
174                 mDPadState &= ~DPAD_STATE_UP;
175                 handled = true;
176                 break;
177             case KeyEvent.KEYCODE_DPAD_DOWN:
178                 mShip.setHeadingY(0);
179                 mDPadState &= ~DPAD_STATE_DOWN;
180                 handled = true;
181                 break;
182             default:
183                 if (isFireKey(keyCode)) {
184                     handled = true;
185                 }
186                 break;
187         }
188         if (handled) {
189             step(event.getEventTime());
190             return true;
191         }
192         return super.onKeyUp(keyCode, event);
193     }
194 
isFireKey(int keyCode)195     private static boolean isFireKey(int keyCode) {
196         return KeyEvent.isGamepadButton(keyCode)
197                 || keyCode == KeyEvent.KEYCODE_DPAD_CENTER
198                 || keyCode == KeyEvent.KEYCODE_SPACE;
199     }
200 
201     @Override
onGenericMotionEvent(MotionEvent event)202     public boolean onGenericMotionEvent(MotionEvent event) {
203         ensureInitialized();
204 
205         // Check that the event came from a joystick since a generic motion event
206         // could be almost anything.
207         if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0
208                 && event.getAction() == MotionEvent.ACTION_MOVE) {
209             // Cache the most recently obtained device information.
210             // The device information may change over time but it can be
211             // somewhat expensive to query.
212             if (mLastInputDevice == null || mLastInputDevice.getId() != event.getDeviceId()) {
213                 mLastInputDevice = event.getDevice();
214                 // It's possible for the device id to be invalid.
215                 // In that case, getDevice() will return null.
216                 if (mLastInputDevice == null) {
217                     return false;
218                 }
219             }
220 
221             // Ignore joystick while the DPad is pressed to avoid conflicting motions.
222             if (mDPadState != 0) {
223                 return true;
224             }
225 
226             // Process all historical movement samples in the batch.
227             final int historySize = event.getHistorySize();
228             for (int i = 0; i < historySize; i++) {
229                 processJoystickInput(event, i);
230             }
231 
232             // Process the current movement sample in the batch.
233             processJoystickInput(event, -1);
234             return true;
235         }
236         return super.onGenericMotionEvent(event);
237     }
238 
processJoystickInput(MotionEvent event, int historyPos)239     private void processJoystickInput(MotionEvent event, int historyPos) {
240         // Get joystick position.
241         // Many game pads with two joysticks report the position of the second joystick
242         // using the Z and RZ axes so we also handle those.
243         // In a real game, we would allow the user to configure the axes manually.
244         float x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_X, historyPos);
245         if (x == 0) {
246             x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_HAT_X, historyPos);
247         }
248         if (x == 0) {
249             x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_Z, historyPos);
250         }
251 
252         float y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_Y, historyPos);
253         if (y == 0) {
254             y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_HAT_Y, historyPos);
255         }
256         if (y == 0) {
257             y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_RZ, historyPos);
258         }
259 
260         // Set the ship heading.
261         mShip.setHeading(x, y);
262         step(historyPos < 0 ? event.getEventTime() : event.getHistoricalEventTime(historyPos));
263     }
264 
265     private static float getCenteredAxis(MotionEvent event, InputDevice device,
266             int axis, int historyPos) {
267         final InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource());
268         if (range != null) {
269             final float flat = range.getFlat();
270             final float value = historyPos < 0 ? event.getAxisValue(axis)
271                     : event.getHistoricalAxisValue(axis, historyPos);
272 
273             // Ignore axis values that are within the 'flat' region of the joystick axis center.
274             // A joystick at rest does not always report an absolute position of (0,0).
275             if (Math.abs(value) > flat) {
276                 return value;
277             }
278         }
279         return 0;
280     }
281 
282     @Override
283     public void onWindowFocusChanged(boolean hasWindowFocus) {
284         // Turn on and off animations based on the window focus.
285         // Alternately, we could update the game state using the Activity onResume()
286         // and onPause() lifecycle events.
287         if (hasWindowFocus) {
288             getHandler().postDelayed(mAnimationRunnable, ANIMATION_TIME_STEP);
289             mLastStepTime = SystemClock.uptimeMillis();
290         } else {
291             getHandler().removeCallbacks(mAnimationRunnable);
292 
293             mDPadState = 0;
294             if (mShip != null) {
295                 mShip.setHeading(0, 0);
296                 mShip.setVelocity(0, 0);
297             }
298         }
299 
300         super.onWindowFocusChanged(hasWindowFocus);
301     }
302 
303     private void fire() {
304         if (mShip != null && !mShip.isDestroyed()) {
305             Bullet bullet = new Bullet();
306             bullet.setPosition(mShip.getBulletInitialX(), mShip.getBulletInitialY());
307             bullet.setVelocity(mShip.getBulletVelocityX(mBulletSpeed),
308                     mShip.getBulletVelocityY(mBulletSpeed));
309             mBullets.add(bullet);
310         }
311     }
312 
313     private void ensureInitialized() {
314         if (mShip == null) {
315             reset();
316         }
317     }
318 
319     private void reset() {
320         mShip = new Ship();
321         mBullets.clear();
322         mObstacles.clear();
323     }
324 
325     void animateFrame() {
326         long currentStepTime = SystemClock.uptimeMillis();
327         step(currentStepTime);
328 
329         Handler handler = getHandler();
330         if (handler != null) {
331             handler.postAtTime(mAnimationRunnable, currentStepTime + ANIMATION_TIME_STEP);
332             invalidate();
333         }
334     }
335 
336     private void step(long currentStepTime) {
337         float tau = (currentStepTime - mLastStepTime) * 0.001f;
338         mLastStepTime = currentStepTime;
339 
340         ensureInitialized();
341 
342         // Move the ship.
343         mShip.accelerate(tau, mMaxShipThrust, mMaxShipSpeed);
344         if (!mShip.step(tau)) {
345             reset();
346         }
347 
348         // Move the bullets.
349         int numBullets = mBullets.size();
350         for (int i = 0; i < numBullets; i++) {
351             final Bullet bullet = mBullets.get(i);
352             if (!bullet.step(tau)) {
353                 mBullets.remove(i);
354                 i -= 1;
355                 numBullets -= 1;
356             }
357         }
358 
359         // Move obstacles.
360         int numObstacles = mObstacles.size();
361         for (int i = 0; i < numObstacles; i++) {
362             final Obstacle obstacle = mObstacles.get(i);
363             if (!obstacle.step(tau)) {
364                 mObstacles.remove(i);
365                 i -= 1;
366                 numObstacles -= 1;
367             }
368         }
369 
370         // Check for collisions between bullets and obstacles.
371         for (int i = 0; i < numBullets; i++) {
372             final Bullet bullet = mBullets.get(i);
373             for (int j = 0; j < numObstacles; j++) {
374                 final Obstacle obstacle = mObstacles.get(j);
375                 if (bullet.collidesWith(obstacle)) {
376                     bullet.destroy();
377                     obstacle.destroy();
378                     break;
379                 }
380             }
381         }
382 
383         // Check for collisions between the ship and obstacles.
384         for (int i = 0; i < numObstacles; i++) {
385             final Obstacle obstacle = mObstacles.get(i);
386             if (mShip.collidesWith(obstacle)) {
387                 mShip.destroy();
388                 obstacle.destroy();
389                 break;
390             }
391         }
392 
393         // Spawn more obstacles offscreen when needed.
394         // Avoid putting them right on top of the ship.
395         OuterLoop: while (mObstacles.size() < MAX_OBSTACLES) {
396             final float minDistance = mShipSize * 4;
397             float size = mRandom.nextFloat() * (mMaxObstacleSize - mMinObstacleSize)
398                     + mMinObstacleSize;
399             float positionX, positionY;
400             int tries = 0;
401             do {
402                 int edge = mRandom.nextInt(4);
403                 switch (edge) {
404                     case 0:
405                         positionX = -size;
406                         positionY = mRandom.nextInt(getHeight());
407                         break;
408                     case 1:
409                         positionX = getWidth() + size;
410                         positionY = mRandom.nextInt(getHeight());
411                         break;
412                     case 2:
413                         positionX = mRandom.nextInt(getWidth());
414                         positionY = -size;
415                         break;
416                     default:
417                         positionX = mRandom.nextInt(getWidth());
418                         positionY = getHeight() + size;
419                         break;
420                 }
421                 if (++tries > 10) {
422                     break OuterLoop;
423                 }
424             } while (mShip.distanceTo(positionX, positionY) < minDistance);
425 
426             float direction = mRandom.nextFloat() * (float) Math.PI * 2;
427             float speed = mRandom.nextFloat() * (mMaxObstacleSpeed - mMinObstacleSpeed)
428                     + mMinObstacleSpeed;
429             float velocityX = (float) Math.cos(direction) * speed;
430             float velocityY = (float) Math.sin(direction) * speed;
431 
432             Obstacle obstacle = new Obstacle();
433             obstacle.setPosition(positionX, positionY);
434             obstacle.setSize(size);
435             obstacle.setVelocity(velocityX, velocityY);
436             mObstacles.add(obstacle);
437         }
438     }
439 
440     @Override
441     protected void onDraw(Canvas canvas) {
442         super.onDraw(canvas);
443 
444         // Draw the ship.
445         if (mShip != null) {
446             mShip.draw(canvas);
447         }
448 
449         // Draw bullets.
450         int numBullets = mBullets.size();
451         for (int i = 0; i < numBullets; i++) {
452             final Bullet bullet = mBullets.get(i);
453             bullet.draw(canvas);
454         }
455 
456         // Draw obstacles.
457         int numObstacles = mObstacles.size();
458         for (int i = 0; i < numObstacles; i++) {
459             final Obstacle obstacle = mObstacles.get(i);
460             obstacle.draw(canvas);
461         }
462     }
463 
464     static float pythag(float x, float y) {
465         return (float) Math.sqrt(x * x + y * y);
466     }
467 
468     static int blend(float alpha, int from, int to) {
469         return from + (int) ((to - from) * alpha);
470     }
471 
472     static void setPaintARGBBlend(Paint paint, float alpha,
473             int a1, int r1, int g1, int b1,
474             int a2, int r2, int g2, int b2) {
475         paint.setARGB(blend(alpha, a1, a2), blend(alpha, r1, r2),
476                 blend(alpha, g1, g2), blend(alpha, b1, b2));
477     }
478 
479     private abstract class Sprite {
480         protected float mPositionX;
481         protected float mPositionY;
482         protected float mVelocityX;
483         protected float mVelocityY;
484         protected float mSize;
485         protected boolean mDestroyed;
486         protected float mDestroyAnimProgress;
487 
488         public void setPosition(float x, float y) {
489             mPositionX = x;
490             mPositionY = y;
491         }
492 
493         public void setVelocity(float x, float y) {
494             mVelocityX = x;
495             mVelocityY = y;
496         }
497 
498         public void setSize(float size) {
499             mSize = size;
500         }
501 
502         public float distanceTo(float x, float y) {
503             return pythag(mPositionX - x, mPositionY - y);
504         }
505 
506         public float distanceTo(Sprite other) {
507             return distanceTo(other.mPositionX, other.mPositionY);
508         }
509 
510         public boolean collidesWith(Sprite other) {
511             // Really bad collision detection.
512             return !mDestroyed && !other.mDestroyed
513                     && distanceTo(other) <= Math.max(mSize, other.mSize)
514                             + Math.min(mSize, other.mSize) * 0.5f;
515         }
516 
517         public boolean isDestroyed() {
518             return mDestroyed;
519         }
520 
521         public boolean step(float tau) {
522             mPositionX += mVelocityX * tau;
523             mPositionY += mVelocityY * tau;
524 
525             if (mDestroyed) {
526                 mDestroyAnimProgress += tau / getDestroyAnimDuration();
527                 if (mDestroyAnimProgress >= 1.0f) {
528                     return false;
529                 }
530             }
531             return true;
532         }
533 
534         public abstract void draw(Canvas canvas);
535 
536         public abstract float getDestroyAnimDuration();
537 
538         protected boolean isOutsidePlayfield() {
539             final int width = GameView.this.getWidth();
540             final int height = GameView.this.getHeight();
541             return mPositionX < 0 || mPositionX >= width
542                     || mPositionY < 0 || mPositionY >= height;
543         }
544 
545         protected void wrapAtPlayfieldBoundary() {
546             final int width = GameView.this.getWidth();
547             final int height = GameView.this.getHeight();
548             while (mPositionX <= -mSize) {
549                 mPositionX += width + mSize * 2;
550             }
551             while (mPositionX >= width + mSize) {
552                 mPositionX -= width + mSize * 2;
553             }
554             while (mPositionY <= -mSize) {
555                 mPositionY += height + mSize * 2;
556             }
557             while (mPositionY >= height + mSize) {
558                 mPositionY -= height + mSize * 2;
559             }
560         }
561 
562         public void destroy() {
563             mDestroyed = true;
564             step(0);
565         }
566     }
567 
568     private class Ship extends Sprite {
569         private static final float CORNER_ANGLE = (float) Math.PI * 2 / 3;
570         private static final float TO_DEGREES = (float) (180.0 / Math.PI);
571 
572         private float mHeadingX;
573         private float mHeadingY;
574         private float mHeadingAngle;
575         private float mHeadingMagnitude;
576         private final Paint mPaint;
577         private final Path mPath;
578 
579 
580         public Ship() {
581             mPaint = new Paint();
582             mPaint.setStyle(Style.FILL);
583 
584             setPosition(getWidth() * 0.5f, getHeight() * 0.5f);
585             setVelocity(0, 0);
586             setSize(mShipSize);
587 
588             mPath = new Path();
589             mPath.moveTo(0, 0);
590             mPath.lineTo((float)Math.cos(-CORNER_ANGLE) * mSize,
591                     (float)Math.sin(-CORNER_ANGLE) * mSize);
592             mPath.lineTo(mSize, 0);
593             mPath.lineTo((float)Math.cos(CORNER_ANGLE) * mSize,
594                     (float)Math.sin(CORNER_ANGLE) * mSize);
595             mPath.lineTo(0, 0);
596         }
597 
598         public void setHeadingX(float x) {
599             mHeadingX = x;
600             updateHeading();
601         }
602 
603         public void setHeadingY(float y) {
604             mHeadingY = y;
605             updateHeading();
606         }
607 
608         public void setHeading(float x, float y) {
609             mHeadingX = x;
610             mHeadingY = y;
611             updateHeading();
612         }
613 
614         private void updateHeading() {
615             mHeadingMagnitude = pythag(mHeadingX, mHeadingY);
616             if (mHeadingMagnitude > 0.1f) {
617                 mHeadingAngle = (float) Math.atan2(mHeadingY, mHeadingX);
618             }
619         }
620 
621         private float polarX(float radius) {
622             return (float) Math.cos(mHeadingAngle) * radius;
623         }
624 
625         private float polarY(float radius) {
626             return (float) Math.sin(mHeadingAngle) * radius;
627         }
628 
629         public float getBulletInitialX() {
630             return mPositionX + polarX(mSize);
631         }
632 
633         public float getBulletInitialY() {
634             return mPositionY + polarY(mSize);
635         }
636 
637         public float getBulletVelocityX(float relativeSpeed) {
638             return mVelocityX + polarX(relativeSpeed);
639         }
640 
641         public float getBulletVelocityY(float relativeSpeed) {
642             return mVelocityY + polarY(relativeSpeed);
643         }
644 
645         public void accelerate(float tau, float maxThrust, float maxSpeed) {
646             final float thrust = mHeadingMagnitude * maxThrust;
647             mVelocityX += polarX(thrust);
648             mVelocityY += polarY(thrust);
649 
650             final float speed = pythag(mVelocityX, mVelocityY);
651             if (speed > maxSpeed) {
652                 final float scale = maxSpeed / speed;
653                 mVelocityX = mVelocityX * scale;
654                 mVelocityY = mVelocityY * scale;
655             }
656         }
657 
658         @Override
659         public boolean step(float tau) {
660             if (!super.step(tau)) {
661                 return false;
662             }
663             wrapAtPlayfieldBoundary();
664             return true;
665         }
666 
667         public void draw(Canvas canvas) {
668             setPaintARGBBlend(mPaint, mDestroyAnimProgress,
669                     255, 63, 255, 63,
670                     0, 255, 0, 0);
671 
672             canvas.save(Canvas.MATRIX_SAVE_FLAG);
673             canvas.translate(mPositionX, mPositionY);
674             canvas.rotate(mHeadingAngle * TO_DEGREES);
675             canvas.drawPath(mPath, mPaint);
676             canvas.restore();
677         }
678 
679         @Override
680         public float getDestroyAnimDuration() {
681             return 1.0f;
682         }
683     }
684 
685     private class Bullet extends Sprite {
686         private final Paint mPaint;
687 
688         public Bullet() {
689             mPaint = new Paint();
690             mPaint.setStyle(Style.FILL);
691 
692             setSize(mBulletSize);
693         }
694 
695         @Override
696         public boolean step(float tau) {
697             if (!super.step(tau)) {
698                 return false;
699             }
700             return !isOutsidePlayfield();
701         }
702 
703         public void draw(Canvas canvas) {
704             setPaintARGBBlend(mPaint, mDestroyAnimProgress,
705                     255, 255, 255, 0,
706                     0, 255, 255, 255);
707             canvas.drawCircle(mPositionX, mPositionY, mSize, mPaint);
708         }
709 
710         @Override
711         public float getDestroyAnimDuration() {
712             return 0.125f;
713         }
714     }
715 
716     private class Obstacle extends Sprite {
717         private final Paint mPaint;
718 
719         public Obstacle() {
720             mPaint = new Paint();
721             mPaint.setARGB(255, 127, 127, 255);
722             mPaint.setStyle(Style.FILL);
723         }
724 
725         @Override
726         public boolean step(float tau) {
727             if (!super.step(tau)) {
728                 return false;
729             }
730             wrapAtPlayfieldBoundary();
731             return true;
732         }
733 
734         public void draw(Canvas canvas) {
735             setPaintARGBBlend(mPaint, mDestroyAnimProgress,
736                     255, 127, 127, 255,
737                     0, 255, 0, 0);
738             canvas.drawCircle(mPositionX, mPositionY,
739                     mSize * (1.0f - mDestroyAnimProgress), mPaint);
740         }
741 
742         @Override
743         public float getDestroyAnimDuration() {
744             return 0.25f;
745         }
746     }
747 }