1 /* 2 * Copyright (C) 2014 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.android.systemui.egg; 18 19 import android.animation.TimeAnimator; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.Matrix; 25 import android.graphics.Outline; 26 import android.graphics.Paint; 27 import android.graphics.Path; 28 import android.graphics.PorterDuff; 29 import android.graphics.Rect; 30 import android.graphics.drawable.Drawable; 31 import android.graphics.drawable.GradientDrawable; 32 import android.util.AttributeSet; 33 import android.util.Log; 34 import android.view.Gravity; 35 import android.view.KeyEvent; 36 import android.view.MotionEvent; 37 import android.view.View; 38 import android.view.ViewOutlineProvider; 39 import android.view.animation.DecelerateInterpolator; 40 import android.widget.FrameLayout; 41 import android.widget.ImageView; 42 import android.widget.TextView; 43 44 import com.android.systemui.R; 45 46 import java.util.ArrayList; 47 48 public class LLand extends FrameLayout { 49 public static final String TAG = "LLand"; 50 51 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 52 public static final boolean DEBUG_DRAW = false; // DEBUG 53 54 public static final void L(String s, Object ... objects) { 55 if (DEBUG) { 56 Log.d(TAG, String.format(s, objects)); 57 } 58 } 59 60 public static final boolean AUTOSTART = true; 61 public static final boolean HAVE_STARS = true; 62 63 public static final float DEBUG_SPEED_MULTIPLIER = 1f; // 0.1f; 64 public static final boolean DEBUG_IDDQD = false; 65 66 final static int[] POPS = { 67 // resid // spinny! 68 R.drawable.pop_belt, 0, 69 R.drawable.pop_droid, 0, 70 R.drawable.pop_pizza, 1, 71 R.drawable.pop_stripes, 0, 72 R.drawable.pop_swirl, 1, 73 R.drawable.pop_vortex, 1, 74 R.drawable.pop_vortex2, 1, 75 }; 76 77 private static class Params { 78 public float TRANSLATION_PER_SEC; 79 public int OBSTACLE_SPACING, OBSTACLE_PERIOD; 80 public int BOOST_DV; 81 public int PLAYER_HIT_SIZE; 82 public int PLAYER_SIZE; 83 public int OBSTACLE_WIDTH, OBSTACLE_STEM_WIDTH; 84 public int OBSTACLE_GAP; 85 public int OBSTACLE_MIN; 86 public int BUILDING_WIDTH_MIN, BUILDING_WIDTH_MAX; 87 public int BUILDING_HEIGHT_MIN; 88 public int CLOUD_SIZE_MIN, CLOUD_SIZE_MAX; 89 public int STAR_SIZE_MIN, STAR_SIZE_MAX; 90 public int G; 91 public int MAX_V; 92 public float SCENERY_Z, OBSTACLE_Z, PLAYER_Z, PLAYER_Z_BOOST, HUD_Z; 93 public Params(Resources res) { 94 TRANSLATION_PER_SEC = res.getDimension(R.dimen.translation_per_sec); 95 OBSTACLE_SPACING = res.getDimensionPixelSize(R.dimen.obstacle_spacing); 96 OBSTACLE_PERIOD = (int) (OBSTACLE_SPACING / TRANSLATION_PER_SEC); 97 BOOST_DV = res.getDimensionPixelSize(R.dimen.boost_dv); 98 PLAYER_HIT_SIZE = res.getDimensionPixelSize(R.dimen.player_hit_size); 99 PLAYER_SIZE = res.getDimensionPixelSize(R.dimen.player_size); 100 OBSTACLE_WIDTH = res.getDimensionPixelSize(R.dimen.obstacle_width); 101 OBSTACLE_STEM_WIDTH = res.getDimensionPixelSize(R.dimen.obstacle_stem_width); 102 OBSTACLE_GAP = res.getDimensionPixelSize(R.dimen.obstacle_gap); 103 OBSTACLE_MIN = res.getDimensionPixelSize(R.dimen.obstacle_height_min); 104 BUILDING_HEIGHT_MIN = res.getDimensionPixelSize(R.dimen.building_height_min); 105 BUILDING_WIDTH_MIN = res.getDimensionPixelSize(R.dimen.building_width_min); 106 BUILDING_WIDTH_MAX = res.getDimensionPixelSize(R.dimen.building_width_max); 107 CLOUD_SIZE_MIN = res.getDimensionPixelSize(R.dimen.cloud_size_min); 108 CLOUD_SIZE_MAX = res.getDimensionPixelSize(R.dimen.cloud_size_max); 109 STAR_SIZE_MIN = res.getDimensionPixelSize(R.dimen.star_size_min); 110 STAR_SIZE_MAX = res.getDimensionPixelSize(R.dimen.star_size_max); 111 112 G = res.getDimensionPixelSize(R.dimen.G); 113 MAX_V = res.getDimensionPixelSize(R.dimen.max_v); 114 115 SCENERY_Z = res.getDimensionPixelSize(R.dimen.scenery_z); 116 OBSTACLE_Z = res.getDimensionPixelSize(R.dimen.obstacle_z); 117 PLAYER_Z = res.getDimensionPixelSize(R.dimen.player_z); 118 PLAYER_Z_BOOST = res.getDimensionPixelSize(R.dimen.player_z_boost); 119 HUD_Z = res.getDimensionPixelSize(R.dimen.hud_z); 120 } 121 } 122 123 private TimeAnimator mAnim; 124 125 private TextView mScoreField; 126 private View mSplash; 127 128 private Player mDroid; 129 private ArrayList<Obstacle> mObstaclesInPlay = new ArrayList<Obstacle>(); 130 131 private float t, dt; 132 133 private int mScore; 134 private float mLastPipeTime; // in sec 135 private int mWidth, mHeight; 136 private boolean mAnimating, mPlaying; 137 private boolean mFrozen; // after death, a short backoff 138 private boolean mFlipped; 139 140 private int mTimeOfDay; 141 private static final int DAY = 0, NIGHT = 1, TWILIGHT = 2, SUNSET = 3; 142 private static final int[][] SKIES = { 143 { 0xFFc0c0FF, 0xFFa0a0FF }, // DAY 144 { 0xFF000010, 0xFF000000 }, // NIGHT 145 { 0xFF000040, 0xFF000010 }, // TWILIGHT 146 { 0xFFa08020, 0xFF204080 }, // SUNSET 147 }; 148 149 private static Params PARAMS; 150 151 public LLand(Context context) { 152 this(context, null); 153 } 154 155 public LLand(Context context, AttributeSet attrs) { 156 this(context, attrs, 0); 157 } 158 159 public LLand(Context context, AttributeSet attrs, int defStyle) { 160 super(context, attrs, defStyle); 161 setFocusable(true); 162 PARAMS = new Params(getResources()); 163 mTimeOfDay = irand(0, SKIES.length); 164 } 165 166 @Override 167 public boolean willNotDraw() { 168 return !DEBUG; 169 } 170 171 public int getGameWidth() { return mWidth; } 172 public int getGameHeight() { return mHeight; } 173 public float getGameTime() { return t; } 174 public float getLastTimeStep() { return dt; } 175 176 public void setScoreField(TextView tv) { 177 mScoreField = tv; 178 if (tv != null) { 179 tv.setTranslationZ(PARAMS.HUD_Z); 180 if (!(mAnimating && mPlaying)) { 181 tv.setTranslationY(-500); 182 } 183 } 184 } 185 186 public void setSplash(View v) { 187 mSplash = v; 188 } 189 190 @Override 191 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 192 stop(); 193 reset(); 194 if (AUTOSTART) { 195 start(false); 196 } 197 } 198 199 final float hsv[] = {0, 0, 0}; 200 201 private void reset() { 202 L("reset"); 203 final Drawable sky = new GradientDrawable( 204 GradientDrawable.Orientation.BOTTOM_TOP, 205 SKIES[mTimeOfDay] 206 ); 207 sky.setDither(true); 208 setBackground(sky); 209 210 mFlipped = frand() > 0.5f; 211 setScaleX(mFlipped ? -1 : 1); 212 213 setScore(0); 214 215 int i = getChildCount(); 216 while (i-->0) { 217 final View v = getChildAt(i); 218 if (v instanceof GameView) { 219 removeViewAt(i); 220 } 221 } 222 223 mObstaclesInPlay.clear(); 224 225 mWidth = getWidth(); 226 mHeight = getHeight(); 227 228 boolean showingSun = (mTimeOfDay == DAY || mTimeOfDay == SUNSET) && frand() > 0.25; 229 if (showingSun) { 230 final Star sun = new Star(getContext()); 231 sun.setBackgroundResource(R.drawable.sun); 232 final int w = getResources().getDimensionPixelSize(R.dimen.sun_size); 233 sun.setTranslationX(frand(w, mWidth-w)); 234 if (mTimeOfDay == DAY) { 235 sun.setTranslationY(frand(w, (mHeight * 0.66f))); 236 sun.getBackground().setTint(0); 237 } else { 238 sun.setTranslationY(frand(mHeight * 0.66f, mHeight - w)); 239 sun.getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP); 240 sun.getBackground().setTint(0xC0FF8000); 241 242 } 243 addView(sun, new LayoutParams(w, w)); 244 } 245 if (!showingSun) { 246 final boolean dark = mTimeOfDay == NIGHT || mTimeOfDay == TWILIGHT; 247 final float ff = frand(); 248 if ((dark && ff < 0.75f) || ff < 0.5f) { 249 final Star moon = new Star(getContext()); 250 moon.setBackgroundResource(R.drawable.moon); 251 moon.getBackground().setAlpha(dark ? 255 : 128); 252 moon.setScaleX(frand() > 0.5 ? -1 : 1); 253 moon.setRotation(moon.getScaleX() * frand(5, 30)); 254 final int w = getResources().getDimensionPixelSize(R.dimen.sun_size); 255 moon.setTranslationX(frand(w, mWidth - w)); 256 moon.setTranslationY(frand(w, mHeight - w)); 257 addView(moon, new LayoutParams(w, w)); 258 } 259 } 260 261 final int mh = mHeight / 6; 262 final boolean cloudless = frand() < 0.25; 263 final int N = 20; 264 for (i=0; i<N; i++) { 265 final float r1 = frand(); 266 final Scenery s; 267 if (HAVE_STARS && r1 < 0.3 && mTimeOfDay != DAY) { 268 s = new Star(getContext()); 269 } else if (r1 < 0.6 && !cloudless) { 270 s = new Cloud(getContext()); 271 } else { 272 s = new Building(getContext()); 273 274 s.z = (float)i/N; 275 s.setTranslationZ(PARAMS.SCENERY_Z * (1+s.z)); 276 s.v = 0.85f * s.z; // buildings move proportional to their distance 277 hsv[0] = 175; 278 hsv[1] = 0.25f; 279 hsv[2] = 1 * s.z; 280 s.setBackgroundColor(Color.HSVToColor(hsv)); 281 s.h = irand(PARAMS.BUILDING_HEIGHT_MIN, mh); 282 } 283 final LayoutParams lp = new LayoutParams(s.w, s.h); 284 if (s instanceof Building) { 285 lp.gravity = Gravity.BOTTOM; 286 } else { 287 lp.gravity = Gravity.TOP; 288 final float r = frand(); 289 if (s instanceof Star) { 290 lp.topMargin = (int) (r * r * mHeight); 291 } else { 292 lp.topMargin = (int) (1 - r*r * mHeight/2) + mHeight/2; 293 } 294 } 295 296 addView(s, lp); 297 s.setTranslationX(frand(-lp.width, mWidth + lp.width)); 298 } 299 300 mDroid = new Player(getContext()); 301 mDroid.setX(mWidth / 2); 302 mDroid.setY(mHeight / 2); 303 addView(mDroid, new LayoutParams(PARAMS.PLAYER_SIZE, PARAMS.PLAYER_SIZE)); 304 305 mAnim = new TimeAnimator(); 306 mAnim.setTimeListener(new TimeAnimator.TimeListener() { 307 @Override 308 public void onTimeUpdate(TimeAnimator timeAnimator, long t, long dt) { 309 step(t, dt); 310 } 311 }); 312 } 313 314 private void setScore(int score) { 315 mScore = score; 316 if (mScoreField != null) mScoreField.setText(String.valueOf(score)); 317 } 318 319 private void addScore(int incr) { 320 setScore(mScore + incr); 321 } 322 323 private void start(boolean startPlaying) { 324 L("start(startPlaying=%s)", startPlaying?"true":"false"); 325 if (startPlaying) { 326 mPlaying = true; 327 328 t = 0; 329 // there's a sucker born every OBSTACLE_PERIOD 330 mLastPipeTime = getGameTime() - PARAMS.OBSTACLE_PERIOD; 331 332 if (mSplash != null && mSplash.getAlpha() > 0f) { 333 mSplash.setTranslationZ(PARAMS.HUD_Z); 334 mSplash.animate().alpha(0).translationZ(0).setDuration(400); 335 336 mScoreField.animate().translationY(0) 337 .setInterpolator(new DecelerateInterpolator()) 338 .setDuration(1500); 339 } 340 341 mScoreField.setTextColor(0xFFAAAAAA); 342 mScoreField.setBackgroundResource(R.drawable.scorecard); 343 mDroid.setVisibility(View.VISIBLE); 344 mDroid.setX(mWidth / 2); 345 mDroid.setY(mHeight / 2); 346 } else { 347 mDroid.setVisibility(View.GONE); 348 } 349 if (!mAnimating) { 350 mAnim.start(); 351 mAnimating = true; 352 } 353 } 354 355 private void stop() { 356 if (mAnimating) { 357 mAnim.cancel(); 358 mAnim = null; 359 mAnimating = false; 360 mScoreField.setTextColor(0xFFFFFFFF); 361 mScoreField.setBackgroundResource(R.drawable.scorecard_gameover); 362 mTimeOfDay = irand(0, SKIES.length); // for next reset 363 mFrozen = true; 364 postDelayed(new Runnable() { 365 @Override 366 public void run() { 367 mFrozen = false; 368 } 369 }, 250); 370 } 371 } 372 373 public static final float lerp(float x, float a, float b) { 374 return (b - a) * x + a; 375 } 376 377 public static final float rlerp(float v, float a, float b) { 378 return (v - a) / (b - a); 379 } 380 381 public static final float clamp(float f) { 382 return f < 0f ? 0f : f > 1f ? 1f : f; 383 } 384 385 public static final float frand() { 386 return (float) Math.random(); 387 } 388 389 public static final float frand(float a, float b) { 390 return lerp(frand(), a, b); 391 } 392 393 public static final int irand(int a, int b) { 394 return (int) lerp(frand(), (float) a, (float) b); 395 } 396 397 private void step(long t_ms, long dt_ms) { 398 t = t_ms / 1000f; // seconds 399 dt = dt_ms / 1000f; 400 401 if (DEBUG) { 402 t *= DEBUG_SPEED_MULTIPLIER; 403 dt *= DEBUG_SPEED_MULTIPLIER; 404 } 405 406 // 1. Move all objects and update bounds 407 final int N = getChildCount(); 408 int i = 0; 409 for (; i<N; i++) { 410 final View v = getChildAt(i); 411 if (v instanceof GameView) { 412 ((GameView) v).step(t_ms, dt_ms, t, dt); 413 } 414 } 415 416 // 2. Check for altitude 417 if (mPlaying && mDroid.below(mHeight)) { 418 if (DEBUG_IDDQD) { 419 poke(); 420 } else { 421 L("player hit the floor"); 422 stop(); 423 } 424 } 425 426 // 3. Check for obstacles 427 boolean passedBarrier = false; 428 for (int j = mObstaclesInPlay.size(); j-->0;) { 429 final Obstacle ob = mObstaclesInPlay.get(j); 430 if (mPlaying && ob.intersects(mDroid) && !DEBUG_IDDQD) { 431 L("player hit an obstacle"); 432 stop(); 433 } else if (ob.cleared(mDroid)) { 434 if (ob instanceof Stem) passedBarrier = true; 435 mObstaclesInPlay.remove(j); 436 } 437 } 438 439 if (mPlaying && passedBarrier) { 440 addScore(1); 441 } 442 443 // 4. Handle edge of screen 444 // Walk backwards to make sure removal is safe 445 while (i-->0) { 446 final View v = getChildAt(i); 447 if (v instanceof Obstacle) { 448 if (v.getTranslationX() + v.getWidth() < 0) { 449 removeViewAt(i); 450 } 451 } else if (v instanceof Scenery) { 452 final Scenery s = (Scenery) v; 453 if (v.getTranslationX() + s.w < 0) { 454 v.setTranslationX(getWidth()); 455 } 456 } 457 } 458 459 // 3. Time for more obstacles! 460 if (mPlaying && (t - mLastPipeTime) > PARAMS.OBSTACLE_PERIOD) { 461 mLastPipeTime = t; 462 final int obstacley = (int) (Math.random() 463 * (mHeight - 2*PARAMS.OBSTACLE_MIN - PARAMS.OBSTACLE_GAP)) + PARAMS.OBSTACLE_MIN; 464 465 final int inset = (PARAMS.OBSTACLE_WIDTH - PARAMS.OBSTACLE_STEM_WIDTH) / 2; 466 final int yinset = PARAMS.OBSTACLE_WIDTH/2; 467 468 final int d1 = irand(0,250); 469 final Obstacle s1 = new Stem(getContext(), obstacley - yinset, false); 470 addView(s1, new LayoutParams( 471 PARAMS.OBSTACLE_STEM_WIDTH, 472 (int) s1.h, 473 Gravity.TOP|Gravity.LEFT)); 474 s1.setTranslationX(mWidth+inset); 475 s1.setTranslationY(-s1.h-yinset); 476 s1.setTranslationZ(PARAMS.OBSTACLE_Z*0.75f); 477 s1.animate() 478 .translationY(0) 479 .setStartDelay(d1) 480 .setDuration(250); 481 mObstaclesInPlay.add(s1); 482 483 final Obstacle p1 = new Pop(getContext(), PARAMS.OBSTACLE_WIDTH); 484 addView(p1, new LayoutParams( 485 PARAMS.OBSTACLE_WIDTH, 486 PARAMS.OBSTACLE_WIDTH, 487 Gravity.TOP|Gravity.LEFT)); 488 p1.setTranslationX(mWidth); 489 p1.setTranslationY(-PARAMS.OBSTACLE_WIDTH); 490 p1.setTranslationZ(PARAMS.OBSTACLE_Z); 491 p1.setScaleX(0.25f); 492 p1.setScaleY(0.25f); 493 p1.animate() 494 .translationY(s1.h-inset) 495 .scaleX(1f) 496 .scaleY(1f) 497 .setStartDelay(d1) 498 .setDuration(250); 499 mObstaclesInPlay.add(p1); 500 501 final int d2 = irand(0,250); 502 final Obstacle s2 = new Stem(getContext(), 503 mHeight - obstacley - PARAMS.OBSTACLE_GAP - yinset, 504 true); 505 addView(s2, new LayoutParams( 506 PARAMS.OBSTACLE_STEM_WIDTH, 507 (int) s2.h, 508 Gravity.TOP|Gravity.LEFT)); 509 s2.setTranslationX(mWidth+inset); 510 s2.setTranslationY(mHeight+yinset); 511 s2.setTranslationZ(PARAMS.OBSTACLE_Z*0.75f); 512 s2.animate() 513 .translationY(mHeight-s2.h) 514 .setStartDelay(d2) 515 .setDuration(400); 516 mObstaclesInPlay.add(s2); 517 518 final Obstacle p2 = new Pop(getContext(), PARAMS.OBSTACLE_WIDTH); 519 addView(p2, new LayoutParams( 520 PARAMS.OBSTACLE_WIDTH, 521 PARAMS.OBSTACLE_WIDTH, 522 Gravity.TOP|Gravity.LEFT)); 523 p2.setTranslationX(mWidth); 524 p2.setTranslationY(mHeight); 525 p2.setTranslationZ(PARAMS.OBSTACLE_Z); 526 p2.setScaleX(0.25f); 527 p2.setScaleY(0.25f); 528 p2.animate() 529 .translationY(mHeight-s2.h-yinset) 530 .scaleX(1f) 531 .scaleY(1f) 532 .setStartDelay(d2) 533 .setDuration(400); 534 mObstaclesInPlay.add(p2); 535 } 536 537 if (DEBUG_DRAW) invalidate(); 538 } 539 540 @Override 541 public boolean onTouchEvent(MotionEvent ev) { 542 if (DEBUG) L("touch: %s", ev); 543 switch (ev.getAction()) { 544 case MotionEvent.ACTION_DOWN: 545 poke(); 546 return true; 547 case MotionEvent.ACTION_UP: 548 unpoke(); 549 return true; 550 } 551 return false; 552 } 553 554 @Override 555 public boolean onTrackballEvent(MotionEvent ev) { 556 if (DEBUG) L("trackball: %s", ev); 557 switch (ev.getAction()) { 558 case MotionEvent.ACTION_DOWN: 559 poke(); 560 return true; 561 case MotionEvent.ACTION_UP: 562 unpoke(); 563 return true; 564 } 565 return false; 566 } 567 568 @Override 569 public boolean onKeyDown(int keyCode, KeyEvent ev) { 570 if (DEBUG) L("keyDown: %d", keyCode); 571 switch (keyCode) { 572 case KeyEvent.KEYCODE_DPAD_CENTER: 573 case KeyEvent.KEYCODE_DPAD_UP: 574 case KeyEvent.KEYCODE_SPACE: 575 case KeyEvent.KEYCODE_ENTER: 576 case KeyEvent.KEYCODE_BUTTON_A: 577 poke(); 578 return true; 579 } 580 return false; 581 } 582 583 @Override 584 public boolean onKeyUp(int keyCode, KeyEvent ev) { 585 if (DEBUG) L("keyDown: %d", keyCode); 586 switch (keyCode) { 587 case KeyEvent.KEYCODE_DPAD_CENTER: 588 case KeyEvent.KEYCODE_DPAD_UP: 589 case KeyEvent.KEYCODE_SPACE: 590 case KeyEvent.KEYCODE_ENTER: 591 case KeyEvent.KEYCODE_BUTTON_A: 592 unpoke(); 593 return true; 594 } 595 return false; 596 } 597 598 @Override 599 public boolean onGenericMotionEvent (MotionEvent ev) { 600 if (DEBUG) L("generic: %s", ev); 601 return false; 602 } 603 604 private void poke() { 605 L("poke"); 606 if (mFrozen) return; 607 if (!mAnimating) { 608 reset(); 609 start(true); 610 } else if (!mPlaying) { 611 start(true); 612 } 613 mDroid.boost(); 614 if (DEBUG) { 615 mDroid.dv *= DEBUG_SPEED_MULTIPLIER; 616 mDroid.animate().setDuration((long) (200/DEBUG_SPEED_MULTIPLIER)); 617 } 618 } 619 620 private void unpoke() { 621 L("unboost"); 622 if (mFrozen) return; 623 if (!mAnimating) return; 624 mDroid.unboost(); 625 } 626 627 @Override 628 public void onDraw(Canvas c) { 629 super.onDraw(c); 630 631 if (!DEBUG_DRAW) return; 632 633 final Paint pt = new Paint(); 634 pt.setColor(0xFFFFFFFF); 635 final int L = mDroid.corners.length; 636 final int N = L/2; 637 for (int i=0; i<N; i++) { 638 final int x = (int) mDroid.corners[i*2]; 639 final int y = (int) mDroid.corners[i*2+1]; 640 c.drawCircle(x, y, 4, pt); 641 c.drawLine(x, y, 642 mDroid.corners[(i*2+2)%L], 643 mDroid.corners[(i*2+3)%L], 644 pt); 645 } 646 647 pt.setStyle(Paint.Style.STROKE); 648 pt.setStrokeWidth(getResources().getDisplayMetrics().density); 649 650 final int M = getChildCount(); 651 pt.setColor(0x8000FF00); 652 for (int i=0; i<M; i++) { 653 final View v = getChildAt(i); 654 if (v == mDroid) continue; 655 if (!(v instanceof GameView)) continue; 656 if (v instanceof Pop) { 657 final Pop p = (Pop) v; 658 c.drawCircle(p.cx, p.cy, p.r, pt); 659 } else { 660 final Rect r = new Rect(); 661 v.getHitRect(r); 662 c.drawRect(r, pt); 663 } 664 } 665 666 pt.setColor(Color.BLACK); 667 final StringBuilder sb = new StringBuilder("obstacles: "); 668 for (Obstacle ob : mObstaclesInPlay) { 669 sb.append(ob.hitRect.toShortString()); 670 sb.append(" "); 671 } 672 pt.setTextSize(20f); 673 c.drawText(sb.toString(), 20, 100, pt); 674 } 675 676 static final Rect sTmpRect = new Rect(); 677 678 private interface GameView { 679 public void step(long t_ms, long dt_ms, float t, float dt); 680 } 681 682 private class Player extends ImageView implements GameView { 683 public float dv; 684 685 private boolean mBoosting; 686 687 private final float[] sHull = new float[] { 688 0.3f, 0f, // left antenna 689 0.7f, 0f, // right antenna 690 0.92f, 0.33f, // off the right shoulder of Orion 691 0.92f, 0.75f, // right hand (our right, not his right) 692 0.6f, 1f, // right foot 693 0.4f, 1f, // left foot BLUE! 694 0.08f, 0.75f, // sinistram 695 0.08f, 0.33f, // cold shoulder 696 }; 697 public final float[] corners = new float[sHull.length]; 698 699 public Player(Context context) { 700 super(context); 701 702 setBackgroundResource(R.drawable.android); 703 getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP); 704 getBackground().setTint(0xFF00FF00); 705 setOutlineProvider(new ViewOutlineProvider() { 706 @Override 707 public void getOutline(View view, Outline outline) { 708 final int w = view.getWidth(); 709 final int h = view.getHeight(); 710 final int ix = (int) (w * 0.3f); 711 final int iy = (int) (h * 0.2f); 712 outline.setRect(ix, iy, w - ix, h - iy); 713 } 714 }); 715 } 716 717 public void prepareCheckIntersections() { 718 final int inset = (PARAMS.PLAYER_SIZE - PARAMS.PLAYER_HIT_SIZE)/2; 719 final int scale = PARAMS.PLAYER_HIT_SIZE; 720 final int N = sHull.length/2; 721 for (int i=0; i<N; i++) { 722 corners[i*2] = scale * sHull[i*2] + inset; 723 corners[i*2+1] = scale * sHull[i*2+1] + inset; 724 } 725 final Matrix m = getMatrix(); 726 m.mapPoints(corners); 727 } 728 729 public boolean below(int h) { 730 final int N = corners.length/2; 731 for (int i=0; i<N; i++) { 732 final int y = (int) corners[i*2+1]; 733 if (y >= h) return true; 734 } 735 return false; 736 } 737 738 public void step(long t_ms, long dt_ms, float t, float dt) { 739 if (getVisibility() != View.VISIBLE) return; // not playing yet 740 741 if (mBoosting) { 742 dv = -PARAMS.BOOST_DV; 743 } else { 744 dv += PARAMS.G; 745 } 746 if (dv < -PARAMS.MAX_V) dv = -PARAMS.MAX_V; 747 else if (dv > PARAMS.MAX_V) dv = PARAMS.MAX_V; 748 749 final float y = getTranslationY() + dv * dt; 750 setTranslationY(y < 0 ? 0 : y); 751 setRotation( 752 90 + lerp(clamp(rlerp(dv, PARAMS.MAX_V, -1 * PARAMS.MAX_V)), 90, -90)); 753 754 prepareCheckIntersections(); 755 } 756 757 public void boost() { 758 mBoosting = true; 759 dv = -PARAMS.BOOST_DV; 760 761 animate().cancel(); 762 animate() 763 .scaleX(1.25f) 764 .scaleY(1.25f) 765 .translationZ(PARAMS.PLAYER_Z_BOOST) 766 .setDuration(100); 767 setScaleX(1.25f); 768 setScaleY(1.25f); 769 } 770 771 public void unboost() { 772 mBoosting = false; 773 774 animate().cancel(); 775 animate() 776 .scaleX(1f) 777 .scaleY(1f) 778 .translationZ(PARAMS.PLAYER_Z) 779 .setDuration(200); 780 } 781 } 782 783 private class Obstacle extends View implements GameView { 784 public float h; 785 786 public final Rect hitRect = new Rect(); 787 788 public Obstacle(Context context, float h) { 789 super(context); 790 setBackgroundColor(0xFFFF0000); 791 this.h = h; 792 } 793 794 public boolean intersects(Player p) { 795 final int N = p.corners.length/2; 796 for (int i=0; i<N; i++) { 797 final int x = (int) p.corners[i*2]; 798 final int y = (int) p.corners[i*2+1]; 799 if (hitRect.contains(x, y)) return true; 800 } 801 return false; 802 } 803 804 public boolean cleared(Player p) { 805 final int N = p.corners.length/2; 806 for (int i=0; i<N; i++) { 807 final int x = (int) p.corners[i*2]; 808 if (hitRect.right >= x) return false; 809 } 810 return true; 811 } 812 813 @Override 814 public void step(long t_ms, long dt_ms, float t, float dt) { 815 setTranslationX(getTranslationX()-PARAMS.TRANSLATION_PER_SEC*dt); 816 getHitRect(hitRect); 817 } 818 } 819 820 private class Pop extends Obstacle { 821 int mRotate; 822 int cx, cy, r; 823 public Pop(Context context, float h) { 824 super(context, h); 825 int idx = 2*irand(0, POPS.length/2); 826 setBackgroundResource(POPS[idx]); 827 setScaleX(frand() < 0.5f ? -1 : 1); 828 mRotate = POPS[idx+1] == 0 ? 0 : (frand() < 0.5f ? -1 : 1); 829 setOutlineProvider(new ViewOutlineProvider() { 830 @Override 831 public void getOutline(View view, Outline outline) { 832 final int pad = (int) (getWidth() * 0.02f); 833 outline.setOval(pad, pad, getWidth()-pad, getHeight()-pad); 834 } 835 }); 836 } 837 838 public boolean intersects(Player p) { 839 final int N = p.corners.length/2; 840 for (int i=0; i<N; i++) { 841 final int x = (int) p.corners[i*2]; 842 final int y = (int) p.corners[i*2+1]; 843 if (Math.hypot(x-cx, y-cy) <= r) return true; 844 } 845 return false; 846 } 847 848 @Override 849 public void step(long t_ms, long dt_ms, float t, float dt) { 850 super.step(t_ms, dt_ms, t, dt); 851 if (mRotate != 0) { 852 setRotation(getRotation() + dt * 45 * mRotate); 853 } 854 855 cx = (hitRect.left + hitRect.right)/2; 856 cy = (hitRect.top + hitRect.bottom)/2; 857 r = getWidth()/2; 858 } 859 } 860 861 private class Stem extends Obstacle { 862 Paint mPaint = new Paint(); 863 Path mShadow = new Path(); 864 boolean mDrawShadow; 865 866 public Stem(Context context, float h, boolean drawShadow) { 867 super(context, h); 868 mDrawShadow = drawShadow; 869 mPaint.setColor(0xFFAAAAAA); 870 setBackground(null); 871 } 872 873 @Override 874 public void onAttachedToWindow() { 875 super.onAttachedToWindow(); 876 setWillNotDraw(false); 877 setOutlineProvider(new ViewOutlineProvider() { 878 @Override 879 public void getOutline(View view, Outline outline) { 880 outline.setRect(0, 0, getWidth(), getHeight()); 881 } 882 }); 883 } 884 @Override 885 public void onDraw(Canvas c) { 886 final int w = c.getWidth(); 887 final int h = c.getHeight(); 888 final GradientDrawable g = new GradientDrawable(); 889 g.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT); 890 g.setGradientCenter(w * 0.75f, 0); 891 g.setColors(new int[] { 0xFFFFFFFF, 0xFFAAAAAA }); 892 g.setBounds(0, 0, w, h); 893 g.draw(c); 894 if (!mDrawShadow) return; 895 mShadow.reset(); 896 mShadow.moveTo(0,0); 897 mShadow.lineTo(w, 0); 898 mShadow.lineTo(w, PARAMS.OBSTACLE_WIDTH/2+w*1.5f); 899 mShadow.lineTo(0, PARAMS.OBSTACLE_WIDTH/2); 900 mShadow.close(); 901 c.drawPath(mShadow, mPaint); 902 } 903 } 904 905 private class Scenery extends FrameLayout implements GameView { 906 public float z; 907 public float v; 908 public int h, w; 909 public Scenery(Context context) { 910 super(context); 911 } 912 913 @Override 914 public void step(long t_ms, long dt_ms, float t, float dt) { 915 setTranslationX(getTranslationX() - PARAMS.TRANSLATION_PER_SEC * dt * v); 916 } 917 } 918 919 private class Building extends Scenery { 920 public Building(Context context) { 921 super(context); 922 923 w = irand(PARAMS.BUILDING_WIDTH_MIN, PARAMS.BUILDING_WIDTH_MAX); 924 h = 0; // will be setup later, along with z 925 926 setTranslationZ(PARAMS.SCENERY_Z); 927 } 928 } 929 930 private class Cloud extends Scenery { 931 public Cloud(Context context) { 932 super(context); 933 setBackgroundResource(frand() < 0.01f ? R.drawable.cloud_off : R.drawable.cloud); 934 getBackground().setAlpha(0x40); 935 w = h = irand(PARAMS.CLOUD_SIZE_MIN, PARAMS.CLOUD_SIZE_MAX); 936 z = 0; 937 v = frand(0.15f,0.5f); 938 } 939 } 940 941 private class Star extends Scenery { 942 public Star(Context context) { 943 super(context); 944 setBackgroundResource(R.drawable.star); 945 w = h = irand(PARAMS.STAR_SIZE_MIN, PARAMS.STAR_SIZE_MAX); 946 v = z = 0; 947 } 948 } 949 } 950