こんにちは。@stachibanaです。
本エントリーはAndroid Advent Calendar 2012の表エントリーです。本日の裏エントリーは@katsummyさんです。
NDKとかADKとかaaptとかガチなネタが続いている中、空気を読まずにソフトなネタを投入します。
AndEngineでつくるAndroid 2Dゲーム (SMART GAME DEVELOPER) 立花 翔 翔泳社 |
宣伝になりますが、12/13日に翔泳社さんより拙著「AndEngineでつくるAndroid 2Dゲーム」が発売されました。
iPhone業界ではカジュアルゲームで1つで700万稼いだとか、3週間で1000万儲かったとかという話が話題ですね。
さすがにこれは一握りですが、1つのゲームで100万位になった例は結構あります。Androidでもランキングを見ているとその傾向が見られますね。近いうちでしょう。
ということで、今回はゲーム作りを解説してみたいと思います。
これから作るゲームはPlayストアで公開されてますのでまずはそちらをどうぞ。
仮に、「赤い水牛」という飲めばいくらでも働ける炭酸飲料があったとします。それを振って振って振りまくり、噴射させることによって疲れて机で寝てしまっている社員達にぶつけ、翼をさずけられたら素敵だと思いませんか?
Androidの開発環境が揃っている方なら1時間もあれば作れますので、是非体験してみて下さい。
尚、必要な画像素材はここからダウンロードして下さい。ダウンロードできたら、解凍してassetsフォルダにサブフォルダごと放り込んで下さい。
では始めましょう。Androidの開発環境は入っていることを前提に進めます。
※ 本エントリはJavaのこと、Androidのことが普通に分かる方向けに大分はしょって書いています。本の方にはもっともっともーーーーっと優しく書いてありますのでご安心を。
最初にAndEngineを落とします。
AndEngineのサイトを開き、プロジェクトをgitなりzipなりでローカルに保存します。
ライブラリの形式で配布されていますので、落とせたら、Eclipseのメニュー > インポートからインポートします。ワークスペースにコピーすることをおすすめします。
ゲーム画面を作る前に、ゲームの雛形を作りましょう。
でも解説するのがめんどくさいのでここからダウンロードして下さい。
簡単に言うと、リソースの読み込み関係のユーティリティクラスや、シーンの遷移関連の関数等をまとめた抽象クラスとかが出来上がった状態のプロジェクトです。
詳細の解説は本の方に書いてあるのでそちらをどうぞ。
AndEngineライブラリへのパスは同じディレクトリを指定していますので、ワークスペースにコピーしていない方は修正してください。
まず、MainActivityを編集しましょう。以下の実装を加えます。
Activityでは何も描画をせず、起動するシーンを指示し、描画や処理はシーンが行います。
- 最初に起動するシーンをゲームのシーンからトップ画面のシーンに変更
- 毎フレーム加速度センサーから値を取得し、ゲーム進行中ならゲームのシーンに値を渡す実装
- シーン遷移用の実装
- ハードウェアキー押下時の実装
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | package biz.stachibana.ae.redbaffalo; import org.andengine.engine.camera.Camera; import org.andengine.engine.handler.IUpdateHandler; import org.andengine.engine.options.EngineOptions; import org.andengine.engine.options.ScreenOrientation; import org.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy; import org.andengine.entity.scene.Scene; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.view.KeyEvent; public class MainActivity extends MultiSceneActivity implements SensorEventListener { // 画面のサイズ。 private int CAMERA_WIDTH = 480; private int CAMERA_HEIGHT = 800; // 傾きの量 private float velX; private float velY; private float velZ; // センサーマネージャ SensorManager mSensorManager; // Activity起動時最初に呼び出される public EngineOptions onCreateEngineOptions() { // サイズを指定し描画範囲をインスタンス化 final Camera camera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT); // ゲームのエンジンを初期化。 // 第1引数 タイトルバーを表示しないモード // 第2引数 画面は縦向き(幅480、高さ800) // 第3引数 解像度の縦横比を保ったまま最大まで拡大する // 第4引数 描画範囲 EngineOptions eo = new EngineOptions(true, ScreenOrientation.PORTRAIT_FIXED, new RatioResolutionPolicy( CAMERA_WIDTH, CAMERA_HEIGHT), camera); return eo; } // 起動するシーンを返す @Override protected Scene onCreateScene() { // センサー系の処理を管理するクラス mSensorManager = (SensorManager) this .getSystemService(Context.SENSOR_SERVICE); // センサーイベントのリスナーを設定 mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); // ゲーム画面(MainScene)が起動中の時のみ、加速度センサーから取得した値を渡す this.mEngine.registerUpdateHandler(new IUpdateHandler() { public void onUpdate(float pSecondsElapsed) { if (getEngine().getScene() instanceof MainScene) { ((MainScene) getEngine().getScene()).updateByActivity(velX, velY, velZ); } } public void reset() { } }); // InitialSceneをインスタンス化し、返す // 同時に、遷移用の配列にも追加 InitialScene initialScene = new InitialScene(this); getSceneArray().add(initialScene); return initialScene; } // Activityが参照するレイアウトID @Override protected int getLayoutID() { // ActivityのレイアウトのIDを返す return R.layout.activity_main; } // レイアウト内で、シーンがセットされるViewのID @Override protected int getRenderSurfaceViewID() { // SceneがセットされるViewのIDを返す return R.id.renderview; } // 遷移用の配列に新たなシーンを追加 @Override public void appendScene(KeyListenScene scene) { getSceneArray().add(scene); } // 遷移用の配列から全てのシーンを削除し、トップ画面を表示する @Override public void backToInitial() { getSceneArray().clear(); KeyListenScene scene = new InitialScene(this); getSceneArray().add(scene); getEngine().setScene(scene); } // 現在のシーンを切り替える。遷移用の配列の状態は変更しない @Override public void refreshRunningScene(KeyListenScene scene) { getSceneArray().remove(getSceneArray().size() - 1); getSceneArray().add(scene); getEngine().setScene(scene); } // センサーイベント @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } // センサーイベント @Override public void onSensorChanged(SensorEvent event) { synchronized (this) { switch (event.sensor.getType()) { // 加速度センサーの場合 case Sensor.TYPE_ACCELEROMETER: // 値を格納 velX = event.values[1]; velY = event.values[0]; velZ = event.values[2]; break; } } } // ハードウェア押下時の処理 @Override public boolean dispatchKeyEvent(KeyEvent e) { // バックボタンが押された時 if (e.getAction() == KeyEvent.ACTION_DOWN && e.getKeyCode() == KeyEvent.KEYCODE_BACK) { // 起動中のSceneのdispatchKeyEvent関数を呼び出し。追加の処理が必要な時はfalseが // 返ってくる為、処理 if (!getSceneArray().get(getSceneArray().size() - 1) .dispatchKeyEvent(e)) { // Sceneが1つしか起動していない時はゲームを終了 if (getSceneArray().size() == 1) { // キャッシュしたテクスチャを廃棄 ResourceUtil.getInstance(this).resetAllTexture(); finish(); } // 複数のSceneが起動している時は1つ前のシーンへ戻る else { getEngine().setScene( getSceneArray().get(getSceneArray().size() - 2)); getSceneArray().remove(getSceneArray().size() - 1); } } // メニューボタンが押された時 } else if (e.getAction() == KeyEvent.ACTION_DOWN && e.getKeyCode() == KeyEvent.KEYCODE_MENU) { // 起動中のシーンにキーイベントを送信 getSceneArray().get(getSceneArray().size() - 1).dispatchKeyEvent(e); return true; } return true; } } |
次に、MainSceneを編集しましょう。以下の実装を加えます。
MainSceneは実際にゲームを描画するシーンです。
- イニシャライズの処理
- 振り開始までのカウントダウン処理
- 振りによってパワーを蓄積させる処理
- フレーム毎に缶や社員を移動させたり、画像を切り替えたりする処理
- ゲームオーバーの処理
- ゲームオーバー画面からのボタン押下時の処理
MainScene.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 | package biz.stachibana.ae.redbaffalo; import org.andengine.engine.handler.timer.ITimerCallback; import org.andengine.engine.handler.timer.TimerHandler; import org.andengine.entity.modifier.FadeInModifier; import org.andengine.entity.modifier.MoveModifier; import org.andengine.entity.modifier.ParallelEntityModifier; import org.andengine.entity.modifier.RotationByModifier; import org.andengine.entity.modifier.ScaleModifier; import org.andengine.entity.sprite.AnimatedSprite; import org.andengine.entity.sprite.ButtonSprite; import org.andengine.entity.sprite.Sprite; import org.andengine.entity.text.Text; import org.andengine.entity.text.TextOptions; import org.andengine.opengl.font.BitmapFont; import org.andengine.opengl.font.Font; import org.andengine.opengl.texture.Texture; import org.andengine.opengl.texture.TextureOptions; import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas; import org.andengine.opengl.vbo.DrawType; import org.andengine.util.HorizontalAlign; import org.andengine.util.color.Color; import org.andengine.util.modifier.ease.EaseBackOut; import org.andengine.util.modifier.ease.EaseBounceInOut; import android.content.Intent; import android.graphics.Typeface; import android.view.KeyEvent; public class MainScene extends KeyListenScene implements ButtonSprite.OnClickListener { // 社員の状態管理用タグ private static final int TAG_WORKER = 10; // ボタン用タグ private static final int BTN_RETRY = 21; private static final int BTN_MENU = 22; private static final int BTN_RANKING = 23; private static final int BTN_TWEET = 24; // 5秒間で蓄積したパワー(加速度センサーから取得) private int power; // パワーの追加を受け付けるか否か private boolean isAcceptShake; // カウントダウン用整数 private int countDownNum = 4; // 缶画像 private AnimatedSprite canSprite; // 指示のテキスト private Text instructionText; // 缶が社員に当たった時に点滅する画像 private Sprite hitSprite; // 点数 private int score; // 結果画面に表示されるテキスト private Text resultText; // コンストラクタ public MainScene(MultiSceneActivity baseActivity) { super(baseActivity); init(); } // イニシャライザ public void init() { // 閉経画像をインスタンス化しシーンに貼り付け attachChild(getBaseActivity().getResourceUtil() .getSprite("main_bg.png")); // フォント用のTextureを用意 Texture textureCountDown = new BitmapTextureAtlas(getBaseActivity() .getTextureManager(), 512, 512, TextureOptions.BILINEAR_PREMULTIPLYALPHA); // フォントをイニシャライズ Font fontCountDown = new Font(getBaseActivity().getFontManager(), textureCountDown, Typeface.DEFAULT_BOLD, 100, true, Color.BLACK); // EngineのTextureManagerにフォントTextureを読み込み getBaseActivity().getTextureManager().loadTexture(textureCountDown); // FontManagerにフォントを読み込み getBaseActivity().getFontManager().loadFont(fontCountDown); // 読み込んだフォントを利用して得点を表示 // 指示用テキスト用のビットマップフォントを生成 BitmapFont bitmapFont = new BitmapFont(getBaseActivity() .getTextureManager(), getBaseActivity().getAssets(), "font/red.fnt"); // 読み込み bitmapFont.load(); // 指示用テキスト instructionText = new Text(20, 40, bitmapFont, "", 20, new TextOptions( HorizontalAlign.LEFT), getBaseActivity() .getVertexBufferObjectManager(), DrawType.DYNAMIC); // 貼り付け attachChild(instructionText); // 缶画像をイニシャライズ。1枚の画像を縦1マス、横3マスに区切ってイニシャライズし、1マス目を使ってインスタンス化 canSprite = getBaseActivity().getResourceUtil().getAnimatedSprite( "main_can.png", 1, 3); // x座標は画面中心、y座標は210に設定 placeToCenterX(canSprite, 210); // 重なりを設定。引数が大きい方が上に表示される canSprite.setZIndex(2); // 貼り付け attachChild(canSprite); // z-indexを反映 sortChildren(); // 缶が社員に当たった時に点滅する画像 hitSprite = getBaseActivity().getResourceUtil().getSprite( "main_hit.png"); hitSprite.setAlpha(0); placeToCenterX(hitSprite, 320); attachChild(hitSprite); hitSprite.setZIndex(1); sortChildren(); // 振り開始までのカウントダウン開始 registerUpdateHandler(new TimerHandler(1, new ITimerCallback() { @Override public void onTimePassed(TimerHandler pTimerHandler) { countDown(); } })); } // 振り開始までのカウントダウン public void countDown() { // 振り開始前 if (!isAcceptShake) { countDownNum--; if (countDownNum == 0) { countDownNum = 5; instructionText.setText("5秒間振れ!"); instructionText.setPosition(getBaseActivity().getEngine() .getCamera().getWidth() / 2 - instructionText.getWidth() / 2, instructionText.getY()); isAcceptShake = true; } else { instructionText.setText("" + countDownNum); instructionText.setPosition(getBaseActivity().getEngine() .getCamera().getWidth() / 2 - instructionText.getWidth() / 2, instructionText.getY()); } // 1秒後に再び関数countDown()を呼び出す registerUpdateHandler(new TimerHandler(1.0f, new ITimerCallback() { public void onTimePassed(TimerHandler pTimerHandler) { countDown(); } })); // 振り受付中 } else { countDownNum--; if (countDownNum > 0) { // 1秒後に再び関数countDown()を呼び出す TimerHandler delayHandler = new TimerHandler(1.0f, new ITimerCallback() { public void onTimePassed(TimerHandler pTimerHandler) { countDown(); } }); registerUpdateHandler(delayHandler); } else { // 振りの受付を終了 isAcceptShake = false; instructionText.setText("終了!"); instructionText.setPosition(getBaseActivity().getEngine() .getCamera().getWidth() / 2 - instructionText.getWidth() / 2, instructionText.getY()); // 缶画像をアニメーションさせながら画面下端に移動 canSprite.registerEntityModifier(new ParallelEntityModifier( new RotationByModifier(1.0f, 2340), new MoveModifier( 1.0f, canSprite.getX(), canSprite.getX(), canSprite.getY(), canSprite.getY() + 230, EaseBackOut.getInstance()), new ScaleModifier( 1.0f, 1, 0.1f))); // 指示用テキストを消去。社員を準備する処理を行う関数startWakeUpを呼び出す registerUpdateHandler(new TimerHandler(2.0f, new ITimerCallback() { public void onTimePassed(TimerHandler pTimerHandler) { instructionText.detachSelf(); startWakeUp(); } })); } } } // Activityから加速度センサーの値が渡ってくる public void updateByActivity(float velX, float velY, float velZ) { // 振り受け中でなければ(カウントダウン時、ゲームオーバー後等) 何もしない if (!isAcceptShake) { return; } // パワーを蓄積 power += Math.round((Math.abs(velX) + Math.abs(velY) + Math.abs(velZ))); // 蓄積したパワーによって缶の画像をふくらんだ物に変更 if (power > 5000 && canSprite.getCurrentTileIndex() == 0) { canSprite.setCurrentTileIndex(1); } else if (power > 10000 && canSprite.getCurrentTileIndex() == 1) { canSprite.setCurrentTileIndex(2); } } // 的となる社員を準備 public void startWakeUp() { // 社員をシーンに貼り付け。 for (int i = 0; i < 7; i++) { Sprite utsu = getBaseActivity().getResourceUtil().getSprite( "utsu.png"); placeToCenterX(utsu, 250 - utsu.getHeight() * i); utsu.setTag(TAG_WORKER); attachChild(utsu); sortChildren(); } // 缶を画面中心まで移動 canSprite.registerEntityModifier(new MoveModifier(0.1f, canSprite .getX(), canSprite.getX(), canSprite.getY(), canSprite.getY() - 300)); // 毎フレーム毎にupdateHandlerを呼び出し開始 registerUpdateHandler(new TimerHandler(0.1f, new ITimerCallback() { @Override public void onTimePassed(TimerHandler pTimerHandler) { registerUpdateHandler(updateHandler); } })); } // 缶が飛んでいる缶の描画を行うハンドラ。毎フレーム呼び出される public TimerHandler updateHandler = new TimerHandler(1f / 60f, true, new ITimerCallback() { public void onTimePassed(TimerHandler pTimerHandler) { // 残りパワーによって社員のスクロールスピードを調整。段々遅く。 float speed; if (power > 5000) { speed = 40; power -= 20; } else if (power > 4000) { speed = 30; power -= 15; } else if (power > 3000) { speed = 20; power -= 10; } else if (power > 2000) { speed = 10; power -= 5; } else if (power > 1000) { speed = 5; power -= 3; } else { speed = 0; // アップデートハンドラを停止 MainScene.this.unregisterUpdateHandler(updateHandler); // 2秒後にゲームオーバー処理 registerUpdateHandler(new TimerHandler(2.0f, new ITimerCallback() { @Override public void onTimePassed( TimerHandler pTimerHandler) { showGameOver(); return; } })); } // シーン上の画像を1つづつ取得 for (int i = 0; i < getChildCount(); i++) { if (getChildByIndex(i) instanceof Sprite) { Sprite worker = (Sprite) getChildByIndex(i); // 社員の画像なら移動処理 if (worker.getTag() == TAG_WORKER) { // 移動 worker.setPosition(worker.getX(), worker.getY() + speed); // 画面外に出たら画面上に移動 if (worker.getY() > 800 + 154) { worker.setPosition(worker.getX(), worker.getY() - 154 * 7); // 不透明に worker.setAlpha(1); // 子要素を削除 worker.detachChildren(); // 画面中央まで来たら } else if (worker.getY() > 200 + 154) { if (worker.getAlpha() != 0) { // 透明に worker.setAlpha(0); // 翼を授ける!(画像に画像を追加) Sprite fine = getBaseActivity() .getResourceUtil().getSprite( "fine.png"); worker.attachChild(fine); // ヒット画像を点滅 hitSprite.setAlpha(1); registerUpdateHandler(new TimerHandler( 0.05f, new ITimerCallback() { @Override public void onTimePassed( TimerHandler pTimerHandler) { hitSprite.setAlpha(0); } })); //スコアをインクリメント score++; } } } } } } }); @Override public void prepareSoundAndMusic() { } @Override public boolean dispatchKeyEvent(KeyEvent e) { return false; } // ゲームオーバー処理 public void showGameOver() { Sprite resultBg = getBaseActivity().getResourceUtil().getSprite( "result_bg.png"); placeToCenter(resultBg); attachChild(resultBg); BitmapFont bitmapFont = new BitmapFont(getBaseActivity() .getTextureManager(), getBaseActivity().getAssets(), "font/result.fnt"); bitmapFont.load(); resultText = new Text(46, 73, bitmapFont, "0", 20, new TextOptions( HorizontalAlign.LEFT), getBaseActivity() .getVertexBufferObjectManager()); resultBg.attachChild(resultText); // スコアアップ registerUpdateHandler(resultIncrementHandler); // ボタンを追加 ButtonSprite btnRetry = getBaseActivity().getResourceUtil() .getButtonSprite("result_btn_01.png", "result_btn_01_p.png"); btnRetry.setPosition(33, 220); btnRetry.setTag(BTN_RETRY); btnRetry.setOnClickListener(this); resultBg.attachChild(btnRetry); registerTouchArea(btnRetry); ButtonSprite btnMenu = getBaseActivity().getResourceUtil() .getButtonSprite("result_btn_02.png", "result_btn_02_p.png"); btnMenu.setPosition(33, 312); btnMenu.setTag(BTN_MENU); btnMenu.setOnClickListener(this); resultBg.attachChild(btnMenu); registerTouchArea(btnMenu); ButtonSprite btnRanking = getBaseActivity().getResourceUtil() .getButtonSprite("result_btn_03.png", "result_btn_03_p.png"); btnRanking.setPosition(33, 404); btnRanking.setTag(BTN_RANKING); btnRanking.setOnClickListener(this); resultBg.attachChild(btnRanking); registerTouchArea(btnRanking); ButtonSprite btnShare = getBaseActivity().getResourceUtil() .getButtonSprite("result_btn_04.png", "result_btn_04_p.png"); btnShare.setPosition(33, 496); btnShare.setTag(BTN_TWEET); btnShare.setOnClickListener(this); resultBg.attachChild(btnShare); registerTouchArea(btnShare); // 上から滑り落ちてくるエフェクト resultBg.registerEntityModifier(new ParallelEntityModifier( new FadeInModifier(1.0f), new MoveModifier(1.0f, resultBg .getX(), resultBg.getX(), resultBg.getY() - 500, resultBg.getY(), EaseBounceInOut.getInstance()))); } // スコアを1づつ増やしながら表示 public TimerHandler resultIncrementHandler = new TimerHandler(1f / 60f, true, new ITimerCallback() { @Override public void onTimePassed(TimerHandler pTimerHandler) { if (Integer.parseInt(resultText.getText().toString()) < score) { resultText.setText("" + (Integer.parseInt(resultText.getText() .toString()) + 1)); } else { MainScene.this .unregisterUpdateHandler(resultIncrementHandler); } } }); // ボタン押下時の処理 @Override public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX, float pTouchAreaLocalY) { switch (pButtonSprite.getTag()) { case BTN_RETRY: MainScene newScene = new MainScene(getBaseActivity()); getBaseActivity().refreshRunningScene(newScene); break; case BTN_MENU: getBaseActivity().backToInitial(); break; case BTN_RANKING: break; case BTN_TWEET: Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType("text/plain"); sendIntent.putExtra(Intent.EXTRA_TEXT, "Androidゲーム「赤い水牛」で" + score + "人日ゲット!ダウンロード&作り方 → http://stachibana.biz #androidadvent2012"); getBaseActivity().startActivity(sendIntent); break; } } } |
次に、InitialSceneを編集しましょう。以下の実装を加えます。
InitialSceneはゲームのトップ画面(メニュー画面)を表示するシーンです。
- タイトル画像の追加
- タイトルロゴの追加・アニメーション
- ボタンの追加・アニメーション
- ランキングの表示処理
InitialScene.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | package biz.stachibana.ae.redbaffalo; import org.andengine.entity.modifier.DelayModifier; import org.andengine.entity.modifier.FadeInModifier; import org.andengine.entity.modifier.MoveModifier; import org.andengine.entity.modifier.ParallelEntityModifier; import org.andengine.entity.modifier.SequenceEntityModifier; import org.andengine.entity.sprite.ButtonSprite; import org.andengine.entity.sprite.Sprite; import org.andengine.util.modifier.ease.EaseBackInOut; import org.andengine.util.modifier.ease.EaseBounceInOut; import android.content.Intent; import android.net.Uri; import android.view.KeyEvent; public class InitialScene extends KeyListenScene implements ButtonSprite.OnClickListener { // ボタン用タグ private static final int INITIAL_START = 1; private static final int INITIAL_RANKING = 2; private static final int INITIAL_INFO = 3; // コンストラクタ public InitialScene(MultiSceneActivity context) { super(context); init(); } // イニシャライザ @Override public void init() { // 背景画像を追加 attachChild(getBaseActivity().getResourceUtil().getSprite( "initial_bg.png")); // 鬱社員を追加 Sprite title01 = getBaseActivity().getResourceUtil().getSprite( "initial_title_01.png"); placeToCenterX(title01, 100); attachChild(title01); // ロゴを追加 Sprite title02 = getBaseActivity().getResourceUtil().getSprite( "initial_title_02.png"); placeToCenterX(title02, 290); attachChild(title02); title02.setAlpha(0); // ぼわ〜んっとアニメーション title02.registerEntityModifier(new ParallelEntityModifier( new FadeInModifier(1.0f), new MoveModifier(1.0f, title02.getX(), title02.getX(), title02.getY() + 100, title02.getY(), EaseBounceInOut.getInstance()))); // ボタンを追加 ButtonSprite btnStart = getBaseActivity().getResourceUtil() .getButtonSprite("initial_btn_01.png", "initial_btn_01_p.png"); placeToCenterX(btnStart, 570); btnStart.setTag(INITIAL_START); btnStart.setOnClickListener(this); attachChild(btnStart); registerTouchArea(btnStart); // ボタンは初期段階では画面右外に配置。1つづつ時間差で滑り込んでくるアニメーション float btnX = btnStart.getX(); btnStart.setPosition(btnStart.getX() + getBaseActivity().getEngine().getCamera().getWidth(), btnStart.getY()); btnStart.registerEntityModifier(new SequenceEntityModifier( new DelayModifier(1.4f), new MoveModifier(1.0f, btnStart.getX(), btnX, btnStart.getY(), btnStart.getY(), EaseBackInOut.getInstance()))); ButtonSprite btnRanking = getBaseActivity().getResourceUtil() .getButtonSprite("initial_btn_02.png", "initial_btn_02_p.png"); placeToCenterX(btnRanking, 660); btnRanking.setTag(INITIAL_RANKING); btnRanking.setOnClickListener(this); attachChild(btnRanking); registerTouchArea(btnRanking); btnX = btnRanking.getX(); btnRanking.setPosition(btnRanking.getX() + getBaseActivity().getEngine().getCamera().getWidth(), btnRanking.getY()); btnRanking.registerEntityModifier(new SequenceEntityModifier( new DelayModifier(1.6f), new MoveModifier(1.0f, btnRanking .getX(), btnX, btnRanking.getY(), btnRanking.getY(), EaseBackInOut.getInstance()))); ButtonSprite btnRecommend = getBaseActivity().getResourceUtil() .getButtonSprite("initial_btn_03.png", "initial_btn_03_p.png"); btnRecommend.setPosition(200, 20); btnRecommend.setTag(INITIAL_INFO); btnRecommend.setOnClickListener(this); attachChild(btnRecommend); registerTouchArea(btnRecommend); btnX = btnRecommend.getX(); btnRecommend.setPosition(btnRecommend.getX() + getBaseActivity().getEngine().getCamera().getWidth(), btnRecommend.getY()); btnRecommend.registerEntityModifier(new SequenceEntityModifier( new DelayModifier(1.8f), new MoveModifier(1.0f, btnRecommend .getX(), btnX, btnRecommend.getY(), btnRecommend.getY(), EaseBackInOut.getInstance()))); } @Override public void prepareSoundAndMusic() { } @Override public boolean dispatchKeyEvent(KeyEvent e) { return false; } // ボタン押下時の処理 public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX, float pTouchAreaLocalY) { switch (pButtonSprite.getTag()) { case INITIAL_START: // リソースの解放 ResourceUtil.getInstance(getBaseActivity()).resetAllTexture(); KeyListenScene scene = new MainScene(getBaseActivity()); // MainSceneへ移動 getBaseActivity().getEngine().setScene(scene); // 遷移管理用配列に追加 getBaseActivity().appendScene(scene); break; case INITIAL_RANKING: break; case INITIAL_INFO: Intent it = new Intent(Intent.ACTION_VIEW, Uri.parse("http://stachibana.biz")); getBaseActivity().startActivity(it); break; } } } |
オンラインランキングはOpenFeintが定番だったのですが、サービス終了してしまいました。
Pankiaとかscoreloopとかありますが実装がめんどくさいものばかりですので、シンプルに実装できるRank Parkを使いましょう。
まだ出たばかりで最小限の機能しかないですが、5分で実装できて見た目もなかなかなのでオススメです。
Rank Parkのサイトにアクセス、アプリの登録をし、SDKをダウンロードします。
SDKはjarなのでlibsに放り込んで、プロジェクトに追加してください。
追加できたら、AndroidManifest.xmlを編集します。
AndroidManifest.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // // 略 // <!-- 追加 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="13" /> // // 略 // </activity> <!-- 追加 --> <activity android:name="com.rankpark.Rankpark_Ranking" android:screenOrientation="portrait" > </activity> </application> |
続いて、InitialSceneです。importは省略。
InitialScene.java
1 2 3 4 5 6 7 8 | case INITIAL_RANKING: // 追加 // ランキングを表示 RankPark.rp_view(getBaseActivity(), getBaseActivity() .getPackageName(), Settings.Secure.getString( getBaseActivity().getContentResolver(), Settings.Secure.ANDROID_ID)); break; |
最後に、MainSceneです。スコアの登録と表示を行います。同じくimportは省略。
MainScene.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // ゲームオーバー処理 public void showGameOver() { // // 略 // // 上から滑り落ちてくるエフェクト resultBg.registerEntityModifier(new ParallelEntityModifier( new FadeInModifier(1.0f), new MoveModifier(1.0f, resultBg .getX(), resultBg.getX(), resultBg.getY() - 500, resultBg.getY(), EaseBounceInOut.getInstance()))); // 追加 // スコアをポスト getBaseActivity().runOnUiThread(new Runnable() { @Override public void run() { RankPark.rp_register(getBaseActivity(), getBaseActivity().getPackageName(), Settings.Secure.getString( getBaseActivity().getContentResolver(), Settings.Secure.ANDROID_ID), "" + score); } }); } // // 略 // case BTN_RANKING: // 追加 // ランキングを表示 RankPark.rp_view(getBaseActivity(), getBaseActivity() .getPackageName(), Settings.Secure.getString( getBaseActivity().getContentResolver(), Settings.Secure.ANDROID_ID)); break; |
これでランキングの実装も完了です。ゲームオーバーの度にスコアがサーバーにポストされ、ランキングに参加できるようになります。
AndEngineではサウンドも簡単に再生出来ます。
でも時間が無く出来ませんでした。1000ダウンロード位行ったら音も入れます。スミマセン。
本にはきちんと解説してありますのでご安心を!
ということで、赤い水牛ゲームの完成です。
完成したプロジェクトはここからダウンロードして下さいね。権利は完全に放棄しますので、どのファイルでもご自由にお使い下さい。
尚、Rank Parkのjarはライセンス読んでないので同梱してません。Rank Parkのサイトから落としてください。
もう個人の規模でAndroidのアプリを作ってもダウンロードされません!ゲーム作りましょう!
この記事を読んで、少しでも興味がわいた方は是非拙著を手にとってみて下さいね。赤い水牛の規模ですと、デザインにかかった時間を除くと、半日で作れるようになります。
AndEngineでつくるAndroid 2Dゲーム (SMART GAME DEVELOPER) 立花 翔 翔泳社 |
本を読んでいる中で、分からないことがありましたら質問して下さい!
明日の表は @out_of_kaya さん、裏は @tarotaro4 さんです。
最後に、機会を作って下さったようてんさん、ありがとうございました!
最近のコメント