LLandpublic class LLand extends android.widget.FrameLayout
Fields Summary |
---|
public static final String | TAG | public static final boolean | DEBUG | public static final boolean | DEBUG_DRAW | public static final boolean | AUTOSTART | public static final boolean | HAVE_STARS | public static final float | DEBUG_SPEED_MULTIPLIER | public static final boolean | DEBUG_IDDQD | static final int[] | POPS | private android.animation.TimeAnimator | mAnim | private android.os.Vibrator | mVibrator | private android.media.AudioManager | mAudioManager | private final android.media.AudioAttributes | mAudioAttrs | private android.widget.TextView | mScoreField | private android.view.View | mSplash | private Player | mDroid | private ArrayList | mObstaclesInPlay | private float | t | private float | dt | private int | mScore | private float | mLastPipeTime | private int | mWidth | private int | mHeight | private boolean | mAnimating | private boolean | mPlaying | private boolean | mFrozen | private boolean | mFlipped | private int | mTimeOfDay | private static final int | DAY | private static final int | NIGHT | private static final int | TWILIGHT | private static final int | SUNSET | private static final int[] | SKIES | private static Params | PARAMS | final float[] | hsv | static final android.graphics.Rect | sTmpRect |
Constructors Summary |
---|
public LLand(android.content.Context context)
this(context, null);
| public LLand(android.content.Context context, android.util.AttributeSet attrs)
this(context, attrs, 0);
| public LLand(android.content.Context context, android.util.AttributeSet attrs, int defStyle)
super(context, attrs, defStyle);
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
setFocusable(true);
PARAMS = new Params(getResources());
mTimeOfDay = irand(0, SKIES.length);
// we assume everything will be laid out left|top
setLayoutDirection(LAYOUT_DIRECTION_LTR);
|
Methods Summary |
---|
public static void | L(java.lang.String s, java.lang.Object objects) // DEBUG
if (DEBUG) {
Slog.d(TAG, objects.length == 0 ? s : String.format(s, objects));
}
| private void | addScore(int incr)
setScore(mScore + incr);
| public static final float | clamp(float f)
return f < 0f ? 0f : f > 1f ? 1f : f;
| public static final float | frand()
return (float) Math.random();
| public static final float | frand(float a, float b)
return lerp(frand(), a, b);
| public int | getGameHeight() return mHeight;
| public float | getGameTime() return t;
| public int | getGameWidth() return mWidth;
| public float | getLastTimeStep() return dt;
| public static final int | irand(int a, int b)
return (int) lerp(frand(), (float) a, (float) b);
| public static final float | lerp(float x, float a, float b)
return (b - a) * x + a;
| public void | onDraw(android.graphics.Canvas c)
super.onDraw(c);
if (!DEBUG_DRAW) return;
final Paint pt = new Paint();
pt.setColor(0xFFFFFFFF);
final int L = mDroid.corners.length;
final int N = L/2;
for (int i=0; i<N; i++) {
final int x = (int) mDroid.corners[i*2];
final int y = (int) mDroid.corners[i*2+1];
c.drawCircle(x, y, 4, pt);
c.drawLine(x, y,
mDroid.corners[(i*2+2)%L],
mDroid.corners[(i*2+3)%L],
pt);
}
pt.setStyle(Paint.Style.STROKE);
pt.setStrokeWidth(getResources().getDisplayMetrics().density);
final int M = getChildCount();
pt.setColor(0x8000FF00);
for (int i=0; i<M; i++) {
final View v = getChildAt(i);
if (v == mDroid) continue;
if (!(v instanceof GameView)) continue;
if (v instanceof Pop) {
final Pop p = (Pop) v;
c.drawCircle(p.cx, p.cy, p.r, pt);
} else {
final Rect r = new Rect();
v.getHitRect(r);
c.drawRect(r, pt);
}
}
pt.setColor(Color.BLACK);
final StringBuilder sb = new StringBuilder("obstacles: ");
for (Obstacle ob : mObstaclesInPlay) {
sb.append(ob.hitRect.toShortString());
sb.append(" ");
}
pt.setTextSize(20f);
c.drawText(sb.toString(), 20, 100, pt);
| public boolean | onGenericMotionEvent(android.view.MotionEvent ev)
L("generic: %s", ev);
return false;
| public boolean | onKeyDown(int keyCode, android.view.KeyEvent ev)
L("keyDown: %d", keyCode);
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_SPACE:
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_BUTTON_A:
poke();
return true;
}
return false;
| public boolean | onKeyUp(int keyCode, android.view.KeyEvent ev)
L("keyDown: %d", keyCode);
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_SPACE:
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_BUTTON_A:
unpoke();
return true;
}
return false;
| protected void | onSizeChanged(int w, int h, int oldw, int oldh)
stop();
reset();
if (AUTOSTART) {
start(false);
}
| public boolean | onTouchEvent(android.view.MotionEvent ev)
L("touch: %s", ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
poke();
return true;
case MotionEvent.ACTION_UP:
unpoke();
return true;
}
return false;
| public boolean | onTrackballEvent(android.view.MotionEvent ev)
L("trackball: %s", ev);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
poke();
return true;
case MotionEvent.ACTION_UP:
unpoke();
return true;
}
return false;
| private void | poke()
L("poke");
if (mFrozen) return;
if (!mAnimating) {
reset();
start(true);
} else if (!mPlaying) {
start(true);
}
mDroid.boost();
if (DEBUG) {
mDroid.dv *= DEBUG_SPEED_MULTIPLIER;
mDroid.animate().setDuration((long) (200/DEBUG_SPEED_MULTIPLIER));
}
| public void | reset()
L("reset");
final Drawable sky = new GradientDrawable(
GradientDrawable.Orientation.BOTTOM_TOP,
SKIES[mTimeOfDay]
);
sky.setDither(true);
setBackground(sky);
mFlipped = frand() > 0.5f;
setScaleX(mFlipped ? -1 : 1);
setScore(0);
int i = getChildCount();
while (i-->0) {
final View v = getChildAt(i);
if (v instanceof GameView) {
removeViewAt(i);
}
}
mObstaclesInPlay.clear();
mWidth = getWidth();
mHeight = getHeight();
boolean showingSun = (mTimeOfDay == DAY || mTimeOfDay == SUNSET) && frand() > 0.25;
if (showingSun) {
final Star sun = new Star(getContext());
sun.setBackgroundResource(R.drawable.sun);
final int w = getResources().getDimensionPixelSize(R.dimen.sun_size);
sun.setTranslationX(frand(w, mWidth-w));
if (mTimeOfDay == DAY) {
sun.setTranslationY(frand(w, (mHeight * 0.66f)));
sun.getBackground().setTint(0);
} else {
sun.setTranslationY(frand(mHeight * 0.66f, mHeight - w));
sun.getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP);
sun.getBackground().setTint(0xC0FF8000);
}
addView(sun, new LayoutParams(w, w));
}
if (!showingSun) {
final boolean dark = mTimeOfDay == NIGHT || mTimeOfDay == TWILIGHT;
final float ff = frand();
if ((dark && ff < 0.75f) || ff < 0.5f) {
final Star moon = new Star(getContext());
moon.setBackgroundResource(R.drawable.moon);
moon.getBackground().setAlpha(dark ? 255 : 128);
moon.setScaleX(frand() > 0.5 ? -1 : 1);
moon.setRotation(moon.getScaleX() * frand(5, 30));
final int w = getResources().getDimensionPixelSize(R.dimen.sun_size);
moon.setTranslationX(frand(w, mWidth - w));
moon.setTranslationY(frand(w, mHeight - w));
addView(moon, new LayoutParams(w, w));
}
}
final int mh = mHeight / 6;
final boolean cloudless = frand() < 0.25;
final int N = 20;
for (i=0; i<N; i++) {
final float r1 = frand();
final Scenery s;
if (HAVE_STARS && r1 < 0.3 && mTimeOfDay != DAY) {
s = new Star(getContext());
} else if (r1 < 0.6 && !cloudless) {
s = new Cloud(getContext());
} else {
s = new Building(getContext());
s.z = (float)i/N;
s.setTranslationZ(PARAMS.SCENERY_Z * (1+s.z));
s.v = 0.85f * s.z; // buildings move proportional to their distance
hsv[0] = 175;
hsv[1] = 0.25f;
hsv[2] = 1 * s.z;
s.setBackgroundColor(Color.HSVToColor(hsv));
s.h = irand(PARAMS.BUILDING_HEIGHT_MIN, mh);
}
final LayoutParams lp = new LayoutParams(s.w, s.h);
if (s instanceof Building) {
lp.gravity = Gravity.BOTTOM;
} else {
lp.gravity = Gravity.TOP;
final float r = frand();
if (s instanceof Star) {
lp.topMargin = (int) (r * r * mHeight);
} else {
lp.topMargin = (int) (1 - r*r * mHeight/2) + mHeight/2;
}
}
addView(s, lp);
s.setTranslationX(frand(-lp.width, mWidth + lp.width));
}
mDroid = new Player(getContext());
mDroid.setX(mWidth / 2);
mDroid.setY(mHeight / 2);
addView(mDroid, new LayoutParams(PARAMS.PLAYER_SIZE, PARAMS.PLAYER_SIZE));
mAnim = new TimeAnimator();
mAnim.setTimeListener(new TimeAnimator.TimeListener() {
@Override
public void onTimeUpdate(TimeAnimator timeAnimator, long t, long dt) {
step(t, dt);
}
});
| public static final float | rlerp(float v, float a, float b)
return (v - a) / (b - a);
| private void | setScore(int score)
mScore = score;
if (mScoreField != null) {
mScoreField.setText(DEBUG_IDDQD ? "??" : String.valueOf(score));
}
| public void | setScoreField(android.widget.TextView tv)
mScoreField = tv;
if (tv != null) {
tv.setTranslationZ(PARAMS.HUD_Z);
if (!(mAnimating && mPlaying)) {
tv.setTranslationY(-500);
}
}
| public void | setSplash(android.view.View v)
mSplash = v;
| public void | start(boolean startPlaying)
L("start(startPlaying=%s)", startPlaying?"true":"false");
if (startPlaying) {
mPlaying = true;
t = 0;
// there's a sucker born every OBSTACLE_PERIOD
mLastPipeTime = getGameTime() - PARAMS.OBSTACLE_PERIOD;
if (mSplash != null && mSplash.getAlpha() > 0f) {
mSplash.setTranslationZ(PARAMS.HUD_Z);
mSplash.animate().alpha(0).translationZ(0).setDuration(400);
mScoreField.animate().translationY(0)
.setInterpolator(new DecelerateInterpolator())
.setDuration(1500);
}
mScoreField.setTextColor(0xFFAAAAAA);
mScoreField.setBackgroundResource(R.drawable.scorecard);
mDroid.setVisibility(View.VISIBLE);
mDroid.setX(mWidth / 2);
mDroid.setY(mHeight / 2);
} else {
mDroid.setVisibility(View.GONE);
}
if (!mAnimating) {
mAnim.start();
mAnimating = true;
}
| private void | step(long t_ms, long dt_ms)
t = t_ms / 1000f; // seconds
dt = dt_ms / 1000f;
if (DEBUG) {
t *= DEBUG_SPEED_MULTIPLIER;
dt *= DEBUG_SPEED_MULTIPLIER;
}
// 1. Move all objects and update bounds
final int N = getChildCount();
int i = 0;
for (; i<N; i++) {
final View v = getChildAt(i);
if (v instanceof GameView) {
((GameView) v).step(t_ms, dt_ms, t, dt);
}
}
// 2. Check for altitude
if (mPlaying && mDroid.below(mHeight)) {
if (DEBUG_IDDQD) {
poke();
unpoke();
} else {
L("player hit the floor");
thump();
stop();
}
}
// 3. Check for obstacles
boolean passedBarrier = false;
for (int j = mObstaclesInPlay.size(); j-->0;) {
final Obstacle ob = mObstaclesInPlay.get(j);
if (mPlaying && ob.intersects(mDroid) && !DEBUG_IDDQD) {
L("player hit an obstacle");
thump();
stop();
} else if (ob.cleared(mDroid)) {
if (ob instanceof Stem) passedBarrier = true;
mObstaclesInPlay.remove(j);
}
}
if (mPlaying && passedBarrier) {
addScore(1);
}
// 4. Handle edge of screen
// Walk backwards to make sure removal is safe
while (i-->0) {
final View v = getChildAt(i);
if (v instanceof Obstacle) {
if (v.getTranslationX() + v.getWidth() < 0) {
removeViewAt(i);
}
} else if (v instanceof Scenery) {
final Scenery s = (Scenery) v;
if (v.getTranslationX() + s.w < 0) {
v.setTranslationX(getWidth());
}
}
}
// 3. Time for more obstacles!
if (mPlaying && (t - mLastPipeTime) > PARAMS.OBSTACLE_PERIOD) {
mLastPipeTime = t;
final int obstacley =
(int)(frand() * (mHeight - 2*PARAMS.OBSTACLE_MIN - PARAMS.OBSTACLE_GAP)) +
PARAMS.OBSTACLE_MIN;
final int inset = (PARAMS.OBSTACLE_WIDTH - PARAMS.OBSTACLE_STEM_WIDTH) / 2;
final int yinset = PARAMS.OBSTACLE_WIDTH/2;
final int d1 = irand(0,250);
final Obstacle s1 = new Stem(getContext(), obstacley - yinset, false);
addView(s1, new LayoutParams(
PARAMS.OBSTACLE_STEM_WIDTH,
(int) s1.h,
Gravity.TOP|Gravity.LEFT));
s1.setTranslationX(mWidth+inset);
s1.setTranslationY(-s1.h-yinset);
s1.setTranslationZ(PARAMS.OBSTACLE_Z*0.75f);
s1.animate()
.translationY(0)
.setStartDelay(d1)
.setDuration(250);
mObstaclesInPlay.add(s1);
final Obstacle p1 = new Pop(getContext(), PARAMS.OBSTACLE_WIDTH);
addView(p1, new LayoutParams(
PARAMS.OBSTACLE_WIDTH,
PARAMS.OBSTACLE_WIDTH,
Gravity.TOP|Gravity.LEFT));
p1.setTranslationX(mWidth);
p1.setTranslationY(-PARAMS.OBSTACLE_WIDTH);
p1.setTranslationZ(PARAMS.OBSTACLE_Z);
p1.setScaleX(0.25f);
p1.setScaleY(0.25f);
p1.animate()
.translationY(s1.h-inset)
.scaleX(1f)
.scaleY(1f)
.setStartDelay(d1)
.setDuration(250);
mObstaclesInPlay.add(p1);
final int d2 = irand(0,250);
final Obstacle s2 = new Stem(getContext(),
mHeight - obstacley - PARAMS.OBSTACLE_GAP - yinset,
true);
addView(s2, new LayoutParams(
PARAMS.OBSTACLE_STEM_WIDTH,
(int) s2.h,
Gravity.TOP|Gravity.LEFT));
s2.setTranslationX(mWidth+inset);
s2.setTranslationY(mHeight+yinset);
s2.setTranslationZ(PARAMS.OBSTACLE_Z*0.75f);
s2.animate()
.translationY(mHeight-s2.h)
.setStartDelay(d2)
.setDuration(400);
mObstaclesInPlay.add(s2);
final Obstacle p2 = new Pop(getContext(), PARAMS.OBSTACLE_WIDTH);
addView(p2, new LayoutParams(
PARAMS.OBSTACLE_WIDTH,
PARAMS.OBSTACLE_WIDTH,
Gravity.TOP|Gravity.LEFT));
p2.setTranslationX(mWidth);
p2.setTranslationY(mHeight);
p2.setTranslationZ(PARAMS.OBSTACLE_Z);
p2.setScaleX(0.25f);
p2.setScaleY(0.25f);
p2.animate()
.translationY(mHeight-s2.h-yinset)
.scaleX(1f)
.scaleY(1f)
.setStartDelay(d2)
.setDuration(400);
mObstaclesInPlay.add(p2);
}
if (DEBUG_DRAW) invalidate();
| public void | stop()
if (mAnimating) {
mAnim.cancel();
mAnim = null;
mAnimating = false;
mScoreField.setTextColor(0xFFFFFFFF);
mScoreField.setBackgroundResource(R.drawable.scorecard_gameover);
mTimeOfDay = irand(0, SKIES.length); // for next reset
mFrozen = true;
postDelayed(new Runnable() {
@Override
public void run() {
mFrozen = false;
}
}, 250);
}
| private void | thump()
if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
// No interruptions. Not even game haptics.
return;
}
mVibrator.vibrate(80, mAudioAttrs);
| private void | unpoke()
L("unboost");
if (mFrozen) return;
if (!mAnimating) return;
mDroid.unboost();
| public boolean | willNotDraw()
return !DEBUG;
|
|