package rabbitescape.ui.android; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.SurfaceHolder; import java.util.ArrayList; import java.util.List; import rabbitescape.engine.Thing; import rabbitescape.engine.World; import rabbitescape.engine.util.Util; import rabbitescape.render.AnimationCache; import rabbitescape.render.AnimationLoader; import rabbitescape.render.BitmapCache; import rabbitescape.render.GraphPaperBackground; import rabbitescape.render.PolygonBuilder; import rabbitescape.render.Overlay; import rabbitescape.render.Renderer; import rabbitescape.render.SoundPlayer; import rabbitescape.render.Sprite; import rabbitescape.render.SpriteAnimator; import rabbitescape.render.Vertex; import rabbitescape.render.gameloop.Graphics; import rabbitescape.render.gameloop.WaterAnimation; public class AndroidGraphics implements Graphics { private static final float MIN_INITIAL_TILE_SIZE = 32f; private static final float MIN_TILE_SIZE = 16f; private final BitmapCache<AndroidBitmap> bitmapCache; private final SoundPlayer soundPlayer; private final World world; private final WaterAnimation waterAnimation; private final AnimationCache animationCache; private final AndroidPaint paint; /** * Set when the surface becomes available. */ public SurfaceHolder surfaceHolder; public float renderingTileSize; public int levelWidthPixels; public int levelHeightPixels; private int screenWidthPixels; private int screenHeightPixels; private int prevScrollX; private int prevScrollY; public int scrollX; public int scrollY; private int lastFrame = -1; private boolean soundOn = true; private static final AndroidPaint white = makePaint( Color.WHITE ); private static final AndroidPaint graphPaperMajor = makePaint( Color.rgb( 205, 212, 220 ), Paint.ANTI_ALIAS_FLAG ); private static final AndroidPaint graphPaperMinor = makePaint( Color.rgb( 235, 243, 255 ), Paint.ANTI_ALIAS_FLAG ); private static final AndroidPaint waterColor = makePaint( Color.argb( 100, 10, 100, 220 ), Paint.ANTI_ALIAS_FLAG ); private static final AndroidPaint dullOverlay = makePaint( Color.argb( 200, 70, 70, 70 ) ); private static final AndroidPaint greenText = makePaint( Color.rgb( 100, 255, 100 ), Paint.ANTI_ALIAS_FLAG ); static { waterColor.paint.setStyle( Paint.Style.FILL ); } private static AndroidPaint makePaint( int color ) { Paint p = new Paint(); p.setColor( color ); return new AndroidPaint( p ); } private static AndroidPaint makePaint( int color, int flags ) { Paint p = new Paint( flags ); p.setColor( color ); return new AndroidPaint( p ); } public AndroidGraphics( BitmapCache<AndroidBitmap> bitmapCache, SoundPlayer soundPlayer, World world, WaterAnimation waterAnimation, int scrollX, int scrollY ) { this.bitmapCache = bitmapCache; this.soundPlayer = soundPlayer; this.world = world; this.waterAnimation = waterAnimation; this.scrollX = scrollX; this.scrollY = scrollY; this.surfaceHolder = null; this.animationCache = new AnimationCache( new AnimationLoader() ); this.paint = new AndroidPaint( new Paint() ); // These will be set properly when we draw, so we know screen size this.screenWidthPixels = -1; this.screenHeightPixels = -1; this.renderingTileSize = -1; this.levelWidthPixels = -1; this.levelHeightPixels = -1; } private float initialTileSize() { // Try to fit the whole level on screen float retX = screenWidthPixels / world.size.width; float retY = screenHeightPixels / world.size.height; float ret = ( retX < retY ) ? retX : retY; if ( ret < MIN_INITIAL_TILE_SIZE ) { ret = MIN_INITIAL_TILE_SIZE; } return ret; } @Override public void draw( int frame ) { lastFrame = frame; if ( surfaceHolder == null ) { System.err.println( "Error: AndroidGraphics - drawing without a surfaceHolder!" ); return; } Canvas canvas = surfaceHolder.lockCanvas(); if ( canvas == null ) { return; } try { synchronized ( surfaceHolder ) { actuallyDrawGraphics( canvas, frame ); } } finally { surfaceHolder.unlockCanvasAndPost( canvas ); } } public void redraw() { if ( -1 == lastFrame ) { return; } soundOn = false; draw( lastFrame ); soundOn = true; } @Override public void rememberScrollPos() { prevScrollX = scrollX; prevScrollY = scrollY; } @Override public void drawIfScrolled( int frame ) { if ( prevScrollX != scrollX || prevScrollY != scrollY ) { draw( frame ); prevScrollX = scrollX; prevScrollY = scrollY; } } @Override public void dispose() { } private void actuallyDrawGraphics( Canvas canvas, int frame ) { if ( screenWidthPixels != canvas.getWidth() || screenHeightPixels != canvas.getHeight() ) { screenWidthPixels = canvas.getWidth(); screenHeightPixels = canvas.getHeight(); adjustRenderingTileSize( initialTileSize() ); } drawToCanvas( canvas, -scrollX, -scrollY, frame ); } public void adjustRenderingTileSize( float newSize ) { // Make sure size >= 16 and at least 5 tiles are visible in each direction this.renderingTileSize = chooseRenderingTileSize( newSize ); levelWidthPixels = (int)( renderingTileSize * world.size.width ); levelHeightPixels = (int)( renderingTileSize * world.size.height ); scrollBy(0, 0); } private float chooseRenderingTileSize( float suggestedSize ) { if ( suggestedSize < MIN_TILE_SIZE ) { return MIN_TILE_SIZE; } else { float maxSize = maxSize(); if ( suggestedSize > maxSize ) { return maxSize; } else { return suggestedSize; } } } private float maxSize() { // One fifth of the shortest screen dimension // i.e. no less than five tiles are visible in each direction float retX = screenWidthPixels / 5; float retY = screenHeightPixels / 5; return ( retX < retY ) ? retX : retY; } private void drawToCanvas( Canvas canvas, int offsetX, int offsetY, int frameNum ) { AndroidCanvas androidCanvas = new AndroidCanvas( canvas ); Renderer<AndroidBitmap, AndroidPaint> renderer = new Renderer<AndroidBitmap, AndroidPaint>( offsetX, offsetY, (int)renderingTileSize, bitmapCache ); SpriteAnimator animator = new SpriteAnimator( world, animationCache ); GraphPaperBackground.drawBackground( world, renderer, androidCanvas, white, graphPaperMajor, graphPaperMinor ); drawPolygons( waterAnimation.calculatePolygons(), androidCanvas, renderer ); List<Sprite> sprites = animator.getSprites( frameNum ); if ( soundOn ) { soundPlayer.play(sprites); } renderer.render(androidCanvas, sprites, paint); if ( world.paused ) { tacticalOverlay( renderer, androidCanvas, world ); } } private void tacticalOverlay( Renderer renderer, AndroidCanvas androidCanvas, World world ) { androidCanvas.drawColor( dullOverlay ); Overlay overlay = new Overlay( world ); greenText.paint.setTextAlign( Paint.Align.CENTER ); float h = renderer.tileSize / 4 ; greenText.paint.setTextSize( h ); for ( Thing t : overlay.items ) { String notation = overlay.at(t.x, t.y); String[] lines = Util.split(notation, "\n"); for (int i = 0; i < lines.length; i++) { int x = renderer.offsetX + t.x * renderer.tileSize; int y = renderer.offsetY + t.y * renderer.tileSize + (int)( (float)i * h ); x += ( renderer.tileSize ) / 2; // centre y += ( renderer.tileSize - h * lines.length ) / 2 ; androidCanvas.drawText( lines[i], (float)x, (float)y, greenText); } } } void drawPolygons( List<PolygonBuilder> polygons, AndroidCanvas androidCanvas, Renderer<AndroidBitmap, AndroidPaint> renderer ) { float f = renderer.tileSize / 32f; for ( PolygonBuilder pb: polygons ) { rabbitescape.render.androidlike.Path rePath = pb.path( f, new Vertex( renderer.offsetX, renderer.offsetY ) ); androidCanvas.drawPath( rePath, waterColor ); } } public void scrollBy( float x, float y ) { scrollX += x; scrollY += y; if ( levelWidthPixels < screenWidthPixels ) { scrollX = -( screenWidthPixels - levelWidthPixels ) / 2; } else if ( scrollX < 0 ) { scrollX = 0; } else if ( scrollX > levelWidthPixels - screenWidthPixels ) { scrollX = levelWidthPixels - screenWidthPixels; } if ( levelHeightPixels < screenHeightPixels ) { scrollY = -( screenHeightPixels - levelHeightPixels ) / 2; } else if ( scrollY < 0 ) { scrollY = 0; } else if ( scrollY > levelHeightPixels - screenHeightPixels ) { scrollY = levelHeightPixels - screenHeightPixels; } } public void scaleRenderingTileSize( float scaleFactor, float focusX, float focusY ) { float newFocusX = scaleFactor * ( scrollX + focusX ); float newFocusY = scaleFactor * ( scrollY + focusY ); adjustRenderingTileSize( renderingTileSize * scaleFactor ); float movedFocusX = scrollX + focusX; float movedFocusY = scrollY + focusY; scrollBy( newFocusX - movedFocusX, newFocusY - movedFocusY ); } }