Handler postDelay() leading unexpected variable changes - android

I am working on a game and I am using postDelay() to make the animations smoother. A problem I am running into is that if I execute the method with postDelay() in it, the variables that the method operates on no longer gets changed in unexpected ways. This leads to weird behavior such as the bitmaps not drawing properly.
Here are the troublesome parts:
synchronized private void rotate(int dir) {
final int originalAngle = angle; //angle is the suspicious variable
final int steps = 4;
for (int i = 0; i != steps; i++) {
smoothAnimateRotate(i, steps, originalAngle, dir);
}
finishAnimateRotate(steps);
}
private void smoothAnimateRotate(final int i, final int steps, final int originalAngle, final int dir) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
angle = originalAngle + dir*(90*(i+1))/steps;
rotateStep();
}
}, 100 * (i + 1));
}
private void rotateStep() {
angle = (angle+1800) % 360;
viewdx = (int) (Math.cos(radify(angle))*(1<<16));
viewdy = (int) (Math.sin(radify(angle))*(1<<16));
moveStep();
}
private void moveStep() {
notifyViewerRedraw() ;
try {
Thread.currentThread().sleep(25);
} catch (Exception e) { }
}
When rotate() is called very quickly, I think the variable angle somehow gets messed up. This doesn't happen when I call rotate() about one second apart, but if I do it very quickly, the angle, which is only suppose to be either 0, 90, 180, or 270 gets changed to 45 degrees or something else. I can't figure out why and debugging hasn't provided fruitful results. So how do I make sure angle does get modified in such unexpected manner?

I fixed it. I put a boolean called isRunning inside the Runnables and switched it on and off.
synchronized private void rotate(int dir) {
final int originalAngle = angle;
final int steps = 4;
if (!isRunning) {
for (int i = 0; i != steps; i++) {
smoothAnimateRotate(i, steps, originalAngle, dir);
}
finishAnimateRotate(steps);
}
}
private void smoothAnimateRotate(final int i, final int steps, final int originalAngle, final int dir) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
isRunning = true;
angle = originalAngle + dir*(90*(i+1))/steps;
rotateStep();
}
}, 100 * (i + 1));
}
private void finishAnimateRotate(final int steps) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
rotateFinish();
isRunning = false;
}
}, 100 * (steps + 1));
}

Related

How to animate seekbar thumb to move to a specific position

