落下運動の表現
等速運動の表現では,画面上をそれぞれに与えられた速さで動く物体(描画対象を)を表現しました.
ところが,一般に,物体には加速度が加わっています.
運動する物体(例えば,乗物)上にいると,運動の方向と加速度の方向が一致しているときは,加速/減速として,感じられます.
運動の方向と加速度の方向が異なると,横Gあるいは上下動として,感じられます.
下の図は,加速度と運動の方向が一致する場合ですが,水平方向だけではなく,
さて,加速度の方向と異なる方向に初速度の成分があった場合,どうなるでしょう.
下の図は,鉛直にかかる加速度(一般に「重力(加速度)」と呼ばれる)に対して,水平方向に初速度を与えた場合の運動を模式的に表しています.
初速度の鉛直方向の速さを0とした場合でも,時間経過と共に速度が増していくことが分かります.
落下運動の実装例
以下に,水平方向にのみ初速度を与えた落下運動を表現するためのコードをしまします.
このプログラムでは,画面を左から右になぞった距離に応じてX軸方向の初速度が与えられ,円が飛び出し,落下していきます.
package jp.ac.bunkyo.a2dgames; /** * 2Dゲームで落下する物体の描画を説明するActivity * @author Hidenao Abe (hidenao@shonan.bunkyo.ac.jp) */ /* Copyright 2013 Hidenao Abe (hidenao@shonan.bunkyo.ac.jp) 本ソースコードは,Apache License Version 2.0(「本ライセンス」)に基づいてライセンスされます。あなたがこのファイルを使用するためには、本ライセンスに従わなければなりません。本ライセンスのコピーは下記の場所から入手できます。 http://www.apache.org/licenses/LICENSE-2.0 適用される法律または書面での同意によって命じられない限り、本ライセンスに基づいて頒布されるソフトウェアは、明示黙示を問わず、いかなる保証も条件もなしに「現状のまま」頒布されます。本ライセンスでの権利と制限を規定した文言については、本ライセンスを参照してください。 (この日本語訳は,http://sourceforge.jp/projects/opensource/wiki/licenses%2FApache_License_2.0によるものです) */ import java.util.Random; import jp.ac.bunkyo.a2dgames.MotionUni.MUView; import android.os.Bundle; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.Window; import android.view.WindowManager; public class MotionFall extends Activity { MFView m_view; @Override protected void onCreate(Bundle savedInstanceState) { //Activityをフルスクリーン表示にする requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); super.onCreate(savedInstanceState); //setContentView(R.layout.activity_motion_fall); m_view = new MFView(this); setContentView(m_view); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.motion_fall, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); switch(item.getItemId()){//itemを表示するため,res/menu/*.xmlにItem要素を追加すること case R.id.menu_finishing : //強制的にActivityを終了する finish(); return true; } return false; } @Override protected void onPause(){ super.onPause(); m_view.pause(); if(isFinishing()){ m_view.finish(); } } @Override protected void onResume(){ //Activityの開始・再開処理 super.onResume(); m_view.resume(); //スレッドの開始処理はActivityの開始・再開に合わせて実行する } @Override protected void onStop(){ super.onStop(); m_view.finish(); Thread.interrupted(); } //高速なアニメーションなどに対応したSurfaceViewを継承したカスタムViewを作成 class MFView extends SurfaceView implements Runnable, SurfaceHolder.Callback{ Paint paint; //Viewの持つCanvasへの描画機能(以下で紹介するのはごく一部) Random rand; //乱数発生器 int screen_width, screen_height; int bgColor=Color.WHITE; Canvas canvas; SurfaceHolder s_holder; Thread renderThread; Context pContext; volatile boolean isRunning=false; //スレッドの実行制御を行うための変数 // 描画対象を管理するメンバ変数 int n=10; //任意数の描画対象を管理するには,配列ではなくListなどを利用する(ただし,アクセサ(set/get)は利用しないほうが良いとされる) boolean[] isVisible = new boolean[n]; //描画範囲にあるかどうかかを判定する int[] x = new int[n]; //描画位置のX座標 int[] y = new int[n]; //描画位置のY座標 int[] radial = new int[n]; //描画する円の半径 float[] velocity_x = new float[n];//描画する円の移動速度(任意数のピクセル/10msecを速度とする) float[] velocity_y = new float[n];//描画する円の移動速度(任意数のピクセル/10msecを速度とする) float start_x, end_x; long touch_down, touch_up; private final float G = 0.98f; //加速度を設定(g=0.098px/frame^2で重力加速度を想定) public MFView(Context context) { super(context); pContext=context; paint = new Paint(); rand = new Random(); for(int i=0; i<n; i++){ isVisible[i] = false; } s_holder = getHolder(); } public void run(){ //ゲームロジック,描画位置を別スレッドで計算 long start = System.currentTimeMillis(); long duration = 0; while(isRunning){ if(! s_holder.getSurface().isValid()){ continue; } canvas = s_holder.lockCanvas(); if(canvas != null){ long loop_start = System.currentTimeMillis(); //座標の更新(ゲームなら,ここでゲームロジック(のメソッド群)を実装) for(int i=0; i<n; i++){ if(isVisible[i]){ long now = System.currentTimeMillis(); //y[i] = y[i] + velocity[i]/((int)(now-start)*10+1); velocity_y[i] = velocity_y[i] + G*(float)(now - start)/100.0f/2.0f; y[i] = y[i] + (int)(velocity_y[i] * (float)(now - start)/100.0f); //Y軸方向には加速度に従って加速する運動(100.0fはフレームレート) x[i] = x[i] + (int)(velocity_x[i] * (float)(now - start)/100.0f); //X軸方向には等速運動 if(y[i] > screen_height){ isVisible[i] = false; } } } drawContents(); //描画を実行 s_holder.unlockCanvasAndPost(canvas); duration = loop_start - System.currentTimeMillis(); start = System.currentTimeMillis(); } try{ Thread.sleep(100 - duration);//次の描画まで100msecの間隔を空ける //(フレームレートを厳守するのであれば,sleepさせるのではなく,描画処理を一定間隔で呼び出すようにする) }catch(InterruptedException e){ } } } public void drawContents(){//描画を担当するメソッド canvas.drawColor(bgColor); //キャンバスをbgColorで指定した色で塗りつぶす paint.setColor(Color.BLACK); paint.setTextSize(20); canvas.drawText("初速を決めるため画面を左から右へなぞってください", 10, 20, paint); screen_width=canvas.getWidth(); screen_height=canvas.getHeight(); //円の描画 paint.setStyle(Style.FILL); //塗りつぶし paint.setColor(Color.RED); //色を指定 for(int i=0; i<n; i++){ if(isVisible[i]){ canvas.drawCircle(x[i],y[i],radial[i], //中心の座標(x, y),半径 paint); } } } public void pause(){ //中断処理 isRunning = false; //必要であれば,ゲームデータなどの一時保存を行う while(true){ try { renderThread.join(); break; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public void finish(){ //終了処理メソッド synchronized (renderThread) { isRunning = false; } try { renderThread.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } ((Activity)pContext).finish(); //Context経由でActivityを終了 } public void resume(){ isRunning = true; renderThread = new Thread(this); renderThread.start(); } public boolean onTouchEvent(MotionEvent event) { //SurfaceViewにおいて画面タッチイベントを処理 switch(event.getAction()){ case MotionEvent.ACTION_DOWN: //指が画面に触れたとき start_x = event.getX(); break; case MotionEvent.ACTION_UP: //指が画面から離れたとき case MotionEvent.ACTION_CANCEL: end_x = event.getX(); for(int i=0; i<n; i++){ if(isVisible[i] == false){ isVisible[i] = true; x[i] = 0; y[i] = 100; radial[i] = rand.nextInt(20)+1; if(end_x - start_x < 50){ velocity_x[i] = 5.0f; } else{ velocity_x[i] = (end_x - start_x)/10.0f; } velocity_y[i]=0.0f; break; } } break; } return true; } @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { // TODO 自動生成されたメソッド・スタブ } @Override public void surfaceCreated(SurfaceHolder arg0) { } @Override public void surfaceDestroyed(SurfaceHolder arg0) { // TODO 自動生成されたメソッド・スタブ finish(); } } }
実行画面は,画面を同じような間隔で,同じような幅でなぞった場合の実行の様子をあらわしています.