SlideShare ist ein Scribd-Unternehmen logo
1 von 62
第17回Android勉強会in札幌

Androidプログラミング初心者のための
       ゲームアプリ開発入門



      2012年11月17日
     みずもん @mizmon21
         水田 雅彦
                        1
目次
1.   MIZMON21とは
2.   はじめに
3.   開発
4.   仕上げ
5.   完成
6.   まとめ




                  2
MIZMON21とは




             3
自己紹介
氏名      水田雅彦
HN      みずもん(英語ではmizmon21)、MMQ大臣
誕生日     5月18日おうし座 A型
出身地     北海道旭川市
職暦      1991年4月~ 某重電機メーカー勤務
            FAコンピューター等の開発・FAE業務に従事
        2001年4月~ 某電子機器メーカー勤務
            LSI製品の開発・FAE業務に従事
保有資格    自動車運転免許普通1種、第四級アマチュア無線技士、
        1級電気工事施工管理技士、第3種電気主任技術者、
        情報処理技術者第2種、初級システムアドミニストレータ、
        JSTQBテスト技術者資格認定Foundation Level
出願特許    20件
モットー    仕事も遊びも全力全開(壊)!
好きなもの   クルマ、野球、アニメ、萌え系など
                                          4
Android的な活動
Mizmon21のアンドロイドなweb
http://mizmon21.jp/
       今日の資料やアプリ(apk)もここに保管

Googleプレイにて数本のAndroidアプリを公開中
https://market.android.com/developer?pub=mizmon21


アンドロイダー公認デベロッパ
http://androider.jp/developer/4180c245837998ccd98f12d1147032e1/


                                                              5
中の人について




          6
はじめに




       7
本題



     Androidでオリジナルな
       ゲームを作ろう!




                      8
こんな症状の方に
• とりあえずEclipseをインストールしてみたもの
  の何を作っていいか迷ってる人
• よくわからないけどアプリを自作したい人
• なかなか重たい腰があげられない人
• 飽きっぽい人
                     そういう自分はまず
                      @override
                     でつまづいたw


とにかく一本アプリをつくってみて自信をつける
    まずはそこからはじまるのです
                              9
目的


       飽きずに最後まで作りきる

•   楽しみながら開発を進めていく
•   難しい話はまずは置いといて
•   コピペでまずは動かしてみる
•   Androidの文法とかはGoogle先生に聞く

                               10
どんなゲームを作る?

   最終的な完成の姿を想像力を
   働かせて妄想する
• ストレスの多い日常を解消したい
• 2Dアクションゲーム
• 「もぐらたたき」的な何かをつくってみる

【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch ( みずもんタッチ )

                           11
開発フロー
 要求確認

    要件定義

        外部設計

           詳細設計

                  実装

                       テスト
ウオーターフォール開発は
   忍耐が必要                     リリース

                                12
コツコツ積み上げる
     設計

要件        実装   画像を動かす            時間制限
     検査



               タッチを検出する          音をいれる


                当たり判定            バランス調整


               スコアをつける            リリース

                     反復開発でアプリの
                      成長を楽しむ
                                          13
開発




     14
ゲームの基本(SurfaceviewとThread)
【概要】
 ゲームの基本であるSurfaceviewとThreadの導入
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch1 ( 01MizmonTouch )
【作成クラス】
 MizmonTouch:Activityの派生クラス
 GameView:Surfaceviewの派生クラス
       SurfaceHolder.Callbackインターフェース
 GameViewThead:Threadの派生クラス




                                        15
ゲームの基本(高速描画)
                                                  ダブルバッファリング

                     View           Surfaceview

                ・高速描画に不向き         ・高速描画可能
                ・onDraw()で描画      ・定期的な再描画可
                          ↑       能
                Invalidateで呼び出し   ・独立した描画
                ・数fps程度           ・数十fps程度



• View
   アプリケーション内で描画
    ⇒ 高速描画に不向き                               次の画像の準備
• Surfaceview
   アプリケーションのスレッドと描画のスレッドが独立
    ⇒ 高速で定期的な描画が容易

                      Surfaceviewの活用
                                                          16
ゲームの基本(定期的な動作)
 イベントドリブン(イベント駆動型)
       外部から何かしらのイベントを発生時に実行
                       例)タッチ、ボタン押下など
    アクションゲームなアプリには不向き
  タッチ処理() {
    ……
  }                  定期定期に処理させたい
  ※ タッチされることに
  より実行される



     スレッド(処理の実行単位)
 イベント入力せずとも何かしら処理をさせることができる(ループ処理)
           ゲームアプリに最適
                             スレッド処理() {
                               …….
                             }
                             ※継続して実行
                                          17
ゲームの基本(コード解説)
public class MizmonTouch01 extends Activity {
   // ************** SurfaceHolder.Callbackの3兄弟 **********************
      @Override
           private onCreate(Bundle
      public GameViewThread extends Thread {mThread;// スレッドのインスタンス
        class void GameViewThreadsavedInstanceState) {
           // surface生成時にコールバックされる メインループ動作フラグ(外部アクセス)
               …
              private boolean      bRunning= false;//
           @Override
             ///*********************** 外部から呼ばれるメソッド ***********************/
                GameViewを画面としてセットする
           public void surfaceCreated(SurfaceHolder holder) {
             GameView gameView = new GameView(this);
               public GameViewThread(SurfaceHolder surfaceHolder) {
                  mThread = new GameViewThread(holder);
             setContentView(gameView);
                      this.mHolder = surfaceHolder;                // スレッド生成しインスタンス化
      }        } mThread.enableRunning(true);                      // スレッド内のメインループ動作を許可する
}                 mThread.start();
               // メインループの動作許可設定                                    // スレッドを起動する(try~catchで囲む)
           } public void enableRunning(boolean flag) {
           // surface変更時にコールバックされる
class GameView extends SurfaceView implements SurfaceHolder.Callback {
                      this.bRunning = flag;    // メインループ動作許可
           @Override
        public GameView(Context context) {
               }
           public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
               …
               /*********************** メインループ ***********************/
                  // なにもしない
             //サーフェイスホルダーを取得(SurfaceView)
               // スレッドを起動すると呼ばれる
           } SurfaceHolder holder = getHolder();
               @Override
                コールバックを設定(SurfaceHolder)
           ////public void run() {
              surface破棄時にコールバックされる
           @Override
             holder.addCallback(this); {
                      while(bRunning)
      } public void surfaceDestroyed(SurfaceHolder holder) {
                             …     // canvasにテキストを描画する
                  mThread.enableRunning(false);
                             …                         // スレッド内のメインループ動作を停止する
                                   // ループが一定時間の間隔で回るための処理
                  mThread.join(); //スリープ
                             …                         // スレッドを停止させる(try~catchで囲む)
           }          }
   }           }
                                                                                               18
      }
キャラクターを動かす
【概要】
 1体のキャラクター(敵)を固定位置で動かす
 もぐら叩きのモグラの用に下からせりあがってくる感じ
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch2 ( 02MizmonTouch )
【修正メソッド】
 GameViewThreadクラス内
   GameViewThread():初期化する変数を追加
【新規メソッド】
 GameViewThreadクラス内
   moveEnemy() : 敵の動作処理
   genVirtualDisplay() : 仮想画面を生成
   doDraw() : bmpを画面(View)に貼り付け
   main() : メイン処理(Threadのループから呼ばれる)

                                      19
キャラクターを動かす(仮想画面の導入)
   ① キャラクターの元画像を時間に応じサイズの上部から切り
     出す
   ② 仮想画面への貼り付けは画像サイズの底辺を基準に①画
     像を張り付ける
   ③ 時間とともに①の切り出しサイズを変化させる
               キャラクターの元画像                                 仮想画面
              (rctOriginalCurrentSize)                (rctCurrentArea)
(left, top)                              時間とともに
                                         変化させる             (left, top)




                      (right, bottom)             (right, bottom)        20
キャラクターを動かす(コード解説)
/*********************** 外部から呼ばれるメソッド ***********************/
// コンストラクタ final intENEMY_COUNT_MAX = 10;// 敵の最大動作回数
   private static
public// ***** 画面関係の処理 ***** surfaceHolder, Context context) {
        GameViewThread(SurfaceHolder
   private void moveEnemy() {
      … 仮想画面(ビットマップ)を生成する
       // if(0 >= nCount) {
       private void genVirtualDisplay() { //
                 nAddition = +1;                      // リソースのインスタンスを取得
      Resources r = context.getResources(); 引っ込みきったので折り返して出現させる
      // 仮想画面をCreate= new Canvas(imgVdGame); <= nCount) {
          } else if(0 <canvas
              Canvas nAddition && ENEMY_COUNT_MAX                                     // 仮想画面の下地を生成
                 nAddition = -1;
              Paint paint =new Paint();     // 最大サイズまで出現した場合、増分をマイナスにする
      imgVdGame = Bitmap.createBitmap(VD_WIDTH, VD_HEIGHT, Bitmap.Config.ARGB_8888);   // Paintをインスタンス化
      // 表示する敵画像をインスタンス化
          } else {
              paint.setAntiAlias(true);
                                            // 特に何もしない
      imgEnemy = BitmapFactory.decodeResource(r, 255));
              paint.setColor(Color.argb(255, 255, 255, R.drawable.enemy1);
                 ;
          敵画像サイズを取得
       // } canvas.drawColor(Color.argb(255, 0, 0, 32));                              // 背景を塗りつぶし
      rctEnemyOriginalSize = new Rect(0, // rctOriginalCurrentSize, rctCurrentArea, paint);
          nCount += nAddition;               0, imgEnemy.getWidth(), imgEnemy.getHeight());
              canvas.drawBitmap(imgEnemy, nCountを加算する                                            // 敵の貼り付け
}      }
          // オリジナルサイズの敵画像のサイズを更新
       // ***** 画面関係の処理 *****
/*********************** 内部処理 ***********************/
          // nCountから高さを算出
       // bmpを画面に貼り付け
           敵の処理 (int)(rctEnemyOriginalSize.bottom * nCount / ENEMY_COUNT_MAX);
// *****int height doDraw(Canvas canvas) {
       private void =*****
// 敵の動作   rctOriginalCurrentSize = new Rect(rctEnemyOriginalSize.left, rctEnemyOriginalSize.top,
              Paint paint=new Paint();                                 // Paintをインスタンス化
public Rect paint.setAntiAlias(true); // オリジナルサイズの敵画像の現在のnCountに応じたサイズ
               rctOriginalCurrentSize;                   rctEnemyOriginalSize.right, height);
                                         // 仮想画面上の現在の占有座標
public Rect paint.setColor(Color.argb(255, 255, 255, 255));
               rctCurrentArea;
public int 現在の敵の座標を更新 // (0~MAX)
               nCount = 1;
          // canvas.drawColor(Color.argb(255, 0, 0, 32));              // 背景を塗りつぶし
public int まずはnCountから高さを算出
               nAddition = +1;           // nCountの増分
          // canvas.drawBitmap(imgVdGame, 0, 0, paint);                // 生成したbmp(仮想画面)を画面に表示
       } height = (int)(rctEnemyOriginalSize.bottom * nCount / ENEMY_COUNT_MAX);
                                                                         今回は仮想画面をそのまま
       // ***** メイン処理new Rect(rctEnemyOriginalSize.left, rctEnemyOriginalSize.bottom - height,
          rctCurrentArea = *****
       // Thread内ループから定期的に呼ばれる(50msごと)                                           Viewに表示してる
                                                         rctEnemyOriginalSize.right, rctEnemyOriginalSize.bottom);
   } private void main() {
              moveEnemy();                                             // 敵情報更新
              genVirtualDisplay();                                     // 仮想画面生成
       }                                                                                                        21
ランダムに表示する
【概要】
  キャラクターの管理クラスを作成し一つのオブジェクトとして管理
【サンプルプロジェクト名(アプリケーション名)】
  MizmonTouch3 ( 03MizmonTouch )
【修正メソッド】
  GameViewクラス内
       surfaceChanged() : 実画面サイズをThreadへ通知
  GameViewThreadクラス内
       GameViewThread() : 初期化する変数を追加
       genVirtualDisplay() : dispEnemy()を呼び出しを追加
       moveEnemy() : キャラクターの生成と動作更新
【新規メソッド】
  EnemyInfoクラス
       一体のキャラクターの管理クラス管理する
  GameViewThreadクラス内
       setSurfaceSize() : 実画面サイズの取得
       genEnemy() : 敵の生成
       updateEnemy() : 敵の更新
       dispEnemy() : 敵の表示(genVirtualDisplay()のサブ)
       doDraw() : 仮想画面を実画面へフィッティング


                                                    22
ランダムに表示する(オブジェクト化)
         ランダムな大きさでランダム
            な位置に表示


        複数の変数で管理するのは大変!




        1体のキャラクターを管理するクラ
        ス(構造体)を定義しひとつのオブ
        ジェクトとして管理
        (EnemyInfoクラス)

                         23
ランダムに表示する(フィッティング)
  仮想画面               実画面




           フィッティング




 フィッティング


解像度やサイズの違いによる機種
依存性を極力排除
                           24
