ライブ壁紙でライフゲーム

前回はぶっきらぼうにライフゲームのコードだけ載せたので、
今回はライブ壁紙のコードも晒します。


ポイントは以下の通り

  • 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