/* * Copyright (C) 2016 Google Inc. All Rights Reserved. * * 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.google.android.apps.santatracker.rocketsleigh; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Rect; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.SoundPool; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.CountDownTimer; import android.os.Handler; import android.os.Vibrator; import android.support.v4.app.FragmentActivity; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.animation.AlphaAnimation; import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; import android.widget.HorizontalScrollView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.VideoView; import com.google.android.apps.santatracker.invites.AppInvitesFragment; import com.google.android.apps.santatracker.util.AnalyticsManager; import com.google.android.apps.santatracker.util.ImmersiveModeHelper; import com.google.android.apps.santatracker.util.MeasurementManager; import com.google.firebase.analytics.FirebaseAnalytics; import java.io.IOException; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.TreeMap; public class RocketSleighActivity extends FragmentActivity implements View.OnTouchListener, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, OnClickListener, SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener { private ImageView mElf; private ImageView mThrust; private Bitmap mElfBitmap; private Bitmap mBurnBitmap; private Bitmap mThrustBitmap; private Bitmap mSmokeBitmpap; private Bitmap mCurrentTrailBitmap; private LinearLayout mElfLayout; private int mElfState = 0; // 0 - 100% ... 3 - 25% 4 - Parachute elf. private boolean mElfIsHit = false; private long mElfHitTime = 0; private float mElfPosX = 100; // Pixels from the left edge private float mElfPosY = 200; // Pixels from the top edge private float mElfVelX = 0.3f; // Horizontal speed. private float mElfVelY = 0.0f; // Vertical speed. private float mElfAccelY = 0.0f; // Acceleration due to thrust if user is touching screen. private float mThrustAccelY; // Vertical acceleration in pixel velocity per second of thrust. private float mGravityAccelY; // Vertical acceleration due to gravity in pixel velocity per second. private long mLastTime; private float mElfScale; private CountDownTimer mCountDownTimer; // Achievments private boolean mHit = false; private boolean mHitLevel = false; private boolean mCleanLevel = false; private boolean mPresentBonus = false; private boolean mRainingPresents = false; private ImageView mPlus100; private ImageView mPlus500; private AlphaAnimation m100Anim; private AlphaAnimation m500Anim; private LinearLayout mObstacleLayout; private HorizontalScrollView mObstacleScroll; private int mSlotWidth; // This is the width of an ornament "slot". Obstacles can span multiple slots. private Random mRandom; private int mLastTopHeight = 0; private int mLastBottomHeight = 0; private LinearLayout mBackgroundLayout; private HorizontalScrollView mBackgroundScroll; private LinearLayout mForegroundLayout; private HorizontalScrollView mForegroundScroll; private float mScaleY = 1.0f; private float mScaleX = 1.0f; private TextView mScoreText; private String mScoreLabel; private ImageView mPlayPauseButton; private int mScreenHeight; private int mScreenWidth; private View mControlView; private GestureDetector mGestureDetector; private MotionEvent mDownEvent; private TextView mCountdown; private boolean mIsTv = true; private boolean mIsPlaying = false; private boolean mMoviePlaying = false; private boolean mCountdownStarted = false; private int mLevel = 0; // There are six levels. private long mScore = 0; private int mPresentCount = 0; // 5 in a row gets a bonus... private int mBackgroundCount = 0; // 5 copies of backgrounds per level private int mTransitionImagesCount = 0; // Some level transitions have transition images. private LayoutInflater mInflater; private long mLastFrameTime = 0; private Vibrator mVibrator; private Handler mHandler; private VideoView mIntroVideo; private View mIntroControl; private MediaPlayer mBackgroundPlayer; // For sound effects private SoundPool mSoundPool; private int mCrashSound1; private int mCrashSound2; private int mCrashSound3; private int mGameOverSound; private int mJetThrustSound; private int mLevelUpSound; private int mScoreBigSound; private int mScoreSmallSound; private int mJetThrustStream; private View mBigPlayButtonLayout; private ImageButton mBigPlayButton; private ImageView mExit; private FirebaseAnalytics mMeasurement; private AppInvitesFragment mInvitesFragment; private static final String LOG_TAG = RocketSleighActivity.class.getSimpleName(); private static final int SLOTS_PER_SCREEN = 10; private static final int[] BACKGROUNDS = { R.drawable.bg_jet_pack_1, R.drawable.bg_jet_pack_2, R.drawable.bg_jet_pack_3, R.drawable.bg_jet_pack_4, R.drawable.bg_jet_pack_5, R.drawable.bg_jet_pack_6 }; private Bitmap[] mBackgrounds; private Bitmap[] mBackgrounds2; private static final int[] FOREGROUNDS = { R.drawable.img_snow_ground_tiles, R.drawable.img_snow_ground_tiles, R.drawable.img_snow_ground_tiles, R.drawable.img_snow_ground_tiles, -1, -1 }; private static final int[] EXIT_TRANSITIONS = { -1, -1, -1, R.drawable.bg_transition_2, -1, R.drawable.bg_transition_4, }; private Bitmap[] mExitTransitions; private static final int[] ENTRY_TRANSITIONS = { -1, -1, R.drawable.bg_transition_1, -1, R.drawable.bg_transition_3, -1 }; private Bitmap[] mEntryTransitions; private static final int[] ELF_IMAGES = { R.drawable.img_jetelf_100, R.drawable.img_jetelf_75, R.drawable.img_jetelf_50, R.drawable.img_jetelf_25, R.drawable.img_jetelf_0 }; private Bitmap[] mElfImages; private static final int[] ELF_HIT_IMAGES = { R.drawable.img_jetelf_100_hit, R.drawable.img_jetelf_75_hit, R.drawable.img_jetelf_50_hit, R.drawable.img_jetelf_25_hit, }; private Bitmap[] mElfHitImages; private static final int[] ELF_BURN_IMAGES = { R.drawable.img_jet_burn_100, R.drawable.img_jet_burn_75, R.drawable.img_jet_burn_50, R.drawable.img_jet_burn_25 }; private Bitmap[] mElfBurnImages; private static final int[] ELF_THRUST_IMAGES = { R.drawable.img_jet_thrust_100, R.drawable.img_jet_thrust_75, R.drawable.img_jet_thrust_50, R.drawable.img_jet_thrust_25 }; private Bitmap[] mElfThrustImages; private static final int[] ELF_SMOKE_IMAGES = { R.drawable.img_jet_smoke_100_hit, R.drawable.img_jet_smoke_75_hit, R.drawable.img_jet_smoke_50_hit, R.drawable.img_jet_smoke_25_hit }; private Bitmap[] mElfSmokeImages; private static final int[] GIFT_BOXES = { R.drawable.img_gift_blue_jp, R.drawable.img_gift_green_jp, R.drawable.img_gift_yellow_jp, R.drawable.img_gift_purple_jp, R.drawable.img_gift_red_jp }; private Bitmap[] mGiftBoxes; // Top, Bottom, Background private static final int[] WOOD_OBSTACLES = { -1, R.drawable.img_pine_1_bottom, R.drawable.img_pine_0, -1, R.drawable.img_pine_2_bottom, R.drawable.img_pine_0, -1, R.drawable.img_pine_3_bottom, R.drawable.img_pine_0, -1, R.drawable.img_pine_4_bottom, R.drawable.img_pine_0, R.drawable.img_pine_1_top, -1, R.drawable.img_pine_0, R.drawable.img_pine_2_top, -1, R.drawable.img_pine_0, R.drawable.img_pine_3_top, -1, R.drawable.img_pine_0, R.drawable.img_pine_4_top, -1, R.drawable.img_pine_0, -1, R.drawable.img_birch_1_bottom, R.drawable.img_birch_0, -1, R.drawable.img_birch_2_bottom, R.drawable.img_birch_0, -1, R.drawable.img_birch_3_bottom, R.drawable.img_birch_0, -1, R.drawable.img_birch_4_bottom, R.drawable.img_birch_0, R.drawable.img_birch_1_top, -1, R.drawable.img_birch_0, R.drawable.img_birch_2_top, -1, R.drawable.img_birch_0, R.drawable.img_birch_3_top, -1, R.drawable.img_birch_0, R.drawable.img_birch_4_top, -1, R.drawable.img_birch_0, -1, R.drawable.img_tree_1_bottom, R.drawable.img_birch_0, -1, R.drawable.img_tree_2_bottom, R.drawable.img_birch_0, -1, R.drawable.img_tree_3_bottom, R.drawable.img_birch_0, -1, R.drawable.img_tree_4_bottom, R.drawable.img_birch_0, -1, R.drawable.img_tree_5_bottom, R.drawable.img_birch_0, -1, R.drawable.img_tree_6_bottom, R.drawable.img_birch_0, R.drawable.img_tree_1_top, -1, R.drawable.img_birch_0, R.drawable.img_tree_2_top, -1, R.drawable.img_birch_0, R.drawable.img_tree_3_top, -1, R.drawable.img_birch_0, R.drawable.img_tree_4_top, -1, R.drawable.img_birch_0, R.drawable.img_tree_5_top, -1, R.drawable.img_birch_0, R.drawable.img_tree_6_top, -1, R.drawable.img_birch_0, -1, R.drawable.img_owl, -1, -1, R.drawable.img_log_elf, -1, -1, R.drawable.img_bear_big, -1, R.drawable.img_bear_little }; private TreeMap<Integer, Bitmap> mWoodObstacles; private ArrayList<Integer> mWoodObstacleList; private int mWoodObstacleIndex = 0; // Top and bottom, no backgrounds private static final int[] CAVE_OBSTACLES = { R.drawable.img_icicle_small_3, -1, R.drawable.img_icicle_small_4, -1, R.drawable.img_icicle_med_3, -1, R.drawable.img_icicle_med_4, -1, R.drawable.img_icicle_lrg_2, -1, -1, R.drawable.img_icicle_small_1, -1, R.drawable.img_icicle_small_2, -1, R.drawable.img_icicle_med_1, -1, R.drawable.img_icicle_med_2, -1, R.drawable.img_icicle_lrg_1, R.drawable.img_icicle_small_3, R.drawable.img_icicle_small_1, R.drawable.img_icicle_small_3, R.drawable.img_icicle_small_2, R.drawable.img_icicle_small_4, R.drawable.img_icicle_small_1, R.drawable.img_icicle_small_4, R.drawable.img_icicle_small_2, R.drawable.img_2_bats, -1, R.drawable.img_3_bats, -1, R.drawable.img_4_bats, -1, R.drawable.img_5_bats, -1, -1, R.drawable.img_yeti, -1, R.drawable.img_mammoth, -1, R.drawable.img_snow_kiss, -1, R.drawable.img_snowman }; private TreeMap<Integer, Bitmap> mCaveObstacles; private ArrayList<Integer> mCaveObstacleList; private int mCaveObstacleIndex = 0; private final static int[] FACTORY_OBSTACLES = { R.drawable.img_icecream_drop, R.drawable.img_icecream_0, R.drawable.img_icecream_drop, R.drawable.img_icecream_1, R.drawable.img_mint_drop_top, R.drawable.img_mint_drop_bottom, R.drawable.img_mint_stack_top, R.drawable.img_mint_stack_bottom, -1, R.drawable.img_candy_cane_0, R.drawable.img_candy_cane_1, -1, -1, R.drawable.img_lollipops, -1, R.drawable.img_choco_fountn, -1, R.drawable.img_candy_buttons, -1, R.drawable.img_mint_gondola, -1, R.drawable.img_candy_cane_0, R.drawable.img_candy_cane_1, -1, -1, R.drawable.img_lollipops, -1, R.drawable.img_choco_fountn, -1, R.drawable.img_candy_buttons, -1, R.drawable.img_mint_gondola, -1, R.drawable.img_candy_cane_0, R.drawable.img_candy_cane_1, -1, -1, R.drawable.img_lollipops, -1, R.drawable.img_choco_fountn, -1, R.drawable.img_candy_buttons, -1, R.drawable.img_mint_gondola }; private TreeMap<Integer, Bitmap> mFactoryObstacles; private ArrayList<Integer> mFactoryObstacleList; private int mFactoryObstacleIndex = 0; private Runnable mGameLoop = new Runnable() { @Override public void run() { processFrame(); } }; @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(LOG_TAG, "onCreate() : " + savedInstanceState); setContentView(R.layout.activity_jet_pack_elf); // App Invites mInvitesFragment = AppInvitesFragment.getInstance(this); // App Measurement mMeasurement = FirebaseAnalytics.getInstance(this); MeasurementManager.recordScreenView(mMeasurement, getString(R.string.analytics_screen_rocket)); // [ANALYTICS SCREEN]: Rocket Sleigh AnalyticsManager.initializeAnalyticsTracker(this); AnalyticsManager.sendScreenView(R.string.analytics_screen_rocket); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { ImmersiveModeHelper.setImmersiveSticky(getWindow()); ImmersiveModeHelper.installSystemUiVisibilityChangeListener(getWindow()); } mIntroVideo = (VideoView) findViewById(R.id.intro_view); mIntroControl = findViewById(R.id.intro_control_view); if (savedInstanceState == null) { String path = "android.resource://" + getPackageName() + "/" + R.raw.jp_background; mBackgroundPlayer = new MediaPlayer(); try { mBackgroundPlayer.setDataSource(this, Uri.parse(path)); mBackgroundPlayer.setLooping(true); mBackgroundPlayer.prepare(); mBackgroundPlayer.start(); } catch (IOException e) { e.printStackTrace(); } boolean nomovie = false; if (getIntent().getBooleanExtra("nomovie", false)) { nomovie = true; } else if (Build.MANUFACTURER.toUpperCase().contains("SAMSUNG")) { // nomovie = true; } if (!nomovie) { mIntroControl.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { endIntro(); } }); path = "android.resource://" + getPackageName() + "/" + R.raw.intro_wipe; mIntroVideo.setVideoURI(Uri.parse(path)); mIntroVideo.setOnCompletionListener(this); mIntroVideo.start(); mMoviePlaying = true; } else { mIntroControl.setOnClickListener(null); mIntroControl.setVisibility(View.GONE); mIntroVideo.setVisibility(View.GONE); } } else { mIntroControl.setOnClickListener(null); mIntroControl.setVisibility(View.GONE); mIntroVideo.setVisibility(View.GONE); } mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); // For hit indication. mHandler = new Handler(); // Get the main UI handler for posting update events DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); Log.d(LOG_TAG, "Width: " + dm.widthPixels + " Height: " + dm.heightPixels + " Density: " + dm.density); mScreenHeight = dm.heightPixels; mScreenWidth = dm.widthPixels; mSlotWidth = mScreenWidth / SLOTS_PER_SCREEN; // Setup the random number generator mRandom = new Random(); mRandom.setSeed( System.currentTimeMillis()); // This is ok. We are not looking for cryptographically secure random here! mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); // Setup the background/foreground mBackgroundLayout = (LinearLayout) findViewById(R.id.background_layout); mBackgroundScroll = (HorizontalScrollView) findViewById(R.id.background_scroll); mForegroundLayout = (LinearLayout) findViewById(R.id.foreground_layout); mForegroundScroll = (HorizontalScrollView) findViewById(R.id.foreground_scroll); mBackgrounds = new Bitmap[6]; mBackgrounds2 = new Bitmap[6]; mExitTransitions = new Bitmap[6]; mEntryTransitions = new Bitmap[6]; // Need to vertically scale background to fit the screen. Checkthe image size // compared to screen size and scale appropriately. We will also use the matrix to translate // as we move through the level. Bitmap bmp = BitmapFactory.decodeResource(getResources(), BACKGROUNDS[0]); Log.d(LOG_TAG, "Bitmap Width: " + bmp.getWidth() + " Height: " + bmp.getHeight() + " Screen Width: " + dm.widthPixels + " Height: " + dm.heightPixels); mScaleY = (float) dm.heightPixels / (float) bmp.getHeight(); mScaleX = (float) (dm.widthPixels * 2) / (float) bmp .getWidth(); // Ensure that a single bitmap is 2 screens worth of time. (Stock xxhdpi image is 3840x1080) if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) { Bitmap tmp = Bitmap.createScaledBitmap(bmp, mScreenWidth * 2, mScreenHeight, false); if (tmp != bmp) { bmp.recycle(); bmp = tmp; } } BackgroundLoadTask.createTwoBitmaps(bmp, mBackgrounds, mBackgrounds2, 0); // Load the initial background view addNextImages(0); addNextImages(0); mWoodObstacles = new TreeMap<Integer, Bitmap>(); mWoodObstacleList = new ArrayList<Integer>(); mWoodObstacleIndex = 0; // We need the bitmaps, so we do pre-load here synchronously. initObstaclesAndPreLoad(WOOD_OBSTACLES, 3, mWoodObstacles, mWoodObstacleList); mCaveObstacles = new TreeMap<Integer, Bitmap>(); mCaveObstacleList = new ArrayList<Integer>(); mCaveObstacleIndex = 0; initObstacles(CAVE_OBSTACLES, 2, mCaveObstacleList); mFactoryObstacles = new TreeMap<Integer, Bitmap>(); mFactoryObstacleList = new ArrayList<Integer>(); mFactoryObstacleIndex = 0; initObstacles(FACTORY_OBSTACLES, 2, mFactoryObstacleList); // Setup the elf mElf = (ImageView) findViewById(R.id.elf_image); mThrust = (ImageView) findViewById(R.id.thrust_image); mElfLayout = (LinearLayout) findViewById(R.id.elf_container); loadElfImages(); updateElf(false); // Elf should be the same height relative to the height of the screen on any platform. Matrix scaleMatrix = new Matrix(); mElfScale = ((float) dm.heightPixels * 0.123f) / (float) mElfBitmap .getHeight(); // On a 1920x1080 xxhdpi screen, this makes the elf 133 pixels which is the height of the drawable. scaleMatrix.preScale(mElfScale, mElfScale); mElf.setImageMatrix(scaleMatrix); mThrust.setImageMatrix(scaleMatrix); mElfPosX = (dm.widthPixels * 15) / 100; // 15% Into the screen mElfPosY = (dm.heightPixels - ((float) mElfBitmap.getHeight() * mElfScale)) / 2; // About 1/2 way down. mElfVelX = (float) dm.widthPixels / 3000.0f; // We start at 3 seconds for a full screen to scroll. mGravityAccelY = (float) (2 * dm.heightPixels) / (float) Math.pow((1.2 * 1000.0), 2.0); // a = 2*d/t^2 Where d = height in pixels and t = 1.2 seconds mThrustAccelY = (float) (2 * dm.heightPixels) / (float) Math.pow((0.7 * 1000.0), 2.0); // a = 2*d/t^2 Where d = height in pixels and t = 0.7 seconds // Setup the control view mControlView = findViewById(R.id.control_view); mGestureDetector = new GestureDetector(this, this); mGestureDetector.setIsLongpressEnabled(true); mGestureDetector.setOnDoubleTapListener(this); mScoreLabel = getString(R.string.score); mScoreText = (TextView) findViewById(R.id.score_text); mScoreText.setText("0"); mPlayPauseButton = (ImageView) findViewById(R.id.play_pause_button); mExit = (ImageView) findViewById(R.id.exit); // Is Tv? mIsTv = TvUtil.isTv(this); if (mIsTv) { mScoreText.setText(mScoreLabel + ": 0"); mPlayPauseButton.setVisibility(View.GONE); mExit.setVisibility(View.GONE); // move scoreLayout position to the Top-Right corner. View scoreLayout = findViewById(R.id.score_layout); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) scoreLayout.getLayoutParams(); params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM); params.addRule(RelativeLayout.ALIGN_PARENT_TOP); params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT); params.addRule(RelativeLayout.ALIGN_PARENT_LEFT); final int marginTop = getResources().getDimensionPixelOffset(R.dimen.overscan_margin_top); final int marginLeft = getResources().getDimensionPixelOffset(R.dimen.overscan_margin_left); params.setMargins(marginLeft, marginTop, 0, 0); scoreLayout.setLayoutParams(params); scoreLayout.setBackground(null); scoreLayout.findViewById(R.id.score_text_seperator).setVisibility(View.GONE); } else { mPlayPauseButton.setEnabled(false); mPlayPauseButton.setOnClickListener(this); mExit.setOnClickListener(this); } mBigPlayButtonLayout = findViewById(R.id.big_play_button_layout); mBigPlayButtonLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // No interaction with the screen below this one. return true; } }); mBigPlayButton = (ImageButton) findViewById(R.id.big_play_button); mBigPlayButton.setOnClickListener(this); // For showing points when getting presents. mPlus100 = (ImageView) findViewById(R.id.plus_100); m100Anim = new AlphaAnimation(1.0f, 0.0f); m100Anim.setDuration(1000); m100Anim.setFillBefore(true); m100Anim.setFillAfter(true); mPlus500 = (ImageView) findViewById(R.id.plus_500); m500Anim = new AlphaAnimation(1.0f, 0.0f); m500Anim.setDuration(1000); m500Anim.setFillBefore(true); m500Anim.setFillAfter(true); // Get the obstacle layouts ready. No obstacles on the first screen of a level. // Prime with a screen full of obstacles. mObstacleLayout = (LinearLayout) findViewById(R.id.obstacles_layout); mObstacleScroll = (HorizontalScrollView) findViewById(R.id.obstacles_scroll); // Initialize the present bitmaps. These are used repeatedly so we keep them loaded. mGiftBoxes = new Bitmap[GIFT_BOXES.length]; for (int i = 0; i < GIFT_BOXES.length; i++) { mGiftBoxes[i] = BitmapFactory.decodeResource(getResources(), GIFT_BOXES[i]); } // Add starting obstacles. First screen has presents. Next 3 get obstacles. addFirstScreenPresents(); // addFinalPresentRun(); // This adds 2 screens of presents // addNextObstacles(0, 1); addNextObstacles(0, 3); // Setup the sound pool mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0); mSoundPool.setOnLoadCompleteListener(this); mCrashSound1 = mSoundPool.load(this, R.raw.jp_crash_1, 1); mCrashSound2 = mSoundPool.load(this, R.raw.jp_crash_2, 1); mCrashSound3 = mSoundPool.load(this, R.raw.jp_crash_3, 1); mGameOverSound = mSoundPool.load(this, R.raw.jp_game_over, 1); mJetThrustSound = mSoundPool.load(this, R.raw.jp_jet_thrust, 1); mLevelUpSound = mSoundPool.load(this, R.raw.jp_level_up, 1); mScoreBigSound = mSoundPool.load(this, R.raw.jp_score_big, 1); mScoreSmallSound = mSoundPool.load(this, R.raw.jp_score_small, 1); mJetThrustStream = 0; if (!mMoviePlaying) { doCountdown(); } } @Override public void onResume() { super.onResume(); Log.d(LOG_TAG, "onResume()"); } @Override public void onPause() { Log.d(LOG_TAG, "onPause()"); if (mMoviePlaying) { if (mIntroVideo != null) { // We are only here if home or lock is pressed or another app (phone) // interrupts. We just go to the pause screen and start the game when // we come back. mIntroVideo.stopPlayback(); mIntroVideo.setVisibility(View.GONE); mIntroControl.setOnClickListener(null); mIntroControl.setVisibility(View.GONE); } mMoviePlaying = false; mIsPlaying = true; // this will make pause() show the pause button. } else if (mCountdownStarted) { mCountdown.setVisibility(View.GONE); mCountDownTimer.cancel(); mCountdownStarted = false; mIsPlaying = true; // this will make pause() show the pause button. } pause(); super.onPause(); } @Override public void onStart() { super.onStart(); mInvitesFragment.getInvite(new AppInvitesFragment.GetInvitationCallback() { @Override public void onInvitation(String invitationId, String deepLink) { Log.d(LOG_TAG, "onInvitation:" + deepLink); } }, false); } @Override public void onDestroy() { super.onDestroy(); Log.d(LOG_TAG, "onDestroy()"); releaseResources(); } private void releaseResources() { if (mSoundPool != null) { if (mBackgroundPlayer != null) { mBackgroundPlayer.stop(); mBackgroundPlayer.release(); mBackgroundPlayer = null; } if (mJetThrustStream > 0) { mSoundPool.stop(mJetThrustStream); mJetThrustStream = 0; } mSoundPool.unload(mCrashSound1); mSoundPool.unload(mCrashSound2); mSoundPool.unload(mCrashSound3); mSoundPool.unload(mGameOverSound); mSoundPool.unload(mJetThrustSound); mSoundPool.unload(mLevelUpSound); mSoundPool.unload(mScoreBigSound); mSoundPool.unload(mScoreSmallSound); mSoundPool.release(); mSoundPool = null; } // recylce big bitmaps as soon as possible. releaseBitmapArray(mBackgrounds); releaseBitmapArray(mBackgrounds2); releaseBitmapArray(mEntryTransitions); releaseBitmapArray(mExitTransitions); releaseBitmapArray(mGiftBoxes); releaseBitmapArray(mElfBurnImages); releaseBitmapArray(mElfImages); releaseBitmapArray(mElfThrustImages); releaseBitmapArray(mElfHitImages); releaseBitmapArray(mElfSmokeImages); releaseIntegerBitmapMap(mWoodObstacles); releaseIntegerBitmapMap(mCaveObstacles); releaseIntegerBitmapMap(mFactoryObstacles); } @Override public void onConfigurationChanged(Configuration config) { // We are eating the config changes so that we don't get destroyed/recreated and again // destroyed/recreated when the lock button is pressed! Log.e(LOG_TAG, "Config change: " + config); super.onConfigurationChanged(config); } @Override public void onBackPressed() { if (mMoviePlaying) { if (mIntroVideo != null) { mIntroVideo.stopPlayback(); mIntroVideo.setVisibility(View.GONE); mIntroControl.setOnClickListener(null); mIntroControl.setVisibility(View.GONE); } mMoviePlaying = false; super.onBackPressed(); } else if (mCountdownStarted) { if (mCountDownTimer != null) { mCountDownTimer.cancel(); mCountDownTimer = null; } mCountdownStarted = false; super.onBackPressed(); } else { if (mIsPlaying) { pause(); } else if (mIsTv) { finish(); } else { play(); } } } @Override public boolean onSingleTapConfirmed(MotionEvent e) { return false; } @Override public boolean onDoubleTap(MotionEvent e) { return false; } @Override public boolean onDoubleTapEvent(MotionEvent e) { return false; } @Override public boolean onDown(MotionEvent e) { return false; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public void onLongPress(MotionEvent e) { } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: //fall through case KeyEvent.KEYCODE_BUTTON_A: if (mIsPlaying) { mElfAccelY = mThrustAccelY; if (!mElfIsHit) { updateElfThrust(1); } mJetThrustStream = mSoundPool.play(mJetThrustSound, 1.0f, 1.0f, 1, -1, 1.0f); } else if (!mCountdownStarted && !mMoviePlaying){ //game is paused. resume it. mBigPlayButton.setPressed(true); } return true; } return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: //fall through case KeyEvent.KEYCODE_BUTTON_A: if (mIsPlaying) { mElfAccelY = 0.0f; if (!mElfIsHit) { updateElfThrust(0); } if (mJetThrustStream > 0) { mSoundPool.stop(mJetThrustStream); mJetThrustStream = 0; } } else if (mMoviePlaying) { endIntro(); } else if (mBigPlayButton.isPressed()){ mBigPlayButton.setPressed(false); mBigPlayButton.performClick(); } return true; case KeyEvent.KEYCODE_BUTTON_B: onBackPressed(); return true; } return super.onKeyUp(keyCode, event); } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mDownEvent = event; mElfAccelY = mThrustAccelY; if (!mElfIsHit) { updateElfThrust(1); } mJetThrustStream = mSoundPool.play(mJetThrustSound, 1.0f, 1.0f, 1, -1, 1.0f); } else if ((event.getActionMasked() == MotionEvent.ACTION_UP) || (event.getActionMasked() == MotionEvent.ACTION_CANCEL)) { mDownEvent = null; mElfAccelY = 0.0f; if (!mElfIsHit) { updateElfThrust(0); } if (mJetThrustStream > 0) { mSoundPool.stop(mJetThrustStream); mJetThrustStream = 0; } } // return mGestureDetector.onTouchEvent(event); return true; } @Override public void onClick(View view) { if (view == mPlayPauseButton) { if (mIsPlaying) { pause(); } else { play(); } } else if (view == mBigPlayButton) { if (!mIsPlaying) { mBigPlayButtonLayout.setVisibility(View.GONE); doCountdown(); } } else if (view == mExit) { finish(); } } @Override public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { } @Override public void onCompletion(MediaPlayer mp) { endIntro(); } private void endIntro() { mMoviePlaying = false; mIntroControl.setOnClickListener(null); mIntroControl.setVisibility(View.GONE); mIntroVideo.setVisibility(View.GONE); doCountdown(); } private void processFrame() { long newTime = System.currentTimeMillis(); long time = newTime - mLastTime; boolean end = false; if (time > 60) { Log.e("LONG", "Frame time took too long! Time: " + time + " Last process frame: " + mLastFrameTime + " Count: " + mBackgroundCount + " Level: " + mLevel); } // We don't want to jump too far so, if real time is > 60 treat it as 33. On screen will seem to slow // down instaead of "jump" if (time > 60) { time = 33; } // Score is based on time + presents. Right now 100 point per second played. No presents yet if (mLevel < 6) { mScore += time; } if (mIsTv) { mScoreText.setText(mScoreLabel + ": " + NumberFormat.getNumberInstance().format((mScore / 10))); } else { mScoreText.setText(NumberFormat.getNumberInstance().format((mScore / 10))); } float scroll = mElfVelX * time; // Do collision detection first... // The elf can't collide if it is within 2 seconds of colliding previously. if (mElfIsHit) { if ((newTime - mElfHitTime) > 2000) { // Move to next state. if (mElfState < 4) { mElfState++; AnalyticsManager.sendEvent(getString(R.string.analytics_screen_rocket), getString(R.string.analytics_action_rocket_hit), null, mElfState); if (mElfState == 4) { mSoundPool.play(mGameOverSound, 1.0f, 1.0f, 2, 0, 1.0f); // No more control... mControlView.setOnTouchListener(null); mElfAccelY = 0.0f; if (mJetThrustStream != 0) { mSoundPool.stop(mJetThrustStream); } } } updateElf(false); mElfIsHit = false; } } else if (mElfState == 4) { // Don't do any collision detection for parachute elf. Just let him fall... } else { // Find the obstacle(s) we might be colliding with. It can only be one of the first 3 obstacles. for (int i = 0; i < 3; i++) { View view = mObstacleLayout.getChildAt(i); if (view == null) { // No more obstacles... break; } int[] tmp = new int[2]; view.getLocationOnScreen(tmp); // If the start of this view is past the center of the elf, we are done if (tmp[0] > mElfPosX) { break; } if (RelativeLayout.class.isInstance(view)) { // this is an obstacle layout. View topView = view.findViewById(R.id.top_view); View bottomView = view.findViewById(R.id.bottom_view); if ((topView != null) && topView.getVisibility() == View.VISIBLE) { topView.getLocationOnScreen(tmp); Rect obsRect = new Rect(tmp[0], tmp[1], tmp[0] + topView.getWidth(), tmp[1] + topView.getHeight()); if (obsRect.contains((int) mElfPosX, (int) mElfPosY + mElfBitmap.getHeight() / 2)) { handleCollision(); } } if (!mElfIsHit) { if ((bottomView != null) && bottomView.getVisibility() == View.VISIBLE) { bottomView.getLocationOnScreen(tmp); Rect obsRect = new Rect(tmp[0], tmp[1], tmp[0] + bottomView.getWidth(), tmp[1] + bottomView.getHeight()); if (obsRect.contains((int) mElfPosX, (int) mElfPosY + mElfBitmap.getHeight() / 2)) { // Special case for the mammoth obstacle... if (bottomView.getTag() != null) { if (((mElfPosX - tmp[0]) / (float) bottomView.getWidth()) > 0.25f) { // We are over the mammoth not the spike. lower the top of the rect and test again. obsRect.top = (int) (tmp[1] + ( (float) bottomView.getHeight() * 0.18f)); if (obsRect.contains((int) mElfPosX, (int) mElfPosY + mElfBitmap.getHeight() / 2)) { handleCollision(); } } } else { handleCollision(); } } } } } else if (FrameLayout.class.isInstance(view)) { // Present view FrameLayout frame = (FrameLayout) view; if (frame.getChildCount() > 0) { ImageView presentView = (ImageView) frame.getChildAt(0); presentView.getLocationOnScreen(tmp); Rect presentRect = new Rect(tmp[0], tmp[1], tmp[0] + presentView.getWidth(), tmp[1] + presentView.getHeight()); mElfLayout.getLocationOnScreen(tmp); Rect elfRect = new Rect(tmp[0], tmp[1], tmp[0] + mElfLayout.getWidth(), tmp[1] + mElfLayout.getHeight()); if (elfRect.intersect(presentRect)) { // We got a present! mPresentCount++; if (mPresentCount < 4) { mSoundPool.play(mScoreSmallSound, 1.0f, 1.0f, 2, 0, 1.0f); mScore += 1000; // 100 points. Score is 10x displayed score. mPlus100.setVisibility(View.VISIBLE); if (mElfPosY > (mScreenHeight / 2)) { mPlus100.setY(mElfPosY - (mElfLayout.getHeight() + mPlus100 .getHeight())); } else { mPlus100.setY(mElfPosY + mElfLayout.getHeight()); } mPlus100.setX(mElfPosX); if (m100Anim.hasStarted()) { m100Anim.reset(); } mPlus100.startAnimation(m100Anim); } else { mSoundPool.play(mScoreBigSound, 1.0f, 1.0f, 2, 0, 1.0f); mScore += 5000; // 500 points. Score is 10x displayed score. if (!mRainingPresents) { mPresentCount = 0; } mPlus500.setVisibility(View.VISIBLE); if (mElfPosY > (mScreenHeight / 2)) { mPlus500.setY(mElfPosY - (mElfLayout.getHeight() + mPlus100 .getHeight())); } else { mPlus500.setY(mElfPosY + mElfLayout.getHeight()); } mPlus500.setX(mElfPosX); if (m500Anim.hasStarted()) { m500Anim.reset(); } mPlus500.startAnimation(m500Anim); mPresentBonus = true; } frame.removeView(presentView); } else if (elfRect.left > presentRect.right) { mPresentCount = 0; } } } } } if (mForegroundLayout.getChildCount() > 0) { int currentX = mForegroundScroll.getScrollX(); View view = mForegroundLayout.getChildAt(0); int newX = currentX + (int) scroll; if (newX > view.getWidth()) { newX -= view.getWidth(); mForegroundLayout.removeViewAt(0); } mForegroundScroll.setScrollX(newX); } // Scroll obstacle views if (mObstacleLayout.getChildCount() > 0) { int currentX = mObstacleScroll.getScrollX(); View view = mObstacleLayout.getChildAt(0); int newX = currentX + (int) scroll; if (newX > view.getWidth()) { newX -= view.getWidth(); mObstacleLayout.removeViewAt(0); } mObstacleScroll.setScrollX(newX); } // Scroll the background and foreground if (mBackgroundLayout.getChildCount() > 0) { int currentX = mBackgroundScroll.getScrollX(); View view = mBackgroundLayout.getChildAt(0); int newX = currentX + (int) scroll; if (newX > view.getWidth()) { newX -= view.getWidth(); mBackgroundLayout.removeViewAt(0); if (view.getTag() != null) { Pair<Integer, Integer> pair = (Pair<Integer, Integer>) view.getTag(); int type = pair.first; int level = pair.second; if (type == 0) { if (mBackgrounds[level] != null) { mBackgrounds[level].recycle(); mBackgrounds[level] = null; } else if (mBackgrounds2[level] != null) { mBackgrounds2[level].recycle(); mBackgrounds2[level] = null; } } else if (type == 1) { if (mExitTransitions[level] != null) { mExitTransitions[level].recycle(); mExitTransitions[level] = null; } } else if (type == 2) { if (mEntryTransitions[level] != null) { mEntryTransitions[level].recycle(); mEntryTransitions[level] = null; } } } if (mBackgroundCount == 5) { if (mLevel < 6) { // Pre-fetch next levels backgrounds // end level uses the index 1 background... int level = (mLevel == 5) ? 1 : (mLevel + 1); BackgroundLoadTask task = new BackgroundLoadTask(getResources(), mLevel + 1, BACKGROUNDS[level], EXIT_TRANSITIONS[mLevel], // Exit transitions are for the current level... ENTRY_TRANSITIONS[level], mScaleX, mScaleY, mBackgrounds, mBackgrounds2, mExitTransitions, mEntryTransitions, mScreenWidth, mScreenHeight); task.execute(); addNextImages(mLevel, true); addNextObstacles(mLevel, 2); } // Fetch first set of obstacles if the next level changes from woods to cave or cave to factory if (mLevel == 1) { // Next level will be caves. Get bitmaps for the first 20 obstacles. ObstacleLoadTask task = new ObstacleLoadTask(getResources(), CAVE_OBSTACLES, mCaveObstacles, mCaveObstacleList, 0, 2, mScaleX, mScaleY); task.execute(); } else if (mLevel == 3) { // Next level will be factory. Get bitmaps for the first 20 obstacles. ObstacleLoadTask task = new ObstacleLoadTask(getResources(), FACTORY_OBSTACLES, mFactoryObstacles, mFactoryObstacleList, 0, 2, mScaleX, mScaleY); task.execute(); } mBackgroundCount++; } else if (mBackgroundCount == 7) { // Add transitions and/or next level if (mLevel < 5) { addNextTransitionImages(mLevel + 1); if (mTransitionImagesCount > 0) { addNextObstacleSpacer(mTransitionImagesCount); } addNextImages(mLevel + 1); // First screen of each new level has no obstacles if ((mLevel % 2) == 1) { addNextObstacleSpacer(1); addNextObstacles(mLevel + 1, 1); } else { addNextObstacles(mLevel + 1, 2); } } else if (mLevel == 5) { addNextTransitionImages(mLevel + 1); if (mTransitionImagesCount > 0) { addNextObstacleSpacer(mTransitionImagesCount); } addFinalImages(); } mBackgroundCount++; } else if (mBackgroundCount == 9) { // Either the transition or the next level is showing if (this.mTransitionImagesCount > 0) { mTransitionImagesCount--; } else { if (mLevel == 1) { // Destroy the wood obstacle bitmaps Thread thread = new Thread(new Runnable() { @Override public void run() { synchronized (mWoodObstacles) { for (Bitmap bmp : mWoodObstacles.values()) { bmp.recycle(); } mWoodObstacles.clear(); } } }); thread.start(); } else if (mLevel == 3) { // Destroy the cave obstacle bitmaps Thread thread = new Thread(new Runnable() { @Override public void run() { synchronized (mCaveObstacles) { for (Bitmap bmp : mCaveObstacles.values()) { bmp.recycle(); } mCaveObstacles.clear(); } } }); thread.start(); } else if (mLevel == 5) { // Destroy the factory obstacle bitmaps Thread thread = new Thread(new Runnable() { @Override public void run() { synchronized (mFactoryObstacles) { for (Bitmap bmp : mFactoryObstacles.values()) { bmp.recycle(); } mFactoryObstacles.clear(); } } }); thread.start(); } mLevel++; // Add an event for clearing this level - note we don't increment mLevel as // it's 0-based and we're tracking the previous level. AnalyticsManager.sendEvent(getString(R.string.analytics_screen_rocket), getString(R.string.analytics_action_rocket_level), null, mLevel); // Achievements if (!mHitLevel) { mCleanLevel = true; } mHitLevel = false; if (mLevel == 5) { mPlus100.setSelected(true); mPlus500.setSelected(true); } else if (mLevel == 6) { mPlus100.setSelected(false); mPlus500.setSelected(false); } if (mLevel < 6) { mSoundPool.play(mLevelUpSound, 1.0f, 1.0f, 2, 0, 1.0f); addNextImages(mLevel); addNextObstacles(mLevel, 2); } mBackgroundCount = 0; } } else { if ((mBackgroundCount % 2) == 1) { if (mLevel < 6) { addNextImages(mLevel); addNextObstacles(mLevel, 2); } } mBackgroundCount++; } } int current = mBackgroundScroll.getScrollX(); mBackgroundScroll.setScrollX(newX); if ((mLevel == 6) && (mBackgroundScroll.getScrollX() == current)) { end = true; } } // Check on the elf boolean hitBottom = false; boolean hitTop = false; float deltaY = mElfVelY * time; mElfPosY = mElfLayout.getY() + deltaY; if (mElfPosY < 0.0f) { mElfPosY = 0.0f; mElfVelY = 0.0f; hitTop = true; } else if (mElfPosY > (mScreenHeight - mElfLayout.getHeight())) { mElfPosY = mScreenHeight - mElfLayout.getHeight(); mElfVelY = 0.0f; hitBottom = true; } else { // Remember -Y is up! mElfVelY += (mGravityAccelY * time - mElfAccelY * time); } mElfLayout.setY(mElfPosY); // Rotate the elf to indicate thrust, dive. float rot = (float) (Math.atan(mElfVelY / mElfVelX) * 120.0 / Math.PI); mElfLayout.setRotation(rot); mElf.invalidate(); // Update the time and spawn the next call to processFrame. mLastTime = newTime; mLastFrameTime = System.currentTimeMillis() - newTime; if (!end) { if ((mElfState < 4) || !hitBottom) { if (mLastFrameTime < 16) { mHandler.postDelayed(mGameLoop, 16 - mLastFrameTime); } else { mHandler.post(mGameLoop); } } else { endGame(); } } else { // Whatever the final stuff is, do it here. mPlayPauseButton.setEnabled(false); mPlayPauseButton.setVisibility(View.INVISIBLE); endGame(); } } private void handleCollision() { // Achievements mHit = true; mHitLevel = true; // Collision! mElfIsHit = true; mElfHitTime = System.currentTimeMillis(); updateElf(true); mVibrator.vibrate(500); if (mElfState == 0) { mSoundPool.play(mCrashSound1, 1.0f, 1.0f, 2, 0, 1.0f); } else if (mElfState == 1) { mSoundPool.play(mCrashSound2, 1.0f, 1.0f, 2, 0, 1.0f); } else if (mElfState == 2) { mSoundPool.play(mCrashSound3, 1.0f, 1.0f, 2, 0, 1.0f); } else if (mElfState == 3) { mSoundPool.play(mCrashSound3, 1.0f, 1.0f, 2, 0, 1.0f); } } private void doCountdown() { mCountdownStarted = true; mPlayPauseButton.setEnabled(false); // Start the countdown if (mCountdown == null){ mCountdown = (TextView) findViewById(R.id.countdown_text); } mCountdown.setVisibility(View.VISIBLE); mCountdown.setTextColor(Color.WHITE); mCountdown.setText("3"); mCountDownTimer = new CountDownTimer(3500, 100) { @Override public void onTick(long millisUntilFinished) { int time = (int) ((millisUntilFinished + 500) / 1000); if (time == 3) { mCountdown.setText("3"); } else if (time == 2) { mCountdown.setText("2"); } else if (time == 1) { mCountdown.setText("1"); } else if (time == 0) { mCountdown.setText("Go!"); } } @Override public void onFinish() { mCountdownStarted = false; mPlayPauseButton.setEnabled(true); play(); mCountdown.setVisibility(View.GONE); } }; mCountDownTimer.start(); } private int mLastObstacle = 0; // 0 - spacer, 1 - upper obstacle, 2 - lower obstacle. These are flags so top + bottom is 3. private void addFirstScreenPresents() { // First 4 slots have no nothing. for (int i = 0; i < Math.min(4, SLOTS_PER_SCREEN); i++) { View view = new View(this); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mSlotWidth, mScreenHeight); mObstacleLayout.addView(view, lp); } // Generate a SIN like pattern; float center = (float) ((mScreenHeight - mGiftBoxes[0].getHeight()) / 2); float presentHeight = (float) mGiftBoxes[0].getHeight(); float[] heights = new float[]{ center, center - presentHeight, center - (1.5f * presentHeight), center - presentHeight, center, center + presentHeight, center + (1.5f * presentHeight), center + presentHeight, center }; // Add presents to the end if (SLOTS_PER_SCREEN > 4) { for (int i = 0; i < (SLOTS_PER_SCREEN - 4); i++) { // Which one? Bitmap bmp = mGiftBoxes[mRandom.nextInt(mGiftBoxes.length)]; ImageView iv = new ImageView(this); iv.setLayerType(View.LAYER_TYPE_HARDWARE, null); iv.setImageBitmap(bmp); // Position the present float left = (mSlotWidth - bmp.getWidth()) / 2; float top = heights[(i % heights.length)]; FrameLayout frame = new FrameLayout(this); LayoutParams flp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); frame.addView(iv, flp); iv.setTranslationX(left); iv.setTranslationY(top); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mSlotWidth, LinearLayout.LayoutParams.MATCH_PARENT); mObstacleLayout.addView(frame, lp); } } // Account for rounding errors in mSlotWidth int extra = (mScreenWidth - (SLOTS_PER_SCREEN * mSlotWidth)); if (extra > 0) { // Add filler to ensure sync with background/foreground scrolls! LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(extra, LinearLayout.LayoutParams.MATCH_PARENT); View view = new View(this); mObstacleLayout.addView(view, lp); } mLastObstacle = 0; } private void addFinalPresentRun() { // Two spacers at the begining. View view = new View(this); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mSlotWidth, mScreenHeight); mObstacleLayout.addView(view, lp); view = new View(this); mObstacleLayout.addView(view, lp); // All of these presents are 500 points (but only if you're awesome) if (mElfState == 0) { mRainingPresents = true; } // SIN wave of presents in the middle float center = (float) (mScreenHeight / 2); float amplitude = (float) (mScreenHeight / 4); int count = (3 * SLOTS_PER_SCREEN) - 4; for (int i = 0; i < count; i++) { float x = (float) ((mSlotWidth - mGiftBoxes[0].getWidth()) / 2); float y = center + (amplitude * (float) Math .sin(2.0 * Math.PI * (double) i / (double) count)); Bitmap bmp = mGiftBoxes[mRandom.nextInt(mGiftBoxes.length)]; ImageView iv = new ImageView(this); iv.setImageBitmap(bmp); iv.setLayerType(View.LAYER_TYPE_HARDWARE, null); FrameLayout frame = new FrameLayout(this); LayoutParams flp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); frame.addView(iv, flp); iv.setTranslationX(x); iv.setTranslationY(y); mObstacleLayout.addView(frame, lp); } // Two spacers at the end. view = new View(this); mObstacleLayout.addView(view, lp); view = new View(this); mObstacleLayout.addView(view, lp); // Account for rounding errors in mSlotWidth int extra = ((3 * mScreenWidth) - (3 * SLOTS_PER_SCREEN * mSlotWidth)); if (extra > 0) { // Add filler to ensure sync with background/foreground scrolls! lp = new LinearLayout.LayoutParams(extra, LinearLayout.LayoutParams.MATCH_PARENT); view = new View(this); mObstacleLayout.addView(view, lp); } } // Pre-populate the random list of obstacles so we can background fetch them. private void initObstacles(int[] resources, int stride, ArrayList<Integer> list) { // Select 200 obstacles randomly. This should be enough for each type of obstacles. // We will wrap if we need more. for (int i = 0; i < 200; i++) { list.add(mRandom.nextInt(resources.length / stride)); } } // Pre-populate the random list of obstacles and pre-load some of the bitmaps. private void initObstaclesAndPreLoad(int[] resources, int stride, TreeMap<Integer, Bitmap> map, ArrayList<Integer> list) { initObstacles(resources, stride, list); // Load the bitmaps for the first 20 obstacles. for (int i = 0; i < 20; i++) { int obstacle = list.get(i); for (int j = (obstacle * stride); j < ((obstacle + 1) * stride); j++) { // Check just in case something is wonky if (j < resources.length) { int id = resources[j]; if (id != -1) { // Only need to load it once... if (!map.containsKey(id)) { Bitmap bmp = BitmapFactory.decodeResource(getResources(), id); if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) { Bitmap tmp = Bitmap.createScaledBitmap(bmp, (int) ((float) bmp.getWidth() * mScaleX), (int) ((float) bmp.getHeight() * mScaleY), false); if (tmp != bmp) { bmp.recycle(); } map.put(id, tmp); } else { map.put(id, bmp); } } } } } } } private void addNextObstacleSpacer(int screens) { if (screens > 0) { View view = new View(this); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mScreenWidth * screens, mScreenHeight); mObstacleLayout.addView(view, lp); mLastObstacle = 0; } } private void addNextObstacles(int level, int screens) { if (level < 2) { addWoodObstacles(screens); } else if (level < 4) { addCaveObstacles(screens); } else { addFactoryObstacles(screens); } } private void addWoodObstacles(int screens) { int totalSlots = screens * SLOTS_PER_SCREEN; for (int i = 0; i < totalSlots; ) { // Any given "slot" has a 1 in 3 chance of having an obstacle if (mRandom.nextInt(3) == 0) { View view = mInflater.inflate(R.layout.obstacle_layout, null); ImageView top = (ImageView) view.findViewById(R.id.top_view); ImageView bottom = (ImageView) view.findViewById(R.id.bottom_view); ImageView back = (ImageView) view.findViewById(R.id.back_view); // Which obstacle? int width = 0; // int obstacle = mRandom.nextInt((WOOD_OBSTACLES.length/3)); if ((mWoodObstacleIndex % 20) == 0) { ObstacleLoadTask task = new ObstacleLoadTask(getResources(), WOOD_OBSTACLES, mWoodObstacles, mWoodObstacleList, mWoodObstacleIndex + 20, 3, mScaleX, mScaleY); task.execute(); } int obstacle = mWoodObstacleList.get(mWoodObstacleIndex++); if (mWoodObstacleIndex >= mWoodObstacleList.size()) { mWoodObstacleIndex = 0; } int topIndex = obstacle * 3; int bottomIndex = topIndex + 1; int backIndex = topIndex + 2; if (WOOD_OBSTACLES[backIndex] != -1) { Bitmap bmp = null; synchronized (mWoodObstacles) { bmp = mWoodObstacles.get(WOOD_OBSTACLES[backIndex]); } while (bmp == null) { synchronized (mWoodObstacles) { bmp = mWoodObstacles.get(WOOD_OBSTACLES[backIndex]); if (bmp == null) { try { mWoodObstacles.wait(); } catch (InterruptedException e) { } } } } width = bmp.getWidth(); back.setImageBitmap(bmp); } else { back.setVisibility(View.GONE); } int currentObstacle = 0; // Same values as mLastObstacle if (WOOD_OBSTACLES[topIndex] != -1) { currentObstacle |= 1; Bitmap bmp = null; synchronized (mWoodObstacles) { bmp = mWoodObstacles.get(WOOD_OBSTACLES[topIndex]); } while (bmp == null) { synchronized (mWoodObstacles) { bmp = mWoodObstacles.get(WOOD_OBSTACLES[topIndex]); if (bmp == null) { try { mWoodObstacles.wait(); } catch (InterruptedException e) { } } } } width = bmp.getWidth(); top.setImageBitmap(bmp); } else { top.setVisibility(View.GONE); } if (WOOD_OBSTACLES[bottomIndex] != -1) { currentObstacle |= 2; Bitmap bmp = null; synchronized (mWoodObstacles) { bmp = mWoodObstacles.get(WOOD_OBSTACLES[bottomIndex]); } while (bmp == null) { synchronized (mWoodObstacles) { bmp = mWoodObstacles.get(WOOD_OBSTACLES[bottomIndex]); if (bmp == null) { try { mWoodObstacles.wait(); } catch (InterruptedException e) { } } } } if (bmp.getWidth() > width) { width = bmp.getWidth(); } bottom.setImageBitmap(bmp); } else { bottom.setVisibility(View.GONE); } int slots = (width / mSlotWidth) + 2; // If last obstacle had a top and this is a bottom or vice versa, insert a space if ((mLastObstacle & 0x1) > 0) { if ((currentObstacle & 0x2) > 0) { addSpaceOrPresent(mSlotWidth); i++; } } else if ((mLastObstacle & 0x2) > 0) { if ((currentObstacle & 0x1) > 0) { addSpaceOrPresent(mSlotWidth); i++; } } // If the new obstacle is too wide for the remaining space, skip it and fill spacer instead if ((i + slots) > totalSlots) { addSpaceOrPresent(mSlotWidth * (totalSlots - i)); i = totalSlots; } else { mLastObstacle = currentObstacle; LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(slots * mSlotWidth, LinearLayout.LayoutParams.WRAP_CONTENT); view.setLayoutParams(lp); mObstacleLayout.addView(view); i += slots; } } else { addSpaceOrPresent(mSlotWidth); i++; } } // Account for rounding errors in mSlotWidth int extra = ((screens * mScreenWidth) - (totalSlots * mSlotWidth)); if (extra > 0) { // Add filler to ensure sync with background/foreground scrolls! LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(extra, LinearLayout.LayoutParams.MATCH_PARENT); View view = new View(this); mObstacleLayout.addView(view, lp); } } private void addCaveObstacles(int screens) { int totalSlots = screens * SLOTS_PER_SCREEN; for (int i = 0; i < totalSlots; ) { // Any given "slot" has a 1 in 3 chance of having an obstacle if (mRandom.nextInt(2) == 0) { View view = mInflater.inflate(R.layout.obstacle_layout, null); ImageView top = (ImageView) view.findViewById(R.id.top_view); ImageView bottom = (ImageView) view.findViewById(R.id.bottom_view); ImageView back = (ImageView) view.findViewById(R.id.back_view); // Which obstacle? int width = 0; if ((mCaveObstacleIndex % 20) == 0) { ObstacleLoadTask task = new ObstacleLoadTask(getResources(), CAVE_OBSTACLES, mCaveObstacles, mCaveObstacleList, mCaveObstacleIndex + 20, 2, mScaleX, mScaleY); task.execute(); } int obstacle = mCaveObstacleList.get(mCaveObstacleIndex++); if (mCaveObstacleIndex >= mCaveObstacleList.size()) { mCaveObstacleIndex = 0; } // int obstacle = mRandom.nextInt((CAVE_OBSTACLES.length/2)); int topIndex = obstacle * 2; int bottomIndex = topIndex + 1; back.setVisibility(View.GONE); int currentObstacle = 0; // Same values as mLastObstacle if (CAVE_OBSTACLES[topIndex] != -1) { currentObstacle |= 1; Bitmap bmp = null; synchronized (mCaveObstacles) { bmp = mCaveObstacles.get(CAVE_OBSTACLES[topIndex]); } while (bmp == null) { synchronized (mCaveObstacles) { bmp = mCaveObstacles.get(CAVE_OBSTACLES[topIndex]); if (bmp == null) { try { mCaveObstacles.wait(); } catch (InterruptedException e) { } } } } width = bmp.getWidth(); top.setImageBitmap(bmp); } else { top.setVisibility(View.GONE); } if (CAVE_OBSTACLES[bottomIndex] != -1) { currentObstacle |= 2; Bitmap bmp = null; synchronized (mCaveObstacles) { bmp = mCaveObstacles.get(CAVE_OBSTACLES[bottomIndex]); } while (bmp == null) { synchronized (mCaveObstacles) { bmp = mCaveObstacles.get(CAVE_OBSTACLES[bottomIndex]); if (bmp == null) { try { mCaveObstacles.wait(); } catch (InterruptedException e) { } } } } if (bmp.getWidth() > width) { width = bmp.getWidth(); } bottom.setImageBitmap(bmp); if (CAVE_OBSTACLES[bottomIndex] == R.drawable.img_mammoth) { // Special case... bottom.setTag(true); } } else { bottom.setVisibility(View.GONE); } int slots = (width / mSlotWidth); slots += 2; // If last obstacle had a top and this is a bottom or vice versa, insert a space if ((mLastObstacle & 0x1) > 0) { if ((currentObstacle & 0x2) > 0) { addSpaceOrPresent(mSlotWidth); i++; } } else if ((mLastObstacle & 0x2) > 0) { if ((currentObstacle & 0x1) > 0) { addSpaceOrPresent(mSlotWidth); i++; } } // If the new obstacle is too wide for the remaining space, skip it and fill spacer instead if ((i + slots) > totalSlots) { addSpaceOrPresent(mSlotWidth * (totalSlots - i)); i = totalSlots; } else { mLastObstacle = currentObstacle; LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(slots * mSlotWidth, LinearLayout.LayoutParams.WRAP_CONTENT); view.setLayoutParams(lp); mObstacleLayout.addView(view); i += slots; } } else { addSpaceOrPresent(mSlotWidth); i++; } } // Account for rounding errors in mSlotWidth int extra = ((screens * mScreenWidth) - (totalSlots * mSlotWidth)); if (extra > 0) { // Add filler to ensure sync with background/foreground scrolls! LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(extra, LinearLayout.LayoutParams.MATCH_PARENT); View view = new View(this); mObstacleLayout.addView(view, lp); } } private void addFactoryObstacles(int screens) { int totalSlots = screens * SLOTS_PER_SCREEN; for (int i = 0; i < totalSlots; ) { // Any given "slot" has a 1 in 3 chance of having an obstacle if (mRandom.nextInt(2) == 0) { View view = mInflater.inflate(R.layout.obstacle_layout, null); ImageView top = (ImageView) view.findViewById(R.id.top_view); ImageView bottom = (ImageView) view.findViewById(R.id.bottom_view); ImageView back = (ImageView) view.findViewById(R.id.back_view); // Which obstacle? int width = 0; // int obstacle = mRandom.nextInt((FACTORY_OBSTACLES.length/2)); if ((mFactoryObstacleIndex % 20) == 0) { ObstacleLoadTask task = new ObstacleLoadTask(getResources(), FACTORY_OBSTACLES, mFactoryObstacles, mFactoryObstacleList, mFactoryObstacleIndex + 20, 2, mScaleX, mScaleY); task.execute(); } int obstacle = mFactoryObstacleList.get(mFactoryObstacleIndex++); if (mFactoryObstacleIndex >= mFactoryObstacleList.size()) { mFactoryObstacleIndex = 0; } int topIndex = obstacle * 2; int bottomIndex = topIndex + 1; back.setVisibility(View.GONE); int currentObstacle = 0; // Same values as mLastObstacle if (FACTORY_OBSTACLES[topIndex] != -1) { currentObstacle |= 1; Bitmap bmp = null; synchronized (mFactoryObstacles) { bmp = mFactoryObstacles.get(FACTORY_OBSTACLES[topIndex]); } while (bmp == null) { synchronized (mFactoryObstacles) { bmp = mFactoryObstacles.get(FACTORY_OBSTACLES[topIndex]); if (bmp == null) { try { mFactoryObstacles.wait(); } catch (InterruptedException e) { } } } } width = bmp.getWidth(); top.setImageBitmap(bmp); } else { top.setVisibility(View.GONE); } if (FACTORY_OBSTACLES[bottomIndex] != -1) { currentObstacle |= 2; Bitmap bmp = null; synchronized (mFactoryObstacles) { bmp = mFactoryObstacles.get(FACTORY_OBSTACLES[bottomIndex]); } while (bmp == null) { synchronized (mFactoryObstacles) { bmp = mFactoryObstacles.get(FACTORY_OBSTACLES[bottomIndex]); if (bmp == null) { try { mFactoryObstacles.wait(); } catch (InterruptedException e) { } } } } if (bmp.getWidth() > width) { width = bmp.getWidth(); } bottom.setImageBitmap(bmp); } else { bottom.setVisibility(View.GONE); } int slots = (width / mSlotWidth); if ((width % mSlotWidth) != 0) { slots++; } // If last obstacle had a top and this is a bottom or vice versa, insert a space if ((mLastObstacle & 0x1) > 0) { if ((currentObstacle & 0x2) > 0) { addSpaceOrPresent(mSlotWidth); i++; } } else if ((mLastObstacle & 0x2) > 0) { if ((currentObstacle & 0x1) > 0) { addSpaceOrPresent(mSlotWidth); i++; } } // If the new obstacle is too wide for the remaining space, skip it and fill spacer instead if ((i + slots) > totalSlots) { addSpaceOrPresent(mSlotWidth * (totalSlots - i)); i = totalSlots; } else { mLastObstacle = currentObstacle; LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(slots * mSlotWidth, LinearLayout.LayoutParams.WRAP_CONTENT); view.setLayoutParams(lp); mObstacleLayout.addView(view); i += slots; } } else { addSpaceOrPresent(mSlotWidth); i++; } } // Account for rounding errors in mSlotWidth int extra = ((screens * mScreenWidth) - (totalSlots * mSlotWidth)); if (extra > 0) { // Add filler to ensure sync with background/foreground scrolls! LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(extra, LinearLayout.LayoutParams.MATCH_PARENT); View view = new View(this); view.setLayerType(View.LAYER_TYPE_HARDWARE, null); mObstacleLayout.addView(view, lp); } } private void addSpaceOrPresent(int width) { if (width > 0) { mLastObstacle = 0; // 1/3 chance of a present. LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(width, LinearLayout.LayoutParams.MATCH_PARENT); if (mRandom.nextInt(3) == 0) { // Present! // Which one? Bitmap bmp = mGiftBoxes[mRandom.nextInt(mGiftBoxes.length)]; ImageView iv = new ImageView(this); iv.setLayerType(View.LAYER_TYPE_HARDWARE, null); iv.setImageBitmap(bmp); // Position the present int left = mRandom.nextInt(width / 2) + (width / 4) - ( (int) ((float) bmp.getWidth() * mScaleX) / 2); int top = mRandom.nextInt(mScreenHeight / 2) + (mScreenHeight / 4) - ( (int) ((float) bmp.getHeight() * mScaleY) / 2); FrameLayout frame = new FrameLayout(this); LayoutParams flp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); frame.addView(iv, flp); iv.setTranslationX(left); iv.setTranslationY(top); mObstacleLayout.addView(frame, lp); } else { // Space View view = new View(this); mObstacleLayout.addView(view, lp); } } } private void addNextImages(int level) { addNextImages(level, false); } private void addNextImages(int level, boolean recycle) { if (level < BACKGROUNDS.length) { // Add the background image ImageView iv = new ImageView(this); iv.setLayerType(View.LAYER_TYPE_HARDWARE, null); // This is being background loaded. Should already be loaded, but if not, wait. while (mBackgrounds[level] == null) { synchronized (mBackgrounds) { if (mBackgrounds[level] == null) { try { mBackgrounds.wait(); } catch (InterruptedException e) { } } } } iv.setImageBitmap(mBackgrounds[level]); if (recycle) { iv.setTag(new Pair<Integer, Integer>(0, level)); } LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); mBackgroundLayout.addView(iv, lp); iv = new ImageView(this); iv.setLayerType(View.LAYER_TYPE_HARDWARE, null); if (recycle) { iv.setTag(new Pair<Integer, Integer>(0, level)); } iv.setImageBitmap(mBackgrounds2[level]); mBackgroundLayout.addView(iv, lp); // Add the foreground image if (FOREGROUNDS[level] == -1) { View view = new View(this); lp = new LinearLayout.LayoutParams(mScreenWidth * 2, 10); mForegroundLayout.addView(view, lp); } else { iv = new ImageView(this); iv.setBackgroundResource(R.drawable.img_snow_ground_tiles); if (recycle) { iv.setTag(level); } lp = new LinearLayout.LayoutParams(mScreenWidth * 2, LinearLayout.LayoutParams.WRAP_CONTENT); mForegroundLayout.addView(iv, lp); iv = new ImageView(this); if (recycle) { iv.setTag(level); } iv.setBackgroundResource(R.drawable.img_snow_ground_tiles); mForegroundLayout.addView(iv, lp); } } } // This is the level we are moving TO. private void addNextTransitionImages(int level) { mTransitionImagesCount = 0; if ((level > 0) && ((level - 1) < EXIT_TRANSITIONS.length)) { if (EXIT_TRANSITIONS[level - 1] != -1) { // Add the exit transition image ImageView iv = new ImageView(this); iv.setTag(new Pair<Integer, Integer>(1, (level - 1))); // This is being background loaded. Should already be loaded, but if not, wait. while (mExitTransitions[level - 1] == null) { synchronized (mExitTransitions) { if (mExitTransitions[level - 1] == null) { try { mExitTransitions.wait(); } catch (InterruptedException e) { continue; } } } } iv.setImageBitmap(mExitTransitions[level - 1]); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); mBackgroundLayout.addView(iv, lp); // No foreground on transistions. Transition images are a single screen long View view = new View(this); lp = new LinearLayout.LayoutParams(mScreenWidth, 10); mForegroundLayout.addView(view, lp); mTransitionImagesCount++; } } if ((level > 0) && (level < ENTRY_TRANSITIONS.length)) { if (ENTRY_TRANSITIONS[level] != -1) { // Add the exit transition image ImageView iv = new ImageView(this); iv.setTag(new Pair<Integer, Integer>(2, level)); // This is being background loaded. Should already be loaded, but if not, wait. while (mEntryTransitions[level] == null) { synchronized (mEntryTransitions) { if (mEntryTransitions[level] == null) { try { mEntryTransitions.wait(); } catch (InterruptedException e) { continue; } } } } iv.setImageBitmap(mEntryTransitions[level]); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); mBackgroundLayout.addView(iv, lp); // No foreground on transistions. Transition images are a single screen long View view = new View(this); lp = new LinearLayout.LayoutParams(mScreenWidth, 10); mForegroundLayout.addView(view, lp); mTransitionImagesCount++; } } } private void addFinalImages() { addNextImages(1); addNextImages(1); // Add presents addFinalPresentRun(); // Add final screen. This is a two screen background. ImageView iv = new ImageView(this); iv.setTag(true); Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.bg_finish); if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) { Bitmap tmp = Bitmap.createScaledBitmap(bmp, mScreenWidth * 2, mScreenHeight, false); if (bmp != tmp) { bmp.recycle(); } bmp = tmp; } iv.setImageBitmap(bmp); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); mBackgroundLayout.addView(iv, lp); View view = new View(this); lp = new LinearLayout.LayoutParams(mScreenWidth * 2, 10); mForegroundLayout.addView(view, lp); addNextObstacleSpacer(2); } // Load the level 1 images right now since we need to display them. // Load the rest on a thread. // We preload all of these because the transitions can be quick. // They are not very big, relatively speaking. private void loadElfImages() { mElfImages = new Bitmap[ELF_IMAGES.length]; mElfHitImages = new Bitmap[ELF_HIT_IMAGES.length]; mElfBurnImages = new Bitmap[ELF_BURN_IMAGES.length]; mElfThrustImages = new Bitmap[ELF_THRUST_IMAGES.length]; mElfSmokeImages = new Bitmap[ELF_SMOKE_IMAGES.length]; mElfImages[0] = BitmapFactory.decodeResource(getResources(), ELF_IMAGES[0]); mElfHitImages[0] = BitmapFactory.decodeResource(getResources(), ELF_HIT_IMAGES[0]); mElfBurnImages[0] = BitmapFactory.decodeResource(getResources(), ELF_BURN_IMAGES[0]); mElfThrustImages[0] = BitmapFactory.decodeResource(getResources(), ELF_THRUST_IMAGES[0]); mElfSmokeImages[0] = BitmapFactory.decodeResource(getResources(), ELF_SMOKE_IMAGES[0]); Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 1; i < ELF_IMAGES.length; i++) { mElfImages[i] = BitmapFactory.decodeResource(getResources(), ELF_IMAGES[i]); } for (int i = 1; i < ELF_HIT_IMAGES.length; i++) { mElfHitImages[i] = BitmapFactory .decodeResource(getResources(), ELF_HIT_IMAGES[i]); } for (int i = 1; i < ELF_BURN_IMAGES.length; i++) { mElfBurnImages[i] = BitmapFactory .decodeResource(getResources(), ELF_BURN_IMAGES[i]); } for (int i = 1; i < ELF_THRUST_IMAGES.length; i++) { mElfThrustImages[i] = BitmapFactory .decodeResource(getResources(), ELF_THRUST_IMAGES[i]); } for (int i = 1; i < ELF_SMOKE_IMAGES.length; i++) { mElfSmokeImages[i] = BitmapFactory .decodeResource(getResources(), ELF_SMOKE_IMAGES[i]); } } }); thread.start(); } private void updateElf(boolean hit) { float thrustWidth = 0.0f; if (hit) { // Just update the elf drawable mElf.setImageDrawable(null); mElfBitmap.recycle(); mElfBitmap = mElfHitImages[mElfState]; mElf.setImageBitmap(mElfBitmap); updateElfThrust(2); thrustWidth = (float) mCurrentTrailBitmap.getWidth() * mElfScale; } else { // New state for elf recycle, reload and reset. mElf.setImageDrawable(null); if (mElfBitmap != null) { mElfBitmap.recycle(); } mThrust.setImageDrawable(null); if (mBurnBitmap != null) { mBurnBitmap.recycle(); } if (mThrustBitmap != null) { mThrustBitmap.recycle(); } if (mSmokeBitmpap != null) { mSmokeBitmpap.recycle(); } if (mElfState < 4) { mBurnBitmap = mElfBurnImages[mElfState]; mThrustBitmap = mElfThrustImages[mElfState]; mSmokeBitmpap = mElfSmokeImages[mElfState]; mElfBitmap = mElfImages[mElfState]; if (mElfAccelY > 0.0f) { updateElfThrust(1); } else { updateElfThrust(0); } thrustWidth = (float) mCurrentTrailBitmap.getWidth() * mElfScale; } else { mElfBitmap = mElfImages[4]; mThrust.setVisibility(View.GONE); } mElf.setImageBitmap(mElfBitmap); } float offset = thrustWidth + ((float) mElfBitmap.getWidth() / 2.0f); mElfLayout.setX(mElfPosX - offset); mElfLayout.setY(mElfPosY); mElfLayout.setPivotX(offset); mElfLayout.setPivotY((float) mElfBitmap.getHeight() * 3.0f / 4.0f); float rot = (float) (Math.atan(mElfVelY / mElfVelX) * 120.0 / Math.PI); mElfLayout.setRotation(rot); mElfLayout.invalidate(); } // 0 - burn, 1 - thrust, 2 - smoke, 3 - gone private void updateElfThrust(int type) { switch (type) { case 0: mCurrentTrailBitmap = mBurnBitmap; break; case 1: mCurrentTrailBitmap = mThrustBitmap; break; case 2: mCurrentTrailBitmap = mSmokeBitmpap; break; case 3: default: mCurrentTrailBitmap = null; break; } if (mCurrentTrailBitmap != null) { mThrust.setImageBitmap(mCurrentTrailBitmap); } } private void pause() { if (mIsPlaying) { mBigPlayButtonLayout.setVisibility(View.VISIBLE); if (!mIsTv) { mExit.setVisibility(View.VISIBLE); } } mIsPlaying = false; mHandler.removeCallbacks(mGameLoop); mControlView.setOnTouchListener(null); mPlayPauseButton.setImageResource(R.drawable.play_button_jp); if (mJetThrustStream > 0) { mSoundPool.stop(mJetThrustStream); mJetThrustStream = 0; } if (mBackgroundPlayer != null) { mBackgroundPlayer.pause(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { ImmersiveModeHelper.setImmersiveStickyWithActionBar(getWindow()); } } private void play() { mIsPlaying = true; mBigPlayButtonLayout.setVisibility(View.GONE); mLastTime = System.currentTimeMillis(); mControlView.setOnTouchListener(this); mHandler.post(mGameLoop); mPlayPauseButton.setImageResource(R.drawable.pause_button_jp); mExit.setVisibility(View.GONE); if (mBackgroundPlayer != null) { mBackgroundPlayer.start(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { ImmersiveModeHelper.setImmersiveSticky(getWindow()); } } private void endGame() { mIsPlaying = false; Intent intent = new Intent(this.getApplicationContext(), EndGameActivity.class); intent.putExtra("score", (long) (mScore / 10)); AnalyticsManager.sendEvent(getString(R.string.analytics_screen_rocket), getString(R.string.analytics_action_rocket_final_score), null, mScore / 10); if (mCleanLevel) { intent.putExtra(getString(R.string.achievement_safe_tapper), true); } if (!mHit) { intent.putExtra(getString(R.string.achievement_untouchable), true); } if (mPresentBonus) { intent.putExtra(getString(R.string.achievement_hidden_presents), true); } if ((mScore / 10) > 10000) { intent.putExtra(getString(R.string.achievement_rocket_junior_score_10000), true); } if ((mScore / 10) > 30000) { intent.putExtra(getString(R.string.achievement_rocket_intermediate_score_30000), true); } if ((mScore / 10) > 50000) { intent.putExtra(getString(R.string.achievement_rocket_pro_score_50000), true); } startActivity(intent); overridePendingTransition(R.anim.fade_in, R.anim.fade_out); finish(); } private void releaseIntegerBitmapMap(Map<Integer, Bitmap> map) { Iterator<Map.Entry<Integer, Bitmap>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Integer, Bitmap> entry = it.next(); Bitmap bitmap = entry.getValue(); if (bitmap != null) { bitmap.recycle(); } } } private void releaseBitmapArray(Bitmap[] bitmapArray) { if (bitmapArray != null) { for (Bitmap bitmap : bitmapArray) { if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); } } } } }