ランダムに表示する(コード解説)
// 敵1体の情報をまとめるクラス
  class GameViewThread extends Thread {
       画面サイズの取得
    // // 敵の更新(敵が消滅したらfalseを返す)
class EnemyInfo {
         // コンストラクタ
             敵の生成
    public// 敵の貼り付け
       private boolean bAlive; width, int height)// 生死判別フラグ
             void setSurfaceSize(int
      public boolean updateEnemy(EnemyInfo enemy){          {
         public GameViewThread(SurfaceHolder surfaceHolder, Context context) {
         private EnemyInfo genEnemy() { canvas) {
           synchronizeddispEnemy(Canvas
            private void (mHolder) {
                               rctOriginalCurrentSize; // オリジナルサイズの敵のnCountに応じたサイズ
      publicif(0 >= enemy.nWaitCount) {
               Rect                                               // 動作カウントの更新なのかを判断する
                                 :
                  // enemy.nWaitCount = enemy.nSpeed; //// 生死の初期化(新規生成なのでtrue)
                EnemyInfo rv = new EnemyInfo();
                      仮想画面と実画面の比率を算出// 仮想画面上の敵のサイズ
      public Rect Paintをインスタンス化
                   //          rctEnemySize;                         ウェイトの初期化
                   仮想画面の座標を記録
                rv.bAlive = true; Paint();
                //float mult_width = (float)VD_WIDTH / (float)width;
                   Paint paint =new
      public Rect if(0 >= enemy.nCount) {
                               rctOccupationArea;          // 仮想画面上の最大占有座標
                                                                  // 動作カウントの更新
                   敵のサイズを決定
                rctVd =mult_height = (float)VD_HEIGHT / (float)height;
                //float new Rect(0, 0, VD_WIDTH, VD_HEIGHT);
                   paint.setAntiAlias(true);
      public Rect              rctCurrentArea;
                            return false;                  // 仮想画面上の現在の占有座標
                                                                  // 引っ込みきったのでインスタンスを削除
                int敵情報の初期化(int)(Math.random()*
                    enemy_width =
                //float mult;
                                                           // 動作速度(1動作あたりのスレッドサイクル数。
      public int } else if(0 < enemy.nAddition &&(ENEMY_COUNT_MAX <= enemy.nCount ||
                               nSpeed;
                Enemy = new EnemyInfo(); - ENEMY_WIDTH_MIN) + ENEMY_WIDTH_MIN);
                        (ENEMY_WIDTH_MAX
                  // 実画面のサイズに引き延ばす辺(幅または高さ)を判断する
                   // 敵の貼り付け                               //           0:ウェイトなし、MAX:MAXサイクルで1動作)
                                                        1 > (Math.random() * ENEMY_COUNT_MAX))) {
         }      int enemy_heightmult_height){
                  if(mult_width < = (int)enemy_width// ウェイト挿入回数(初期値はnSpeed値
                                                             * rctEnemyOriginalSize.bottom / rctEnemyOriginalSize.right;
      public int paint.setColor(Color.argb(Enemy.nAlpha, 255, 255, 255));
                               nWaitCount;
                            enemy.nAddition = -1;                 // 最大サイズまで出現した場合または
                int enemy_x = (int)(Math.random()*(VD_WIDTH - enemy_width));
                          mult = mult_height;
                   canvas.drawBitmap(imgEnemy, Enemy.rctOriginalCurrentSize, Enemy.rctCurrentArea, paint);
                                                           // 1サイクルごとに-1される。0になったら1コマ動かす)
                                                                  // ランダムで折り返す場合、 増分をマイナスにする
            } int: else{ { nCount;                                          // ***** 敵の処理 *****
                int enemy_y = (int)(Math.random()*(VD_HEIGHT - enemy_height));
                  } } else
      public                                               // bAlive=true, bAppearance=true:出現時のカウント
                rv.rctEnemySize = new Rect(0, 0, enemy_width, enemy_height);
                          mult = mult_width;                                // 敵の動作          // 敵のサイズを設定
                            ;                              // bAive=true, bAppearance=false: 引っ込む時のカウント
                                                                  // 特に何もしない
            ***** 仮想画面関係の処理
         // // bmpを画面に貼り付け= *****
                rv.rctOccupationArea
                  } }                                                       private 敵の画面内の占有座標を設定
                                                                                 // void moveEnemy() {
                                                           // bAlive=false : やれたとき(Hit時)のカウント
                      void doDraw(Canvas canvas) { enemy_x + enemy_width,敵情報の更新
                // 仮想画面(ビットマップ)を生成する
                        new Rect(enemy_x, enemy_y,
            private 仮想画面を拡大して張り付ける実画面上の座標を算出する
      public int// enemy.nCount += enemy.nAddition; nCountの増分
                               nAddition;                  //     // nCountを加算する
                                                                                  // enemy_y + enemy_height);
                       x2=(int)((float)VD_WIDTH / mult); 表示時の透過率 // オリジナルサイズの敵画像座標の初期化
         private void genVirtualDisplay() {
                rv.rctOriginalCurrentSize =
      public intint// オリジナルサイズの敵画像のサイズを更新、nCountから高さを算出
                   // Paintをインスタンス化
                               nAlpha;                     //
                                                                                  if(!updateEnemy(Enemy)){
                   仮想画面の下地を生成                                                     // 敵が消滅したら敵を生成する
                //int y2=(int)((float)VD_HEIGHT / mult); rctEnemyOriginalSize.top, rctEnemyOriginalSize.right, 0);
                        new Rect(rctEnemyOriginalSize.left,
                   Paint paint=new Paint();
}                    enemy.rctOriginalCurrentSize.bottom =
                Canvas canvas = new Canvas(imgVdGame); / (float)2); // 現在の敵の大きさを設定
                rv.rctCurrentArea =                                                      Enemy = genEnemy();
                  int x1=(int)((float)width / (float)2 - (float)x2 enemy.nCount / ENEMY_COUNT_MAX);
                   paint.setAntiAlias(true);
                            (int)(rctEnemyOriginalSize.bottom *
                   背景を塗りつぶし                                                       }
                //int y1=(int)((float)height / (float)2+- enemy_height, enemy_x + enemy_width, enemy_y + enemy_height);
                       new Rect(enemy_x, enemy_y 255, 255));
class GameView extends SurfaceView implements (float)y2 / (float)2);
                   paint.setColor(Color.argb(255, 255, SurfaceHolder.Callback {
                     // 現在の敵の座標を更新、nCountから高さを算出
                // 敵の動作速度を設定
                canvas.drawColor(Color.argb(255, 0, 0, 32));                }
               : x2int height = (int)(enemy.rctEnemySize.bottom * enemy.nCount / ENEMY_COUNT_MAX);
                      += x1;
                   敵の貼り付け
                rv.nAddition = +1;
                //y2 背景を塗りつぶし
                   // += y1;                                                        :
      @Override enemy.rctCurrentArea.top = enemy.rctOccupationArea.bottom - height;
                dispEnemy(canvas);
                rv.nSpeed = (int)(Math.random()*(ENEMY_SPEED_MAX + 1));
                else 実画面の座標を記録
                  // {
                   canvas.drawColor(Color.argb(255, 0, 0, 32));
      public}void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         }      rv.nWaitCountRect(x1, y1, x2, y2);
                  rctRd = new = rv.nSpeed; // まだウェイトが必要な場合の処理、ウェイトカウントを減らす
              mThread.setSurfaceSize(width, height); // 画面サイズをスレッドに通知する
                     enemy.nWaitCount --;
      }    } } rv.nCount = 1;
                   // 生成したbmp(仮想画面)を画面に表示
}          bDisplayReady = true; // 敵の透過率を設定 rctRd, paint);
                rv.nAlpha = 255;                // 画面準備完了
                   canvas.drawBitmap(imgVdGame, rctVd,
              return true;
    } } }       return rv;
         }                                                                                                               25
複数表示する
【概要】
 複数のキャラクターの管理
 背景もついでに表示
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch4 ( 04MizmonTouch )
【修正メソッド】
 GameViewThreadクラス内
   GameViewThread() : 初期化する変数を追加
   moveEnemy() : 複数のキャラクターに対応
   genEnemy() : 既存のキャラクターと
                  重ならない処理を追加
   genVirtualDisplay() : 背景画像の表示を追加

                                      26
複数表示する(リスト化)

                         複数のキャラクターを管理する


                        • オブジェクト化したキャラクター
                          をリスト化
                         listEnemys = new ArrayList<EnemyInfo>();

                        • 複数のキャラクターを生成、更
                          新および表示処理を追加
                         全リストの処理を常に行う(for文)

                        • 新規キャラクターは重ならないよ
                          うに生成
  rctOccupationArea変数   • 背景も表示(おまけ)
キャラクターの生成は排他的に
                                                                    27
複数表示する(コード解説)
// コンストラクタ
   private void moveEnemy() {
public GameViewThread(SurfaceHolder surfaceHolder, Context context) {
           敵の生成
        //// 全ての敵を更新する
      this.mHolder = surfaceHolder;
        private EnemyInfo genEnemy()0<=i
          for(int i=listEnemys.size()-1 ; {
      // リソースのインスタンスを取得 ; i--){
                       :
                // 敵情報の更新
      Resources r = context.getResources();
                 敵の画面内の占有座標を設定
              //if(!updateEnemy(listEnemys.get(i))){
                       :listEnemys.remove(i);             // 敵が消滅したらリストからも削除
      // 各画像をインスタンス化
              //}既に出現中の敵と重なっていないか判定する
      imgVdGame = Bitmap.createBitmap(VD_WIDTH, VD_HEIGHT, Bitmap.Config.ARGB_8888);
          } for(EnemyInfo item : listEnemys) {
      imgBg = BitmapFactory.decodeResource(r, R.drawable.bg);
                     if(Rect.intersects(rv.rctOccupationArea, item.rctOccupationArea)){
      imgEnemy = BitmapFactory.decodeResource(r, R.drawable.enemy1);
          // 新規に敵を追加可能か判断する  // 重なっていれば敵を生成しない
                             return null;
          if(ENEMY_NUMBER_MAX > listEnemys.size()) {
      // 背景画像のオリジナルサイズを取得
                     }
      rctBgSize = 追加可能なら新規に出現するか判断する
                // new Rect(0, 0, imgBg.getWidth(), imgBg.getHeight());
              } if(ENEMY_GEN_PROBABILITY > Math.random()*100) {
              // オリジナルサイズの敵画像座標の初期化
                        // 新規に敵を生成
      // 敵画像のオリジナルサイズを取得
                       :// 敵生成に失敗の際は5回までリトライする
      rctEnemyOriginalSize = new Rect(0, 0, imgEnemy.getWidth(), imgEnemy.getHeight());
        }               for(int i=0 ; 5>i ; i++) {
                       :        EnemyInfo new_enemy = genEnemy();
      // 仮想画面の座標を記録
        private void genVirtualDisplay() {
                                if(null != new_enemy) {
      rctVd = new Rect(0, 0, VD_WIDTH, VD_HEIGHT);
                       :                listEnemys.add(new_enemy);     // 敵生成に成功したらリストに追加する
              // Paintをインスタンス化          break;
      // 敵情報の初期化
              Paint paint =new Paint();
                                }
      listEnemys = new ArrayList<EnemyInfo>();
              paint.setAntiAlias(true);
                        }
}
              //}仮想画面に背景画像を貼り付ける
          } canvas.drawBitmap(imgBg, rctBgSize, rctVd, paint);
   }                   :
        }                                                                                     28
タッチを検出する
【概要】
 タッチイベントを解析してアクションと座標を検出
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch5 ( 05MizmonTouch )
    (MizmonTouch1から作成)
【追加メソッド】
 GameViewクラス内
   onTouchEvent() : タッチイベント処理
 GameViewThreadクラス内
   doTouchEvent() : タッチの検出処理



                                  29
タッチを検出する(座標検出)
• タッチの検出
 – ActivityクラスのonTouchEvent()をオーバーライド
 – 引数としてMotionEventが渡される
 – Surfaceviewでタッチイベントを取得する
   コンストラクタで下記を宣言することにより可能
   setFocusable(true); // フォーカスを受け取る
• アクションと座標の解析                          (0,0)
 MotionEventには必要な情報が詰まっている
 – アクションの取得
   getAction():ダウン、アップ、移動アクション
 – 座標の取得                                   (X, Y)
   getX():画面上のX座標                             X
   getY():画面上のY座標
 他にもいろいろ便利なメソッドが備わっている

                                                    30
タッチを検出する(コード解説)
class GameView extends SurfaceView implements SurfaceHolder.Callback {
   class GameViewThread extends Thread {
      private GameViewThread                               mThread; // スレッドのインスタンス
   :         :
        @Override
   // ***** タッチ入力処理 *****
      public GameView(Context context) {
   // タッチ処理 run() {
        public void
               :
               super(context);
          private HashMap<String,PointF> points = new HashMap<String,PointF>();
               //サーフェイスホルダーを取得(SurfaceView)
               while(bRunning) {
          private String strMotion;
                      :
               SurfaceHolder holder = getHolder();
          public boolean doTouchEvent(MotionEvent event) {
               // コールバックを設定(SurfaceHolder)
                 int action = event.getAction();
                      try {
                 int count =:event.getPointerCount();
               holder.addCallback(this);
                 int index =// タッチ座標を画面に表示
                               (action & MotionEvent.ACTION_POINTER_ID_MASK) >>
               // キーイベントが取得できるようにフォーカスを受け取れるようにしておく
                 MotionEvent.ACTION_POINTER_ID_SHIFT;
               setFocusable(true);
                              paint.setColor(Color.MAGENTA);
                 // タッチ動作を判別
      }                       Object[] keys=points.keySet().toArray();
                 switch (action & MotionEvent.ACTION_MASK) {
      // タッチイベントを実装      case MotionEvent.ACTION_DOWN:
      @Override               for (int i=0;i<keys.length;i++) {
                                 points.put("" + event.getPointerId(index), new PointF(event.getX(), event.getY()));
      public boolean onTouchEvent(MotionEvent event) {
                                      PointF pos=(PointF)points.get(keys[i]);
                                 strMotion = "ACTION_DOWN";
             try {               break;
                                      canvas.drawText(strMotion+"="+(int)pos.x+", "+(int)pos.y, 0, 40*j,paint); j++;
                                 :
                     // 実処理はスレッドの中で行う
                              }
                         case MotionEvent.ACTION_MOVE:
                     mThread.doTouchEvent(event);
                      }
                      (Exception e) { i=0;i<count;i++) {
                                 for (int
             } catch } catch(Exception e) {
                                         points.get("" + event.getPointerId(i)).x = event.getX(i);
             }        } finally {
                                         points.get("" + event.getPointerId(i)).y = event.getY(i);
                      :
             return true;        }
      }        }                 strMotion = "ACTION_MOVE";
        }      :                 break;
}                }
                 return true;
          }
                                                                                                                       31
タッチを仮想画面に反映
【概要】
 タッチ座標を仮想画面座標に変換
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch6 ( 06MizmonTouch )
   (MizmonTouch5から作成)
   (MizmonTouch3の関数を利用)
【追加メソッド】
 GameViewThreadクラス内
   genVirtualDisplay():タッチ座標にアイコン表示

   以下はMizmonTouch3から
   setSurfaceSize() : 実画面サイズの取得
   doDraw() : 仮想画面を実画面へフィッティング
   main() : メイン処理


                                      32
タッチを仮想画面に反映(座標変換)
    実画面                     仮想画面




                           (X´, Y´)
                  座標変換
 (X, Y)
                  fMult倍




実画面上の座標→仮想画面上の座標に変換
 X´ = fMult × X
 Y´ = fMult × Y
                                      33
タッチを仮想画面に反映(コード解説)
private void genVirtualDisplay() {
      synchronized (mHolder) {
           // 仮想画面の下地を生成
           Canvas canvas = new Canvas(imgVdGame);                排他処理のおまじない
          // Paintをインスタンス化
          Paint paint =new Paint();                              次の処理は別スレッドで動作
          paint.setAntiAlias(true);                              • タッチ処理
          paint.setColor(Color.argb(255, 255, 255, 255));
                                                                 • 表示処理
          // 背景を塗りつぶし
          canvas.drawColor(Color.argb(255, 32, 32, 32));
                                                                 そのため処理中に値が変化す
          // アイコンの貼り付け
          Object[] keys=points.keySet().toArray();                      ると都合が悪いためロックする
          for (int i=0 ; i<keys.length ; i++) {                         例) のタイミングで座標で
                 // 座標の取得
                 PointF pos=(PointF)points.get(keys[i]);                    タッチ座標がかわるなど
                 // 座標の変換                                               よって、doTouchEvent()も
                 int x = (int)((pos.x - rctRd.left) * fMult);
                 int y = (int)((pos.y - rctRd.top) * fMult);            Synchronizedでロックしている
                 // アイコンの貼り付け
                 canvas.drawBitmap(imgIcon, x - imgIcon.getWidth()/2, y - imgIcon.getHeight()/2, paint);
                 // 座標も表示してみる
                 paint.setTextSize(24);
                 paint.setColor(Color.CYAN);
                 canvas.drawText(x + ", " + y, 0, 40 * i + 200, paint);
          }
     }
}

                                                                                                       34
当たり判定
【概要】
 タッチ座標を検出しキャラクターの有無で当たり判定
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch7 ( 07MizmonTouch )
   (MizmonTouch4と6から作成)
【修正メソッド】
 doTouchEvent() … Downのみを検出対象
【追加メソッド】
 rotateImage() : BMPを指定の角度にする
 dispTouch() : ハンマー表示(genVirtualDisplay())
 judgeHit() : 当たり判定(main()から呼ばれる)


                                             35
当たり判定(ダウンアクションだけを検出)
  仮想画面
                    • タッチ時はハンマーをアニメーション化
                     画像はコンストラクタで回転して生成
                    • タッチ検出
                     コンセプトはもぐら叩き
                     ⇒ダウンアクションのみ使用
  rctCurrentArea      マルチタッチは使用しない
rctOccupationArea   • 当たり判定
                     実際の表示エリアとタッチエリアで判定
                      rctCurrentArea - rctTouch
                     図の例では○が当たり判定となる              Hit

                    • Hit検出後キャラクターを消去

                                                  36
当たり判定(コード解説)
public GameViewThread(SurfaceHolder surfaceHolder, Context context) {
    // タッチ位置の画像貼り付け
               :
         各画像をインスタンス化
    private void dispTouch(Canvas canvas){
      // // 敵の当たり判定
      imgHammer1 = BitmapFactory.decodeResource(r, R.drawable.hammer);
           // Paintをインスタンス化
         private void judgeHit() {
      // 回転したハンマー画像を生成
           Paint paint =new Paint();
                synchronized (mHolder) {
      imgHammer2if(JUDGEMENT_TIME -2 <= nJudgementCount && null != rctTouch) {
                         = rotateImage(imgHammer1, 330);
           paint.setAntiAlias(true);
      imgHammer3 = rotateImage(imgHammer1, 300);
                               for(int i=listEnemys.size()-1 ; 0<=i ; i--){
           // タッチ位置にハンマーの貼り付け
               :                      EnemyInfo enemy = listEnemys.get(i);
}          if(null != rctTouch) { // 当たり判定
                  paint.setColor(Color.argb(255, 255, 255, 255));
                                      if(Rect.intersects(rctTouch, enemy.rctCurrentArea)){
                  // 時間とともに回転させる      // 重なっていれば敵を消滅し、リストからも削除
// ビットマップ回転させる    if(JUDGEMENT_TIME - listEnemys.remove(i); {
                                              1 <= nJudgementCount)
private Bitmap rotateImage(Bitmap bmp, float angle) { rctHammerSize, rctTouch, paint);
                          canvas.drawBitmap(imgHammer1,
                                      }
      int w = bmp.getWidth();
                  } else if(JUDGEMENT_TIME -2 == nJudgementCount) {
                               }
      int h = bmp.getHeight();
                          canvas.drawBitmap(imgHammer2, rctHammerSize, rctTouch, paint);
                               // 当たり判定時間をデクリメント
      Matrix matrix ={ new Matrix();
                  } else       nJudgementCount --;
      matrix.postRotate(angle, (float)w / 2f, (float)h{/ 2f);
                       } else if(0 <= nJudgementCount)                     // 画像を回転させるおまじない
                          canvas.drawBitmap(imgHammer3, rctHammerSize, rctTouch, paint);
      Bitmap dmy_img=Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
                  }            // 当たり判定時間をデクリメント
      Canvas dmy_canvasnJudgementCount --;
           }                     = new Canvas(dmy_img);
    } dmy_canvas.drawBitmap(bmp, 0, 0, null);
                       } else {
      return Bitmap.createBitmap(dmy_img, 0, 0, w, h, matrix, true);
                               // 当たり判定時間が超過したのでタッチ情報を削除
}                              rctTouch = null;
                       }
                }
         }
                                                                                              37
当たり処理
【概要】
 当たり検出後のキャラクタのアクションを実装
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch8 ( 08MizmonTouch )
   (MizmonTouch7から作成)
【追加メソッド】
 updateEnemy() : 長くなったので二つに分ける
 updateEnemyAlive() : 通常の更新
 updateEnemyGone() : あたり判定後の更新
 judgeHit() : 当たり判定時の処理をremoveから
             生死フラグの処理に変更
 dispEnemy() :敵の生死判別しそれに
              合わせた画像を貼り付ける


                                   38
当たり処理(アクション)

                    ドロン
             Hit!




       Hit検出後消えるだけじゃ寂しい
    Hit検出時に当たりアクションを追加

     キャラクター別画像へ差し替え

                          39
当たり処理(コード解説)
// 敵の更新(敵が消滅したらfalseを返す)
  // 敵の通常更新(敵が消滅したらfalseを返す)
private boolean updateEnemy(EnemyInfo enemy){
    // やられた敵の更新(敵が消滅したらfalseを返す)
  private boolean updateEnemyAlive(EnemyInfo enemy) {
      booleanvoid updateEnemyGone(EnemyInfo enemy) {
       private rv = judgeHit() {
         // boolean true;
    private動作カウントの更新
          private void dispEnemy(Canvas canvas) { {
            if(ENEMY_OUT_COUNT_MAX <= enemy.nCount)
          動作カウントの更新がどうかをウェイトから判断する
                synchronized (mHolder) {
      // if(0 >= enemy.nCount) {
                                        // 敵やれれ処理のカウントが最大時にインスタンスを削除
                  // return false;
                   //引っ込みきったのでインスンスを削除
                      Paintをインスタンス化 -2 <= nJudgementCount && null != rctTouch) {
                        if(JUDGEMENT_TIME
      if(0 >= enemy.nWaitCount) {
            }
            //// returnpaint =newi=listEnemys.size()-1 ; 0<=i ; i--){
                   Paint false;
                   ウェイトの初期化Paint();
                nCountを加算する     for(int
         } else if(0 < enemy.nAddition && (ENEMY_COUNT_MAX <= enemy.nCount || 1 > (Math.random() *
                   paint.setAntiAlias(true);
            enemy.nCount += enemy.nAddition; enemy = listEnemys.get(i);
                                         EnemyInfo
               enemy.nWaitCount = enemy.nSpeed;
         ENEMY_COUNT_MAX))) 当たり判定
            if(ENEMY_COUNT_MAX// { enemy.nCount){
                                           >=
                      最大サイズまで出現した場合またはランダムで折り返す場合
                  // // 敵が既定の最大サイズになるまでの処理
                   敵の生死を判断 判定するのはbAlive=trueの敵のみ
                      敵の貼り付け//
                   //増分をマイナスにする
               // // // オリジナルサイズの敵画像のサイズを更新
               if(enemy.bAlive) {item : listEnemys)&& Rect.intersects(rctTouch, enemy.rctCurrentArea)){
                  enemy.nAdditionif(enemy.bAlive {
                   for(EnemyInfo = -1;
                     // nCountから高さを算出
                                              // 重なっていれば生死フラグの処理をして消滅(やられ)処理を始める
         } else { enemy.rctOriginalCurrentSize.bottom = (int)(rctEnemyOriginalSize.bottom * enemy.nCount / ENEMY_COUNT_MAX);
                         //paint.setColor(Color.argb(item.nAlpha, 255, 255, 255));
                            敵が生きている場合の更新処理
                     // 特に何もしない
                        現在の敵の座標を更新
                  ; // // 敵の生死を確認しその状態に合わせた画像を貼り付ける
                                              enemy.bAlive = false;
         }               rv = updateEnemyAlive(enemy);
                     // nCountから高さを算出         enemy.nAddition = +1;                               // 引っ込んでる最中でも引き戻す
                     int if(item.bAlive){
               } else { height = (int)(enemy.rctEnemySize.bottom * enemy.nCount / ENEMY_COUNT_MAX);
                                              enemy.nSpeed = 0;                                   // 動作速度を最速にセットする
                         // 敵がやられている場合の更新処理 item.rctOriginalCurrentSize, item.rctCurrentArea, paint);
                                   canvas.drawBitmap(imgEnemy,
                     enemy.rctCurrentArea.top = enemy.rctOccupationArea.bottom - height;
         // nCountを加算する                       listEnemys.set(i, enemy);
            } else {
                         rv}= +={enemy.nAddition;
                             else
         enemy.nCountupdateEnemyGone(enemy);
                     // 敵やられ処理時で既定の最大サイズを超えた後の消去処理
                                         }
                                   canvas.drawBitmap(imgEnemyOut, item.rctOriginalCurrentSize, item.rctCurrentArea, paint);
               } // まずは敵画像のカレントサイズをオリジナルサイズにする
         // オリジナルサイズの敵画像のサイズを更新 }
                           }
      } else { enemy.rctOriginalCurrentSize = rctEnemyOriginalSize;
         // nCountから高さを算出       // 当たり判定時間をデクリメント
                   } // 敵の座標を更新
               // まだウェイトが必要な場合の処理
         enemy.rctOriginalCurrentSize.bottom = (int)(rctEnemyOriginalSize.bottom * enemy.nCount /
                                nJudgementCount --;
                     // nCountから高さを算出
          } // ウェイトカウントを減らす
         ENEMY_COUNT_MAX);
                     int}heightif(0 <= nJudgementCount) {
                          else = (int)(enemy.rctEnemySize.bottom * enemy.nCount / ENEMY_COUNT_MAX);
               enemy.nWaitCount --;
                                // 当たり判定時間をデクリメント
                     enemy.rctCurrentArea.top = enemy.rctOccupationArea.bottom - height;
      }  // 現在の敵の座標を更新
                     // nCountから幅を算出
                                nJudgementCount --;
         // nCountから高さを算出
                     int width = (int)((enemy.rctEnemySize.right * enemy.nCount / ENEMY_COUNT_MAX) - enemy.rctEnemySize.right);
         int height}= (int)(enemy.rctEnemySize.bottom * enemy.nCount / ENEMY_COUNT_MAX);
                          else {
                     enemy.rctCurrentArea.left = enemy.rctOccupationArea.left - (int)(width / 2);
                                // 当たり判定時間が超過したのでタッチ情報を削除
      return rv;enemy.rctCurrentArea.right = enemy.rctOccupationArea.right + (int)(width / 2);
         enemy.rctCurrentArea.top = enemy.rctOccupationArea.bottom - height;
}                    // 透過率を更新(だんだん薄くしていく)
                                rctTouch = null;
         return true;enemy.nAlpha = 255 - 255 * (enemy.nCount - ENEMY_COUNT_MAX) / (ENEMY_OUT_COUNT_MAX - ENEMY_COUNT_MAX);
                        }
  }         }
                }
            return true;
    } }                                                                                                                         40
