加速度センサーを使った例としてよくあるのが、ボールがスマホの傾きで転がるゲーム性のあるアプリです。
2021.2.1
Accelerometer
前回は加速度センサーの値を取得する方法でしたが、それを応用した球ころがしをスマホを傾けることによりやって見たいと思います。
コードはAndroid Studioから取り出せるSampleの中の「Accelemrometer Play」からボールが転がる箇所を切り出して簡単にしたものです。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
//package your.package.name; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Handler; import android.os.Looper; public class MainActivity extends AppCompatActivity implements SensorEventListener { private SensorManager sensorManager; private CanvasView canvasView; private float sensorX; private float sensorY; private float sensorZ; private final int period = 100; // 'Handler()' is deprecated as of API 30: Android 11.0 (R) private final Handler handler = new Handler(Looper.getMainLooper()); private Runnable runnable; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Get an instance of the SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); canvasView = new CanvasView(this, null); setContentView(canvasView); timerSet(); } @Override protected void onResume() { super.onResume(); Sensor accel = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); // Listenerの登録 sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_GAME); } @Override protected void onPause() { super.onPause(); // Listenerを解除 sensorManager.unregisterListener(this); stopTimerTask(); } @Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { sensorX = event.values[0]; sensorY = event.values[1]; sensorZ = event.values[2]; } } private void timerSet(){ runnable = new Runnable() { @Override public void run() { canvasView.setPosition(sensorX,sensorY); handler.postDelayed(this, period); } }; handler.post(runnable); } private void stopTimerTask(){ handler.removeCallbacks(runnable); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } } |
‘Handler()’ is deprecated as of API 30: Android 11.0 (R)
Android 11 からHandler()が非推奨になりました。Handlerがなくなったわけではありません、nullにならないようにする必要があるということです。
センサーの値を取り込むところは前のページでやった基本と同じです。
X, Y方向の値のみ取り出してCanvasでDrawします。
タイミングをある程度とりたいのでtimer.scheduleを使い100msec毎のセンサー値をCanvasに送ります。
CanvasView.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
//package your.package.name; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import android.content.Context; public class CanvasView extends View { private final Paint paint; private final Bitmap bmp; private float xpos; private float ypos; private float preX; private float preY; public CanvasView(Context context, AttributeSet attrs) { super(context, attrs); paint = new Paint(); bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ball); } @Override protected void onDraw(Canvas canvas) { // 背景、半透明 canvas.drawColor(Color.argb(125, 0, 0, 255)); canvas.drawBitmap(bmp, (int)(getWidth() / 2 + xpos), (int)(getHeight()/2+ypos), paint); // xpos, yposの座標表示 //paint.setStyle(Paint.Style.FILL_AND_STROKE); //paint.setStrokeWidth(1); //paint.setTextSize(50); //paint.setColor(Color.argb(255, 255, 255, 255)); //canvas.drawText("xpos: "+String.valueOf(xpos), 120, 600, paint); //canvas.drawText("ypos: "+String.valueOf(ypos), 120, 650, paint); } public void setPosition(float xp, float yp) { float dT = 0.8f; final float ax = -xp*2; final float ay = yp*2; xpos += preX*dT + ax*dT*dT; preX += ax*dT; ypos += preY*dT + ay*dT*dT; preY += ay*dT; // 再描画 invalidate(); } } |
ボールの画像は適当な画像をres\drawableに入れて試してください。
また、ボールの座標を表示したい場合は drawText を使うと可能です。
setPositionではセンサーの生データをフィルタリングしています。
ここはsampleでのやり方を参考にしています。
生データはちょっとした動きに過敏に反応するため
メディアンフィルターとローパスを組み合わせた方がいいようです
sampleでは時間的要素を加味していますが、今回timerを使っているのである程度等間隔と想定してその部分は簡略化しています。
この辺りは、描画時間の遅れを相殺するようにした方がいいでしょう。Unityでもそういったことをしています。
関連ページ:
Canvas クリアーして再描画
カウントアップするタイマー
Sensor 一覧を取得する
加速度センサー:Accelerometer
加速度センサーで球ころがし
Gyroscope ジャイロセンサー