Flex、AIR、Java、Androidなど

12月20日 2012

【AndEngine】30分でカジュアルゲームを作ってみよう!

Posted by: tachibana In: Android| プログラミング

赤い水牛

こんにちは。@stachibanaです。

本エントリーはAndroid Advent Calendar 2012の表エントリーです。本日の裏エントリーは@katsummyさんです。

NDKとかADKとかaaptとかガチなネタが続いている中、空気を読まずにソフトなネタを投入します。

AndEngineでつくるAndroid 2Dゲーム (SMART GAME DEVELOPER) AndEngineでつくるAndroid 2Dゲーム (SMART GAME DEVELOPER)
立花 翔

翔泳社
売り上げランキング : 12826

Amazonで詳しく見る

宣伝になりますが、12/13日に翔泳社さんより拙著「AndEngineでつくるAndroid 2Dゲーム」が発売されました。

iPhone業界ではカジュアルゲームで1つで700万稼いだとか、3週間で1000万儲かったとかという話が話題ですね。

さすがにこれは一握りですが、1つのゲームで100万位になった例は結構あります。Androidでもランキングを見ているとその傾向が見られますね。近いうちでしょう。

ということで、今回はゲーム作りを解説してみたいと思います。

これから作るゲームはPlayストアで公開されてますのでまずはそちらをどうぞ。

赤い水牛
赤い水牛
赤い水牛

仮に、「赤い水牛」という飲めばいくらでも働ける炭酸飲料があったとします。それを振って振って振りまくり、噴射させることによって疲れて机で寝てしまっている社員達にぶつけ、翼をさずけられたら素敵だと思いませんか?

Androidの開発環境が揃っている方なら1時間もあれば作れますので、是非体験してみて下さい。

尚、必要な画像素材はここからダウンロードして下さい。ダウンロードできたら、解凍してassetsフォルダにサブフォルダごと放り込んで下さい。

赤い水牛

では始めましょう。Androidの開発環境は入っていることを前提に進めます。

※ 本エントリはJavaのこと、Androidのことが普通に分かる方向けに大分はしょって書いています。本の方にはもっともっともーーーーっと優しく書いてありますのでご安心を。

AndEngineの準備

最初にAndEngineを落とします。

AndEngineのサイトを開き、プロジェクトをgitなりzipなりでローカルに保存します。

ライブラリの形式で配布されていますので、落とせたら、Eclipseのメニュー > インポートからインポートします。ワークスペースにコピーすることをおすすめします。

ゲームの雛形の作成

ゲーム画面を作る前に、ゲームの雛形を作りましょう。

でも解説するのがめんどくさいのでここからダウンロードして下さい。

簡単に言うと、リソースの読み込み関係のユーティリティクラスや、シーンの遷移関連の関数等をまとめた抽象クラスとかが出来上がった状態のプロジェクトです。

詳細の解説は本の方に書いてあるのでそちらをどうぞ。

AndEngineライブラリへのパスは同じディレクトリを指定していますので、ワークスペースにコピーしていない方は修正してください。

MainActivityの編集

まず、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は実際にゲームを描画するシーンです。

  • イニシャライズの処理
  • 振り開始までのカウントダウン処理
  • 振りによってパワーを蓄積させる処理
  • フレーム毎に缶や社員を移動させたり、画像を切り替えたりする処理
  • ゲームオーバーの処理
  • ゲームオーバー画面からのボタン押下時の処理

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はゲームのトップ画面(メニュー画面)を表示するシーンです。

  • タイトル画像の追加
  • タイトルロゴの追加・アニメーション
  • ボタンの追加・アニメーション
  • ランキングの表示処理

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) AndEngineでつくるAndroid 2Dゲーム (SMART GAME DEVELOPER)
立花 翔

翔泳社
売り上げランキング : 12826

Amazonで詳しく見る

本を読んでいる中で、分からないことがありましたら質問して下さい!

明日の表は @out_of_kaya さん、裏は @tarotaro4 さんです。

最後に、機会を作って下さったようてんさん、ありがとうございました!

EasyFreeAds Blog News Facebook Twitter Myspace Friendfeed Technorati del.icio.us Digg Google Yahoo Buzz StumbleUpon

5 Responses to "【AndEngine】30分でカジュアルゲームを作ってみよう!"

1 | Androidアプリ開発向けにAmazon web services(AWS)に自分専用の執事(jenkins)を用意して、快適なアプリ開発環境を構築 | Bescottee

12月24日 2012 at 11:43 AM

Avatar

[...] 2012 向けのエントリを書かせて頂います。 昨日は、表が@stachibanaさんの 【AndEngine】30分でカジュアルゲームを作ってみよう!で、裏が @katsummyさんの CREADOR GRANOESTE: ViewHolderとValidatorForAndroid [...]

2 | dim_san

1月6日 2013 at 1:42 PM

Avatar

AndEngineでつくるAndroid 2Dゲーム (SMART GAME DEVELOPER)
楽しく読ませていただきました。
GAMEの作り方を知らない私にも理解できました。

MultiSceneActivityのgetResourceUtilはインスタンス変数を返却するのではなく、
ResourceUtil.getInstanceからの返却がいいような気がしました。

3 | しろ

2月26日 2013 at 1:36 AM

Avatar

はじめまして
ここのソースの権利なのですけど、ブログやニコ生コミュニティなどで勝手に(間違っているかもしれない)解説をするみたいな感じで使ってもいいのでしょうか?

ちなみに本はまだ届いてませんけど買いました。

5 | miyata

2月7日 2014 at 11:54 AM

Avatar

はじめまして
本買わせていただきました。プログラミング初心者です。
andengineを使って電子書籍リーダーのような縦スクロールを実装したいのですが、どうすればいいですか

Categories

 

2015年10月
« 4月    
 1234
567891011
12131415161718
19202122232425
262728293031  

About

Author: tachibana

  • ちょっとしたことはTwitterに書いています。こっちはアプリの公開等の時に更新されます。
  • 最近はもっぱらJavaとObjective Cです。AS3は飽きました。
  • スクリプト言語ではPerlが好きでしたが最近はGAE/Jで何でもやってます。
  • Linuxは自宅サーバー建てるのがやっとのレベルです。前の会社で何日も徹夜してやったのはいい思い出です。
  • アプリへのご要望などご意見等ありましたらお気軽にご連絡下さい。

Alternative content here