得点をつける
【概要】
 累積得点(スコア)を実装
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch9 ( 09MizmonTouch )
   (MizmonTouch8から作成)
【追加メソッド】
 EnemyInfoクラス : 持ち点変数を追加
 genEnemy() : 持ち点登録を追加
 updateEnemyAlive() : 持ち点の減算部を追加
 judgeHit() :当たり判定でHitした場合に
             持ち点をスコアに加算をする
 genVirtualDisplay() :下記を呼び出し
   dispGetPoint 獲得得点の表示
   dispGameInfo スコアを仮想画面に表示
   dispTextテキストを表示


                                   41
得点をつける(アクション)
     Score           Score
     256      スコア    272
                         16pts.
                                   獲得ポイント
                             ドロン
              当たり!




• キャラクター管理クラスに各キャラクター固有の持ち点を
  保持する変数をEnemyInfoクラスに追加
• キャラクター生成時に持ち点を算出しオブジェクトに登録
• 該当の敵が1動作するたびに持ち点を減算する
    (とりあえず半分づつ減らす。但し、1より下回らない)
• 当たり判定で獲得ポイントの表示とスコアへの加算
                                     42
得点をつける(コード解説)
// 敵1体の情報をまとめるクラス
   // 敵の通常更新(敵が消滅したらfalseを返す)
class EnemyInfo {
       // 倒した敵の獲得得点を表示する
   private boolean updateEnemyAlive(EnemyInfo enemy) {
           // テキストを表示する
               :
       private void dispGetPoint(Canvas canvas) {
          :private void dispText(Canvas canvas, String text, int x, int y, Typeface type, int alpha, int size, int align) {
      public int                 nAlpha;                  // 表示時の透過率
              // 倒した敵の獲得得点を表示する
                  // Paintをインスタンス化
          // 持ち点の更新nPoint;
      public int                                          // 持ち点
              for(EnemyInfo=new :Paint();
                  Paint paint item listEnemys) {
          if(1 < enemy.nPoint) {
}
                 enemy.nPoint /= 2; ENEMY_COUNT_MAX < item.nCount) {
                      if(!item.bAlive &&
                  paint.setAntiAlias(true);
          }       paint.setTextSize(size);
                              String pts = item.nPoint + "pts.";    // サイズの設定
                  paint.setTypeface(type);                          // フォントを設定
          returnint widthint(int)(paint.measureText(text)); // 描画幅を抽出
                    true;      =
                                  x = (int)(VD_WIDTH / 2);
   }                          int y = (int)(VD_HEIGHT / 2 - (item.nCount) * 10);
class GameViewThread extends Thread {
   private voidif(DISP_RIGHT == align) { pts, x, y, Typeface.DEFAULT_BOLD, item.nAlpha, DISP_PTS_SIZE,
      :
                              dispText(canvas,
                    judgeHit() {
                              DISP_CENTER);
      // 敵の生成 // (mHolder) {
          synchronized 右寄せの場合の座標変換
                      } x -=
      private EnemyInfo (int)(width + {2); nJudgementCount && null != rctTouch) {
                 if(JUDGEMENT_TIME -2 <=
                                genEnemy()
              } } else if(DISP_CENTER == align) { ; 0<=i ; i--){
                           for(int i=listEnemys.size()-1
             :
       }                  // センターの場合の座標変換
                                  EnemyInfo enemy = listEnemys.get(i);
             // 持ち点を設定    x -= (int)(width / 2 + 1);&& Rect.intersects(rctTouch, enemy.rctCurrentArea)){
                                  if(enemy.bAlive
             rv.nPoint { 100 * (ENEMY_SPEED_MAX - rv.nSpeed + 1) *
                  } else =
       // スコア等ゲーム情報を表示する
             (10 - (int)(10 左寄せは何もしない
                                          :
                          ; // * (enemy_width canvas) {
                                                                              テキストを表示
                                                 - ENEMY_WIDTH_MIN) / (ENEMY_WIDTH_MAX -
                                          // 得点を加算する
       private void dispGameInfo(Canvas
             ENEMY_WIDTH_MIN)));
                  }
              // スコアタイトルを表示する += enemy.nPoint;
                                          nScore
                  // 影の描画}
              String title = mContext.getString(R.string.txt_score);
             return rv;}== alpha) {
                  if(255                                         キャラクターの大きさ、スピードにより                       影付き
              dispText(canvas, title, 0, DIAP_TITLE_POS_Y, Typeface.DEFAULT, 255, DISP_TITLE_SIZE, DISP_LEFT);
      }                   // alphaが255のときのみ表示
              // スコアを表示する  :
                          paint.setColor(Color.argb(alpha, 0, 0, 0));         持ち点を算出する
      :          }
              String score = "" + nScore;
                          canvas.drawText(text, x + 2, y + 2, paint);
          } dispText(canvas, score, 0, DIAP_INFO_POS_Y, Typeface.DEFAULT_BOLD, 255, DISP_INFO_SIZE, DISP_LEFT);
                  }
   } }            // 本体の描画
                  paint.setColor(Color.argb(alpha, 255, 255, 255));
                  canvas.drawText(text, x, y, paint);
           }                                                                                                                43
スタート画面を作る
【概要】
  ゲーム全体の構成(状態管理)の整理
【サンプルプロジェクト名(アプリケーション名)】
  MizmonTouch10 ( 10MizmonTouch )
       (MizmonTouch9から作成)
【追加・修正】
  game.xml : 基本レイアウトを記述
  MizmonTouch10クラス
       OnCreate() : game.xmlをviewに設定
  GameViewクラス
       GameView () : レイアウトxmlから呼び出されるとき、AttributeSet引数を追加する
       setButtonInstance : レイアウトxml上のボタンインスタンスをセットする
       setTextViewInstancce : レイアウトxml上のテキストインスタンスをセットする
       gameStart() : activity上のボタンを押されたら呼び出される
       genThread() : 新設。スレッドのインスタンス化が長くなりそうなので
       surfaceCreated() : Threadのインスタンス化の代わりにgenThreadメソッドを呼び出し
  GameViewThreadクラス
       GameViewThread() : Message用の引数を追加
       setStart() : ゲームステートをセットする
       doTouchEvent
       doStart() : ゲームスタート処理
       setState() : ゲームステートをセットする
                    GameViewへのメッセージを生成して送り付ける
       genVirtualDisplay() : ゲームステート毎の表示設定
       dispReady() : ゲーム開始前のカウントダウンを表示
       main() : ゲームステート応じた処理に変更
       controlState() : ステートの更新確認
                         ゲーム開始前のカウントダウンもここでやる




                                                                  44
スタート画面を作る(ステート管理)
    View        GameViewクラス                  GameViewThreadクラス
               画面全体の制御をする                      ゲーム自体の制御

                  ボタンが押された        通知(setStart)
  テキスト                                              menu
   ボタン                                              状態遷移

                                             play          ready
テキストやボタンは
 game.xmlで定義    handleMessage         テキストやボタンを非表示
                   テキストやボタン              (sendMessage)
   Ready画面           の制御
     Play画面                                      Menu:ボタンを表示
               GameViewThread → GameViewへは       Ready:カウントダウン
               message Handlerを使用する              Play:ゲーム実行中

いきなりゲームスタートも心の準備が…
                                                     状態(ステート)
     メニュー画面やスタート画面の追加                                  の管理
                                                                 45
スタート画面を作る(コード解説)
<?xml version="1.0" encoding="utf-8"?>
 publicGameView extends SurfaceView implements SurfaceHolder.Callback {
   class GameViewThread extendsextends Activity {
           class MizmonTouch10
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"                              @Override
    class
        // ***** メイン処理
  android:layout_width="fill_parent"
                                                Thread {
      /*********************** 内部処理 ***********************/
         private 仮想画面関係の処理 ***** // Buttonのインスタンス
                                            btStart;
         ***** システム ***** mHandler; // TextViewのインスタンス
         //private Handler
      //// private TextView *****
              *****
                                                                                               game.xml
                       GameView mGameView; // ハンドラ // GameViewのインスタンス surfaceCreated(SurfaceHolder holder) {
           private Button                                                                            public void
           仮想画面(ビットマップ)を生成する>
         @Overridemain() { tvText;
  android:layout_height="fill_parent"                             // システムのステートのセット                          // スレッド生成
             // ゲーム全体のステート(状態遷移)
         private void
         ゲームのスタート処理 == context, AttributeSet attrs) {setState(EnumState state)genThread(holder);
      //private void genVirtualDisplay() {
           publicif(EnumState.play eCurrentState) private ゲーム中だけの処理
                     GameView(Context
         public void onCreate(Bundle savedInstanceState) {
        <jp.mizmon21.android.mizmontouch10.GameView
             enum EnumState{{                                     {    //  void                               {
      private void doStart() (mHolder) {
                 synchronized                                            synchronized (mHolder) {
                       menu, 当たり判定
                     super(context, attrs);
                             //
          android:id="@+id/gameview"
               // 各変数の初期化     :            :                                          ここでGameViewを
                                                                               // メニュー画面                    :
                     : ready,judgeHit();                                        int bt_visibility = View.INVISIBLE;
                                                                                                     }
                           switch(eCurrentState) {
          android:layout_width="fill_parent"
                  // game.xmlを画面としてセットする                                       // ゲーム開始カウントダウン
           } nScore = 0; 敵情報更新
                             //
                                      case menu:
                       play,moveEnemy();                          // 特に何もしない(game.xmlを表示)
                                                                               // ゲーム動作中 : 貼り付けてる
                                                                                int tv_visibility = View.INVISIBLE;
          android:layout_height="fill_parent" />
               nCountSub = CYCLE_COUNT;
                  setContentView(R.layout.game);
           // ゲームスレッドの生成 break;
             } nCount = READY_COUNT_DOWN;
        <RelativeLayout
                                                                                String tv_text = "";
                   }                                                            // ステートをセットする
                                      case ready:
           private void genThread(SurfaceHolder holder) {
                  // GameViewのインスタンスを取得                           // ゲーム開始前のカウントダウンを表示する
             private仮想画面生成 eCurrentState = EnumState.menu;//
                   // EnumState
          android:layout_width="fill_parent"   dispReady(canvas); それぞれのステート動作判断はこの中で行う          現在のゲームのステート
                     // メソッドのインスタンス化
                   genVirtualDisplay();                             //          eCurrentState = state;
                  mGameView = (GameView)findViewById(R.id.gameview);
             /*********************** 外部から呼ばれるメソッド ***********************/
          android:layout_height="fill_parent"  break;
               // 敵情報の初期化GameViewThread(holder, mContext,                                                        View
                   // ステートの更新判定                                                 switch(eCurrentState){
             //listEnemys = new ArrayList<EnemyInfo>();敵の貼り付け new Handler() {
                コンストラクタ new play:
                     mThread =
                                      case
          android:gravity="center_horizontal">                    //                                            gameview // メニュー表示
                   controlState();
                     @Override                                                          case menu: (FramELayout)
                                               dispEnemy(canvas);
             public GameViewThread(SurfaceHolder surfaceHolder, Context context, Handler handler) {
         } // // 画面ボタンのインスタンス取得と動作処理の登録 bt_visibility = View.VISIBLE;
                  ゲームステートの変更タッチ位置にアイコンの貼り付け
                     public void handleMessage(Message message) {
                       this.mHandler = handler;//             // ハンドラーのインスタンス化
                                                                                                             (GameView)
         // ステートの更新
                 <Button // メッセージハンドラの生成(Threadからのメッセージをここで受け取る)
               setState(EnumState.ready);
                  Button bt_start = dispTouch(canvas);
                                               (Button)findViewById(R.id.button1); tv_visibility = View.VISIBLE;
                         :
      } privateandroid:id="@+id/button1“
                     void controlState() {
                                               // 倒した敵の獲得得点を表示する                                  { ここはこれまでと同じ
                                                                                                            (Relative Layout)
                  bt_start.setOnClickListener(new View.OnClickListener() tv_text = mContext.getString(R.string.tv_menu);
                       // ゲームステートの変更
                   switch(eCurrentState) {btStart.setVisibility(message.getData().getInt("bt_visibility"));
                       :                       dispGetPoint(canvas);                             break;
                                          tvText.setVisibility(message.getData().getInt("tv_visibility"));
                                                   // 特に何もしない(ボタン入力のため)
                             case menu: onClick(View menuに状態を遷移にさせる
                   android:text="@string/bt_game“// v) {
                              public void // スコアの表示をする
                       setState(EnumState.menu);
                                                                                                                game.xml上の
             }
                                }
                                        // dispGameInfo(canvas);
                                                   // カウンタの更新
                                                                                        case ready:
                                          tvText.setText(message.getData().getString("tv_text"));
                                       break;
                       : case ready:ボタンがクリックされた時に呼び出されます                                case play:
                                                                                                                  textView1 // ゲーム開始カウントダウン
                                                                                                                               // ゲーム動作中
             // スタートボタンが押されたときの処理。ゲームをスタートさせる  break;
                                        mGameView.gameStart();
                   android:visibility="visible" />
                     });               nCountSub --;                                    default:                 ボタンの処理
                                      default:
             public void setStart(){ >= nCountSub) {
           }                  }
                       doStart();
                                       if(0
                                               break;         // readyに状態を遷移させる
                                                                                                 break;            button1
                 <TextView                      nCountSub = CYCLE_COUNT;}
           public void}setButtonInstance(Button button_start) {
             } } });                            nCount --;
                   android:id="@+id/textView1" // レイアウト上のbuttonのインスタンス化
                     btStart = button_start; > nCount) {                        // メッセージを生成しGameViewに送り付ける
                  //:テキストのインスタンスを取得
        } } // タッチ処理
                                                if(0
                                                                                Message msg = mHandler.obtainMessage();
                                                        // カウントダウンがゼロになったらゲームスタート
             public boolean doTouchEvent(MotionEvent event) {
                  TextView tv_text = (TextView)findViewById(R.id.textView1);
           publicandroid:text=""(mHolder) { setState(EnumState.play); bundle = new Bundle();
                     void setTextViewInstancce(TextView text_view) {Bundle
        // ゲーム開始前のカウントダウンを表示する
                       synchronized
                  //:XMLの各パーツのインスタンスをGameViewに教えてやる
                     tvText = text_view; }                                      bundle.putInt("bt_visibility", bt_visibility);
                                  if(EnumState.play == { // レイアウト上のtextviewをインスタンス化
        private void dispReady(Canvas canvas)eCurrentState) { // 状態がplayのときのみタッチを有効にする
                                       }
                  mGameView.setButtonInstance(bt_start); bundle.putInt("tv_visibility", tv_visibility);
                   android:visibility="visible" />
           } // カウントダウンを表示する                 :
                                       break;                                   bundle.putString("tv_text", tv_text);
           // ゲーム開始ボタンが押された時の処理
                 String count = (0 == nCount) ? mContext.getString(R.string.txt_start) : "" + nCount;
                  mGameView.setTextViewInstancce(tv_text);
                             case play:
                                  }                // とりあえず無限動作のため何もしない
                 int x = (int)((VD_WIDTH / 2));
        </RelativeLayout>                                                       msg.setData(bundle);
         } public void (int)(VD_HEIGHT / 2);
                            gameStart() {
                 int } = default:
                       y
                                       break;
                     mThread.setStart();
</FrameLayout>return true;                                                      mHandler.sendMessage(msg);
                                                             // GameViewThreadにスタートを通知
 }               int size = (int)(96 * (float)(1f - (nCountSub - 1f) / CYCLE_COUNT));
                                       break;                            }
           } } dispText(canvas, count, x, y, Typeface.DEFAULT_BOLD, 255, size, DISP_CENTER);
                   }                                              }                                                                      46
        }}
終了処理
【概要】
 時間制限を設けて終了処理を実装
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch11 ( 11MizmonTouch )
   (MizmonTouch10から作成)
【追加メソッド】
 GameViewThreadクラス内
   setState() : ゲームオーバーの処理を追加
   ganVirtualDisplay() : 各ステートの処理を追加
   dispGameInfo() : 残り時間の表示を追加
   dispFinish() : 終了時の演出を追加
   main() : ゲームプレイ中の残り時間を更新
   controlState() : ゲーム中の残り時間の更新
                    終了時演出のカウンタを更新

                                       47
終了処理(ゲームオーバー)
無限に続き終わらない…

    30秒一本勝負にする
• ゲーム残り時間処理を実装               状態遷移図

• ゲームオーバー時の表示を実装                  menu     ボタン押下


• ステートを追加      タッチ
                         game
 – finish:ゲーム終了時演出       over             ready
 – gameover:タッチ検出待ち                         カウントダウン

                      演出終了
                         finish           play
                                  30秒経過
                                                 48
