ライブ壁紙でライフゲーム
前回はぶっきらぼうにライフゲームのコードだけ載せたので、
今回はライブ壁紙のコードも晒します。
ポイントは以下の通り
- LifeGameWallpaperEngineのコンストラクタで_lifeGameを初期化
- onSurfaceChangedで描画設定を初期化
- drawMainでイロイロしてる
あと、端末の向きが変わるとonSurfaceChangedが呼ばれるので、
ライフゲームの計算結果は引き継いだまま続行されるのがミソです。
ちなみに、このソースコードはgoogleの公式サイトからパクってます。
サンプルコード一覧のここ。
で、サンプルコードにあったタッチイベントを利用して、
そのタッチした場所に生命を誕生させるなんていうのは、
touchProcessに実装しています。
package your.domain.LifeGameWallpaper;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
//import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
public class LifeGameWallpaper extends WallpaperService {
private final Handler _handler = new Handler();
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public Engine onCreateEngine() {
// Log.d( "lifegame", "onCreateEngine" );
return new LifeGameWallpaperEngine();
}
class LifeGameWallpaperEngine extends Engine {
private final long DRAW_INTERVAL = 1000 / 8; // 描画間隔
private int _screenW, _screenH;
private int _nx, _ny;
private int _x0, _y0;
private int _size;
private Point _offset = new Point( 0, 0 );
private Paint _paintBorn, _paintStay;
private LifeGame _lifeGame;
private byte[] _dst;
private float _touchX, _touchY;
private final Runnable _drawLifeGame = new Runnable() {
public void run() {
drawMain();
}
};
private boolean _visible;
public LifeGameWallpaperEngine() {
// ライフゲームの計算量に直結するので、動作を見ながらもっさりしないように決める
_lifeGame = new LifeGame( 80, 80 );
_dst = new byte[ _lifeGame.getWidth() * _lifeGame.getHeight() ];
{
// 初期パターンのコピー
final byte[][][] patterns = {
{ // ドングリ
{ 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0 },
{ 1, 1, 0, 0, 1, 1, 1 }
},
{ // ダイハード
{ 0, 0, 0, 0, 0, 0, 1, 0 },
{ 1, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 1, 1 }
}
};
byte[][] pattern = patterns[0];
int offsetX = (_lifeGame.getWidth() - pattern[0].length) / 2;
int offsetY = (_lifeGame.getHeight() - pattern.length) / 2;
_lifeGame.setPattern( pattern, offsetX, offsetY );
}
_lifeGame.exec( _dst );
_paintBorn = new Paint();
_paintBorn.setColor( Color.RED );
_paintStay = new Paint();
_paintStay.setColor( Color.YELLOW );
// 無効な座標で初期化
_touchX = _touchY = -1f;
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate( surfaceHolder );
setTouchEventsEnabled( true );
}
@Override
public void onDestroy() {
super.onDestroy();
_handler.removeCallbacks( _drawLifeGame );
}
@Override
public void onVisibilityChanged(boolean visible) {
_visible = visible;
if ( _visible ) {
drawMain();
}
else {
_handler.removeCallbacks( _drawLifeGame );
}
}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged( holder, format, width, height );
_screenW = width;
_screenH = height;
final int SIZE_MIN = 8;
{
// 端数切り上げしないと、
// 描画可能な要素数の計算がライフゲームの幅、もしくは高さを越えてしまう
int w = (int)Math.ceil( (float)_screenW / _lifeGame.getWidth() );
int h = (int)Math.ceil( (float)_screenH / _lifeGame.getHeight() );
// 長い方を正方形の高さとして採用する
_size = ( w < h ) ? h : w;
// 高さのクリップ
if ( _size < SIZE_MIN ) {
_size = SIZE_MIN;
}
}
// ライフゲーム空間のどの範囲を描画するか確定する
_nx = _screenW / _size;
_ny = _screenH / _size;
_x0 = (_lifeGame.getWidth() - _nx) / 2;
_y0 = (_lifeGame.getHeight() - _ny) / 2;
// 描画対象の中心に描画するための描画開始座標を計算
_offset = new Point(
(_screenW - (_nx * _size)) / 2,
(_screenH - (_ny * _size)) / 2
);
/*
Log.d( "onSurfaceChanged", "_screenW = " + _screenW );
Log.d( "onSurfaceChanged", "_screenH = " + _screenH );
Log.d( "onSurfaceChanged", "_size = " + _size );
Log.d( "onSurfaceChanged", "_nx = " + _nx );
Log.d( "onSurfaceChanged", "_ny = " + _ny );
Log.d( "onSurfaceChanged", "_x0 = " + _x0 );
Log.d( "onSurfaceChanged", "_y0 = " + _y0 );
Log.d( "onSurfaceChanged", "_offset.x = " + _offset.x );
Log.d( "onSurfaceChanged", "_offset.y = " + _offset.y );
*/
drawMain();
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated( holder );
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed( holder );
_visible = false;
_handler.removeCallbacks( _drawLifeGame );
}
@Override
public void onOffsetsChanged(
float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep,
int xPixelOffset, int yPixelOffset) {
}
@Override
public void onTouchEvent(MotionEvent event) {
if ( event.getAction() == MotionEvent.ACTION_DOWN ) {
_touchX = event.getX();
_touchY = event.getY();
// Log.d( "touch", "X = " + _touchX + ", Y = " + _touchY );
}
super.onTouchEvent( event );
}
private void drawMain() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
long begin = 0, endDraw = 0, endLifeGame = 0;
try {
touchProcess();
c = holder.lockCanvas();
if (c != null) {
begin = System.currentTimeMillis();
_lifeGame.exec( _dst );
endLifeGame = System.currentTimeMillis();
drawRectangles( c );
endDraw = System.currentTimeMillis();
}
} finally {
if ( c != null ) {
holder.unlockCanvasAndPost( c );
}
}
// Log.d( "lifegame", "becnch = " + (endLifeGame - begin) );
// Log.d( "draw", "becnch = " + (endDraw - begin) );
// Reschedule the next redraw
_handler.removeCallbacks( _drawLifeGame );
if ( _visible ) {
long delay = DRAW_INTERVAL - (endDraw - begin);
if ( delay < 0 ) {
delay = 0; // clip
}
_handler.postDelayed( _drawLifeGame, delay );
}
}
private void drawRectangles(Canvas c) {
c.drawColor( Color.BLACK ); // Clear
for (int iy=0; iy<_ny; iy++) {
final float n = _size;
float offsetY = (float)( _offset.y + (iy * _size) );
float offsetX = (float)( _offset.x );
int lineOffset = _lifeGame.getWidth() * (_y0 + iy);
for (int ix=0; ix<_nx; ix++) {
int status = _dst[ lineOffset + _x0 + ix ];
if ( status != 0 ) {
final Paint paint = ( status == 1 ) ? _paintStay : _paintBorn;
c.drawRect(
offsetX, offsetY,
(offsetX + n - 1), (offsetY + n - 1),
paint
);
}
offsetX += n;
}
}
}
private void touchProcess() {
if ( _touchX < 0f || _touchY < 0f ) {
return;
}
final byte[][] pattern = {
{ 1, 1, 1 },
{ 1, 1, 1 },
{ 1, 1, 1 }
};
float x = (_touchX - (float)_offset.x) / (float)_size;
float y = (_touchY - (float)_offset.y) / (float)_size;
int offsetX = _x0 + (int)x - (pattern[0].length / 2);
int offsetY = _y0 + (int)y - (pattern.length / 2);
_lifeGame.setPattern( pattern, offsetX, offsetY );
// タッチした座標の破棄
_touchX = _touchY = -1f;
}
}
}
こんな時間なので、まだ呑んでないよ。
おしまい。
Leave a Comment