I defined different lock positions:
private static final int[] gears = new int[] { 0, 33, 66, 100 };
In the onStopTrackingTouch i calculate which of my lock positions is the closest to the progress state of my SeekBar:
#Override
public void onStopTrackingTouch(final SeekBar seekBar) {
int progress = seekBar.getProgress();
int distance = Math.abs(gears[0] - progress);
int index = 0;
for(int i = 1; i < gears.length; i++) {
int tempDistance = Math.abs(gears[i] - progress);
if(tempDistance < distance) {
index = i;
distance = tempDistance;
}
}
int lock = gears[index];
}
I now need to somehow set the progress state of the SeekBar to my lock position using a smooth animation.
How can i impelement that animation?
EDIT
Thanks to the answer of #bwoogie here the complete working example using a VerticalSeekBar
#Override
public void onStopTrackingTouch(final SeekBar seekBar) {
int progress = seekBar.getProgress();
int distance = Math.abs(gears[0] - progress);
int index = 0;
for(int i = 1; i < gears.length; i++) {
int tempDistance = Math.abs(gears[i] - progress);
if(tempDistance < distance) {
index = i;
distance = tempDistance;
}
}
final int lock = gears[index];
final int tick = 50;
final VerticalSeekBar verticalSeekBar = (VerticalSeekBar)seekBar;
new Thread(new Runnable() {
long now = System.currentTimeMillis();
public void run() {
while(seekBar.getProgress() != lock) {
if(now + tick < System.currentTimeMillis()) {
if(seekBar.getProgress() > lock) {
getActivity().runOnUiThread(new Runnable() {
public void run() {
verticalSeekBar.setMax(100);
verticalSeekBar.setProgressAndThumb(seekBar.getProgress() - 1);
}
});
} else if(seekBar.getProgress() < lock) {
getActivity().runOnUiThread(new Runnable() {
public void run() {
verticalSeekBar.setMax(100);
verticalSeekBar.setProgressAndThumb(seekBar.getProgress() + 1);
}
});
} else {
break;
}
now = System.currentTimeMillis();
}
}
}
}).start();
}
What I would do is create a loop (in a new thread) that checks every few milliseconds if enough time has passed and if so then we can update the seekBar. Here is the basic idea, just throw it into a thread and implement the updateSeekBar() method since its in a thread you wont be able to access the UI thread directly.
int newPos; // the new position we are seeking to
int ticks = 50; //how many milliseconds
long now = System.getCurrentTimeMillis();
while(seekBar.getPosition() != newPos) {
if(now + ticks < System.getCurrentTimeMillis()) {
if(seekBar.getPosition() > newPos) {
updateSeekbar(seekBar.getPosition()--);}
elseif(seekBar.getPosition() < newPos{
updateSeekbar(seekBar.getPosition()++;}
else {
break; // we've reached out goal
}
now = System.getCurrentTimeMillis(); //Edit - forgot to update the time!!
}
}

Android game in surfaceview lagg spikes

guys. I'm playing around with making my very first Android game, but stumbled into a problem. The framerate seems to have random lag spikes. If I comment the background(s) out the framerate gets much smoother. I've looked around SO and can't find anything to solve my problems. I have a feeling it has something to do with allocating a specific amount of time every time I draw, but I don't know how to properly implement such a feature. Any suggestions? Btw, tryed hardware ac, anti etc.
This is the class that starts the surfaceview :
package com.example.glassrunner;
Imports Here
public class Game extends Activity
{
MySurfaceView mySurfaceView;
public SoundPool spool;
private int soundID;
int length=0;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
mySurfaceView = new MySurfaceView(this);
setContentView(mySurfaceView);
}
#Override
protected void onResume()
{
// TODO Auto-generated method stub
super.onResume();
mySurfaceView.onResumeMySurfaceView();
}
#Override
protected void onPause()
{
// TODO Auto-generated method stub
super.onPause();
mySurfaceView.onPauseMySurfaceView();
}
#Override
protected void onDestroy()
{
super.onDestroy();
mySurfaceView = null;
}
}
This is the surfaceview class :
package com.example.glassrunner;
Imports here
public class MySurfaceView extends SurfaceView implements Runnable
{
public static boolean gameOver = false;
SurfaceHolder surfaceHolder;
Thread thread = null;
public Integer score=0;
public SoundPool spool;
private int soundID;
int length=0;
public static MediaPlayer mp;
volatile boolean running = false;
int Yposition = 450;
int Xposition = 50;
Paint textPaint;
long mLastTime;
Bitmap background;
Bitmap background2;
Bitmap lines;
Bitmap runSprite;
Bitmap box;
Paint bitmapPaint ;
Paint textPaint2;
Bitmap scaledBackground ;
Bitmap scaledBackground2 ;
Bitmap scaledLines ;
Bitmap scaledBox;
Canvas canvas;
Paint paint;
int SpX=0;
int SpY=0;
Bitmap[][] sprite;
/** Variables for the counter */
int frameSamplesCollected = 0;
int frameSampleTime = 0;
int fps = 0;
int speed = 5;
Toast GameOverToast;
Context context;
MediaPlayer mMediaPlayer;
public MySurfaceView(Context context)
{
super(context);
this.context = context;
// TODO Auto-generated constructor stub
surfaceHolder = getHolder();
surfaceHolder.setFormat(PixelFormat.RGB_565);
CharSequence text = "Game Over!";
int duration = Toast.LENGTH_SHORT;
GameOverToast = Toast.makeText(context, text, duration);
spool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
soundID = spool.load(context, R.raw.jump, 1);
mp = MediaPlayer.create(context, R.raw.saturdaymorningfunk);
initialization();
}
public void initialization()
{
mp.setLooping(true);
mp.start();
Options options = new Options();
options.inSampleSize = 1/4;
options.inPreferredConfig = Bitmap.Config.RGB_565;
background=BitmapFactory.decodeResource(getResources(),R.drawable.background,options);
lines=BitmapFactory.decodeResource(getResources(),R.drawable.lines);// getting the png from drawable folder
background2=BitmapFactory.decodeResource(getResources(),R.drawable.background2,options);
runSprite=BitmapFactory.decodeResource(getResources(),R.drawable.runsprite);
box=BitmapFactory.decodeResource(getResources(),R.drawable.box);
bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // tool for painting on the canvas
bitmapPaint.setAntiAlias(true);
bitmapPaint.setFilterBitmap(true);
textPaint = new Paint();
textPaint.setColor(Color.RED);
textPaint.setTextSize(32);
textPaint2 = new Paint();
textPaint2.setColor(Color.BLUE);
textPaint2.setTextSize(50);
scaledBackground = Bitmap.createScaledBitmap(background, 2560, 500, true);
scaledBackground2 = Bitmap.createScaledBitmap(background2, 2560, 400, true);
scaledLines = Bitmap.createScaledBitmap(lines, 2560, 30, true);
runSprite = Bitmap.createScaledBitmap(runSprite, 1400, 1000, true);
scaledBox = Bitmap.createScaledBitmap(box, 100, 100, true);
sprite = new Bitmap[4][7];
for(int row=0;row<=3;row++)
{
for(int col=0;col<=6;col++)
{
sprite[row][col] = Bitmap.createBitmap(runSprite, SpX, SpY, 200, 250);
SpX+=200;
}
SpX=0;
SpY+=250;
}
}
public void onResumeMySurfaceView()
{
mp.seekTo(length);
mp.start();
running = true;
thread = new Thread(this);
thread.start();
}
public void onPauseMySurfaceView()
{
mp.pause();
length=mp.getCurrentPosition();
boolean retry = true;
running = false;
while(retry){
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void onDestroyMySurfaceView()
{
mp.stop();
running = false;
thread = null;
thread.stop();
}
private void fps()
{
long now = System.currentTimeMillis();
if (mLastTime != 0)
{
//Time difference between now and last time we were here
int time = (int) (now - mLastTime);
frameSampleTime += time;
frameSamplesCollected++;
//After 10 frames
if (frameSamplesCollected == 10)
{
//Update the fps variable
fps = (int) (10000 / frameSampleTime);
//Reset the sampletime + frames collected
frameSampleTime = 0;
frameSamplesCollected = 0;
}
}
mLastTime = now;
}
public boolean pressDown = false;
public long pressTime;
public boolean onTouchEvent(MotionEvent event)
{
if (event != null)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{ if(Yposition == orgPos)
{
spool.play(soundID, 15, 15, 1, 0, 1f);
pressDown = true;
pressTime = System.currentTimeMillis();
}
}else if (event.getAction() == MotionEvent.ACTION_UP)
{
pressDown = false;
}
}
return true;
}
int x=0;
int y=100;
int x2=0;
int y2=20;
int row=0;
int col=0;
int limit = 100;
int orgPos = 450;
int Xbox = 1280;
int Ybox = 580;
Random r = new Random();
int RBox;
public static String Fscore;
boolean onTop = false;
long now;
long start;
long stop;
long time ;
int spritePosition = 0 ;
int spriteSize;
#Override
public void run()
{
while(running)
{
canvas = null;
if(surfaceHolder.getSurface().isValid())
{
canvas = surfaceHolder.lockCanvas();
fps(); // fps
// Update screen parameters
update();
draw();
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
public void update()
{
if(score<500)
{
speed = 7;
}
else if(score%500 == 0)
{
speed = 7 + (score / 500);
}
if(col==6)
{
row++;
col=0;
}
if(row==4)
{
row=0;
}
score++;
Fscore = score.toString();
if(x>-1280)
{
x-=speed;
}else if(x<=-1280)
{
x=0;
}
if(x2>-1280)
{
x2-=5;
}else if(x2<=-1280)
{
x2=-0;
}
RBox = r.nextInt(999)+1280;
if(Xbox > -100)
{
Xbox-=speed;
}else if(Xbox<=-100)
{
Xbox=RBox;
}
if( (Xposition + 200 == Xbox +40 )&&(Yposition + 250 > Ybox+20)||( Xposition+200<=Xbox+70)&&( Xposition+200>=Xbox+20)&&(Yposition + 250 > Ybox+30) ) // collision
{
GameOverToast.show();
running = false;
spool.release();
mp.release();
Looper.prepare();
Intent database = new Intent(context, MainHighscore.class);
database.putExtra("score", Fscore);
context.startActivity(database);
onDestroyMySurfaceView();
}
now = System.currentTimeMillis();
if(( now - pressTime) <= 600)
{
if(Yposition > limit)
{
Yposition -= 10;
}
}
onTop = false;
if((now - pressTime) >= 600 && (now - pressTime) <= 1200)
{
if(!(Yposition == orgPos))
{
if(Yposition+250 >= Ybox && Xposition+200>=Xbox+70 && Xposition <= Xbox+40)
{
onTop=true;
Yposition = 340;
}else
{
Yposition += 10;
}
}
}
if((now - pressTime) >= 1200)
{
if(Yposition < 450) Yposition +=10;
else Yposition = 450;
}
}
public void draw()
{
canvas.drawColor(Color.WHITE);
//canvas.drawBitmap(scaledBackground, x2,y2, bitmapPaint);
canvas.drawBitmap(scaledBackground2, x,y, bitmapPaint);
canvas.drawBitmap(scaledLines, x,650, bitmapPaint);
canvas.drawText(Fscore, 1050, 50, textPaint2);
canvas.drawText(fps + " fps", getWidth() / 2, getHeight() / 2, textPaint);
canvas.drawBitmap(sprite[row][col],Xposition,Yposition,bitmapPaint );
canvas.drawBitmap(scaledBox,Xbox,Ybox,bitmapPaint);
col++;
}
}
I think your problem might be actually the moving part. Your just drawing too much stuff, and the surfaceView is not meant for that.

Consequences of recycling Bitmaps?

Hi in my application I have an explosion animation that comes up extremely often. when creating an explosion I load 3 images from resources then once the explosion animation is over I recycle those 3 images. I am constantly doing this and have noticed framerate drops. Is their a better way of doing this like static bitmaps or something?
I once made an application, a simple canvas on which bombs exploded.
I used a tiled bitmap composed of each step of the explosion and drew only a part of it, which changes often in order to create the animation effect.
It updates steps automatically following currentTimestamp
So this is the explosion class:
public class ExplosionAnimated {
private static final String TAG = ExplosionAnimated.class.getSimpleName();
private Bitmap mBitmap;
private Rect mSourceRect;
private int mFrameCountX;
private int mFrameCountY;
private int mCurrentFrame;
private long mFrameTicker;
private int mFramePeriod;
private int mSpriteWidth;
private int mSpriteHeight;
private int mX;
private int mY;
private boolean mFinished = false;
public ExplosionAnimated(Bitmap pBitmap, int pX, int pY,
int pFrameCountX, int pFrameCountY, int pFps) {
this.mBitmap = pBitmap;
this.mX = pX;
this.mY = pY;
this.mCurrentFrame = 0;
this.mFrameCountX = pFrameCountX;
this.mFrameCountY = pFrameCountY;
this.mSpriteWidth = pBitmap.getWidth() / pFrameCountX;
this.mSpriteHeight = pBitmap.getHeight() / pFrameCountY;
this.mSourceRect = new Rect(0, 0, this.mSpriteWidth, this.mSpriteHeight);
this.mFramePeriod = 1000 / pFps;
this.mFrameTicker = 0l;
}
public void update(long gameTime) {
if (gameTime > this.mFrameTicker + this.mFramePeriod) {
this.mFrameTicker = gameTime;
this.mCurrentFrame++;
if (this.mCurrentFrame >= this.mFramePeriod) {
this.mCurrentFrame = 0;
this.mFinished = true;
}
}
if (!this.mFinished) {
this.mSourceRect.left = this.mCurrentFrame * this.mSpriteWidth;
this.mSourceRect.right = this.mSourceRect.left + this.mSpriteWidth;
}
}
public void draw(Canvas canvas) {
Rect destRect = new Rect(this.mX, this.mY,
this.mX + this.mSpriteWidth,
this.mY + this.mSpriteHeight);
canvas.drawBitmap(this.mBitmap, this.mSourceRect, destRect, null);
}
public boolean isFinished() {
return this.mFinished;
}
}
These are methods from an object (Bomb.java for exemple) that starts explosion and draw it:
public void explode(Context pContext, Canvas pCanvas) {
this.mState = State.EXPLODING;
this.mExplosion = new ExplosionAnimated(this.mExplosionBitmap,
(int) this.mX, (int) this.mY, 7, 3, 7);
}
public void doDraw(Canvas pCanvas) {
if (this.mState == State.EXPLODING) {
if (this.mExplosion.isFinished()) {
this.mState = State.EXPLODED;
} else {
this.mExplosion.update(System.currentTimeMillis());
this.mExplosion.draw(pCanvas);
}
} else {
pCanvas.drawBitmap(this.mBombBitmap, this.mX, this.mY, null);
}
}
I used a Thread and a SurfaceView to continually draw the bomb (or the explosion), giving just the currentTimestamp to update the explosion.
I hope it helps, and if you need I can show and explain more code

How to make sprites in Android

I'm developing a game which includes sprites.
currently I'm loading a bitmap and using Rectangle to get the right frame from the bitmap.
the problem is loading bitmaps takes too much memory and I wont be able to load several sprites at the same time.
what are my alternatives for doing sprite in Android?
Try this one:
import org.cocos2d.layers.CCLayer;
public class GameLayer extends CCLayer{
CCSprite mSprite;
protected GameLayer() {
super();
CGSize winSize = CCDirector.sharedDirector().winSize();
mSprite = new CCSprite("image.png");
mSprite.setPosition(CGPoint.ccp(mSprite.getContentSize().width/2.0f, mSprite.getContentSize().height/2.0f));
addChild(mSprite);
}
/* It is Sprite class */
import android.graphics.*;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
public class Sprite {
int x;
private int y;
private boolean visibility = true;
private Bitmap sprite;
private int verticalFrame,horizontalFrame;
private int frameWidth,frameHeight;
private int sequence[] = new int[1];
private int maxFrame = 0;
private int currentFrame;
private int color;
private Rect src;
private Rect dest;
private int frameCount;
private Paint p = new Paint();
private int spInitX,spInitY;
private int rotate = 0;
private float currentRotateAngle = 0;
//private Graphics g;
public float velocityY;
public float velocityX;
public int height;
public Sprite(Bitmap sprite){
this.sprite = sprite;
this.frameWidth = sprite.getWidth();
this.frameHeight = sprite.getHeight();
setFrameCount();
}
public Sprite(Bitmap sprite,int frameWidth,int frameHeight){
this.sprite = sprite;
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
setFrameCount();
}
public void rotate(float angle){
currentRotateAngle = angle;
}
public void setImage(Bitmap bm,int frameWidth,int frameHeight){
this.sprite = bm;
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
}
public Bitmap getBitmap(){
return sprite;
}
public void paint(Canvas canvas){
dest.offsetTo(getX(), getY());
// g.drawImage(sprite, x, y,src.left,src.top,frameWidth,frameHeight);
canvas.drawBitmap(sprite, src, dest, null);
}
public int getMaxFrame(){
return maxFrame;
}
public int getFrameSequenceLength(){
return sequence.length;
}
public void setFrameSequence(int seq[]){
sequence = seq;
}
public void previousFrame(){
if(sequence.length > 1){
if(frameCount > 0){
setFrame(sequence[frameCount]);
frameCount--;
}else{
frameCount = sequence.length - 1;
setFrame(sequence[frameCount]);
}
}else{
setFrame(frameCount);
if(frameCount > 0){
frameCount++;
}else{
frameCount = maxFrame - 1;
}
}
}
public void setPixel(int x,int y){
spInitX = x;
spInitY = y;
}
public void nextFrame(){
if(sequence.length > 1){
if(frameCount < sequence.length){
setFrame(sequence[frameCount]);
frameCount++;
}else{
frameCount = 0;
setFrame(sequence[frameCount]);
}
}else{
setFrame(frameCount);
if(frameCount < maxFrame){
frameCount++;
}else{
frameCount = 0;
}
}
}
public int getFrame(){
return currentFrame;
}
public void setPosition(int x,int y){
this.x = x;
this.y = y;
}
public void setFrameCount(){
verticalFrame = sprite.getHeight() / frameHeight;
horizontalFrame = sprite.getWidth() / frameWidth;
src = new Rect(0,0,frameWidth,frameHeight);
dest = new Rect(0,0,frameWidth,frameHeight);
maxFrame = verticalFrame * horizontalFrame;
}
public void setFrame(int frame){
if(frame < maxFrame){
currentFrame = frame;
}
int hf = currentFrame % horizontalFrame;
int vf = currentFrame / horizontalFrame;
src.left = hf * frameWidth;
src.right = src.left + frameWidth;
src.top = vf * frameHeight;
src.bottom = src.top + frameHeight;
}
public boolean collidesWith(Sprite sp,boolean cl){
int maxHGap = (getWidth() + sp.getWidth())/2;
int maxVGap = (getHeight() + sp.getHeight())/2;
int x = getX() + getWidth()/2;
int y = getY() + getHeight()/2;
int x1 = sp.getX() + sp.getWidth()/2;
int y1 = sp.getY() + sp.getHeight()/2;
if(Math.abs(x - x1) < maxHGap && Math.abs(y - y1) < maxVGap){
return true;
}
return false;
}
public void setVisible(boolean v){
visibility = v;
}
public final boolean isVisible(){
return visibility;
}
public int getX(){
return x;
}
public int getY(){
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void move(int moveX,int moveY){
setX(getX()+moveX);
setY(getY()+moveY);
//this.y+=y;
//this.x+=x;
}
public final int getWidth(){
return frameWidth;
}
public final int getHeight(){
return frameHeight;
}
public void setEventY(int i) {
// TODO Auto-generated method stub
}
public int getEventY() {
// TODO Auto-generated method stub
return 0;
}
}
/*Create Main Thread Class Also */
import java.text.DecimalFormat;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
public class MainThread extends Thread {
boolean isPaused;
private static final String TAG = MainThread.class.getSimpleName();
// desired fps
private final static int MAX_FPS = 50;
// maximum number of frames to be skipped
private final static int MAX_FRAME_SKIPS = 5;
// the frame period
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
/* Stuff for stats */
private DecimalFormat df = new DecimalFormat("0.##"); // 2 dp
// we'll be reading the stats every second
private final static int STAT_INTERVAL = 1000; // ms
// the average will be calculated by storing
// the last n FPSs
private final static int FPS_HISTORY_NR = 10;
// last time the status was stored
private long lastStatusStore = 0;
// the status time counter
private long statusIntervalTimer = 0l;
// number of frames skipped since the game started
private long totalFramesSkipped = 0l;
// number of frames skipped in a store cycle (1 sec)
private long framesSkippedPerStatCycle = 0l;
// number of rendered frames in an interval
private int frameCountPerStatCycle = 0;
private long totalFrameCount = 0l;
// the last FPS values
private double fpsStore[];
// the number of times the stat has been read
private long statsCount = 0;
// the average FPS since the game started
private double averageFps = 0.0;
// Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
// The actual view that handles inputs
// and draws to the surface
private GameView gv;
// flag to hold game state
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder, GameView gv) {
super();
this.surfaceHolder = surfaceHolder;
this.gv = gv;
}
public MainThread(Setting setting) {
// TODO Auto-generated constructor stub
}
public void setPause(int i) {
synchronized (gv.getHolder()) {
if (i == 0) {
isPaused = false;
}
if (i == 1) {
isPaused = true;
}
}
}
#Override
public void run() {
Canvas canvas;
Log.d(TAG, "Starting game loop");
initTimingElements();
long beginTime; // the time when the cycle begun
long timeDiff; // the time it took for the cycle to execute
int sleepTime; // ms to sleep (<0 if we're behind)
int framesSkipped; // number of frames being skipped
sleepTime = 0;
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
beginTime = System.currentTimeMillis();
framesSkipped = 0; // resetting the frames skipped
// update game state
this.gv.update();
// render state to the screen
// draws the canvas on the panel
this.gv.render(canvas);
// calculate how long did the cycle take
timeDiff = System.currentTimeMillis() - beginTime;
// calculate sleep time
sleepTime = (int) (FRAME_PERIOD - timeDiff);
if (sleepTime > 0) {
// if sleepTime > 0 we're OK
try {
// send the thread to sleep for a short period
// very useful for battery saving
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
}
}
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
// we need to catch up
this.gv.update(); // update without rendering
sleepTime += FRAME_PERIOD; // add frame period to check
// if in next frame
framesSkipped++;
}
if (framesSkipped > 0) {
Log.d(TAG, "Skipped:" + framesSkipped);
}
// for statistics
framesSkippedPerStatCycle += framesSkipped;
// calling the routine to store the gathered statistics
storeStats();
}
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
private void storeStats() {
frameCountPerStatCycle++;
totalFrameCount++;
// assuming that the sleep works each call to storeStats
// happens at 1000/FPS so we just add it up
// statusIntervalTimer += FRAME_PERIOD;
// check the actual time
statusIntervalTimer += (System.currentTimeMillis() - statusIntervalTimer);
if (statusIntervalTimer >= lastStatusStore + STAT_INTERVAL) {
// calculate the actual frames pers status check interval
double actualFps = (double) (frameCountPerStatCycle / (STAT_INTERVAL / 1000));
// stores the latest fps in the array
fpsStore[(int) statsCount % FPS_HISTORY_NR] = actualFps;
// increase the number of times statistics was calculated
statsCount++;
double totalFps = 0.0;
// sum up the stored fps values
for (int i = 0; i < FPS_HISTORY_NR; i++) {
totalFps += fpsStore[i];
}
// obtain the average
if (statsCount < FPS_HISTORY_NR) {
// in case of the first 10 triggers
averageFps = totalFps / statsCount;
} else {
averageFps = totalFps / FPS_HISTORY_NR;
}
// saving the number of total frames skipped
totalFramesSkipped += framesSkippedPerStatCycle;
// resetting the counters after a status record (1 sec)
framesSkippedPerStatCycle = 0;
statusIntervalTimer = 0;
frameCountPerStatCycle = 0;
statusIntervalTimer = System.currentTimeMillis();
lastStatusStore = statusIntervalTimer;
// Log.d(TAG, "Average FPS:" + df.format(averageFps));
gv.setAvgFps("FPS: " + df.format(averageFps));
}
}
private void initTimingElements() {
// initialise timing elements
fpsStore = new double[FPS_HISTORY_NR];
for (int i = 0; i < FPS_HISTORY_NR; i++) {
fpsStore[i] = 0.0;
}
Log.d(TAG + ".initTimingElements()",
"Timing elements for stats initialised");
}
}

Collision Detection/Remove object from ArrayList

I am currently trying to test collision between a falling object and a box. I understand basic collision detection, but my problem here is that I have to test it for an indefinite number of falling objects. When these objects(blossoms) are created, they are stored in an ArrayList. The ArrayList handles the drawing of the object on the canvas (using a for each to update the position). My problem comes when a blossom is "caught" in the box. How do I make it disappear from the screen/removed from the array list without a null reference happening? I can show you the logic I have so far...please let me know what you think. I am REALLY stuck, but feel like I'm so close to figuring this out.
Blossom Class
public class Blossom{
private Bitmap blossom;
private int blossom_x = 0;
private int blossom_y = 0;
private int blossomWidth = 0;
private int blossomHeight = 0;
private boolean hit = false;
private Random generator = new Random();
RectF blossomRect;
private static final String TAG = "Debug";
public Blossom(Bitmap bitmap)
{
blossom = bitmap;
blossomWidth = blossom.getWidth();
blossomHeight = blossom.getHeight();
blossom_x = generator.nextInt(320-blossom.getWidth());
blossomRect = new RectF(blossom_x,blossom_y, blossom_x + blossomWidth, blossom_y + blossomHeight);
}
public Bitmap getBitmap()
{
return blossom;
}
public Boolean hit(int boxLeft, int boxTop, int boxRight,int boxBottom)
{
if(blossom_x > boxLeft & blossom_y > boxTop
& blossom_x + blossom.getWidth() < boxRight
& blossom_y + blossom.getHeight() < boxBottom)
{
Log.v(TAG, "Collision Detected");
return true;
}
else
{
return false;
}
}
public float getBlossomX()
{
return blossom_x;
}
public float getBlossomY()
{
return blossom_y;
}
public float getBlossomWidth()
{
return blossomWidth;
}
public float getBlossomHeight()
{
return blossomHeight;
}
public void Draw(Canvas canvas)
{
//draws the flower falling
if (hit == false)
{
canvas.drawBitmap(blossom, blossom_x,
blossom_y = blossom_y+5 , null);
}
}
public void UpdatePosition()
{
blossomRect.set(blossom_x, blossom_y, blossom_x + 25, blossom_y + 25);
}
}
BoardView
public class BoardView extends SurfaceView implements SurfaceHolder.Callback{
Context mContext;
Bitmap box =
(BitmapFactory.decodeResource
(getResources(), R.drawable.box));
private BoardThread thread;
private int box_x = 140;
private int box_y = 378;
private int boxWidth = box.getWidth();
private int boxHeight = box.getHeight();
private ArrayList<Blossom> blossomArrayList = new ArrayList<Blossom>();
private int blossomNum = 0;
private String score;
private int currentScore = 0;
Thread timer;
boolean mode = false;
boolean game = false;
private static final String TAG = "Debug";
final Paint scorePaint = new Paint();
public BoardView(Context context){
super(context);
scorePaint.setColor(Color.BLACK);
scorePaint.setTextSize(12);
scorePaint.setTypeface(Typeface.MONOSPACE);
//surfaceHolder provides canvas that we draw on
getHolder().addCallback(this);
// controls drawings
thread = new BoardThread(getHolder(),this, blossomArrayList, box_x, box_y,
boxWidth, boxHeight);
timer = new Thread(){
public void run(){
//makes sure the player still has 3 lives left
while(game == false){
uiCallback.sendEmptyMessage(0);
try {
Thread.sleep(2000); // wait two seconds before drawing the next flower
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //sleep for 2 seconds
}
}
};
timer.start();
//intercepts touch events
setFocusable(true);
}
#Override
public void onDraw(Canvas canvas){
canvas.drawColor(Color.WHITE);
score = "SCORE: " + currentScore;
//note: pay attention to order you draw things
//don't change order or else blossoms will fall
//on top of box, not "into" it.
//display the scoreboard
canvas.drawText(score,240,420,scorePaint);
//draw box and set start location
for(Blossom blossom: blossomArrayList) // getting errors here
{
blossom.Draw(canvas);
blossom.hit(box_x,box_y, box_x + boxWidth, box_y + boxHeight);
}
canvas.drawBitmap(box, box_x, box_y, null);
}
#Override
public boolean onTouchEvent(MotionEvent event){
//handles movement of box
if(event.getAction() == MotionEvent.ACTION_DOWN){
if(event.getX() > box_x & event.getY() > box_y &
event.getX() < box_x + boxWidth & event.getY() < box_y + boxHeight)
{
mode = true;
}
}
if(event.getAction() == MotionEvent.ACTION_MOVE) {
if(event.getX() > box_x & event.getY() > box_y &
event.getX() < box_x + boxWidth & event.getY() < box_y + boxHeight)
{
mode = true;
}
if(mode == true){
box_x = (int)event.getX();
}
}
if(event.getAction() == MotionEvent.ACTION_UP){
mode = false;
}
invalidate();
return true;
}
#Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height ){
Log.v(TAG, "Surface Changed");
//somehow these don't seem to be working
}
#Override
public void surfaceCreated(SurfaceHolder holder){
thread.startRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder){
Log.v(TAG, "Surface Destroyed");
//somehow these don't seem to be working
thread.startRunning(false);
thread.stop();
timer.interrupt();
timer.stop();
}
private Handler uiCallback = new Handler(){
public void handleMessage(Message msg){
//add a new blossom to the blossom ArrayList!!
blossomArrayList.add(new Blossom(
(BitmapFactory.decodeResource
(getResources(), R.drawable.blossom))));
blossomNum++;
//remove neccesary blossoms from list
Log.v(TAG, "Number of Blossoms =" + blossomNum);
}
};
}
BoardThread
public class BoardThread extends Thread {
private SurfaceHolder surfaceHolder;
private BoardView boardView;
private ArrayList<Blossom> blossomArrayList;
private int boxX;
private int boxY;
private int boxWidth;
private int boxHeight;
private boolean mrun =false;
public BoardThread(SurfaceHolder holder, BoardView boardView2,
ArrayList<Blossom> blossomArrayList1,
int box_x, int box_y, int boxW, int boxH) {
surfaceHolder = holder;
boardView=boardView2;
blossomArrayList = blossomArrayList1;
boxX = box_x;
boxY = box_y;
boxW = boxWidth;
boxH = boxHeight;
}
public void startRunning(boolean run) {
mrun=run;
}
#Override
public void run() {
super.run();
Canvas canvas;
while (mrun) {
canvas=null;
try {
canvas = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
//test for collision
Collision(blossomArrayList, boxX, boxY, boxWidth, boxHeight);
// draw flowers
boardView.onDraw(canvas); // and getting errors here - concurrent
}
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
public void Collision(ArrayList<Blossom> blossomArrayList, int box_x, int box_y,
int boxWidth, int boxHeight)
{
for(Blossom blossom: blossomArrayList)
{
blossom.UpdatePosition();
if(blossom.hit(box_x,box_y, box_x + boxWidth, box_y + boxHeight) == true)
{
///if flower is caught, add to score
//currentScore += 100;
}
}
}
}
One way you could do this is to have a field on the Blossom that indicates whether it is active or not, then only draw it if it is active. If it is inactive, another Blossom could replace it in the list.
Setting a visibility flag is one way to go, however I'd recommend against it since you are adding an indeterminate amount of Bitmaps to an ArrayList...you'll find yourself running out of memory pretty quickly. Change your collision detection iterator from a foreach to a handwritten loop, this will avoid concurrency issues you may run to in the code you have listed above.
for (int i = 0; i < blossomArrayList.size(); i++)
{
if(blossom.hit(box_x,box_y, box_x + boxWidth, box_y + boxHeight)) {
blossomArrayList.remove(i);
}
}
Additionally, I'd recommend changing all your ArrayList foreach iterators to hand written for loops, as iterating ArrayLists (but not any other object) is relatively slow on Android and can lead to unexpected concurrency issues.
Thirdly, it seems as though you only need to run your Collision() method once after your UpdatePositions loop has completed since you're already checking every Blossom in your Collision() method.

Categories

Resources