終了処理(コード解説)
// ゲーム全体のステート(状態遷移)
enum 仮想画面(ビットマップ)を生成する
    // EnumState {
        // スコア等ゲーム情報を表示する
    private void genVirtualDisplay() {
       menu,                         // メニュー画面
        private void メイン処理 *****
       ready, ***** dispGameInfo(Canvas canvas) {
            //
           synchronized (mHolder) {
            private void main() // ゲーム開始カウントダウン
                       :             {
       play, // 残り時間タイトルを表示する        // ゲーム動作中
                     if(EnumState.play == eCurrentState) {
                    switch(eCurrentState) { // 当たり判定
                                     // ゲーム終了
       finish, String titlejudgeHit();
                              = mContext.getString(R.string.txt_time);
       gameover,case menu: // ゲームオーバー
                     }                    // 特に何もしない
                dispText(canvas, title, VD_WIDTH, DIAP_TITLE_POS_Y, Typeface.DEFAULT, 255, DISP_TITLE_SIZE, DISP_RIGHT);
}                            break;
                // 残り時間を表示する eCurrentState || EnumState.finish == eCurrentState || EnumState.gameover == eCurrentState) {
                     if(EnumState.play ==
                    case ready:           // ゲーム開始前のカウントダウンを表示する
                             moveEnemy(); // 敵情報更新
                String time = String.format("%.1f", nGameTime);                     // 小数点以下1位まで表示する
// システムのステートのセット :   }
                dispText(canvas, time, VD_WIDTH, DIAP_INFO_POS_Y, Typeface.DEFAULT_BOLD, 255, DISP_INFO_SIZE, DISP_RIGHT);
                               :
                             break;
private void setState(EnumState state) {
            }       case play:
       synchronized (mHolder) {
                // スコアタイトルを表示する
                  :                       :
               ステートの更新
            // switch(eCurrentState){
                :            break;
        } private void controlState() {
                    casecase menu:
                          finish:                                // メニュー表示
                     switch(eCurrentState) {
                             // 敵の貼り付け
                                     :
                               :
                             dispEnemy(canvas);
        // ゲームオーバーのロゴを表示する   casebreak;
                                   finish:
                             // スコアの表示をする
                         case ready: カウンタの更新
                                       //                        // ゲーム開始カウントダウン
        private void dispFinish(Canvas canvas) {
                             dispGameInfo(canvas);
                         case play: nCount ++;                   // ゲーム動作中
                // ゲームオーバーメッセージを表示する
                             // ゲーム終了のロゴを表示する // ゲーム終了
                         case finish:  if(TIME_GAME_OVER < nCount) {
                String text dispFinish(canvas);
                              = mContext.getString(R.string.txt_finish);
                                  break;      // カウンターが規定値に達したらメニューへ戻る
                int x = (int)((VD_WIDTH /setState(EnumState.gameover);
                         case gameover: 2));
                             break;                              // ゲームオーバー
                int case gameover:
                    y = (int)(VD_HEIGHT / 2);View.INVISIBLE;
                                       }
                                  bt_visibility =
                             // 敵の貼り付け - (float)nCount
                                       break;
                int size = (int)(56 * (float)(1fView.VISIBLE; / CYCLE_COUNT)) + 40;
                                  tv_visibility =
                             casetv_text mContext.getString(R.string.tv_gameover);
                                   gameover:
                             dispEnemy(canvas);
                size = (40 > size) ? 40 :=size;
                                       // タッチ検出確認
                             // スコアの表示をする
                                  break;
                dispText(canvas, text, x, y, Typeface.DEFAULT_BOLD, 255, size, DISP_CENTER);
        }                default: if(null != rctTouch)
                             dispGameInfo(canvas); {
                             break;
                                  break;      // タッチが検出されたらMenuへ戻る
                                              setState(EnumState.menu);
               } default:              }
                             break; break;
               // メッセージを生成しGameViewに送り付ける
                  : }        default:
       } }                             break;
} }                  }
            }
                                                                                                                        49
効果音や音楽を鳴らす
【概要】
 効果音やBGMを鳴らす仕組みを実装
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch12 ( 12MizmonTouch )
   (MizmonTouch11から作成)
【修正・追加メソッド】
 GameViewThread() : 効果音関係諸々をインスタンス化
 setState() : 効果音を鳴らす依頼を追加
 judgeHit() : 効果音を鳴らす依頼を追加
 playSound() : 音を鳴らすかどうか判断する
 startBgm() : BGM再生開始
 stopBgm() : BGM再生停止
【音源】
 「 On-Jin ~音人~」さん からお借りしました
 http://www.yen-soft.com/ssse/

                                      50
効果音や音楽を鳴らす
                音を鳴らす方法
            soundPool と mediaPlayer
soundPool                mediaPlayer
    単発音に向いている               尺の長い音に向いている
     (繰り返しが得意)                (繰り返しが苦手)

     効果音                       効果音
     • スタート音                   • BGM
     • アタック音
     • 空振りの音
     • タイムアップの音



                                       51
効果音や音楽を鳴らす(コード解説)
// 効果音関係
   // システムのステートのセット
private static final int             SOUND_START= 0;                  // スタート
        // 敵の当たり判定 SOUND_FINISH= 1;
   private void setState(EnumState state) {
private static final int                                              // 終了
        private void効果音関係 *****/
             /***** int judgeHit() SOUND_HIT = 2;
           synchronized (mHolder) {
private static final                 {                                // 敵あたり(当たった音)
             // final int : (mHolder) {
                synchronized SOUND_MISS = 3;
private static SoundPoolを利用して効果音を鳴らす                                  // 敵はずれ(空振りの音)
                   switch(eCurrentState){ -2 <==nJudgementCount && null != rctTouch) {
                         if(JUDGEMENT_TIME
             private void playSound() {
private static final int[][]         SOUND_GROUP {                    // 各サウンドプレイヤーで鳴らす効果音の定義
                     // 効果音出力許可されているか判断する ; i--){
                                  for(int i=listEnemys.size()-1 ; 0<=i
                             :
                  {SOUND_START, SOUND_FINISH},                        // サウンドプレイヤー0で出力するグループ
                     if(bEnableSound) EnemyInfo enemy = listEnemys.get(i);
                                            {
                  {SOUND_HIT, ready:// 当たり判定。判定するのはbAlive=trueの敵のみ
                             case SOUND_MISS}
                              // サウンドプレイヤー毎に鳴らすべき効果音があるかチェック                          // ゲーム開始カウントダウン
                                                                      // サウンドプレイヤー1で出力するグループ
};                            for(int i = 0 if(enemy.bAlive && Rect.intersects(rctTouch, スタート時の効果音を鳴らす
                                       bPlaySound[SOUND_START] = true; {
                                             ; i < SOUND_GROUP.length ; i++)          // enemy.rctCurrentArea)){
private boolean                        break;
                                     bEnableSound;              :     // 効果音出力許可フラグ
                                       // サウンドプレーヤーグループの各効果音で鳴らす依頼があるかチェック
private MediaPlayer case play:                          < SOUND_GROUP[i].length ; j++)// ゲーム動作中
                                       for(int j = 0 ; 効果音を鳴らす メディアプレイヤーのインスタンス(BGM用)
                                     mediaPlayer //j= null;           //               {
private SoundPool                    soundPool 対象の効果音のIDを取り出す
                                       startBgm();bPlaySound[SOUND_HIT] = true; // BGM再生開始
                                                  //    = null;       // サウンドプールのインスタンス(効果音用)
private int[]                                           敵あたり判定効果音をならしたよフラグを立てる
                                       break; int // = new = SOUND_GROUP[i][j];
                                     nSoundIds sound_id int[4]; // それぞれの効果音のID
private boolean[]            case finish:         // 対象の効果音について鳴らす依頼があるか判断
                                                      bPlayHitSound = true;
                                     bPlaySound = new boolean[4];                     // ゲーム終了
                                                                                   // 効果音を鳴らす依頼フラグ(Trueで鳴らす)
private int[]                               } if(bPlaySound[sound_id]) {
                                     nLastSoundId = new int[2]; = true;
                                       bPlaySound[SOUND_FINISH]                       // 終了時の効果音を鳴らす
                                                                                   // 最後に鳴らした効果音(サウンドプレイヤー2個分)
                                  }                      // 現在鳴らしている効果音を停止
private boolean                        stopBgm();
                                     bPlayHitSound; --;               // 敵あたり効果音を鳴らしたよフラグ
                                                                                      // BGM再生終了
                                                                            // 当たり判定時間をデクリメント
                                  nJudgementCountsoundPool.stop(nLastSoundId[i]);
/*********************** 外部から呼ばれるメソッド ***********************/
                                       break;
                         } else if(JUDGEMENT_TIME -新たな効果音を鳴らす {
                                                         // 3 == nJudgementCount)
// コンストラクタ                   : if(!bPlayHitSound) nLastSoundId[i] = soundPool.play(nSoundIds[sound_id], 1.0f, 1.0f, 1, 0, 1);
                                                          {
                             default: // 効果音を鳴らす
public GameViewThread(SurfaceHolder surfaceHolder, Context context, Handler handler)第1引数:鳴らす音のID
                                                         // 依頼フラグを下げる                             {
       :                               break;
                                            bPlaySound[SOUND_MISS] = true;= false;
                                                         bPlaySound[sound_id]      第1引数:プールする最大の数(サウンドプレイヤーの数)
                                                                                                 第2引数、第3引数:左右の音量(0.0~1.0)
       // 各効果音をインスタンス化 }     } } else {                                            第2引数:Streamのタイプ(通常はSTREAM_MUSIC)
                                                                                                 第4引数:プライオリティ(0が一番優先度が高い)
       bEnableSound = true; } // 敵をやっつけたフラグを下げる
                   :                                                               第3引数:クオリティ(デフォルトは0)
                                                                                                 第5引数:ループ回数(-1:無限ループ、0:単発)
                                            bPlayHitSound = false;
       soundPool = new}SoundPool(2, AudioManager.STREAM_MUSIC, 0);
           }
                                  }                                                              第6引数:再生速度(0.5~2.0倍)
                     }
   } nSoundIds[SOUND_START] = soundPool.load(mContext, R.raw.start, 1);                          // スタート(プレイヤー0)
             }                    nJudgementCount --;                       // 当たり判定時間をデクリメント // BGMを停止する
       nSoundIds[SOUND_FINISH] = soundPool.load(mContext, R.raw.finish, 1);                      // 終了(プレイヤー0)
                         } else if(0 <= nJudgementCount) {
             // BGMを再生開始する                                                                    private void stopBgm() {
       nSoundIds[SOUND_HIT] = soundPool.load(mContext, R.raw.pico, 1);
                                  nJudgementCount --;
             private void startBgm() {                                                           // 敵あたり(プレイヤー1)
                                                                            // 当たり判定時間をデクリメント != mediaPlayer) {
                                                                                                     if (null
       nSoundIds[SOUND_MISS] = soundPool.load(mContext, R.raw.hazure, 1);
                     // 効果音出力許可されているか判断する
                         } else {                                                                // 敵はずれ(プレイヤー1)
                                                                                                              mediaPlayer.stop();
       :             if(bEnableSound) { = null;
                                  rctTouch                                  // 当たり判定時間が超過したのでタッチ情報を削除         mediaPlayer.reset();
}                             mediaPlayer = MediaPlayer.create(mContext, R.raw.bgm);                          mediaPlayer.release();
                         } mediaPlayer.start();                                                               mediaPlayer = null;
                }             mediaPlayer.setVolume(1.0f, 1.0f);                                     }
        }            }                                                                        }                                      52
             }
仕上げ




      53
最終チェック
          とにかくやりこんで
        気が付いたところを列挙する
• 改善点
 – 途中でやめても音が止まらない
 – 音量のバランス
• 全体的な使い勝手
 – 音なしモードが欲しい
 – タイトルロゴの挿入
• ゲームバランスの調整
 – 画像
 – 敵の出現パターン
 – 難易度
                        54
改善点
• 途中でやめても音が終わらない
 – ゲーム終了処理を追加
 – トップクラスにonPause()を追加し停止処理させる
 – GameViewクラスに停止処理を行うgameExit()を追加
 – GameViewThreadクラスにsetExit(),releaseSound()を追加

• 音量のバランス
 – 当たり時の音大きい                   各効果音やBGMに
 – はずれ時の音聞こえない                 static値を設定した




                                                   55
全体的な使い勝手
• 音なしモードが欲しい
 – menuに重ねて2個ボタンを追加
   「サウンド・オン」と「サウンド・オフ」を排他で表示
 – ボタンの制御はbEnableSoundフラグの状態で切
   り替える
 – bEnableSoundフラグで効果音とBGMをオン・オフ
   する
• タイトルロゴの挿入
 – タイトルロゴを表示するdispTitle()を作成
 – genVirtualDisplay()からmenu状態時に呼び出し

                                       56
ゲームバランスの調整
• 画像
 – やっぱり画像キモイ
 「クリップアートファクトリー」さん から画像をお借りしました
   http://www.printout.jp/clipart/index.html
 – 画像は差し替えるだけでOK                   同時表示数
                                   サイズ
• 敵の出現パターン
 – 開始直後はパラパラと敵が出現
 – 時間経過とともに同時表示数を増加
• 難易度                                          時間
 – 開始直後のサイズはなるべく大きく表示
 – 出現確立を高めに設定(時間経過でmaxを増やすため)

                                               57
完成!
【概要】
 ゲーム性が向上
【サンプルプロジェクト名(アプリケーション名)】
 MizmonTouch13 ( 13MizmonTouch )
   (MizmonTouch12から作成)




                                   58
まとめ




      59
余力があれば
細かな調整や演出を加える
• ポーズ機能の実装
• 空振りしたらペナルティとか
• 連続コンボでボーナス得点獲得とか
• ステージ制を導入(Win, Lost)
• ハイスコアを登録する
• 世界ランキング機能もいれてみる
など

                        60
終わりに
• 環境の充実
 パソコン黎明期では考えられないほど簡単にアプリ
 が作れる
  ⇒プラットフォームもイロイロ
• デザインはやっぱり重要
 グラフィックを違うだけでまったく雰囲気が変わる
  ⇒ゲームは見た目が大事
• ヒットゲームはアイデアで勝負
• Googleプレイでアプリ公開
  ⇒そして日々のアプリDL数をみてニヤニヤする

                           61
面白いアプリを開発して
世界の海原に漕ぎ出そう!




           -以 上-
               62

Weitere ähnliche Inhalte

Was ist angesagt?

CEDEC 2016 Metal と Vulkan を用いた水彩画レンダリング技法の紹介
CEDEC 2016 Metal と Vulkan を用いた水彩画レンダリング技法の紹介CEDEC 2016 Metal と Vulkan を用いた水彩画レンダリング技法の紹介
CEDEC 2016 Metal と Vulkan を用いた水彩画レンダリング技法の紹介Drecom Co., Ltd.
 
Direct xとopenglの隠蔽実装例
Direct xとopenglの隠蔽実装例Direct xとopenglの隠蔽実装例
Direct xとopenglの隠蔽実装例tecopark
 
ARコンテンツ作成勉強会:Myoを用いたVRコンテンツ開発
ARコンテンツ作成勉強会:Myoを用いたVRコンテンツ開発ARコンテンツ作成勉強会:Myoを用いたVRコンテンツ開発
ARコンテンツ作成勉強会:Myoを用いたVRコンテンツ開発Takashi Yoshinaga
 
SurfaceTextureとシェーダを使って遊んでみる
SurfaceTextureとシェーダを使って遊んでみるSurfaceTextureとシェーダを使って遊んでみる
SurfaceTextureとシェーダを使って遊んでみるTatsuya Matsumoto
 
HoloLensハンズオン:AirTap & SpatialMapping編
HoloLensハンズオン:AirTap & SpatialMapping編HoloLensハンズオン:AirTap & SpatialMapping編
HoloLensハンズオン:AirTap & SpatialMapping編Takashi Yoshinaga
 
【Unite Tokyo 2019】【あら簡単】インテルのGPAを使ってあなたのUnityタイトルを高速化
【Unite Tokyo 2019】【あら簡単】インテルのGPAを使ってあなたのUnityタイトルを高速化【Unite Tokyo 2019】【あら簡単】インテルのGPAを使ってあなたのUnityタイトルを高速化
【Unite Tokyo 2019】【あら簡単】インテルのGPAを使ってあなたのUnityタイトルを高速化UnityTechnologiesJapan002
 
CEDEC 2020 - 高品質かつ低負荷な3Dライブを実現するシェーダー開発 ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スク...
CEDEC 2020 - 高品質かつ低負荷な3Dライブを実現するシェーダー開発 ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スク...CEDEC 2020 - 高品質かつ低負荷な3Dライブを実現するシェーダー開発 ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スク...
CEDEC 2020 - 高品質かつ低負荷な3Dライブを実現するシェーダー開発 ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スク...KLab Inc. / Tech
 
OpenCV4Androidで画像処理アプリのススメ
OpenCV4Androidで画像処理アプリのススメOpenCV4Androidで画像処理アプリのススメ
OpenCV4Androidで画像処理アプリのススメMasaki Otsuki
 
Halide, Darkroom - 並列化のためのソフトウェア・研究
Halide, Darkroom - 並列化のためのソフトウェア・研究Halide, Darkroom - 並列化のためのソフトウェア・研究
Halide, Darkroom - 並列化のためのソフトウェア・研究Yuichi Yoshida
 
History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法Yoshifumi Kawai
 
ゴルフゲームでUnityの限界を突破する方法
ゴルフゲームでUnityの限界を突破する方法ゴルフゲームでUnityの限界を突破する方法
ゴルフゲームでUnityの限界を突破する方法Nohina Hidenari
 
Unityのポストエフェクトで遊ぶ!
Unityのポストエフェクトで遊ぶ!Unityのポストエフェクトで遊ぶ!
Unityのポストエフェクトで遊ぶ!Yamato Honda
 
【Unite Tokyo 2019】「禍つヴァールハイト」Timelineだから可能だった!モバイルに最適化されたリアルタイム3D演出!
【Unite Tokyo 2019】「禍つヴァールハイト」Timelineだから可能だった!モバイルに最適化されたリアルタイム3D演出!【Unite Tokyo 2019】「禍つヴァールハイト」Timelineだから可能だった!モバイルに最適化されたリアルタイム3D演出!
【Unite Tokyo 2019】「禍つヴァールハイト」Timelineだから可能だった!モバイルに最適化されたリアルタイム3D演出!UnityTechnologiesJapan002
 
簡単!OpenGL ES 2.0フラグメントシェーダー
簡単!OpenGL ES 2.0フラグメントシェーダー簡単!OpenGL ES 2.0フラグメントシェーダー
簡単!OpenGL ES 2.0フラグメントシェーダーEiji Kamiya
 
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~Entity Compon...
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~Entity Compon...【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~Entity Compon...
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~Entity Compon...Unity Technologies Japan K.K.
 
A Framework for LightUp Applications of Grani
A Framework for LightUp Applications of GraniA Framework for LightUp Applications of Grani
A Framework for LightUp Applications of GraniYoshifumi Kawai
 
Flashup13 Basic Training of Flare3D
Flashup13 Basic Training of Flare3DFlashup13 Basic Training of Flare3D
Flashup13 Basic Training of Flare3DKatsushi Suzuki
 
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例Yoshifumi Kawai
 
50分でわかるブループリントについて
50分でわかるブループリントについて50分でわかるブループリントについて
50分でわかるブループリントについてMasahiko Nakamura
 

