/*
|
* Copyright (C) 2015 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.systemui.egg;
|
|
import android.animation.LayoutTransition;
|
import android.animation.TimeAnimator;
|
import android.content.Context;
|
import android.content.res.Resources;
|
import android.graphics.Canvas;
|
import android.graphics.Color;
|
import android.graphics.Matrix;
|
import android.graphics.Outline;
|
import android.graphics.Paint;
|
import android.graphics.Path;
|
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.Rect;
|
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.GradientDrawable;
|
import android.media.AudioAttributes;
|
import android.media.AudioManager;
|
import android.os.Vibrator;
|
import android.util.AttributeSet;
|
import android.util.Log;
|
import android.view.Gravity;
|
import android.view.InputDevice;
|
import android.view.KeyEvent;
|
import android.view.LayoutInflater;
|
import android.view.MotionEvent;
|
import android.view.View;
|
import android.view.ViewGroup;
|
import android.view.ViewOutlineProvider;
|
import android.widget.FrameLayout;
|
import android.widget.ImageView;
|
import android.widget.TextView;
|
|
import com.android.internal.logging.MetricsLogger;
|
import com.android.systemui.R;
|
|
import java.util.ArrayList;
|
|
// It's like LLand, but "M"ultiplayer.
|
public class MLand extends FrameLayout {
|
public static final String TAG = "MLand";
|
|
public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
public static final boolean DEBUG_DRAW = false; // DEBUG
|
|
public static final boolean SHOW_TOUCHES = true;
|
|
public static void L(String s, Object ... objects) {
|
if (DEBUG) {
|
Log.d(TAG, objects.length == 0 ? s : String.format(s, objects));
|
}
|
}
|
|
public static final float PI_2 = (float) (Math.PI/2);
|
|
public static final boolean AUTOSTART = true;
|
public static final boolean HAVE_STARS = true;
|
|
public static final float DEBUG_SPEED_MULTIPLIER = 0.5f; // only if DEBUG
|
public static final boolean DEBUG_IDDQD = Log.isLoggable(TAG + ".iddqd", Log.DEBUG);
|
|
public static final int DEFAULT_PLAYERS = 1;
|
public static final int MIN_PLAYERS = 1;
|
public static final int MAX_PLAYERS = 6;
|
|
static final float CONTROLLER_VIBRATION_MULTIPLIER = 2f;
|
|
private static class Params {
|
public float TRANSLATION_PER_SEC;
|
public int OBSTACLE_SPACING, OBSTACLE_PERIOD;
|
public int BOOST_DV;
|
public int PLAYER_HIT_SIZE;
|
public int PLAYER_SIZE;
|
public int OBSTACLE_WIDTH, OBSTACLE_STEM_WIDTH;
|
public int OBSTACLE_GAP;
|
public int OBSTACLE_MIN;
|
public int BUILDING_WIDTH_MIN, BUILDING_WIDTH_MAX;
|
public int BUILDING_HEIGHT_MIN;
|
public int CLOUD_SIZE_MIN, CLOUD_SIZE_MAX;
|
public int STAR_SIZE_MIN, STAR_SIZE_MAX;
|
public int G;
|
public int MAX_V;
|
public float SCENERY_Z, OBSTACLE_Z, PLAYER_Z, PLAYER_Z_BOOST, HUD_Z;
|
public Params(Resources res) {
|
TRANSLATION_PER_SEC = res.getDimension(R.dimen.translation_per_sec);
|
OBSTACLE_SPACING = res.getDimensionPixelSize(R.dimen.obstacle_spacing);
|
OBSTACLE_PERIOD = (int) (OBSTACLE_SPACING / TRANSLATION_PER_SEC);
|
BOOST_DV = res.getDimensionPixelSize(R.dimen.boost_dv);
|
PLAYER_HIT_SIZE = res.getDimensionPixelSize(R.dimen.player_hit_size);
|
PLAYER_SIZE = res.getDimensionPixelSize(R.dimen.player_size);
|
OBSTACLE_WIDTH = res.getDimensionPixelSize(R.dimen.obstacle_width);
|
OBSTACLE_STEM_WIDTH = res.getDimensionPixelSize(R.dimen.obstacle_stem_width);
|
OBSTACLE_GAP = res.getDimensionPixelSize(R.dimen.obstacle_gap);
|
OBSTACLE_MIN = res.getDimensionPixelSize(R.dimen.obstacle_height_min);
|
BUILDING_HEIGHT_MIN = res.getDimensionPixelSize(R.dimen.building_height_min);
|
BUILDING_WIDTH_MIN = res.getDimensionPixelSize(R.dimen.building_width_min);
|
BUILDING_WIDTH_MAX = res.getDimensionPixelSize(R.dimen.building_width_max);
|
CLOUD_SIZE_MIN = res.getDimensionPixelSize(R.dimen.cloud_size_min);
|
CLOUD_SIZE_MAX = res.getDimensionPixelSize(R.dimen.cloud_size_max);
|
STAR_SIZE_MIN = res.getDimensionPixelSize(R.dimen.star_size_min);
|
STAR_SIZE_MAX = res.getDimensionPixelSize(R.dimen.star_size_max);
|
|
G = res.getDimensionPixelSize(R.dimen.G);
|
MAX_V = res.getDimensionPixelSize(R.dimen.max_v);
|
|
SCENERY_Z = res.getDimensionPixelSize(R.dimen.scenery_z);
|
OBSTACLE_Z = res.getDimensionPixelSize(R.dimen.obstacle_z);
|
PLAYER_Z = res.getDimensionPixelSize(R.dimen.player_z);
|
PLAYER_Z_BOOST = res.getDimensionPixelSize(R.dimen.player_z_boost);
|
HUD_Z = res.getDimensionPixelSize(R.dimen.hud_z);
|
|
// Sanity checking
|
if (OBSTACLE_MIN <= OBSTACLE_WIDTH / 2) {
|
L("error: obstacles might be too short, adjusting");
|
OBSTACLE_MIN = OBSTACLE_WIDTH / 2 + 1;
|
}
|
}
|
}
|
|
private TimeAnimator mAnim;
|
private Vibrator mVibrator;
|
private AudioManager mAudioManager;
|
private final AudioAttributes mAudioAttrs = new AudioAttributes.Builder()
|
.setUsage(AudioAttributes.USAGE_GAME).build();
|
|
private View mSplash;
|
private ViewGroup mScoreFields;
|
|
private ArrayList<Player> mPlayers = new ArrayList<Player>();
|
private ArrayList<Obstacle> mObstaclesInPlay = new ArrayList<Obstacle>();
|
|
private float t, dt;
|
|
private float mLastPipeTime; // in sec
|
private int mCurrentPipeId; // basically, equivalent to the current score
|
private int mWidth, mHeight;
|
private boolean mAnimating, mPlaying;
|
private boolean mFrozen; // after death, a short backoff
|
private int mCountdown = 0;
|
private boolean mFlipped;
|
|
private int mTaps;
|
|
private int mTimeOfDay;
|
private static final int DAY = 0, NIGHT = 1, TWILIGHT = 2, SUNSET = 3;
|
private static final int[][] SKIES = {
|
{ 0xFFc0c0FF, 0xFFa0a0FF }, // DAY
|
{ 0xFF000010, 0xFF000000 }, // NIGHT
|
{ 0xFF000040, 0xFF000010 }, // TWILIGHT
|
{ 0xFFa08020, 0xFF204080 }, // SUNSET
|
};
|
|
private int mScene;
|
private static final int SCENE_CITY = 0, SCENE_TX = 1, SCENE_ZRH = 2;
|
private static final int SCENE_COUNT = 3;
|
|
private static Params PARAMS;
|
|
private static float dp = 1f;
|
|
private Paint mTouchPaint, mPlayerTracePaint;
|
|
private ArrayList<Integer> mGameControllers = new ArrayList<>();
|
|
public MLand(Context context) {
|
this(context, null);
|
}
|
|
public MLand(Context context, AttributeSet attrs) {
|
this(context, attrs, 0);
|
}
|
|
public MLand(Context context, 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 - 1);
|
mScene = irand(0, SCENE_COUNT);
|
|
mTouchPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
mTouchPaint.setColor(0x80FFFFFF);
|
mTouchPaint.setStyle(Paint.Style.FILL);
|
|
mPlayerTracePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
mPlayerTracePaint.setColor(0x80FFFFFF);
|
mPlayerTracePaint.setStyle(Paint.Style.STROKE);
|
mPlayerTracePaint.setStrokeWidth(2 * dp);
|
|
// we assume everything will be laid out left|top
|
setLayoutDirection(LAYOUT_DIRECTION_LTR);
|
|
setupPlayers(DEFAULT_PLAYERS);
|
|
MetricsLogger.count(getContext(), "egg_mland_create", 1);
|
}
|
|
@Override
|
public void onAttachedToWindow() {
|
super.onAttachedToWindow();
|
dp = getResources().getDisplayMetrics().density;
|
|
reset();
|
if (AUTOSTART) {
|
start(false);
|
}
|
}
|
|
@Override
|
public boolean willNotDraw() {
|
return !DEBUG;
|
}
|
|
public int getGameWidth() { return mWidth; }
|
public int getGameHeight() { return mHeight; }
|
public float getGameTime() { return t; }
|
public float getLastTimeStep() { return dt; }
|
|
public void setScoreFieldHolder(ViewGroup vg) {
|
mScoreFields = vg;
|
if (vg != null) {
|
final LayoutTransition lt = new LayoutTransition();
|
lt.setDuration(250);
|
mScoreFields.setLayoutTransition(lt);
|
}
|
for (Player p : mPlayers) {
|
mScoreFields.addView(p.mScoreField,
|
new MarginLayoutParams(
|
MarginLayoutParams.WRAP_CONTENT,
|
MarginLayoutParams.MATCH_PARENT));
|
}
|
}
|
|
public void setSplash(View v) {
|
mSplash = v;
|
}
|
|
public static boolean isGamePad(InputDevice dev) {
|
int sources = dev.getSources();
|
|
// Verify that the device has gamepad buttons, control sticks, or both.
|
return (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
|| ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK));
|
}
|
|
public ArrayList getGameControllers() {
|
mGameControllers.clear();
|
int[] deviceIds = InputDevice.getDeviceIds();
|
for (int deviceId : deviceIds) {
|
InputDevice dev = InputDevice.getDevice(deviceId);
|
if (isGamePad(dev)) {
|
if (!mGameControllers.contains(deviceId)) {
|
mGameControllers.add(deviceId);
|
}
|
}
|
}
|
return mGameControllers;
|
}
|
|
public int getControllerPlayer(int id) {
|
final int player = mGameControllers.indexOf(id);
|
if (player < 0 || player >= mPlayers.size()) return 0;
|
return player;
|
}
|
|
@Override
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
dp = getResources().getDisplayMetrics().density;
|
|
stop();
|
|
reset();
|
if (AUTOSTART) {
|
start(false);
|
}
|
}
|
|
final static float hsv[] = {0, 0, 0};
|
|
private static float luma(int bgcolor) {
|
return 0.2126f * (float) (bgcolor & 0xFF0000) / 0xFF0000
|
+ 0.7152f * (float) (bgcolor & 0xFF00) / 0xFF00
|
+ 0.0722f * (float) (bgcolor & 0xFF) / 0xFF;
|
}
|
|
public Player getPlayer(int i) {
|
return i < mPlayers.size() ? mPlayers.get(i) : null;
|
}
|
|
private int addPlayerInternal(Player p) {
|
mPlayers.add(p);
|
realignPlayers();
|
TextView scoreField = (TextView)
|
LayoutInflater.from(getContext()).inflate(R.layout.mland_scorefield, null);
|
if (mScoreFields != null) {
|
mScoreFields.addView(scoreField,
|
new MarginLayoutParams(
|
MarginLayoutParams.WRAP_CONTENT,
|
MarginLayoutParams.MATCH_PARENT));
|
}
|
p.setScoreField(scoreField);
|
return mPlayers.size()-1;
|
}
|
|
private void removePlayerInternal(Player p) {
|
if (mPlayers.remove(p)) {
|
removeView(p);
|
mScoreFields.removeView(p.mScoreField);
|
realignPlayers();
|
}
|
}
|
|
private void realignPlayers() {
|
final int N = mPlayers.size();
|
float x = (mWidth - (N-1) * PARAMS.PLAYER_SIZE) / 2;
|
for (int i=0; i<N; i++) {
|
final Player p = mPlayers.get(i);
|
p.setX(x);
|
x += PARAMS.PLAYER_SIZE;
|
}
|
}
|
|
private void clearPlayers() {
|
while (mPlayers.size() > 0) {
|
removePlayerInternal(mPlayers.get(0));
|
}
|
}
|
|
public void setupPlayers(int num) {
|
clearPlayers();
|
for (int i=0; i<num; i++) {
|
addPlayerInternal(Player.create(this));
|
}
|
}
|
|
public void addPlayer() {
|
if (getNumPlayers() == MAX_PLAYERS) return;
|
addPlayerInternal(Player.create(this));
|
}
|
|
public int getNumPlayers() {
|
return mPlayers.size();
|
}
|
|
public void removePlayer() {
|
if (getNumPlayers() == MIN_PLAYERS) return;
|
removePlayerInternal(mPlayers.get(mPlayers.size() - 1));
|
}
|
|
private void thump(int playerIndex, long ms) {
|
if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
|
// No interruptions. Not even game haptics.
|
return;
|
}
|
if (playerIndex < mGameControllers.size()) {
|
int controllerId = mGameControllers.get(playerIndex);
|
InputDevice dev = InputDevice.getDevice(controllerId);
|
if (dev != null && dev.getVibrator().hasVibrator()) {
|
dev.getVibrator().vibrate(
|
(long) (ms * CONTROLLER_VIBRATION_MULTIPLIER),
|
mAudioAttrs);
|
return;
|
}
|
}
|
mVibrator.vibrate(ms, mAudioAttrs);
|
}
|
|
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);
|
|
int i = getChildCount();
|
while (i-->0) {
|
final View v = getChildAt(i);
|
if (v instanceof GameView) {
|
removeViewAt(i);
|
}
|
}
|
|
mObstaclesInPlay.clear();
|
mCurrentPipeId = 0;
|
|
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 {
|
switch (mScene) {
|
case SCENE_ZRH:
|
s = new Mountain(getContext());
|
break;
|
case SCENE_TX:
|
s = new Cactus(getContext());
|
break;
|
case SCENE_CITY:
|
default:
|
s = new Building(getContext());
|
break;
|
}
|
s.z = (float) i / N;
|
// no more shadows for these things
|
//s.setTranslationZ(PARAMS.SCENERY_Z * (1+s.z));
|
s.v = 0.85f * s.z; // buildings move proportional to their distance
|
if (mScene == SCENE_CITY) {
|
s.setBackgroundColor(Color.GRAY);
|
s.h = irand(PARAMS.BUILDING_HEIGHT_MIN, mh);
|
}
|
final int c = (int)(255f*s.z);
|
final Drawable bg = s.getBackground();
|
if (bg != null) bg.setColorFilter(Color.rgb(c,c,c), PorterDuff.Mode.MULTIPLY);
|
}
|
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));
|
}
|
|
for (Player p : mPlayers) {
|
addView(p); // put it back!
|
p.reset();
|
}
|
|
realignPlayers();
|
|
if (mAnim != null) {
|
mAnim.cancel();
|
}
|
mAnim = new TimeAnimator();
|
mAnim.setTimeListener(new TimeAnimator.TimeListener() {
|
@Override
|
public void onTimeUpdate(TimeAnimator timeAnimator, long t, long dt) {
|
step(t, dt);
|
}
|
});
|
}
|
|
public void start(boolean startPlaying) {
|
L("start(startPlaying=%s)", startPlaying ? "true" : "false");
|
if (startPlaying && mCountdown <= 0) {
|
showSplash();
|
|
mSplash.findViewById(R.id.play_button).setEnabled(false);
|
|
final View playImage = mSplash.findViewById(R.id.play_button_image);
|
final TextView playText = (TextView) mSplash.findViewById(R.id.play_button_text);
|
|
playImage.animate().alpha(0f);
|
playText.animate().alpha(1f);
|
|
mCountdown = 3;
|
post(new Runnable() {
|
@Override
|
public void run() {
|
if (mCountdown == 0) {
|
startPlaying();
|
} else {
|
postDelayed(this, 500);
|
}
|
playText.setText(String.valueOf(mCountdown));
|
mCountdown--;
|
}
|
});
|
}
|
|
for (Player p : mPlayers) {
|
p.setVisibility(View.INVISIBLE);
|
}
|
|
if (!mAnimating) {
|
mAnim.start();
|
mAnimating = true;
|
}
|
}
|
|
public void hideSplash() {
|
if (mSplash != null && mSplash.getVisibility() == View.VISIBLE) {
|
mSplash.setClickable(false);
|
mSplash.animate().alpha(0).translationZ(0).setDuration(300).withEndAction(
|
new Runnable() {
|
@Override
|
public void run() {
|
mSplash.setVisibility(View.GONE);
|
}
|
}
|
);
|
}
|
}
|
|
public void showSplash() {
|
if (mSplash != null && mSplash.getVisibility() != View.VISIBLE) {
|
mSplash.setClickable(true);
|
mSplash.setAlpha(0f);
|
mSplash.setVisibility(View.VISIBLE);
|
mSplash.animate().alpha(1f).setDuration(1000);
|
mSplash.findViewById(R.id.play_button_image).setAlpha(1f);
|
mSplash.findViewById(R.id.play_button_text).setAlpha(0f);
|
mSplash.findViewById(R.id.play_button).setEnabled(true);
|
mSplash.findViewById(R.id.play_button).requestFocus();
|
}
|
}
|
|
public void startPlaying() {
|
mPlaying = true;
|
|
t = 0;
|
// there's a sucker born every OBSTACLE_PERIOD
|
mLastPipeTime = getGameTime() - PARAMS.OBSTACLE_PERIOD;
|
|
hideSplash();
|
|
realignPlayers();
|
mTaps = 0;
|
|
final int N = mPlayers.size();
|
MetricsLogger.histogram(getContext(), "egg_mland_players", N);
|
for (int i=0; i<N; i++) {
|
final Player p = mPlayers.get(i);
|
p.setVisibility(View.VISIBLE);
|
p.reset();
|
p.start();
|
p.boost(-1, -1); // start you off flying!
|
p.unboost(); // not forever, though
|
}
|
}
|
|
public void stop() {
|
if (mAnimating) {
|
mAnim.cancel();
|
mAnim = null;
|
mAnimating = false;
|
mPlaying = false;
|
mTimeOfDay = irand(0, SKIES.length - 1); // for next reset
|
mScene = irand(0, SCENE_COUNT);
|
mFrozen = true;
|
for (Player p : mPlayers) {
|
p.die();
|
}
|
postDelayed(new Runnable() {
|
@Override
|
public void run() {
|
mFrozen = false;
|
}
|
}, 250);
|
}
|
}
|
|
public static final float lerp(float x, float a, float b) {
|
return (b - a) * x + a;
|
}
|
|
public static final float rlerp(float v, float a, float b) {
|
return (v - a) / (b - a);
|
}
|
|
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 static final int irand(int a, int b) {
|
return Math.round(frand((float) a, (float) b));
|
}
|
|
public static int pick(int[] l) {
|
return l[irand(0, l.length-1)];
|
}
|
|
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);
|
}
|
}
|
|
if (mPlaying) {
|
int livingPlayers = 0;
|
for (i = 0; i < mPlayers.size(); i++) {
|
final Player p = getPlayer(i);
|
|
if (p.mAlive) {
|
// 2. Check for altitude
|
if (p.below(mHeight)) {
|
if (DEBUG_IDDQD) {
|
poke(i);
|
unpoke(i);
|
} else {
|
L("player %d hit the floor", i);
|
thump(i, 80);
|
p.die();
|
}
|
}
|
|
// 3. Check for obstacles
|
int maxPassedStem = 0;
|
for (int j = mObstaclesInPlay.size(); j-- > 0; ) {
|
final Obstacle ob = mObstaclesInPlay.get(j);
|
if (ob.intersects(p) && !DEBUG_IDDQD) {
|
L("player hit an obstacle");
|
thump(i, 80);
|
p.die();
|
} else if (ob.cleared(p)) {
|
if (ob instanceof Stem) {
|
maxPassedStem = Math.max(maxPassedStem, ((Stem)ob).id);
|
}
|
}
|
}
|
|
if (maxPassedStem > p.mScore) {
|
p.addScore(1);
|
}
|
}
|
|
if (p.mAlive) livingPlayers++;
|
}
|
|
if (livingPlayers == 0) {
|
stop();
|
|
MetricsLogger.count(getContext(), "egg_mland_taps", mTaps);
|
mTaps = 0;
|
final int playerCount = mPlayers.size();
|
for (int pi=0; pi<playerCount; pi++) {
|
final Player p = mPlayers.get(pi);
|
MetricsLogger.histogram(getContext(), "egg_mland_score", p.getScore());
|
}
|
}
|
}
|
|
// 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);
|
mObstaclesInPlay.remove(v);
|
}
|
} 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;
|
mCurrentPipeId ++;
|
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 (SHOW_TOUCHES || DEBUG_DRAW) invalidate();
|
}
|
|
@Override
|
public boolean onTouchEvent(MotionEvent ev) {
|
L("touch: %s", ev);
|
final int actionIndex = ev.getActionIndex();
|
final float x = ev.getX(actionIndex);
|
final float y = ev.getY(actionIndex);
|
int playerIndex = (int) (getNumPlayers() * (x / getWidth()));
|
if (mFlipped) playerIndex = getNumPlayers() - 1 - playerIndex;
|
switch (ev.getActionMasked()) {
|
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_POINTER_DOWN:
|
poke(playerIndex, x, y);
|
return true;
|
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_POINTER_UP:
|
unpoke(playerIndex);
|
return true;
|
}
|
return false;
|
}
|
|
@Override
|
public boolean onTrackballEvent(MotionEvent ev) {
|
L("trackball: %s", ev);
|
switch (ev.getAction()) {
|
case MotionEvent.ACTION_DOWN:
|
poke(0);
|
return true;
|
case MotionEvent.ACTION_UP:
|
unpoke(0);
|
return true;
|
}
|
return false;
|
}
|
|
@Override
|
public boolean onKeyDown(int keyCode, 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:
|
int player = getControllerPlayer(ev.getDeviceId());
|
poke(player);
|
return true;
|
}
|
return false;
|
}
|
|
@Override
|
public boolean onKeyUp(int keyCode, 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:
|
int player = getControllerPlayer(ev.getDeviceId());
|
unpoke(player);
|
return true;
|
}
|
return false;
|
}
|
|
@Override
|
public boolean onGenericMotionEvent (MotionEvent ev) {
|
L("generic: %s", ev);
|
return false;
|
}
|
|
private void poke(int playerIndex) {
|
poke(playerIndex, -1, -1);
|
}
|
|
private void poke(int playerIndex, float x, float y) {
|
L("poke(%d)", playerIndex);
|
if (mFrozen) return;
|
if (!mAnimating) {
|
reset();
|
}
|
if (!mPlaying) {
|
start(true);
|
} else {
|
final Player p = getPlayer(playerIndex);
|
if (p == null) return; // no player for this controller
|
p.boost(x, y);
|
mTaps++;
|
if (DEBUG) {
|
p.dv *= DEBUG_SPEED_MULTIPLIER;
|
p.animate().setDuration((long) (200 / DEBUG_SPEED_MULTIPLIER));
|
}
|
}
|
}
|
|
private void unpoke(int playerIndex) {
|
L("unboost(%d)", playerIndex);
|
if (mFrozen || !mAnimating || !mPlaying) return;
|
final Player p = getPlayer(playerIndex);
|
if (p == null) return; // no player for this controller
|
p.unboost();
|
}
|
|
@Override
|
public void onDraw(Canvas c) {
|
super.onDraw(c);
|
|
if (SHOW_TOUCHES) {
|
for (Player p : mPlayers) {
|
if (p.mTouchX > 0) {
|
mTouchPaint.setColor(0x80FFFFFF & p.color);
|
mPlayerTracePaint.setColor(0x80FFFFFF & p.color);
|
float x1 = p.mTouchX;
|
float y1 = p.mTouchY;
|
c.drawCircle(x1, y1, 100, mTouchPaint);
|
float x2 = p.getX() + p.getPivotX();
|
float y2 = p.getY() + p.getPivotY();
|
float angle = PI_2 - (float) Math.atan2(x2-x1, y2-y1);
|
x1 += 100*Math.cos(angle);
|
y1 += 100*Math.sin(angle);
|
c.drawLine(x1, y1, x2, y2, mPlayerTracePaint);
|
}
|
}
|
}
|
|
if (!DEBUG_DRAW) return;
|
|
final Paint pt = new Paint();
|
pt.setColor(0xFFFFFFFF);
|
for (Player p : mPlayers) {
|
final int L = p.corners.length;
|
final int N = L / 2;
|
for (int i = 0; i < N; i++) {
|
final int x = (int) p.corners[i * 2];
|
final int y = (int) p.corners[i * 2 + 1];
|
c.drawCircle(x, y, 4, pt);
|
c.drawLine(x, y,
|
p.corners[(i * 2 + 2) % L],
|
p.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 instanceof Player) continue;
|
if (!(v instanceof GameView)) continue;
|
if (v instanceof Pop) {
|
final Pop pop = (Pop) v;
|
c.drawCircle(pop.cx, pop.cy, pop.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);
|
}
|
|
static final Rect sTmpRect = new Rect();
|
|
private interface GameView {
|
public void step(long t_ms, long dt_ms, float t, float dt);
|
}
|
|
private static class Player extends ImageView implements GameView {
|
public float dv;
|
public int color;
|
private MLand mLand;
|
private boolean mBoosting;
|
private float mTouchX = -1, mTouchY = -1;
|
private boolean mAlive;
|
private int mScore;
|
private TextView mScoreField;
|
|
private final int[] sColors = new int[] {
|
//0xFF78C557,
|
0xFFDB4437,
|
0xFF3B78E7,
|
0xFFF4B400,
|
0xFF0F9D58,
|
0xFF7B1880,
|
0xFF9E9E9E,
|
};
|
static int sNextColor = 0;
|
|
private final float[] sHull = new float[] {
|
0.3f, 0f, // left antenna
|
0.7f, 0f, // right antenna
|
0.92f, 0.33f, // off the right shoulder of Orion
|
0.92f, 0.75f, // right hand (our right, not his right)
|
0.6f, 1f, // right foot
|
0.4f, 1f, // left foot BLUE!
|
0.08f, 0.75f, // sinistram
|
0.08f, 0.33f, // cold shoulder
|
};
|
public final float[] corners = new float[sHull.length];
|
|
public static Player create(MLand land) {
|
final Player p = new Player(land.getContext());
|
p.mLand = land;
|
p.reset();
|
p.setVisibility(View.INVISIBLE);
|
land.addView(p, new LayoutParams(PARAMS.PLAYER_SIZE, PARAMS.PLAYER_SIZE));
|
return p;
|
}
|
|
private void setScore(int score) {
|
mScore = score;
|
if (mScoreField != null) {
|
mScoreField.setText(DEBUG_IDDQD ? "??" : String.valueOf(score));
|
}
|
}
|
|
public int getScore() {
|
return mScore;
|
}
|
|
private void addScore(int incr) {
|
setScore(mScore + incr);
|
}
|
|
public void setScoreField(TextView tv) {
|
mScoreField = tv;
|
if (tv != null) {
|
setScore(mScore); // reapply
|
//mScoreField.setBackgroundResource(R.drawable.scorecard);
|
mScoreField.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
mScoreField.setTextColor(luma(color) > 0.7f ? 0xFF000000 : 0xFFFFFFFF);
|
}
|
}
|
|
public void reset() {
|
//setX(mLand.mWidth / 2);
|
setY(mLand.mHeight / 2
|
+ (int)(Math.random() * PARAMS.PLAYER_SIZE)
|
- PARAMS.PLAYER_SIZE / 2);
|
setScore(0);
|
setScoreField(mScoreField); // refresh color
|
mBoosting = false;
|
dv = 0;
|
}
|
|
public Player(Context context) {
|
super(context);
|
|
setBackgroundResource(R.drawable.android);
|
getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP);
|
color = sColors[(sNextColor++%sColors.length)];
|
getBackground().setTint(color);
|
setOutlineProvider(new ViewOutlineProvider() {
|
@Override
|
public void getOutline(View view, Outline outline) {
|
final int w = view.getWidth();
|
final int h = view.getHeight();
|
final int ix = (int) (w * 0.3f);
|
final int iy = (int) (h * 0.2f);
|
outline.setRect(ix, iy, w - ix, h - iy);
|
}
|
});
|
}
|
|
public void prepareCheckIntersections() {
|
final int inset = (PARAMS.PLAYER_SIZE - PARAMS.PLAYER_HIT_SIZE)/2;
|
final int scale = PARAMS.PLAYER_HIT_SIZE;
|
final int N = sHull.length/2;
|
for (int i=0; i<N; i++) {
|
corners[i*2] = scale * sHull[i*2] + inset;
|
corners[i*2+1] = scale * sHull[i*2+1] + inset;
|
}
|
final Matrix m = getMatrix();
|
m.mapPoints(corners);
|
}
|
|
public boolean below(int h) {
|
final int N = corners.length/2;
|
for (int i=0; i<N; i++) {
|
final int y = (int) corners[i*2+1];
|
if (y >= h) return true;
|
}
|
return false;
|
}
|
|
public void step(long t_ms, long dt_ms, float t, float dt) {
|
if (!mAlive) {
|
// float away with the garbage
|
setTranslationX(getTranslationX()-PARAMS.TRANSLATION_PER_SEC*dt);
|
return;
|
}
|
|
if (mBoosting) {
|
dv = -PARAMS.BOOST_DV;
|
} else {
|
dv += PARAMS.G;
|
}
|
if (dv < -PARAMS.MAX_V) dv = -PARAMS.MAX_V;
|
else if (dv > PARAMS.MAX_V) dv = PARAMS.MAX_V;
|
|
final float y = getTranslationY() + dv * dt;
|
setTranslationY(y < 0 ? 0 : y);
|
setRotation(
|
90 + lerp(clamp(rlerp(dv, PARAMS.MAX_V, -1 * PARAMS.MAX_V)), 90, -90));
|
|
prepareCheckIntersections();
|
}
|
|
public void boost(float x, float y) {
|
mTouchX = x;
|
mTouchY = y;
|
boost();
|
}
|
|
public void boost() {
|
mBoosting = true;
|
dv = -PARAMS.BOOST_DV;
|
|
animate().cancel();
|
animate()
|
.scaleX(1.25f)
|
.scaleY(1.25f)
|
.translationZ(PARAMS.PLAYER_Z_BOOST)
|
.setDuration(100);
|
setScaleX(1.25f);
|
setScaleY(1.25f);
|
}
|
|
public void unboost() {
|
mBoosting = false;
|
mTouchX = mTouchY = -1;
|
|
animate().cancel();
|
animate()
|
.scaleX(1f)
|
.scaleY(1f)
|
.translationZ(PARAMS.PLAYER_Z)
|
.setDuration(200);
|
}
|
|
public void die() {
|
mAlive = false;
|
if (mScoreField != null) {
|
//mScoreField.setTextColor(0xFFFFFFFF);
|
//mScoreField.getBackground().setColorFilter(0xFF666666, PorterDuff.Mode.SRC_ATOP);
|
//mScoreField.setBackgroundResource(R.drawable.scorecard_gameover);
|
}
|
}
|
|
public void start() {
|
mAlive = true;
|
}
|
}
|
|
private class Obstacle extends View implements GameView {
|
public float h;
|
|
public final Rect hitRect = new Rect();
|
|
public Obstacle(Context context, float h) {
|
super(context);
|
setBackgroundColor(0xFFFF0000);
|
this.h = h;
|
}
|
|
public boolean intersects(Player p) {
|
final int N = p.corners.length/2;
|
for (int i=0; i<N; i++) {
|
final int x = (int) p.corners[i*2];
|
final int y = (int) p.corners[i*2+1];
|
if (hitRect.contains(x, y)) return true;
|
}
|
return false;
|
}
|
|
public boolean cleared(Player p) {
|
final int N = p.corners.length/2;
|
for (int i=0; i<N; i++) {
|
final int x = (int) p.corners[i*2];
|
if (hitRect.right >= x) return false;
|
}
|
return true;
|
}
|
|
@Override
|
public void step(long t_ms, long dt_ms, float t, float dt) {
|
setTranslationX(getTranslationX()-PARAMS.TRANSLATION_PER_SEC*dt);
|
getHitRect(hitRect);
|
}
|
}
|
|
static final int[] ANTENNAE = new int[] {R.drawable.mm_antennae, R.drawable.mm_antennae2};
|
static final int[] EYES = new int[] {R.drawable.mm_eyes, R.drawable.mm_eyes2};
|
static final int[] MOUTHS = new int[] {R.drawable.mm_mouth1, R.drawable.mm_mouth2,
|
R.drawable.mm_mouth3, R.drawable.mm_mouth4};
|
private class Pop extends Obstacle {
|
int mRotate;
|
int cx, cy, r;
|
// The marshmallow illustration and hitbox is 2/3 the size of its container.
|
Drawable antenna, eyes, mouth;
|
|
|
public Pop(Context context, float h) {
|
super(context, h);
|
setBackgroundResource(R.drawable.mm_head);
|
antenna = context.getDrawable(pick(ANTENNAE));
|
if (frand() > 0.5f) {
|
eyes = context.getDrawable(pick(EYES));
|
if (frand() > 0.8f) {
|
mouth = context.getDrawable(pick(MOUTHS));
|
}
|
}
|
setOutlineProvider(new ViewOutlineProvider() {
|
@Override
|
public void getOutline(View view, Outline outline) {
|
final int pad = (int) (getWidth() * 1f/6);
|
outline.setOval(pad, pad, getWidth()-pad, getHeight()-pad);
|
}
|
});
|
}
|
|
public boolean intersects(Player p) {
|
final int N = p.corners.length/2;
|
for (int i=0; i<N; i++) {
|
final int x = (int) p.corners[i*2];
|
final int y = (int) p.corners[i*2+1];
|
if (Math.hypot(x-cx, y-cy) <= r) return true;
|
}
|
return false;
|
}
|
|
@Override
|
public void step(long t_ms, long dt_ms, float t, float dt) {
|
super.step(t_ms, dt_ms, t, dt);
|
if (mRotate != 0) {
|
setRotation(getRotation() + dt * 45 * mRotate);
|
}
|
|
cx = (hitRect.left + hitRect.right)/2;
|
cy = (hitRect.top + hitRect.bottom)/2;
|
r = getWidth() / 3; // see above re 2/3 container size
|
}
|
|
@Override
|
public void onDraw(Canvas c) {
|
super.onDraw(c);
|
if (antenna != null) {
|
antenna.setBounds(0, 0, c.getWidth(), c.getHeight());
|
antenna.draw(c);
|
}
|
if (eyes != null) {
|
eyes.setBounds(0, 0, c.getWidth(), c.getHeight());
|
eyes.draw(c);
|
}
|
if (mouth != null) {
|
mouth.setBounds(0, 0, c.getWidth(), c.getHeight());
|
mouth.draw(c);
|
}
|
}
|
}
|
|
private class Stem extends Obstacle {
|
Paint mPaint = new Paint();
|
Path mShadow = new Path();
|
GradientDrawable mGradient = new GradientDrawable();
|
boolean mDrawShadow;
|
Path mJandystripe;
|
Paint mPaint2;
|
int id; // use this to track which pipes have been cleared
|
|
public Stem(Context context, float h, boolean drawShadow) {
|
super(context, h);
|
id = mCurrentPipeId;
|
|
mDrawShadow = drawShadow;
|
setBackground(null);
|
mGradient.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
|
mPaint.setColor(0xFF000000);
|
mPaint.setColorFilter(new PorterDuffColorFilter(0x22000000, PorterDuff.Mode.MULTIPLY));
|
|
if (frand() < 0.01f) {
|
mGradient.setColors(new int[]{0xFFFFFFFF, 0xFFDDDDDD});
|
mJandystripe = new Path();
|
mPaint2 = new Paint();
|
mPaint2.setColor(0xFFFF0000);
|
mPaint2.setColorFilter(new PorterDuffColorFilter(0xFFFF0000, PorterDuff.Mode.MULTIPLY));
|
} else {
|
//mPaint.setColor(0xFFA1887F);
|
mGradient.setColors(new int[]{0xFFBCAAA4, 0xFFA1887F});
|
}
|
}
|
|
@Override
|
public void onAttachedToWindow() {
|
super.onAttachedToWindow();
|
setWillNotDraw(false);
|
setOutlineProvider(new ViewOutlineProvider() {
|
@Override
|
public void getOutline(View view, Outline outline) {
|
outline.setRect(0, 0, getWidth(), getHeight());
|
}
|
});
|
}
|
@Override
|
public void onDraw(Canvas c) {
|
final int w = c.getWidth();
|
final int h = c.getHeight();
|
mGradient.setGradientCenter(w * 0.75f, 0);
|
mGradient.setBounds(0, 0, w, h);
|
mGradient.draw(c);
|
|
if (mJandystripe != null) {
|
mJandystripe.reset();
|
mJandystripe.moveTo(0, w);
|
mJandystripe.lineTo(w, 0);
|
mJandystripe.lineTo(w, 2 * w);
|
mJandystripe.lineTo(0, 3 * w);
|
mJandystripe.close();
|
for (int y=0; y<h; y+=4*w) {
|
c.drawPath(mJandystripe, mPaint2);
|
mJandystripe.offset(0, 4 * w);
|
}
|
}
|
|
if (!mDrawShadow) return;
|
mShadow.reset();
|
mShadow.moveTo(0, 0);
|
mShadow.lineTo(w, 0);
|
mShadow.lineTo(w, PARAMS.OBSTACLE_WIDTH * 0.4f + w*1.5f);
|
mShadow.lineTo(0, PARAMS.OBSTACLE_WIDTH * 0.4f);
|
mShadow.close();
|
c.drawPath(mShadow, mPaint);
|
}
|
}
|
|
private class Scenery extends FrameLayout implements GameView {
|
public float z;
|
public float v;
|
public int h, w;
|
public Scenery(Context context) {
|
super(context);
|
}
|
|
@Override
|
public void step(long t_ms, long dt_ms, float t, float dt) {
|
setTranslationX(getTranslationX() - PARAMS.TRANSLATION_PER_SEC * dt * v);
|
}
|
}
|
|
private class Building extends Scenery {
|
public Building(Context context) {
|
super(context);
|
|
w = irand(PARAMS.BUILDING_WIDTH_MIN, PARAMS.BUILDING_WIDTH_MAX);
|
h = 0; // will be setup later, along with z
|
}
|
}
|
|
static final int[] CACTI = { R.drawable.cactus1, R.drawable.cactus2, R.drawable.cactus3 };
|
private class Cactus extends Building {
|
public Cactus(Context context) {
|
super(context);
|
|
setBackgroundResource(pick(CACTI));
|
w = h = irand(PARAMS.BUILDING_WIDTH_MAX / 4, PARAMS.BUILDING_WIDTH_MAX / 2);
|
}
|
}
|
|
static final int[] MOUNTAINS = {
|
R.drawable.mountain1, R.drawable.mountain2, R.drawable.mountain3 };
|
private class Mountain extends Building {
|
public Mountain(Context context) {
|
super(context);
|
|
setBackgroundResource(pick(MOUNTAINS));
|
w = h = irand(PARAMS.BUILDING_WIDTH_MAX / 2, PARAMS.BUILDING_WIDTH_MAX);
|
z = 0;
|
}
|
}
|
private class Cloud extends Scenery {
|
public Cloud(Context context) {
|
super(context);
|
setBackgroundResource(frand() < 0.01f ? R.drawable.cloud_off : R.drawable.cloud);
|
getBackground().setAlpha(0x40);
|
w = h = irand(PARAMS.CLOUD_SIZE_MIN, PARAMS.CLOUD_SIZE_MAX);
|
z = 0;
|
v = frand(0.15f,0.5f);
|
}
|
}
|
|
private class Star extends Scenery {
|
public Star(Context context) {
|
super(context);
|
setBackgroundResource(R.drawable.star);
|
w = h = irand(PARAMS.STAR_SIZE_MIN, PARAMS.STAR_SIZE_MAX);
|
v = z = 0;
|
}
|
}
|
}
|