Was ist angesagt? (20)

CEDEC 2016 Metal と Vulkan を用いた水彩画レンダリング技法の紹介
CEDEC 2016 Metal と Vulkan を用いた水彩画レンダリング技法の紹介CEDEC 2016 Metal と Vulkan を用いた水彩画レンダリング技法の紹介
CEDEC 2016 Metal と Vulkan を用いた水彩画レンダリング技法の紹介
 
Direct xとopenglの隠蔽実装例
Direct xとopenglの隠蔽実装例Direct xとopenglの隠蔽実装例
Direct xとopenglの隠蔽実装例
 
ARコンテンツ作成勉強会:Myoを用いたVRコンテンツ開発
ARコンテンツ作成勉強会:Myoを用いたVRコンテンツ開発ARコンテンツ作成勉強会:Myoを用いたVRコンテンツ開発
ARコンテンツ作成勉強会:Myoを用いたVRコンテンツ開発
 
SurfaceTextureとシェーダを使って遊んでみる
SurfaceTextureとシェーダを使って遊んでみるSurfaceTextureとシェーダを使って遊んでみる
SurfaceTextureとシェーダを使って遊んでみる
 
HoloLensハンズオン:AirTap & SpatialMapping編
HoloLensハンズオン:AirTap & SpatialMapping編HoloLensハンズオン:AirTap & SpatialMapping編
HoloLensハンズオン:AirTap & SpatialMapping編
 
【Unite Tokyo 2019】【あら簡単】インテルのGPAを使ってあなたのUnityタイトルを高速化
【Unite Tokyo 2019】【あら簡単】インテルのGPAを使ってあなたのUnityタイトルを高速化【Unite Tokyo 2019】【あら簡単】インテルのGPAを使ってあなたのUnityタイトルを高速化
【Unite Tokyo 2019】【あら簡単】インテルのGPAを使ってあなたのUnityタイトルを高速化
 
CEDEC 2020 - 高品質かつ低負荷な3Dライブを実現するシェーダー開発 ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スク...
CEDEC 2020 - 高品質かつ低負荷な3Dライブを実現するシェーダー開発 ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スク...CEDEC 2020 - 高品質かつ低負荷な3Dライブを実現するシェーダー開発 ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スク...
CEDEC 2020 - 高品質かつ低負荷な3Dライブを実現するシェーダー開発 ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スク...
 
OpenCV4Androidで画像処理アプリのススメ
OpenCV4Androidで画像処理アプリのススメOpenCV4Androidで画像処理アプリのススメ
OpenCV4Androidで画像処理アプリのススメ
 
Halide, Darkroom - 並列化のためのソフトウェア・研究
Halide, Darkroom - 並列化のためのソフトウェア・研究Halide, Darkroom - 並列化のためのソフトウェア・研究
Halide, Darkroom - 並列化のためのソフトウェア・研究
 
History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
 
ゴルフゲームでUnityの限界を突破する方法
ゴルフゲームでUnityの限界を突破する方法ゴルフゲームでUnityの限界を突破する方法
ゴルフゲームでUnityの限界を突破する方法
 
Unityのポストエフェクトで遊ぶ!
Unityのポストエフェクトで遊ぶ!Unityのポストエフェクトで遊ぶ!
Unityのポストエフェクトで遊ぶ!
 
【Unite Tokyo 2019】「禍つヴァールハイト」Timelineだから可能だった!モバイルに最適化されたリアルタイム3D演出!
【Unite Tokyo 2019】「禍つヴァールハイト」Timelineだから可能だった!モバイルに最適化されたリアルタイム3D演出!【Unite Tokyo 2019】「禍つヴァールハイト」Timelineだから可能だった!モバイルに最適化されたリアルタイム3D演出!
【Unite Tokyo 2019】「禍つヴァールハイト」Timelineだから可能だった!モバイルに最適化されたリアルタイム3D演出!
 
簡単!OpenGL ES 2.0フラグメントシェーダー
簡単!OpenGL ES 2.0フラグメントシェーダー簡単!OpenGL ES 2.0フラグメントシェーダー
簡単!OpenGL ES 2.0フラグメントシェーダー
 
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~Entity Compon...
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~Entity Compon...【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~Entity Compon...
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~Entity Compon...
 
A Framework for LightUp Applications of Grani
A Framework for LightUp Applications of GraniA Framework for LightUp Applications of Grani
A Framework for LightUp Applications of Grani
 
60fpsアクションを実現する秘訣を伝授 解析編
60fpsアクションを実現する秘訣を伝授 解析編60fpsアクションを実現する秘訣を伝授 解析編
60fpsアクションを実現する秘訣を伝授 解析編
 
Flashup13 Basic Training of Flare3D
Flashup13 Basic Training of Flare3DFlashup13 Basic Training of Flare3D
Flashup13 Basic Training of Flare3D
 
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
Metaprogramming Universe in C# - 実例に見るILからRoslynまでの活用例
 
50分でわかるブループリントについて
50分でわかるブループリントについて50分でわかるブループリントについて
50分でわかるブループリントについて
 

Ähnlich wie Androidプログラミング初心者のためのゲームアプリ開発入門

Media Art II 2013 第7回 : openFrameworks 3Dグラフィクス、OpenGL
Media Art II 2013 第7回 : openFrameworks 3Dグラフィクス、OpenGLMedia Art II 2013 第7回 : openFrameworks 3Dグラフィクス、OpenGL
Media Art II 2013 第7回 : openFrameworks 3Dグラフィクス、OpenGLAtsushi Tadokoro
 
C++でのゲームプログラミングをしたときのお話 札幌C++勉強会 #4 〜スタートゲームプログラミング〜
C++でのゲームプログラミングをしたときのお話 札幌C++勉強会 #4 〜スタートゲームプログラミング〜C++でのゲームプログラミングをしたときのお話 札幌C++勉強会 #4 〜スタートゲームプログラミング〜
C++でのゲームプログラミングをしたときのお話 札幌C++勉強会 #4 〜スタートゲームプログラミング〜勝成 鈴江
 
Cocos2d-x(JS) ハンズオン #08「様々な画像描画方法」
Cocos2d-x(JS) ハンズオン #08「様々な画像描画方法」Cocos2d-x(JS) ハンズオン #08「様々な画像描画方法」
Cocos2d-x(JS) ハンズオン #08「様々な画像描画方法」Tomoaki Shimizu
 
Flashup 12 Basic Training of Away3D
Flashup 12 Basic Training of Away3DFlashup 12 Basic Training of Away3D
Flashup 12 Basic Training of Away3DKatsushi Suzuki
 
Html canvas shooting_and_performanceup
Html canvas shooting_and_performanceupHtml canvas shooting_and_performanceup
Html canvas shooting_and_performanceupYohei Munesada
 
Acme minechan
Acme minechanAcme minechan
Acme minechantakesako
 
2012 03-03-titanium plusquicktigame2d
2012 03-03-titanium plusquicktigame2d2012 03-03-titanium plusquicktigame2d
2012 03-03-titanium plusquicktigame2dHiroshi Oyamada
 
怪しいWindowsプログラミング
怪しいWindowsプログラミング怪しいWindowsプログラミング
怪しいWindowsプログラミングnagoya313
 
レゴ×Kinect実験指導書
レゴ×Kinect実験指導書レゴ×Kinect実験指導書
レゴ×Kinect実験指導書Satoshi Fujimoto
 
UnityとnodeとMMDと
UnityとnodeとMMDとUnityとnodeとMMDと
UnityとnodeとMMDとsters
 
Orange Cube 自社フレームワーク 2015/3
Orange Cube 自社フレームワーク 2015/3Orange Cube 自社フレームワーク 2015/3
Orange Cube 自社フレームワーク 2015/3信之 岩永
 
enchant.jsでゲーム制作をはじめてみよう 「パンダの会」バージョン
enchant.jsでゲーム制作をはじめてみよう 「パンダの会」バージョンenchant.jsでゲーム制作をはじめてみよう 「パンダの会」バージョン
enchant.jsでゲーム制作をはじめてみよう 「パンダの会」バージョンRyota Shiroguchi
 
Canvas de shooting 制作のポイント
Canvas de shooting 制作のポイントCanvas de shooting 制作のポイント
Canvas de shooting 制作のポイントYohei Munesada
 
iOS の動画アプリ開発に Xamarin を使ってみた @JXUG #2 East
iOS の動画アプリ開発に Xamarin を使ってみた @JXUG #2 EastiOS の動画アプリ開発に Xamarin を使ってみた @JXUG #2 East
iOS の動画アプリ開発に Xamarin を使ってみた @JXUG #2 Eastirgaly
 
Android カスタムROMの作り方
Android カスタムROMの作り方Android カスタムROMの作り方
Android カスタムROMの作り方Masahiro Hidaka
 
Media Art II 2013 第5回:openFrameworks Addonを使用する
Media Art II 2013 第5回:openFrameworks Addonを使用するMedia Art II 2013 第5回:openFrameworks Addonを使用する
Media Art II 2013 第5回:openFrameworks Addonを使用するAtsushi Tadokoro
 
Pf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsolaPf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsolaandroid sola
 
Cocos2d-x実践講座 in 鹿児島
Cocos2d-x実践講座 in 鹿児島Cocos2d-x実践講座 in 鹿児島
Cocos2d-x実践講座 in 鹿児島Tomoaki Shimizu
 
PF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsolaPF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsolaandroid sola
 

Ähnlich wie Androidプログラミング初心者のためのゲームアプリ開発入門 (20)

Media Art II 2013 第7回 : openFrameworks 3Dグラフィクス、OpenGL
Media Art II 2013 第7回 : openFrameworks 3Dグラフィクス、OpenGLMedia Art II 2013 第7回 : openFrameworks 3Dグラフィクス、OpenGL
Media Art II 2013 第7回 : openFrameworks 3Dグラフィクス、OpenGL
 
C++でのゲームプログラミングをしたときのお話 札幌C++勉強会 #4 〜スタートゲームプログラミング〜
C++でのゲームプログラミングをしたときのお話 札幌C++勉強会 #4 〜スタートゲームプログラミング〜C++でのゲームプログラミングをしたときのお話 札幌C++勉強会 #4 〜スタートゲームプログラミング〜
C++でのゲームプログラミングをしたときのお話 札幌C++勉強会 #4 〜スタートゲームプログラミング〜
 
Cocos2d-x(JS) ハンズオン #08「様々な画像描画方法」
Cocos2d-x(JS) ハンズオン #08「様々な画像描画方法」Cocos2d-x(JS) ハンズオン #08「様々な画像描画方法」
Cocos2d-x(JS) ハンズオン #08「様々な画像描画方法」
 
Flashup 12 Basic Training of Away3D
Flashup 12 Basic Training of Away3DFlashup 12 Basic Training of Away3D
Flashup 12 Basic Training of Away3D
 
Html canvas shooting_and_performanceup
Html canvas shooting_and_performanceupHtml canvas shooting_and_performanceup
Html canvas shooting_and_performanceup
 
Acme minechan
Acme minechanAcme minechan
Acme minechan
 
2012 03-03-titanium plusquicktigame2d
2012 03-03-titanium plusquicktigame2d2012 03-03-titanium plusquicktigame2d
2012 03-03-titanium plusquicktigame2d
 
怪しいWindowsプログラミング
怪しいWindowsプログラミング怪しいWindowsプログラミング
怪しいWindowsプログラミング
 
レゴ×Kinect実験指導書
レゴ×Kinect実験指導書レゴ×Kinect実験指導書
レゴ×Kinect実験指導書
 
UnityとnodeとMMDと
UnityとnodeとMMDとUnityとnodeとMMDと
UnityとnodeとMMDと
 
Orange Cube 自社フレームワーク 2015/3
Orange Cube 自社フレームワーク 2015/3Orange Cube 自社フレームワーク 2015/3
Orange Cube 自社フレームワーク 2015/3
 
enchant.jsでゲーム制作をはじめてみよう 「パンダの会」バージョン
enchant.jsでゲーム制作をはじめてみよう 「パンダの会」バージョンenchant.jsでゲーム制作をはじめてみよう 「パンダの会」バージョン
enchant.jsでゲーム制作をはじめてみよう 「パンダの会」バージョン
 
Canvas de shooting 制作のポイント
Canvas de shooting 制作のポイントCanvas de shooting 制作のポイント
Canvas de shooting 制作のポイント
 
iOS の動画アプリ開発に Xamarin を使ってみた @JXUG #2 East
iOS の動画アプリ開発に Xamarin を使ってみた @JXUG #2 EastiOS の動画アプリ開発に Xamarin を使ってみた @JXUG #2 East
iOS の動画アプリ開発に Xamarin を使ってみた @JXUG #2 East
 
Android カスタムROMの作り方
Android カスタムROMの作り方Android カスタムROMの作り方
Android カスタムROMの作り方
 
Media Art II 2013 第5回:openFrameworks Addonを使用する
Media Art II 2013 第5回:openFrameworks Addonを使用するMedia Art II 2013 第5回:openFrameworks Addonを使用する
Media Art II 2013 第5回:openFrameworks Addonを使用する
 
Pf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsolaPf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsola
 
深掘りARKit
深掘りARKit深掘りARKit
深掘りARKit
 
Cocos2d-x実践講座 in 鹿児島
Cocos2d-x実践講座 in 鹿児島Cocos2d-x実践講座 in 鹿児島
Cocos2d-x実践講座 in 鹿児島
 
PF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsolaPF部2011年12月勉強会.androidsola
PF部2011年12月勉強会.androidsola
 

Androidプログラミング初心者のためのゲームアプリ開発入門

  • 1. 第17回Android勉強会in札幌 Androidプログラミング初心者のための ゲームアプリ開発入門 2012年11月17日 みずもん @mizmon21 水田 雅彦 1
  • 2. 目次 1. MIZMON21とは 2. はじめに 3. 開発 4. 仕上げ 5. 完成 6. まとめ 2
  • 4. 自己紹介 氏名 水田雅彦 HN みずもん(英語ではmizmon21)、MMQ大臣 誕生日 5月18日おうし座 A型 出身地 北海道旭川市 職暦 1991年4月~ 某重電機メーカー勤務 FAコンピューター等の開発・FAE業務に従事 2001年4月~ 某電子機器メーカー勤務 LSI製品の開発・FAE業務に従事 保有資格 自動車運転免許普通1種、第四級アマチュア無線技士、 1級電気工事施工管理技士、第3種電気主任技術者、 情報処理技術者第2種、初級システムアドミニストレータ、 JSTQBテスト技術者資格認定Foundation Level 出願特許 20件 モットー 仕事も遊びも全力全開(壊)! 好きなもの クルマ、野球、アニメ、萌え系など 4
  • 5. Android的な活動 Mizmon21のアンドロイドなweb http://mizmon21.jp/ 今日の資料やアプリ(apk)もここに保管 Googleプレイにて数本のAndroidアプリを公開中 https://market.android.com/developer?pub=mizmon21 アンドロイダー公認デベロッパ http://androider.jp/developer/4180c245837998ccd98f12d1147032e1/ 5
  • 8. 本題 Androidでオリジナルな ゲームを作ろう! 8
  • 9. こんな症状の方に • とりあえずEclipseをインストールしてみたもの の何を作っていいか迷ってる人 • よくわからないけどアプリを自作したい人 • なかなか重たい腰があげられない人 • 飽きっぽい人 そういう自分はまず @override でつまづいたw とにかく一本アプリをつくってみて自信をつける まずはそこからはじまるのです 9
  • 10. 目的 飽きずに最後まで作りきる • 楽しみながら開発を進めていく • 難しい話はまずは置いといて • コピペでまずは動かしてみる • Androidの文法とかはGoogle先生に聞く 10
  • 11. どんなゲームを作る? 最終的な完成の姿を想像力を 働かせて妄想する • ストレスの多い日常を解消したい • 2Dアクションゲーム • 「もぐらたたき」的な何かをつくってみる 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch ( みずもんタッチ ) 11
  • 12. 開発フロー 要求確認 要件定義 外部設計 詳細設計 実装 テスト ウオーターフォール開発は 忍耐が必要 リリース 12
  • 13. コツコツ積み上げる 設計 要件 実装 画像を動かす 時間制限 検査 タッチを検出する 音をいれる 当たり判定 バランス調整 スコアをつける リリース 反復開発でアプリの 成長を楽しむ 13
  • 14. 開発 14
  • 15. ゲームの基本(SurfaceviewとThread) 【概要】 ゲームの基本であるSurfaceviewとThreadの導入 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch1 ( 01MizmonTouch ) 【作成クラス】 MizmonTouch:Activityの派生クラス GameView:Surfaceviewの派生クラス SurfaceHolder.Callbackインターフェース GameViewThead:Threadの派生クラス 15
  • 16. ゲームの基本(高速描画) ダブルバッファリング View Surfaceview ・高速描画に不向き ・高速描画可能 ・onDraw()で描画 ・定期的な再描画可 ↑ 能 Invalidateで呼び出し ・独立した描画 ・数fps程度 ・数十fps程度 • View アプリケーション内で描画 ⇒ 高速描画に不向き 次の画像の準備 • Surfaceview アプリケーションのスレッドと描画のスレッドが独立 ⇒ 高速で定期的な描画が容易 Surfaceviewの活用 16
  • 17. ゲームの基本(定期的な動作) イベントドリブン(イベント駆動型) 外部から何かしらのイベントを発生時に実行 例)タッチ、ボタン押下など アクションゲームなアプリには不向き タッチ処理() { …… } 定期定期に処理させたい ※ タッチされることに より実行される スレッド(処理の実行単位) イベント入力せずとも何かしら処理をさせることができる(ループ処理) ゲームアプリに最適 スレッド処理() { ……. } ※継続して実行 17
  • 18. ゲームの基本(コード解説) public class MizmonTouch01 extends Activity { // ************** SurfaceHolder.Callbackの3兄弟 ********************** @Override private onCreate(Bundle public GameViewThread extends Thread {mThread;// スレッドのインスタンス class void GameViewThreadsavedInstanceState) { // surface生成時にコールバックされる メインループ動作フラグ(外部アクセス) … private boolean bRunning= false;// @Override ///*********************** 外部から呼ばれるメソッド ***********************/ GameViewを画面としてセットする public void surfaceCreated(SurfaceHolder holder) { GameView gameView = new GameView(this); public GameViewThread(SurfaceHolder surfaceHolder) { mThread = new GameViewThread(holder); setContentView(gameView); this.mHolder = surfaceHolder; // スレッド生成しインスタンス化 } } mThread.enableRunning(true); // スレッド内のメインループ動作を許可する } mThread.start(); // メインループの動作許可設定 // スレッドを起動する(try~catchで囲む) } public void enableRunning(boolean flag) { // surface変更時にコールバックされる class GameView extends SurfaceView implements SurfaceHolder.Callback { this.bRunning = flag; // メインループ動作許可 @Override public GameView(Context context) { } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { … /*********************** メインループ ***********************/ // なにもしない //サーフェイスホルダーを取得(SurfaceView) // スレッドを起動すると呼ばれる } SurfaceHolder holder = getHolder(); @Override コールバックを設定(SurfaceHolder) ////public void run() { surface破棄時にコールバックされる @Override holder.addCallback(this); { while(bRunning) } public void surfaceDestroyed(SurfaceHolder holder) { … // canvasにテキストを描画する mThread.enableRunning(false); … // スレッド内のメインループ動作を停止する // ループが一定時間の間隔で回るための処理 mThread.join(); //スリープ … // スレッドを停止させる(try~catchで囲む) } } } } 18 }
  • 19. キャラクターを動かす 【概要】 1体のキャラクター(敵)を固定位置で動かす もぐら叩きのモグラの用に下からせりあがってくる感じ 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch2 ( 02MizmonTouch ) 【修正メソッド】 GameViewThreadクラス内 GameViewThread():初期化する変数を追加 【新規メソッド】 GameViewThreadクラス内 moveEnemy() : 敵の動作処理 genVirtualDisplay() : 仮想画面を生成 doDraw() : bmpを画面(View)に貼り付け main() : メイン処理(Threadのループから呼ばれる) 19
  • 20. キャラクターを動かす(仮想画面の導入) ① キャラクターの元画像を時間に応じサイズの上部から切り 出す ② 仮想画面への貼り付けは画像サイズの底辺を基準に①画 像を張り付ける ③ 時間とともに①の切り出しサイズを変化させる キャラクターの元画像 仮想画面 (rctOriginalCurrentSize) (rctCurrentArea) (left, top) 時間とともに 変化させる (left, top) (right, bottom) (right, bottom) 20
  • 21. キャラクターを動かす(コード解説) /*********************** 外部から呼ばれるメソッド ***********************/ // コンストラクタ final intENEMY_COUNT_MAX = 10;// 敵の最大動作回数 private static public// ***** 画面関係の処理 ***** surfaceHolder, Context context) { GameViewThread(SurfaceHolder private void moveEnemy() { … 仮想画面(ビットマップ)を生成する // if(0 >= nCount) { private void genVirtualDisplay() { // nAddition = +1; // リソースのインスタンスを取得 Resources r = context.getResources(); 引っ込みきったので折り返して出現させる // 仮想画面をCreate= new Canvas(imgVdGame); <= nCount) { } else if(0 <canvas Canvas nAddition && ENEMY_COUNT_MAX // 仮想画面の下地を生成 nAddition = -1; Paint paint =new Paint(); // 最大サイズまで出現した場合、増分をマイナスにする imgVdGame = Bitmap.createBitmap(VD_WIDTH, VD_HEIGHT, Bitmap.Config.ARGB_8888); // Paintをインスタンス化 // 表示する敵画像をインスタンス化 } else { paint.setAntiAlias(true); // 特に何もしない imgEnemy = BitmapFactory.decodeResource(r, 255)); paint.setColor(Color.argb(255, 255, 255, R.drawable.enemy1); ; 敵画像サイズを取得 // } canvas.drawColor(Color.argb(255, 0, 0, 32)); // 背景を塗りつぶし rctEnemyOriginalSize = new Rect(0, // rctOriginalCurrentSize, rctCurrentArea, paint); nCount += nAddition; 0, imgEnemy.getWidth(), imgEnemy.getHeight()); canvas.drawBitmap(imgEnemy, nCountを加算する // 敵の貼り付け } } // オリジナルサイズの敵画像のサイズを更新 // ***** 画面関係の処理 ***** /*********************** 内部処理 ***********************/ // nCountから高さを算出 // bmpを画面に貼り付け 敵の処理 (int)(rctEnemyOriginalSize.bottom * nCount / ENEMY_COUNT_MAX); // *****int height doDraw(Canvas canvas) { private void =***** // 敵の動作 rctOriginalCurrentSize = new Rect(rctEnemyOriginalSize.left, rctEnemyOriginalSize.top, Paint paint=new Paint(); // Paintをインスタンス化 public Rect paint.setAntiAlias(true); // オリジナルサイズの敵画像の現在のnCountに応じたサイズ rctOriginalCurrentSize; rctEnemyOriginalSize.right, height); // 仮想画面上の現在の占有座標 public Rect paint.setColor(Color.argb(255, 255, 255, 255)); rctCurrentArea; public int 現在の敵の座標を更新 // (0~MAX) nCount = 1; // canvas.drawColor(Color.argb(255, 0, 0, 32)); // 背景を塗りつぶし public int まずはnCountから高さを算出 nAddition = +1; // nCountの増分 // canvas.drawBitmap(imgVdGame, 0, 0, paint); // 生成したbmp(仮想画面)を画面に表示 } height = (int)(rctEnemyOriginalSize.bottom * nCount / ENEMY_COUNT_MAX); 今回は仮想画面をそのまま // ***** メイン処理new Rect(rctEnemyOriginalSize.left, rctEnemyOriginalSize.bottom - height, rctCurrentArea = ***** // Thread内ループから定期的に呼ばれる(50msごと) Viewに表示してる rctEnemyOriginalSize.right, rctEnemyOriginalSize.bottom); } private void main() { moveEnemy(); // 敵情報更新 genVirtualDisplay(); // 仮想画面生成 } 21
  • 22. ランダムに表示する 【概要】 キャラクターの管理クラスを作成し一つのオブジェクトとして管理 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch3 ( 03MizmonTouch ) 【修正メソッド】 GameViewクラス内 surfaceChanged() : 実画面サイズをThreadへ通知 GameViewThreadクラス内 GameViewThread() : 初期化する変数を追加 genVirtualDisplay() : dispEnemy()を呼び出しを追加 moveEnemy() : キャラクターの生成と動作更新 【新規メソッド】 EnemyInfoクラス 一体のキャラクターの管理クラス管理する GameViewThreadクラス内 setSurfaceSize() : 実画面サイズの取得 genEnemy() : 敵の生成 updateEnemy() : 敵の更新 dispEnemy() : 敵の表示(genVirtualDisplay()のサブ) doDraw() : 仮想画面を実画面へフィッティング 22
  • 23. ランダムに表示する(オブジェクト化) ランダムな大きさでランダム な位置に表示 複数の変数で管理するのは大変! 1体のキャラクターを管理するクラ ス(構造体)を定義しひとつのオブ ジェクトとして管理 (EnemyInfoクラス) 23
  • 24. ランダムに表示する(フィッティング) 仮想画面 実画面 フィッティング フィッティング 解像度やサイズの違いによる機種 依存性を極力排除 24
  • 25. ランダムに表示する(コード解説) // 敵1体の情報をまとめるクラス class GameViewThread extends Thread { 画面サイズの取得 // // 敵の更新(敵が消滅したらfalseを返す) class EnemyInfo { // コンストラクタ 敵の生成 public// 敵の貼り付け private boolean bAlive; width, int height)// 生死判別フラグ void setSurfaceSize(int public boolean updateEnemy(EnemyInfo enemy){ { public GameViewThread(SurfaceHolder surfaceHolder, Context context) { private EnemyInfo genEnemy() { canvas) { synchronizeddispEnemy(Canvas private void (mHolder) { rctOriginalCurrentSize; // オリジナルサイズの敵のnCountに応じたサイズ publicif(0 >= enemy.nWaitCount) { Rect // 動作カウントの更新なのかを判断する : // enemy.nWaitCount = enemy.nSpeed; //// 生死の初期化(新規生成なのでtrue) EnemyInfo rv = new EnemyInfo(); 仮想画面と実画面の比率を算出// 仮想画面上の敵のサイズ public Rect Paintをインスタンス化 // rctEnemySize; ウェイトの初期化 仮想画面の座標を記録 rv.bAlive = true; Paint(); //float mult_width = (float)VD_WIDTH / (float)width; Paint paint =new public Rect if(0 >= enemy.nCount) { rctOccupationArea; // 仮想画面上の最大占有座標 // 動作カウントの更新 敵のサイズを決定 rctVd =mult_height = (float)VD_HEIGHT / (float)height; //float new Rect(0, 0, VD_WIDTH, VD_HEIGHT); paint.setAntiAlias(true); public Rect rctCurrentArea; return false; // 仮想画面上の現在の占有座標 // 引っ込みきったのでインスタンスを削除 int敵情報の初期化(int)(Math.random()* enemy_width = //float mult; // 動作速度(1動作あたりのスレッドサイクル数。 public int } else if(0 < enemy.nAddition &&(ENEMY_COUNT_MAX <= enemy.nCount || nSpeed; Enemy = new EnemyInfo(); - ENEMY_WIDTH_MIN) + ENEMY_WIDTH_MIN); (ENEMY_WIDTH_MAX // 実画面のサイズに引き延ばす辺(幅または高さ)を判断する // 敵の貼り付け // 0:ウェイトなし、MAX:MAXサイクルで1動作) 1 > (Math.random() * ENEMY_COUNT_MAX))) { } int enemy_heightmult_height){ if(mult_width < = (int)enemy_width// ウェイト挿入回数(初期値はnSpeed値 * rctEnemyOriginalSize.bottom / rctEnemyOriginalSize.right; public int paint.setColor(Color.argb(Enemy.nAlpha, 255, 255, 255)); nWaitCount; enemy.nAddition = -1; // 最大サイズまで出現した場合または int enemy_x = (int)(Math.random()*(VD_WIDTH - enemy_width)); mult = mult_height; canvas.drawBitmap(imgEnemy, Enemy.rctOriginalCurrentSize, Enemy.rctCurrentArea, paint); // 1サイクルごとに-1される。0になったら1コマ動かす) // ランダムで折り返す場合、 増分をマイナスにする } int: else{ { nCount; // ***** 敵の処理 ***** int enemy_y = (int)(Math.random()*(VD_HEIGHT - enemy_height)); } } else public // bAlive=true, bAppearance=true:出現時のカウント rv.rctEnemySize = new Rect(0, 0, enemy_width, enemy_height); mult = mult_width; // 敵の動作 // 敵のサイズを設定 ; // bAive=true, bAppearance=false: 引っ込む時のカウント // 特に何もしない ***** 仮想画面関係の処理 // // bmpを画面に貼り付け= ***** rv.rctOccupationArea } } private 敵の画面内の占有座標を設定 // void moveEnemy() { // bAlive=false : やれたとき(Hit時)のカウント void doDraw(Canvas canvas) { enemy_x + enemy_width,敵情報の更新 // 仮想画面(ビットマップ)を生成する new Rect(enemy_x, enemy_y, private 仮想画面を拡大して張り付ける実画面上の座標を算出する public int// enemy.nCount += enemy.nAddition; nCountの増分 nAddition; // // nCountを加算する // enemy_y + enemy_height); x2=(int)((float)VD_WIDTH / mult); 表示時の透過率 // オリジナルサイズの敵画像座標の初期化 private void genVirtualDisplay() { rv.rctOriginalCurrentSize = public intint// オリジナルサイズの敵画像のサイズを更新、nCountから高さを算出 // Paintをインスタンス化 nAlpha; // if(!updateEnemy(Enemy)){ 仮想画面の下地を生成 // 敵が消滅したら敵を生成する //int y2=(int)((float)VD_HEIGHT / mult); rctEnemyOriginalSize.top, rctEnemyOriginalSize.right, 0); new Rect(rctEnemyOriginalSize.left, Paint paint=new Paint(); } enemy.rctOriginalCurrentSize.bottom = Canvas canvas = new Canvas(imgVdGame); / (float)2); // 現在の敵の大きさを設定 rv.rctCurrentArea = Enemy = genEnemy(); int x1=(int)((float)width / (float)2 - (float)x2 enemy.nCount / ENEMY_COUNT_MAX); paint.setAntiAlias(true); (int)(rctEnemyOriginalSize.bottom * 背景を塗りつぶし } //int y1=(int)((float)height / (float)2+- enemy_height, enemy_x + enemy_width, enemy_y + enemy_height); new Rect(enemy_x, enemy_y 255, 255)); class GameView extends SurfaceView implements (float)y2 / (float)2); paint.setColor(Color.argb(255, 255, SurfaceHolder.Callback { // 現在の敵の座標を更新、nCountから高さを算出 // 敵の動作速度を設定 canvas.drawColor(Color.argb(255, 0, 0, 32)); } : x2int height = (int)(enemy.rctEnemySize.bottom * enemy.nCount / ENEMY_COUNT_MAX); += x1; 敵の貼り付け rv.nAddition = +1; //y2 背景を塗りつぶし // += y1; : @Override enemy.rctCurrentArea.top = enemy.rctOccupationArea.bottom - height; dispEnemy(canvas); rv.nSpeed = (int)(Math.random()*(ENEMY_SPEED_MAX + 1)); else 実画面の座標を記録 // { canvas.drawColor(Color.argb(255, 0, 0, 32)); public}void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } rv.nWaitCountRect(x1, y1, x2, y2); rctRd = new = rv.nSpeed; // まだウェイトが必要な場合の処理、ウェイトカウントを減らす mThread.setSurfaceSize(width, height); // 画面サイズをスレッドに通知する enemy.nWaitCount --; } } } rv.nCount = 1; // 生成したbmp(仮想画面)を画面に表示 } bDisplayReady = true; // 敵の透過率を設定 rctRd, paint); rv.nAlpha = 255; // 画面準備完了 canvas.drawBitmap(imgVdGame, rctVd, return true; } } } return rv; } 25
  • 26. 複数表示する 【概要】 複数のキャラクターの管理 背景もついでに表示 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch4 ( 04MizmonTouch ) 【修正メソッド】 GameViewThreadクラス内 GameViewThread() : 初期化する変数を追加 moveEnemy() : 複数のキャラクターに対応 genEnemy() : 既存のキャラクターと 重ならない処理を追加 genVirtualDisplay() : 背景画像の表示を追加 26
  • 27. 複数表示する(リスト化) 複数のキャラクターを管理する • オブジェクト化したキャラクター をリスト化 listEnemys = new ArrayList<EnemyInfo>(); • 複数のキャラクターを生成、更 新および表示処理を追加 全リストの処理を常に行う(for文) • 新規キャラクターは重ならないよ うに生成 rctOccupationArea変数 • 背景も表示(おまけ) キャラクターの生成は排他的に 27
  • 28. 複数表示する(コード解説) // コンストラクタ private void moveEnemy() { public GameViewThread(SurfaceHolder surfaceHolder, Context context) { 敵の生成 //// 全ての敵を更新する this.mHolder = surfaceHolder; private EnemyInfo genEnemy()0<=i for(int i=listEnemys.size()-1 ; { // リソースのインスタンスを取得 ; i--){ : // 敵情報の更新 Resources r = context.getResources(); 敵の画面内の占有座標を設定 //if(!updateEnemy(listEnemys.get(i))){ :listEnemys.remove(i); // 敵が消滅したらリストからも削除 // 各画像をインスタンス化 //}既に出現中の敵と重なっていないか判定する imgVdGame = Bitmap.createBitmap(VD_WIDTH, VD_HEIGHT, Bitmap.Config.ARGB_8888); } for(EnemyInfo item : listEnemys) { imgBg = BitmapFactory.decodeResource(r, R.drawable.bg); if(Rect.intersects(rv.rctOccupationArea, item.rctOccupationArea)){ imgEnemy = BitmapFactory.decodeResource(r, R.drawable.enemy1); // 新規に敵を追加可能か判断する // 重なっていれば敵を生成しない return null; if(ENEMY_NUMBER_MAX > listEnemys.size()) { // 背景画像のオリジナルサイズを取得 } rctBgSize = 追加可能なら新規に出現するか判断する // new Rect(0, 0, imgBg.getWidth(), imgBg.getHeight()); } if(ENEMY_GEN_PROBABILITY > Math.random()*100) { // オリジナルサイズの敵画像座標の初期化 // 新規に敵を生成 // 敵画像のオリジナルサイズを取得 :// 敵生成に失敗の際は5回までリトライする rctEnemyOriginalSize = new Rect(0, 0, imgEnemy.getWidth(), imgEnemy.getHeight()); } for(int i=0 ; 5>i ; i++) { : EnemyInfo new_enemy = genEnemy(); // 仮想画面の座標を記録 private void genVirtualDisplay() { if(null != new_enemy) { rctVd = new Rect(0, 0, VD_WIDTH, VD_HEIGHT); : listEnemys.add(new_enemy); // 敵生成に成功したらリストに追加する // Paintをインスタンス化 break; // 敵情報の初期化 Paint paint =new Paint(); } listEnemys = new ArrayList<EnemyInfo>(); paint.setAntiAlias(true); } } //}仮想画面に背景画像を貼り付ける } canvas.drawBitmap(imgBg, rctBgSize, rctVd, paint); } : } 28
  • 29. タッチを検出する 【概要】 タッチイベントを解析してアクションと座標を検出 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch5 ( 05MizmonTouch ) (MizmonTouch1から作成) 【追加メソッド】 GameViewクラス内 onTouchEvent() : タッチイベント処理 GameViewThreadクラス内 doTouchEvent() : タッチの検出処理 29
  • 30. タッチを検出する(座標検出) • タッチの検出 – ActivityクラスのonTouchEvent()をオーバーライド – 引数としてMotionEventが渡される – Surfaceviewでタッチイベントを取得する コンストラクタで下記を宣言することにより可能 setFocusable(true); // フォーカスを受け取る • アクションと座標の解析 (0,0) MotionEventには必要な情報が詰まっている – アクションの取得 getAction():ダウン、アップ、移動アクション – 座標の取得 (X, Y) getX():画面上のX座標 X getY():画面上のY座標 他にもいろいろ便利なメソッドが備わっている 30
  • 31. タッチを検出する(コード解説) class GameView extends SurfaceView implements SurfaceHolder.Callback { class GameViewThread extends Thread { private GameViewThread mThread; // スレッドのインスタンス : : @Override // ***** タッチ入力処理 ***** public GameView(Context context) { // タッチ処理 run() { public void : super(context); private HashMap<String,PointF> points = new HashMap<String,PointF>(); //サーフェイスホルダーを取得(SurfaceView) while(bRunning) { private String strMotion; : SurfaceHolder holder = getHolder(); public boolean doTouchEvent(MotionEvent event) { // コールバックを設定(SurfaceHolder) int action = event.getAction(); try { int count =:event.getPointerCount(); holder.addCallback(this); int index =// タッチ座標を画面に表示 (action & MotionEvent.ACTION_POINTER_ID_MASK) >> // キーイベントが取得できるようにフォーカスを受け取れるようにしておく MotionEvent.ACTION_POINTER_ID_SHIFT; setFocusable(true); paint.setColor(Color.MAGENTA); // タッチ動作を判別 } Object[] keys=points.keySet().toArray(); switch (action & MotionEvent.ACTION_MASK) { // タッチイベントを実装 case MotionEvent.ACTION_DOWN: @Override for (int i=0;i<keys.length;i++) { points.put("" + event.getPointerId(index), new PointF(event.getX(), event.getY())); public boolean onTouchEvent(MotionEvent event) { PointF pos=(PointF)points.get(keys[i]); strMotion = "ACTION_DOWN"; try { break; canvas.drawText(strMotion+"="+(int)pos.x+", "+(int)pos.y, 0, 40*j,paint); j++; : // 実処理はスレッドの中で行う } case MotionEvent.ACTION_MOVE: mThread.doTouchEvent(event); } (Exception e) { i=0;i<count;i++) { for (int } catch } catch(Exception e) { points.get("" + event.getPointerId(i)).x = event.getX(i); } } finally { points.get("" + event.getPointerId(i)).y = event.getY(i); : return true; } } } strMotion = "ACTION_MOVE"; } : break; } } return true; } 31
  • 32. タッチを仮想画面に反映 【概要】 タッチ座標を仮想画面座標に変換 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch6 ( 06MizmonTouch ) (MizmonTouch5から作成) (MizmonTouch3の関数を利用) 【追加メソッド】 GameViewThreadクラス内 genVirtualDisplay():タッチ座標にアイコン表示 以下はMizmonTouch3から setSurfaceSize() : 実画面サイズの取得 doDraw() : 仮想画面を実画面へフィッティング main() : メイン処理 32
  • 33. タッチを仮想画面に反映(座標変換) 実画面 仮想画面 (X´, Y´) 座標変換 (X, Y) fMult倍 実画面上の座標→仮想画面上の座標に変換 X´ = fMult × X Y´ = fMult × Y 33
  • 34. タッチを仮想画面に反映(コード解説) private void genVirtualDisplay() { synchronized (mHolder) { // 仮想画面の下地を生成 Canvas canvas = new Canvas(imgVdGame); 排他処理のおまじない // Paintをインスタンス化 Paint paint =new Paint(); 次の処理は別スレッドで動作 paint.setAntiAlias(true); • タッチ処理 paint.setColor(Color.argb(255, 255, 255, 255)); • 表示処理 // 背景を塗りつぶし canvas.drawColor(Color.argb(255, 32, 32, 32)); そのため処理中に値が変化す // アイコンの貼り付け Object[] keys=points.keySet().toArray(); ると都合が悪いためロックする for (int i=0 ; i<keys.length ; i++) { 例) のタイミングで座標で // 座標の取得 PointF pos=(PointF)points.get(keys[i]); タッチ座標がかわるなど // 座標の変換 よって、doTouchEvent()も int x = (int)((pos.x - rctRd.left) * fMult); int y = (int)((pos.y - rctRd.top) * fMult); Synchronizedでロックしている // アイコンの貼り付け canvas.drawBitmap(imgIcon, x - imgIcon.getWidth()/2, y - imgIcon.getHeight()/2, paint); // 座標も表示してみる paint.setTextSize(24); paint.setColor(Color.CYAN); canvas.drawText(x + ", " + y, 0, 40 * i + 200, paint); } } } 34
  • 35. 当たり判定 【概要】 タッチ座標を検出しキャラクターの有無で当たり判定 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch7 ( 07MizmonTouch ) (MizmonTouch4と6から作成) 【修正メソッド】 doTouchEvent() … Downのみを検出対象 【追加メソッド】 rotateImage() : BMPを指定の角度にする dispTouch() : ハンマー表示(genVirtualDisplay()) judgeHit() : 当たり判定(main()から呼ばれる) 35
  • 36. 当たり判定(ダウンアクションだけを検出) 仮想画面 • タッチ時はハンマーをアニメーション化 画像はコンストラクタで回転して生成 • タッチ検出 コンセプトはもぐら叩き ⇒ダウンアクションのみ使用 rctCurrentArea マルチタッチは使用しない rctOccupationArea • 当たり判定 実際の表示エリアとタッチエリアで判定 rctCurrentArea - rctTouch 図の例では○が当たり判定となる Hit • Hit検出後キャラクターを消去 36
  • 37. 当たり判定(コード解説) public GameViewThread(SurfaceHolder surfaceHolder, Context context) { // タッチ位置の画像貼り付け : 各画像をインスタンス化 private void dispTouch(Canvas canvas){ // // 敵の当たり判定 imgHammer1 = BitmapFactory.decodeResource(r, R.drawable.hammer); // Paintをインスタンス化 private void judgeHit() { // 回転したハンマー画像を生成 Paint paint =new Paint(); synchronized (mHolder) { imgHammer2if(JUDGEMENT_TIME -2 <= nJudgementCount && null != rctTouch) { = rotateImage(imgHammer1, 330); paint.setAntiAlias(true); imgHammer3 = rotateImage(imgHammer1, 300); for(int i=listEnemys.size()-1 ; 0<=i ; i--){ // タッチ位置にハンマーの貼り付け : EnemyInfo enemy = listEnemys.get(i); } if(null != rctTouch) { // 当たり判定 paint.setColor(Color.argb(255, 255, 255, 255)); if(Rect.intersects(rctTouch, enemy.rctCurrentArea)){ // 時間とともに回転させる // 重なっていれば敵を消滅し、リストからも削除 // ビットマップ回転させる if(JUDGEMENT_TIME - listEnemys.remove(i); { 1 <= nJudgementCount) private Bitmap rotateImage(Bitmap bmp, float angle) { rctHammerSize, rctTouch, paint); canvas.drawBitmap(imgHammer1, } int w = bmp.getWidth(); } else if(JUDGEMENT_TIME -2 == nJudgementCount) { } int h = bmp.getHeight(); canvas.drawBitmap(imgHammer2, rctHammerSize, rctTouch, paint); // 当たり判定時間をデクリメント Matrix matrix ={ new Matrix(); } else nJudgementCount --; matrix.postRotate(angle, (float)w / 2f, (float)h{/ 2f); } else if(0 <= nJudgementCount) // 画像を回転させるおまじない canvas.drawBitmap(imgHammer3, rctHammerSize, rctTouch, paint); Bitmap dmy_img=Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); } // 当たり判定時間をデクリメント Canvas dmy_canvasnJudgementCount --; } = new Canvas(dmy_img); } dmy_canvas.drawBitmap(bmp, 0, 0, null); } else { return Bitmap.createBitmap(dmy_img, 0, 0, w, h, matrix, true); // 当たり判定時間が超過したのでタッチ情報を削除 } rctTouch = null; } } } 37
  • 38. 当たり処理 【概要】 当たり検出後のキャラクタのアクションを実装 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch8 ( 08MizmonTouch ) (MizmonTouch7から作成) 【追加メソッド】 updateEnemy() : 長くなったので二つに分ける updateEnemyAlive() : 通常の更新 updateEnemyGone() : あたり判定後の更新 judgeHit() : 当たり判定時の処理をremoveから 生死フラグの処理に変更 dispEnemy() :敵の生死判別しそれに 合わせた画像を貼り付ける 38
  • 39. 当たり処理(アクション) ドロン Hit! Hit検出後消えるだけじゃ寂しい Hit検出時に当たりアクションを追加 キャラクター別画像へ差し替え 39
  • 40. 当たり処理(コード解説) // 敵の更新(敵が消滅したらfalseを返す) // 敵の通常更新(敵が消滅したらfalseを返す) private boolean updateEnemy(EnemyInfo enemy){ // やられた敵の更新(敵が消滅したらfalseを返す) private boolean updateEnemyAlive(EnemyInfo enemy) { booleanvoid updateEnemyGone(EnemyInfo enemy) { private rv = judgeHit() { // boolean true; private動作カウントの更新 private void dispEnemy(Canvas canvas) { { if(ENEMY_OUT_COUNT_MAX <= enemy.nCount) 動作カウントの更新がどうかをウェイトから判断する synchronized (mHolder) { // if(0 >= enemy.nCount) { // 敵やれれ処理のカウントが最大時にインスタンスを削除 // return false; //引っ込みきったのでインスンスを削除 Paintをインスタンス化 -2 <= nJudgementCount && null != rctTouch) { if(JUDGEMENT_TIME if(0 >= enemy.nWaitCount) { } //// returnpaint =newi=listEnemys.size()-1 ; 0<=i ; i--){ Paint false; ウェイトの初期化Paint(); nCountを加算する for(int } else if(0 < enemy.nAddition && (ENEMY_COUNT_MAX <= enemy.nCount || 1 > (Math.random() * paint.setAntiAlias(true); enemy.nCount += enemy.nAddition; enemy = listEnemys.get(i); EnemyInfo enemy.nWaitCount = enemy.nSpeed; ENEMY_COUNT_MAX))) 当たり判定 if(ENEMY_COUNT_MAX// { enemy.nCount){ >= 最大サイズまで出現した場合またはランダムで折り返す場合 // // 敵が既定の最大サイズになるまでの処理 敵の生死を判断 判定するのはbAlive=trueの敵のみ 敵の貼り付け// //増分をマイナスにする // // // オリジナルサイズの敵画像のサイズを更新 if(enemy.bAlive) {item : listEnemys)&& Rect.intersects(rctTouch, enemy.rctCurrentArea)){ enemy.nAdditionif(enemy.bAlive { for(EnemyInfo = -1; // nCountから高さを算出 // 重なっていれば生死フラグの処理をして消滅(やられ)処理を始める } else { enemy.rctOriginalCurrentSize.bottom = (int)(rctEnemyOriginalSize.bottom * enemy.nCount / ENEMY_COUNT_MAX); //paint.setColor(Color.argb(item.nAlpha, 255, 255, 255)); 敵が生きている場合の更新処理 // 特に何もしない 現在の敵の座標を更新 ; // // 敵の生死を確認しその状態に合わせた画像を貼り付ける enemy.bAlive = false; } rv = updateEnemyAlive(enemy); // nCountから高さを算出 enemy.nAddition = +1; // 引っ込んでる最中でも引き戻す int if(item.bAlive){ } else { height = (int)(enemy.rctEnemySize.bottom * enemy.nCount / ENEMY_COUNT_MAX); enemy.nSpeed = 0; // 動作速度を最速にセットする // 敵がやられている場合の更新処理 item.rctOriginalCurrentSize, item.rctCurrentArea, paint); canvas.drawBitmap(imgEnemy, enemy.rctCurrentArea.top = enemy.rctOccupationArea.bottom - height; // nCountを加算する listEnemys.set(i, enemy); } else { rv}= +={enemy.nAddition; else enemy.nCountupdateEnemyGone(enemy); // 敵やられ処理時で既定の最大サイズを超えた後の消去処理 } canvas.drawBitmap(imgEnemyOut, item.rctOriginalCurrentSize, item.rctCurrentArea, paint); } // まずは敵画像のカレントサイズをオリジナルサイズにする // オリジナルサイズの敵画像のサイズを更新 } } } else { enemy.rctOriginalCurrentSize = rctEnemyOriginalSize; // nCountから高さを算出 // 当たり判定時間をデクリメント } // 敵の座標を更新 // まだウェイトが必要な場合の処理 enemy.rctOriginalCurrentSize.bottom = (int)(rctEnemyOriginalSize.bottom * enemy.nCount / nJudgementCount --; // nCountから高さを算出 } // ウェイトカウントを減らす ENEMY_COUNT_MAX); int}heightif(0 <= nJudgementCount) { else = (int)(enemy.rctEnemySize.bottom * enemy.nCount / ENEMY_COUNT_MAX); enemy.nWaitCount --; // 当たり判定時間をデクリメント enemy.rctCurrentArea.top = enemy.rctOccupationArea.bottom - height; } // 現在の敵の座標を更新 // nCountから幅を算出 nJudgementCount --; // nCountから高さを算出 int width = (int)((enemy.rctEnemySize.right * enemy.nCount / ENEMY_COUNT_MAX) - enemy.rctEnemySize.right); int height}= (int)(enemy.rctEnemySize.bottom * enemy.nCount / ENEMY_COUNT_MAX); else { enemy.rctCurrentArea.left = enemy.rctOccupationArea.left - (int)(width / 2); // 当たり判定時間が超過したのでタッチ情報を削除 return rv;enemy.rctCurrentArea.right = enemy.rctOccupationArea.right + (int)(width / 2); enemy.rctCurrentArea.top = enemy.rctOccupationArea.bottom - height; } // 透過率を更新(だんだん薄くしていく) rctTouch = null; return true;enemy.nAlpha = 255 - 255 * (enemy.nCount - ENEMY_COUNT_MAX) / (ENEMY_OUT_COUNT_MAX - ENEMY_COUNT_MAX); } } } } return true; } } 40
  • 41. 得点をつける 【概要】 累積得点(スコア)を実装 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch9 ( 09MizmonTouch ) (MizmonTouch8から作成) 【追加メソッド】 EnemyInfoクラス : 持ち点変数を追加 genEnemy() : 持ち点登録を追加 updateEnemyAlive() : 持ち点の減算部を追加 judgeHit() :当たり判定でHitした場合に 持ち点をスコアに加算をする genVirtualDisplay() :下記を呼び出し dispGetPoint 獲得得点の表示 dispGameInfo スコアを仮想画面に表示 dispTextテキストを表示 41
  • 42. 得点をつける(アクション) Score Score 256 スコア 272 16pts. 獲得ポイント ドロン 当たり! • キャラクター管理クラスに各キャラクター固有の持ち点を 保持する変数をEnemyInfoクラスに追加 • キャラクター生成時に持ち点を算出しオブジェクトに登録 • 該当の敵が1動作するたびに持ち点を減算する (とりあえず半分づつ減らす。但し、1より下回らない) • 当たり判定で獲得ポイントの表示とスコアへの加算 42
  • 43. 得点をつける(コード解説) // 敵1体の情報をまとめるクラス // 敵の通常更新(敵が消滅したらfalseを返す) class EnemyInfo { // 倒した敵の獲得得点を表示する private boolean updateEnemyAlive(EnemyInfo enemy) { // テキストを表示する : private void dispGetPoint(Canvas canvas) { :private void dispText(Canvas canvas, String text, int x, int y, Typeface type, int alpha, int size, int align) { public int nAlpha; // 表示時の透過率 // 倒した敵の獲得得点を表示する // Paintをインスタンス化 // 持ち点の更新nPoint; public int // 持ち点 for(EnemyInfo=new :Paint(); Paint paint item listEnemys) { if(1 < enemy.nPoint) { } enemy.nPoint /= 2; ENEMY_COUNT_MAX < item.nCount) { if(!item.bAlive && paint.setAntiAlias(true); } paint.setTextSize(size); String pts = item.nPoint + "pts."; // サイズの設定 paint.setTypeface(type); // フォントを設定 returnint widthint(int)(paint.measureText(text)); // 描画幅を抽出 true; = x = (int)(VD_WIDTH / 2); } int y = (int)(VD_HEIGHT / 2 - (item.nCount) * 10); class GameViewThread extends Thread { private voidif(DISP_RIGHT == align) { pts, x, y, Typeface.DEFAULT_BOLD, item.nAlpha, DISP_PTS_SIZE, : dispText(canvas, judgeHit() { DISP_CENTER); // 敵の生成 // (mHolder) { synchronized 右寄せの場合の座標変換 } x -= private EnemyInfo (int)(width + {2); nJudgementCount && null != rctTouch) { if(JUDGEMENT_TIME -2 <= genEnemy() } } else if(DISP_CENTER == align) { ; 0<=i ; i--){ for(int i=listEnemys.size()-1 : } // センターの場合の座標変換 EnemyInfo enemy = listEnemys.get(i); // 持ち点を設定 x -= (int)(width / 2 + 1);&& Rect.intersects(rctTouch, enemy.rctCurrentArea)){ if(enemy.bAlive rv.nPoint { 100 * (ENEMY_SPEED_MAX - rv.nSpeed + 1) * } else = // スコア等ゲーム情報を表示する (10 - (int)(10 左寄せは何もしない : ; // * (enemy_width canvas) { テキストを表示 - ENEMY_WIDTH_MIN) / (ENEMY_WIDTH_MAX - // 得点を加算する private void dispGameInfo(Canvas ENEMY_WIDTH_MIN))); } // スコアタイトルを表示する += enemy.nPoint; nScore // 影の描画} String title = mContext.getString(R.string.txt_score); return rv;}== alpha) { if(255 キャラクターの大きさ、スピードにより 影付き dispText(canvas, title, 0, DIAP_TITLE_POS_Y, Typeface.DEFAULT, 255, DISP_TITLE_SIZE, DISP_LEFT); } // alphaが255のときのみ表示 // スコアを表示する : paint.setColor(Color.argb(alpha, 0, 0, 0)); 持ち点を算出する : } String score = "" + nScore; canvas.drawText(text, x + 2, y + 2, paint); } dispText(canvas, score, 0, DIAP_INFO_POS_Y, Typeface.DEFAULT_BOLD, 255, DISP_INFO_SIZE, DISP_LEFT); } } } // 本体の描画 paint.setColor(Color.argb(alpha, 255, 255, 255)); canvas.drawText(text, x, y, paint); } 43
  • 44. スタート画面を作る 【概要】 ゲーム全体の構成(状態管理)の整理 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch10 ( 10MizmonTouch ) (MizmonTouch9から作成) 【追加・修正】 game.xml : 基本レイアウトを記述 MizmonTouch10クラス OnCreate() : game.xmlをviewに設定 GameViewクラス GameView () : レイアウトxmlから呼び出されるとき、AttributeSet引数を追加する setButtonInstance : レイアウトxml上のボタンインスタンスをセットする setTextViewInstancce : レイアウトxml上のテキストインスタンスをセットする gameStart() : activity上のボタンを押されたら呼び出される genThread() : 新設。スレッドのインスタンス化が長くなりそうなので surfaceCreated() : Threadのインスタンス化の代わりにgenThreadメソッドを呼び出し GameViewThreadクラス GameViewThread() : Message用の引数を追加 setStart() : ゲームステートをセットする doTouchEvent doStart() : ゲームスタート処理 setState() : ゲームステートをセットする GameViewへのメッセージを生成して送り付ける genVirtualDisplay() : ゲームステート毎の表示設定 dispReady() : ゲーム開始前のカウントダウンを表示 main() : ゲームステート応じた処理に変更 controlState() : ステートの更新確認 ゲーム開始前のカウントダウンもここでやる 44
  • 45. スタート画面を作る(ステート管理) View GameViewクラス GameViewThreadクラス 画面全体の制御をする ゲーム自体の制御 ボタンが押された 通知(setStart) テキスト menu ボタン 状態遷移 play ready テキストやボタンは game.xmlで定義 handleMessage テキストやボタンを非表示 テキストやボタン (sendMessage) Ready画面 の制御 Play画面 Menu:ボタンを表示 GameViewThread → GameViewへは Ready:カウントダウン message Handlerを使用する Play:ゲーム実行中 いきなりゲームスタートも心の準備が… 状態(ステート) メニュー画面やスタート画面の追加 の管理 45
  • 46. スタート画面を作る(コード解説) <?xml version="1.0" encoding="utf-8"?> publicGameView extends SurfaceView implements SurfaceHolder.Callback { class GameViewThread extendsextends Activity { class MizmonTouch10 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" @Override class // ***** メイン処理 android:layout_width="fill_parent" Thread { /*********************** 内部処理 ***********************/ private 仮想画面関係の処理 ***** // Buttonのインスタンス btStart; ***** システム ***** mHandler; // TextViewのインスタンス //private Handler //// private TextView ***** ***** game.xml GameView mGameView; // ハンドラ // GameViewのインスタンス surfaceCreated(SurfaceHolder holder) { private Button public void 仮想画面(ビットマップ)を生成する> @Overridemain() { tvText; android:layout_height="fill_parent" // システムのステートのセット // スレッド生成 // ゲーム全体のステート(状態遷移) private void ゲームのスタート処理 == context, AttributeSet attrs) {setState(EnumState state)genThread(holder); //private void genVirtualDisplay() { publicif(EnumState.play eCurrentState) private ゲーム中だけの処理 GameView(Context public void onCreate(Bundle savedInstanceState) { <jp.mizmon21.android.mizmontouch10.GameView enum EnumState{{ { // void { private void doStart() (mHolder) { synchronized synchronized (mHolder) { menu, 当たり判定 super(context, attrs); // android:id="@+id/gameview" // 各変数の初期化 : : ここでGameViewを // メニュー画面 : : ready,judgeHit(); int bt_visibility = View.INVISIBLE; } switch(eCurrentState) { android:layout_width="fill_parent" // game.xmlを画面としてセットする // ゲーム開始カウントダウン } nScore = 0; 敵情報更新 // case menu: play,moveEnemy(); // 特に何もしない(game.xmlを表示) // ゲーム動作中 : 貼り付けてる int tv_visibility = View.INVISIBLE; android:layout_height="fill_parent" /> nCountSub = CYCLE_COUNT; setContentView(R.layout.game); // ゲームスレッドの生成 break; } nCount = READY_COUNT_DOWN; <RelativeLayout String tv_text = ""; } // ステートをセットする case ready: private void genThread(SurfaceHolder holder) { // GameViewのインスタンスを取得 // ゲーム開始前のカウントダウンを表示する private仮想画面生成 eCurrentState = EnumState.menu;// // EnumState android:layout_width="fill_parent" dispReady(canvas); それぞれのステート動作判断はこの中で行う 現在のゲームのステート // メソッドのインスタンス化 genVirtualDisplay(); // eCurrentState = state; mGameView = (GameView)findViewById(R.id.gameview); /*********************** 外部から呼ばれるメソッド ***********************/ android:layout_height="fill_parent" break; // 敵情報の初期化GameViewThread(holder, mContext, View // ステートの更新判定 switch(eCurrentState){ //listEnemys = new ArrayList<EnemyInfo>();敵の貼り付け new Handler() { コンストラクタ new play: mThread = case android:gravity="center_horizontal"> // gameview // メニュー表示 controlState(); @Override case menu: (FramELayout) dispEnemy(canvas); public GameViewThread(SurfaceHolder surfaceHolder, Context context, Handler handler) { } // // 画面ボタンのインスタンス取得と動作処理の登録 bt_visibility = View.VISIBLE; ゲームステートの変更タッチ位置にアイコンの貼り付け public void handleMessage(Message message) { this.mHandler = handler;// // ハンドラーのインスタンス化 (GameView) // ステートの更新 <Button // メッセージハンドラの生成(Threadからのメッセージをここで受け取る) setState(EnumState.ready); Button bt_start = dispTouch(canvas); (Button)findViewById(R.id.button1); tv_visibility = View.VISIBLE; : } privateandroid:id="@+id/button1“ void controlState() { // 倒した敵の獲得得点を表示する { ここはこれまでと同じ (Relative Layout) bt_start.setOnClickListener(new View.OnClickListener() tv_text = mContext.getString(R.string.tv_menu); // ゲームステートの変更 switch(eCurrentState) {btStart.setVisibility(message.getData().getInt("bt_visibility")); : dispGetPoint(canvas); break; tvText.setVisibility(message.getData().getInt("tv_visibility")); // 特に何もしない(ボタン入力のため) case menu: onClick(View menuに状態を遷移にさせる android:text="@string/bt_game“// v) { public void // スコアの表示をする setState(EnumState.menu); game.xml上の } } // dispGameInfo(canvas); // カウンタの更新 case ready: tvText.setText(message.getData().getString("tv_text")); break; : case ready:ボタンがクリックされた時に呼び出されます case play: textView1 // ゲーム開始カウントダウン // ゲーム動作中 // スタートボタンが押されたときの処理。ゲームをスタートさせる break; mGameView.gameStart(); android:visibility="visible" /> }); nCountSub --; default: ボタンの処理 default: public void setStart(){ >= nCountSub) { } } doStart(); if(0 break; // readyに状態を遷移させる break; button1 <TextView nCountSub = CYCLE_COUNT;} public void}setButtonInstance(Button button_start) { } } }); nCount --; android:id="@+id/textView1" // レイアウト上のbuttonのインスタンス化 btStart = button_start; > nCount) { // メッセージを生成しGameViewに送り付ける //:テキストのインスタンスを取得 } } // タッチ処理 if(0 Message msg = mHandler.obtainMessage(); // カウントダウンがゼロになったらゲームスタート public boolean doTouchEvent(MotionEvent event) { TextView tv_text = (TextView)findViewById(R.id.textView1); publicandroid:text=""(mHolder) { setState(EnumState.play); bundle = new Bundle(); void setTextViewInstancce(TextView text_view) {Bundle // ゲーム開始前のカウントダウンを表示する synchronized //:XMLの各パーツのインスタンスをGameViewに教えてやる tvText = text_view; } bundle.putInt("bt_visibility", bt_visibility); if(EnumState.play == { // レイアウト上のtextviewをインスタンス化 private void dispReady(Canvas canvas)eCurrentState) { // 状態がplayのときのみタッチを有効にする } mGameView.setButtonInstance(bt_start); bundle.putInt("tv_visibility", tv_visibility); android:visibility="visible" /> } // カウントダウンを表示する : break; bundle.putString("tv_text", tv_text); // ゲーム開始ボタンが押された時の処理 String count = (0 == nCount) ? mContext.getString(R.string.txt_start) : "" + nCount; mGameView.setTextViewInstancce(tv_text); case play: } // とりあえず無限動作のため何もしない int x = (int)((VD_WIDTH / 2)); </RelativeLayout> msg.setData(bundle); } public void (int)(VD_HEIGHT / 2); gameStart() { int } = default: y break; mThread.setStart(); </FrameLayout>return true; mHandler.sendMessage(msg); // GameViewThreadにスタートを通知 } int size = (int)(96 * (float)(1f - (nCountSub - 1f) / CYCLE_COUNT)); break; } } } dispText(canvas, count, x, y, Typeface.DEFAULT_BOLD, 255, size, DISP_CENTER); } } 46 }}
  • 47. 終了処理 【概要】 時間制限を設けて終了処理を実装 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch11 ( 11MizmonTouch ) (MizmonTouch10から作成) 【追加メソッド】 GameViewThreadクラス内 setState() : ゲームオーバーの処理を追加 ganVirtualDisplay() : 各ステートの処理を追加 dispGameInfo() : 残り時間の表示を追加 dispFinish() : 終了時の演出を追加 main() : ゲームプレイ中の残り時間を更新 controlState() : ゲーム中の残り時間の更新 終了時演出のカウンタを更新 47
  • 48. 終了処理(ゲームオーバー) 無限に続き終わらない… 30秒一本勝負にする • ゲーム残り時間処理を実装 状態遷移図 • ゲームオーバー時の表示を実装 menu ボタン押下 • ステートを追加 タッチ game – finish:ゲーム終了時演出 over ready – gameover:タッチ検出待ち カウントダウン 演出終了 finish play 30秒経過 48
  • 49. 終了処理(コード解説) // ゲーム全体のステート(状態遷移) enum 仮想画面(ビットマップ)を生成する // EnumState { // スコア等ゲーム情報を表示する private void genVirtualDisplay() { menu, // メニュー画面 private void メイン処理 ***** ready, ***** dispGameInfo(Canvas canvas) { // synchronized (mHolder) { private void main() // ゲーム開始カウントダウン : { play, // 残り時間タイトルを表示する // ゲーム動作中 if(EnumState.play == eCurrentState) { switch(eCurrentState) { // 当たり判定 // ゲーム終了 finish, String titlejudgeHit(); = mContext.getString(R.string.txt_time); gameover,case menu: // ゲームオーバー } // 特に何もしない dispText(canvas, title, VD_WIDTH, DIAP_TITLE_POS_Y, Typeface.DEFAULT, 255, DISP_TITLE_SIZE, DISP_RIGHT); } break; // 残り時間を表示する eCurrentState || EnumState.finish == eCurrentState || EnumState.gameover == eCurrentState) { if(EnumState.play == case ready: // ゲーム開始前のカウントダウンを表示する moveEnemy(); // 敵情報更新 String time = String.format("%.1f", nGameTime); // 小数点以下1位まで表示する // システムのステートのセット : } dispText(canvas, time, VD_WIDTH, DIAP_INFO_POS_Y, Typeface.DEFAULT_BOLD, 255, DISP_INFO_SIZE, DISP_RIGHT); : break; private void setState(EnumState state) { } case play: synchronized (mHolder) { // スコアタイトルを表示する : : ステートの更新 // switch(eCurrentState){ : break; } private void controlState() { casecase menu: finish: // メニュー表示 switch(eCurrentState) { // 敵の貼り付け : : dispEnemy(canvas); // ゲームオーバーのロゴを表示する casebreak; finish: // スコアの表示をする case ready: カウンタの更新 // // ゲーム開始カウントダウン private void dispFinish(Canvas canvas) { dispGameInfo(canvas); case play: nCount ++; // ゲーム動作中 // ゲームオーバーメッセージを表示する // ゲーム終了のロゴを表示する // ゲーム終了 case finish: if(TIME_GAME_OVER < nCount) { String text dispFinish(canvas); = mContext.getString(R.string.txt_finish); break; // カウンターが規定値に達したらメニューへ戻る int x = (int)((VD_WIDTH /setState(EnumState.gameover); case gameover: 2)); break; // ゲームオーバー int case gameover: y = (int)(VD_HEIGHT / 2);View.INVISIBLE; } bt_visibility = // 敵の貼り付け - (float)nCount break; int size = (int)(56 * (float)(1fView.VISIBLE; / CYCLE_COUNT)) + 40; tv_visibility = casetv_text mContext.getString(R.string.tv_gameover); gameover: dispEnemy(canvas); size = (40 > size) ? 40 :=size; // タッチ検出確認 // スコアの表示をする break; dispText(canvas, text, x, y, Typeface.DEFAULT_BOLD, 255, size, DISP_CENTER); } default: if(null != rctTouch) dispGameInfo(canvas); { break; break; // タッチが検出されたらMenuへ戻る setState(EnumState.menu); } default: } break; break; // メッセージを生成しGameViewに送り付ける : } default: } } break; } } } } 49
  • 50. 効果音や音楽を鳴らす 【概要】 効果音やBGMを鳴らす仕組みを実装 【サンプルプロジェクト名(アプリケーション名)】 MizmonTouch12 ( 12MizmonTouch ) (MizmonTouch11から作成) 【修正・追加メソッド】 GameViewThread() : 効果音関係諸々をインスタンス化 setState() : 効果音を鳴らす依頼を追加 judgeHit() : 効果音を鳴らす依頼を追加 playSound() : 音を鳴らすかどうか判断する startBgm() : BGM再生開始 stopBgm() : BGM再生停止 【音源】 「 On-Jin ~音人~」さん からお借りしました http://www.yen-soft.com/ssse/ 50
  • 51. 効果音や音楽を鳴らす 音を鳴らす方法 soundPool と mediaPlayer soundPool mediaPlayer 単発音に向いている 尺の長い音に向いている (繰り返しが得意) (繰り返しが苦手) 効果音 効果音 • スタート音 • BGM • アタック音 • 空振りの音 • タイムアップの音 51
  • 52. 効果音や音楽を鳴らす(コード解説) // 効果音関係 // システムのステートのセット private static final int SOUND_START= 0; // スタート // 敵の当たり判定 SOUND_FINISH= 1; private void setState(EnumState state) { private static final int // 終了 private void効果音関係 *****/ /***** int judgeHit() SOUND_HIT = 2; synchronized (mHolder) { private static final { // 敵あたり(当たった音) // final int : (mHolder) { synchronized SOUND_MISS = 3; private static SoundPoolを利用して効果音を鳴らす // 敵はずれ(空振りの音) switch(eCurrentState){ -2 <==nJudgementCount && null != rctTouch) { if(JUDGEMENT_TIME private void playSound() { private static final int[][] SOUND_GROUP { // 各サウンドプレイヤーで鳴らす効果音の定義 // 効果音出力許可されているか判断する ; i--){ for(int i=listEnemys.size()-1 ; 0<=i : {SOUND_START, SOUND_FINISH}, // サウンドプレイヤー0で出力するグループ if(bEnableSound) EnemyInfo enemy = listEnemys.get(i); { {SOUND_HIT, ready:// 当たり判定。判定するのはbAlive=trueの敵のみ case SOUND_MISS} // サウンドプレイヤー毎に鳴らすべき効果音があるかチェック // ゲーム開始カウントダウン // サウンドプレイヤー1で出力するグループ }; for(int i = 0 if(enemy.bAlive && Rect.intersects(rctTouch, スタート時の効果音を鳴らす bPlaySound[SOUND_START] = true; { ; i < SOUND_GROUP.length ; i++) // enemy.rctCurrentArea)){ private boolean break; bEnableSound; : // 効果音出力許可フラグ // サウンドプレーヤーグループの各効果音で鳴らす依頼があるかチェック private MediaPlayer case play: < SOUND_GROUP[i].length ; j++)// ゲーム動作中 for(int j = 0 ; 効果音を鳴らす メディアプレイヤーのインスタンス(BGM用) mediaPlayer //j= null; // { private SoundPool soundPool 対象の効果音のIDを取り出す startBgm();bPlaySound[SOUND_HIT] = true; // BGM再生開始 // = null; // サウンドプールのインスタンス(効果音用) private int[] 敵あたり判定効果音をならしたよフラグを立てる break; int // = new = SOUND_GROUP[i][j]; nSoundIds sound_id int[4]; // それぞれの効果音のID private boolean[] case finish: // 対象の効果音について鳴らす依頼があるか判断 bPlayHitSound = true; bPlaySound = new boolean[4]; // ゲーム終了 // 効果音を鳴らす依頼フラグ(Trueで鳴らす) private int[] } if(bPlaySound[sound_id]) { nLastSoundId = new int[2]; = true; bPlaySound[SOUND_FINISH] // 終了時の効果音を鳴らす // 最後に鳴らした効果音(サウンドプレイヤー2個分) } // 現在鳴らしている効果音を停止 private boolean stopBgm(); bPlayHitSound; --; // 敵あたり効果音を鳴らしたよフラグ // BGM再生終了 // 当たり判定時間をデクリメント nJudgementCountsoundPool.stop(nLastSoundId[i]); /*********************** 外部から呼ばれるメソッド ***********************/ break; } else if(JUDGEMENT_TIME -新たな効果音を鳴らす { // 3 == nJudgementCount) // コンストラクタ : if(!bPlayHitSound) nLastSoundId[i] = soundPool.play(nSoundIds[sound_id], 1.0f, 1.0f, 1, 0, 1); { default: // 効果音を鳴らす public GameViewThread(SurfaceHolder surfaceHolder, Context context, Handler handler)第1引数:鳴らす音のID // 依頼フラグを下げる { : break; bPlaySound[SOUND_MISS] = true;= false; bPlaySound[sound_id] 第1引数:プールする最大の数(サウンドプレイヤーの数) 第2引数、第3引数:左右の音量(0.0~1.0) // 各効果音をインスタンス化 } } } else { 第2引数:Streamのタイプ(通常はSTREAM_MUSIC) 第4引数:プライオリティ(0が一番優先度が高い) bEnableSound = true; } // 敵をやっつけたフラグを下げる : 第3引数:クオリティ(デフォルトは0) 第5引数:ループ回数(-1:無限ループ、0:単発) bPlayHitSound = false; soundPool = new}SoundPool(2, AudioManager.STREAM_MUSIC, 0); } } 第6引数:再生速度(0.5~2.0倍) } } nSoundIds[SOUND_START] = soundPool.load(mContext, R.raw.start, 1); // スタート(プレイヤー0) } nJudgementCount --; // 当たり判定時間をデクリメント // BGMを停止する nSoundIds[SOUND_FINISH] = soundPool.load(mContext, R.raw.finish, 1); // 終了(プレイヤー0) } else if(0 <= nJudgementCount) { // BGMを再生開始する private void stopBgm() { nSoundIds[SOUND_HIT] = soundPool.load(mContext, R.raw.pico, 1); nJudgementCount --; private void startBgm() { // 敵あたり(プレイヤー1) // 当たり判定時間をデクリメント != mediaPlayer) { if (null nSoundIds[SOUND_MISS] = soundPool.load(mContext, R.raw.hazure, 1); // 効果音出力許可されているか判断する } else { // 敵はずれ(プレイヤー1) mediaPlayer.stop(); : if(bEnableSound) { = null; rctTouch // 当たり判定時間が超過したのでタッチ情報を削除 mediaPlayer.reset(); } mediaPlayer = MediaPlayer.create(mContext, R.raw.bgm); mediaPlayer.release(); } mediaPlayer.start(); mediaPlayer = null; } mediaPlayer.setVolume(1.0f, 1.0f); } } } } 52 }
  • 53. 仕上げ 53
  • 54. 最終チェック とにかくやりこんで 気が付いたところを列挙する • 改善点 – 途中でやめても音が止まらない – 音量のバランス • 全体的な使い勝手 – 音なしモードが欲しい – タイトルロゴの挿入 • ゲームバランスの調整 – 画像 – 敵の出現パターン – 難易度 54
  • 55. 改善点 • 途中でやめても音が終わらない – ゲーム終了処理を追加 – トップクラスにonPause()を追加し停止処理させる – GameViewクラスに停止処理を行うgameExit()を追加 – GameViewThreadクラスにsetExit(),releaseSound()を追加 • 音量のバランス – 当たり時の音大きい 各効果音やBGMに – はずれ時の音聞こえない static値を設定した 55
  • 56. 全体的な使い勝手 • 音なしモードが欲しい – menuに重ねて2個ボタンを追加 「サウンド・オン」と「サウンド・オフ」を排他で表示 – ボタンの制御はbEnableSoundフラグの状態で切 り替える – bEnableSoundフラグで効果音とBGMをオン・オフ する • タイトルロゴの挿入 – タイトルロゴを表示するdispTitle()を作成 – genVirtualDisplay()からmenu状態時に呼び出し 56
  • 57. ゲームバランスの調整 • 画像 – やっぱり画像キモイ 「クリップアートファクトリー」さん から画像をお借りしました http://www.printout.jp/clipart/index.html – 画像は差し替えるだけでOK 同時表示数 サイズ • 敵の出現パターン – 開始直後はパラパラと敵が出現 – 時間経過とともに同時表示数を増加 • 難易度 時間 – 開始直後のサイズはなるべく大きく表示 – 出現確立を高めに設定(時間経過でmaxを増やすため) 57
  • 59. まとめ 59
  • 60. 余力があれば 細かな調整や演出を加える • ポーズ機能の実装 • 空振りしたらペナルティとか • 連続コンボでボーナス得点獲得とか • ステージ制を導入(Win, Lost) • ハイスコアを登録する • 世界ランキング機能もいれてみる など 60
  • 61. 終わりに • 環境の充実 パソコン黎明期では考えられないほど簡単にアプリ が作れる ⇒プラットフォームもイロイロ • デザインはやっぱり重要 グラフィックを違うだけでまったく雰囲気が変わる ⇒ゲームは見た目が大事 • ヒットゲームはアイデアで勝負 • Googleプレイでアプリ公開 ⇒そして日々のアプリDL数をみてニヤニヤする 61