I'm using a SurfaceView and I have to update it as fast as I can. Since the onDraw method isn't called automatically I have to call explicitly. So my first attempt was on threads, but I got some lag. There is a better way to do this? If there is a better solution, can you, please, explain me? I'm new on Android's World!
What I have so far
public void run() {
while(running){
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
Here's my code that I use as a rendering thread. The fps is updated frame dependent. I usually run my games at 30 fps.
(There's some code you won't need, but I'm sire you'll be able to find the pieces you are looking for.)
public class RenderView extends SurfaceView implements Runnable {
Game game;
Bitmap gameScreen;
Thread gameloop;
SurfaceHolder holder;
boolean running;
int sleepTime;
int numberOfFramesSkipped;
int maxFrameSkips;
long beginTime;
long endTime;
long lastTime;
int differenceTime;
int framePeriod;
Canvas frameBuffer;
int frameCount;
public RenderView(int fps, int maxFrameSkips) {
super(game);
this.gameScreen = gameScreen;
this.holder = getHolder();
this.framePeriod = 1000/fps;
this.maxFrameSkips = maxFrameSkips;
lastTime = System.currentTimeMillis();
beginTime = System.currentTimeMillis();
}
#Override
public void run() {
while(running == true) {
if(holder.getSurface().isValid()) {
beginTime = System.currentTimeMillis();
// call your update method here
// render here
frameBuffer = holder.lockCanvas();
this.onDraw();
holder.unlockCanvasAndPost(frameBuffer);
// Frame Per Second Count
frameCount++;
if(lastTime + 1000 < System.currentTimeMillis()) {
lastTime = System.currentTimeMillis();
frameCount = 0;
}
endTime = System.currentTimeMillis();;
differenceTime = (int) (endTime - beginTime);
sleepTime = (int) (framePeriod - differenceTime);
if(sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
else {
while(sleepTime < 0 && numberOfFramesSkipped < this.maxFrameSkips) {
// Call your update method here
sleepTime += framePeriod;
numberOfFramesSkipped++;
}
}
}
}
}
private void renderFrameBuffer() {
// Update the current virtual screen image
game.getCurrentScreen().render();
// Render the current virtual screen to the real phone screen
frameBuffer = holder.lockCanvas();
if(frameBuffer != null) { // Fix for mysterious bug ( FATAL EXCEPTION: Thread)
// The viewing area of our virtual screen on our real screen
// Composition
// First layer (bottom)
frameBuffer.drawBitmap(this.gameScreen, null, game.getWSScreen().getGameScreendst(), null);
// Second layer (top)
frameBuffer.drawBitmap(this.gameScreenExtended, null, game.getWSScreen().getGameScreenextendeddst(), null);
holder.unlockCanvasAndPost(frameBuffer);
}
else {
gameEngineLog.e(classTAG, "Surface has not been created or otherwise cannot be edited");
}
}
public void resume() {
this.running = true;
gameloop = new Thread(this);
gameloop.start();
}
public void pause() {
this.running = false;
running = false;
while(true) {
try {
gameloop.join();
break;
} catch (InterruptedException e) {
// retry
}
}
}
}
public void onDraw() {
}
I suggest you take a look at this link or have a look at
http://code.google.com/p/beginning-android-games/
or http://www.edu4java.com/en/androidgame/androidgame3.html
Related
I recently uploaded my app to the google play store Helicopter Combat
But every now and then it currupts. I got the error report here:
java.lang.ArrayIndexOutOfBoundsException: length=3; index=3
at de.krissini.server.Animation.getImage(Animation.java:36)
That's the refered code:
public class Animation {
private Bitmap[] frames;
private int currentFrame;
private long startTime, delay;
private boolean playedOnce;
public void setFrames(Bitmap[] frames){
this.frames = frames;
currentFrame = 0;
startTime = System.nanoTime();
}
public void setDelay(long d){
delay = d;
}
public void setFrame(int i){
currentFrame = i;
}
public void update(){
long elapsed = (System.nanoTime()-startTime)/1000000;
if(elapsed>delay){
currentFrame++;
startTime = System.nanoTime();
}
if(currentFrame == frames.length){
currentFrame = 0;
playedOnce = true;
}
}
public Bitmap getImage(){
return frames[currentFrame]; // line 36
}
public int getFrame(){
return currentFrame;
}
public boolean playedOnce(){
return playedOnce;
}
}
I understand the error, but how can I prevent it?
I appreciate every answer
The error is caused by the misconception that the length of an array is the same as the max index. Where length starts at 1 and index starts at 0, they always differ 1.
Change this:
if(currentFrame == frames.length ){
// your reset code
}
To this:
if(currentFrame == frames.length - 1){
// your reset code
}
I'm using ffmpeg to video capture for 30 seconds.
#Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (yuvIplimage != null && recording && rec)
{
new SaveFrame().execute(data);
}
}
}
save frame class is below
private class SaveFrame extends AsyncTask<byte[], Void, File> {
long t;
protected File doInBackground(byte[]... arg) {
t = 1000 * (System.currentTimeMillis() - firstTime - pausedTime);
toSaveFrames++;
File pathCache = new File(Environment.getExternalStorageDirectory()+"/DCIM", (System.currentTimeMillis() / 1000L)+ "_" + toSaveFrames + ".tmp");
BufferedOutputStream bos;
try {
bos = new BufferedOutputStream(new FileOutputStream(pathCache));
bos.write(arg[0]);
bos.flush();
bos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
pathCache = null;
toSaveFrames--;
} catch (IOException e) {
e.printStackTrace();
pathCache = null;
toSaveFrames--;
}
return pathCache;
}
#Override
protected void onPostExecute(File filename)
{
if(filename!=null)
{
savedFrames++;
tempList.add(new FileFrame(t,filename));
}
}
}
finally i add all frames with crop and rotation
private class AddFrame extends AsyncTask<Void, Integer, Void> {
private int serial = 0;
#Override
protected Void doInBackground(Void... params) {
for(int i=0; i<tempList.size(); i++)
{
byte[] bytes = new byte[(int) tempList.get(i).file.length()];
try {
BufferedInputStream buf = new BufferedInputStream(new FileInputStream(tempList.get(i).file));
buf.read(bytes, 0, bytes.length);
buf.close();
IplImage image = IplImage.create(imageWidth, imageHeight, IPL_DEPTH_8U, 2);
// final int startY = 640*(480-480)/2;
// final int lenY = 640*480;
// yuvIplimage.getByteBuffer().put(bytes, startY, lenY);
// final int startVU = 640*480+ 640*(480-480)/4;
// final int lenVU = 640* 480/2;
// yuvIplimage.getByteBuffer().put(bytes, startVU, lenVU);
if (tempList.get(i).time > recorder.getTimestamp()) {
recorder.setTimestamp(tempList.get(i).time);
}
image = cropImage(image);
image = rotate(image, 270);
// image = rotateImage(image);
recorder.record(image);
Log.i(LOG_TAG, "record " + i);
image = null;
serial++;
publishProgress(serial);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (com.googlecode.javacv.FrameRecorder.Exception e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Integer... serial) {
int value = serial[0];
creatingProgress.setProgress(value);
}
#Override
protected void onPostExecute(Void v)
{
creatingProgress.dismiss();
if (recorder != null && recording) {
recording = false;
Log.v(LOG_TAG,"Finishing recording, calling stop and release on recorder");
try {
recorder.stop();
recorder.release();
finish();
startActivity(new Intent(RecordActivity.this,AnswerViewActivity.class));
} catch (FFmpegFrameRecorder.Exception e) {
e.printStackTrace();
}
recorder = null;
}
}
}
my crop and rotate method are below
private IplImage cropImage(IplImage src)
{
cvSetImageROI(src, r);
IplImage cropped = IplImage.create(imageHeight, imageHeight, IPL_DEPTH_8U, 2);
cvCopy(src, cropped);
return cropped;
}
public static IplImage rotate(IplImage image, double angle) {
IplImage copy = opencv_core.cvCloneImage(image);
IplImage rotatedImage = opencv_core.cvCreateImage(opencv_core.cvGetSize(copy), copy.depth(), copy.nChannels());
CvMat mapMatrix = opencv_core.cvCreateMat( 2, 3, opencv_core.CV_32FC1 );
//Define Mid Point
CvPoint2D32f centerPoint = new CvPoint2D32f();
centerPoint.x(copy.width()/2);
centerPoint.y(copy.height()/2);
//Get Rotational Matrix
opencv_imgproc.cv2DRotationMatrix(centerPoint, angle, 1.0, mapMatrix);
//Rotate the Image
opencv_imgproc.cvWarpAffine(copy, rotatedImage, mapMatrix, opencv_imgproc.CV_INTER_CUBIC + opencv_imgproc.CV_WARP_FILL_OUTLIERS, opencv_core.cvScalarAll(170));
opencv_core.cvReleaseImage(copy);
opencv_core.cvReleaseMat(mapMatrix);
return rotatedImage;
}
my final video crop and rotate but green frames and colored frames mixed with this.
How to fix this problem. I'm not aware of iplimage. In some blogs they mention its YUV format. first u need to convert Y and then convert UV.
How to solve this problem?
I have modified the onPreviewFrame method of this Open Source Android Touch-To-Record library to take transpose and resize a captured frame.
I defined "yuvIplImage" as following in my setCameraParams() method.
IplImage yuvIplImage = IplImage.create(mPreviewSize.height, mPreviewSize.width, opencv_core.IPL_DEPTH_8U, 2);
Also initialize your videoRecorder object as following, giving width as height and vice versa.
//call initVideoRecorder() method like this to initialize videoRecorder object of FFmpegFrameRecorder class.
initVideoRecorder(strVideoPath, mPreview.getPreviewSize().height, mPreview.getPreviewSize().width, recorderParameters);
//method implementation
public void initVideoRecorder(String videoPath, int width, int height, RecorderParameters recorderParameters)
{
Log.e(TAG, "initVideoRecorder");
videoRecorder = new FFmpegFrameRecorder(videoPath, width, height, 1);
videoRecorder.setFormat(recorderParameters.getVideoOutputFormat());
videoRecorder.setSampleRate(recorderParameters.getAudioSamplingRate());
videoRecorder.setFrameRate(recorderParameters.getVideoFrameRate());
videoRecorder.setVideoCodec(recorderParameters.getVideoCodec());
videoRecorder.setVideoQuality(recorderParameters.getVideoQuality());
videoRecorder.setAudioQuality(recorderParameters.getVideoQuality());
videoRecorder.setAudioCodec(recorderParameters.getAudioCodec());
videoRecorder.setVideoBitrate(1000000);
videoRecorder.setAudioBitrate(64000);
}
This is my onPreviewFrame() method:
#Override
public void onPreviewFrame(byte[] data, Camera camera)
{
long frameTimeStamp = 0L;
if(FragmentCamera.mAudioTimestamp == 0L && FragmentCamera.firstTime > 0L)
{
frameTimeStamp = 1000L * (System.currentTimeMillis() - FragmentCamera.firstTime);
}
else if(FragmentCamera.mLastAudioTimestamp == FragmentCamera.mAudioTimestamp)
{
frameTimeStamp = FragmentCamera.mAudioTimestamp + FragmentCamera.frameTime;
}
else
{
long l2 = (System.nanoTime() - FragmentCamera.mAudioTimeRecorded) / 1000L;
frameTimeStamp = l2 + FragmentCamera.mAudioTimestamp;
FragmentCamera.mLastAudioTimestamp = FragmentCamera.mAudioTimestamp;
}
synchronized(FragmentCamera.mVideoRecordLock)
{
if(FragmentCamera.recording && FragmentCamera.rec && lastSavedframe != null && lastSavedframe.getFrameBytesData() != null && yuvIplImage != null)
{
FragmentCamera.mVideoTimestamp += FragmentCamera.frameTime;
if(lastSavedframe.getTimeStamp() > FragmentCamera.mVideoTimestamp)
{
FragmentCamera.mVideoTimestamp = lastSavedframe.getTimeStamp();
}
try
{
yuvIplImage.getByteBuffer().put(lastSavedframe.getFrameBytesData());
IplImage bgrImage = IplImage.create(mPreviewSize.width, mPreviewSize.height, opencv_core.IPL_DEPTH_8U, 4);// In my case, mPreviewSize.width = 1280 and mPreviewSize.height = 720
IplImage transposed = IplImage.create(mPreviewSize.height, mPreviewSize.width, yuvIplImage.depth(), 4);
IplImage squared = IplImage.create(mPreviewSize.height, mPreviewSize.height, yuvIplImage.depth(), 4);
int[] _temp = new int[mPreviewSize.width * mPreviewSize.height];
Util.YUV_NV21_TO_BGR(_temp, data, mPreviewSize.width, mPreviewSize.height);
bgrImage.getIntBuffer().put(_temp);
opencv_core.cvTranspose(bgrImage, transposed);
opencv_core.cvFlip(transposed, transposed, 1);
opencv_core.cvSetImageROI(transposed, opencv_core.cvRect(0, 0, mPreviewSize.height, mPreviewSize.height));
opencv_core.cvCopy(transposed, squared, null);
opencv_core.cvResetImageROI(transposed);
videoRecorder.setTimestamp(lastSavedframe.getTimeStamp());
videoRecorder.record(squared);
}
catch(com.googlecode.javacv.FrameRecorder.Exception e)
{
e.printStackTrace();
}
}
lastSavedframe = new SavedFrames(data, frameTimeStamp);
}
}
This code uses a method "YUV_NV21_TO_BGR", which I found from this link
Basically this method is used to resolve, which I call as, "The Green Devil problem on Android", just like yours. I was having the same issue and wasted almost 3-4 days. Before adding "YUV_NV21_TO_BGR" method when I just took transpose of YuvIplImage, more importantly a combination of transpose, flip (with or without resizing), there was greenish output in resulting video. This "YUV_NV21_TO_BGR" method saved the day. Thanks to #David Han from above google groups thread.
Also you should know that all this processing (transpose, flip and resize), in onPreviewFrame, takes much time which causes a very serious hit on Frames Per Second (FPS) rate. When I used this code, inside onPreviewFrame method, the resulting FPS of the recorded video was down to 3 frames/sec from 30fps.
I would advise not to use this approach. Rather you can go for post-recording processing (transpose, flip and resize) of your video file using JavaCV in an AsyncTask. Hope this helps.
#Override
public void onPreviewFrame(byte[] data, Camera camera) {
//IplImage newImage = cvCreateImage(cvGetSize(yuvIplimage), IPL_DEPTH_8U, 1);
if (recording) {
videoTimestamp = 1000 * (System.currentTimeMillis() - startTime);
yuvimage = IplImage.create(imageWidth, imageHeight * 3 / 2, IPL_DEPTH_8U,1);
yuvimage.getByteBuffer().put(data);
rgbimage = IplImage.create(imageWidth, imageHeight, IPL_DEPTH_8U, 3);
opencv_imgproc.cvCvtColor(yuvimage, rgbimage, opencv_imgproc.CV_YUV2BGR_NV21);
IplImage rotateimage=null;
try {
recorder.setTimestamp(videoTimestamp);
int rot=0;
switch (degrees) {
case 0:
rot =1;
rotateimage=rotate(rgbimage,rot);
break;
case 180:
rot = -1;
rotateimage=rotate(rgbimage,rot);
break;
default:
rotateimage=rgbimage;
}
recorder.record(rotateimage);
} catch (FFmpegFrameRecorder.Exception e) {
e.printStackTrace();
}
}
}
IplImage rotate(IplImage IplSrc,int angle) {
IplImage img= IplImage.create(IplSrc.height(), IplSrc.width(), IplSrc.depth(), IplSrc.nChannels());
cvTranspose(IplSrc, img);
cvFlip(img, img, angle);
return img;
}
}
after many searches this works for me.
I have really wired problem..I searced a lot in google and here and con't solve my problem.
I have main activity which create and start 3 threads (only "gameThread" is relevant now).
When I click on the back buttom it seems ok , but when I come back to the activity and the thread continue running , it seems it run duplicate - I see only one ContentView on the screen, but there is more one running (maybe behind).
I hope I explain myselg good..
this is the code:
package com.example.newpingpong;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.widget.Toast;
/*
* The main activity class of the Ping Pong game
*/
public class MainActivity extends Activity
{
/*
* The following class variables hold references to the objects the game is composed of -
* PingPongView - main view,
* gameBall - the ball in the game,
* gamePaddle - the paddle in the game,
* gameThread - main thread updating the position of the ball,
* paddleMoverThread - another thread which is responsible for moving the paddle.
*/
private PingPongView gameView;
private Tray gamePaddle;
private GameLevel gameLevel;
private GameLevel [] levels;
//threads
private PingPongGame gameThread;
private PaddleMover paddleMoverThread;
private PresentThread giftThread;
private Messages message;
//balls container
private BallView [] ballViewArr;
private Ball [] ballArr;
//gifts container
private Gift[] giftArr;
private GiftView[] GiftViewArr;
public GameSounds gameSounds;
private int screenWidth;
private int screenHeight;
private TrayView paddleView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
Toast.makeText(this, "onCreate!", Toast.LENGTH_SHORT).show();
// Get the message from the intent
Intent intent = getIntent();
String levelChosed = intent.getStringExtra(ImageAdapter.EXTRA_MESSAGE);
int levelChosed_int = Integer.parseInt(levelChosed);
Const.currentLevel = levelChosed_int;
Const.isCompleteThisLevel = false;
GameLevelView gameLevelView;
//for sounds
if(gameSounds == null){
gameSounds = new GameSounds();
gameSounds.addSound(Const.CATCH_GIFT, R.raw.cathc_gift, getBaseContext());
gameSounds.addSound(Const.GIFT_START_FALL, R.raw.gift_start_fall, getBaseContext());
gameSounds.addSound(Const.HIT_BLOCK, R.raw.hit_block, getBaseContext());
gameSounds.addSound(Const.HIT_KIR, R.raw.hit_kir, getBaseContext());
gameSounds.addSound(Const.GAME_OVER, R.raw.game_over, getBaseContext());
gameSounds.addSound(Const.HIT_PADDLE, R.raw.hit_paddle, getBaseContext());
}
if(this.ballViewArr == null){
this.ballViewArr = new BallView[Const.MAX_BALLS];
this.ballArr = new Ball[Const.MAX_BALLS];
this.giftArr = new Gift[Const.MAX_GIFTS];
this.GiftViewArr = new GiftView[Const.MAX_GIFTS];;
// Getting the screen width & height
screenWidth = this.getWindowManager().getDefaultDisplay().getWidth();
screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();
for(int i=0 ; i< Const.MAX_BALLS ; i++){
this.ballArr[i] = new Ball(screenWidth / 2, screenHeight / 2, screenWidth, screenHeight);
this.ballViewArr[i] = new BallView(this, this.ballArr[i]);
this.ballArr[i].setMode(Const.NOT_ACTIVE);
}
this.ballArr[0].setMode(Const.ACTIVE);
for(int i=0 ; i< Const.MAX_GIFTS ; i++){
this.giftArr[i] = new Gift(screenWidth / 2, screenHeight / 4,screenWidth, null, Const.NOT_ACTIVE);
this.GiftViewArr[i] = new GiftView(this, this.giftArr[i]);
}
// Creating the paddle
gamePaddle = new Tray(screenWidth / 2, (int)(screenHeight * 0.75), screenWidth);
// Creating the paddle view, and giving it the gameBall as a parameter
paddleView = new TrayView(this, gamePaddle);
levels = new GameLevel[Const.LevelsAmount];
levels[0] = new GameLevel(Const.level0, screenWidth , screenHeight, this.giftArr);
levels[1] = new GameLevel(Const.level1, screenWidth , screenHeight, this.giftArr);
levels[2] = new GameLevel(Const.level2,screenWidth , screenHeight, this.giftArr);
}
gameLevel=levels[levelChosed_int];
gameLevelView = new GameLevelView(this,gameLevel);
// Creating the game view
this.gameView = new PingPongView(this);
message = new Messages(this , screenWidth , screenHeight);
// Give the gameView our ballView & paddleView.
gameView.setViews(paddleView,gameLevelView , message ,this.ballViewArr , this.GiftViewArr);
// Setting the gameView as the main view for the PingPong activity.
setContentView(gameView);
if(gameThread == null){
//create the main thread
gameThread = new PingPongGame(this.getResources(), gamePaddle, gameView, gameLevel , message , ballArr , gameSounds);
//create the thread responsible for moving the paddle
paddleMoverThread = new PaddleMover(gamePaddle, gameView);
//create the thread responsible for present
giftThread = new PresentThread(gamePaddle , gameView , gameLevel, message , giftArr , ballArr,gameSounds );
gameThread.start();
paddleMoverThread.start();
giftThread.start();
}
}
//This method is automatically called when the user touches the screen
#Override
public boolean onTouchEvent(MotionEvent event)
{
float destination;
// Toast.makeText(this, "try!", Toast.LENGTH_SHORT).show();
//get the x coordinate of users' press
destination = event.getX();
//notify the paddle mover thread regarding the new destination
gamePaddle.setPaddleDestination(destination);
return true;
}
#Override
protected void onPause() {
Toast.makeText(this, "onPause!", Toast.LENGTH_SHORT).show();
Const.toStop = true;
while(Const.toStopMessageHasRecieved == false){
} //the thread not see the message that he shoult stop
//here the thrad recived the message and stoped.
super.onPause();
}
#Override
public void onResume()
{
Toast.makeText(this, "onResume!", Toast.LENGTH_SHORT).show();
for(int i=0 ; i< Const.MAX_BALLS ; i++){
this.ballArr[i] = new Ball(screenWidth / 2, screenHeight / 2, screenWidth, screenHeight);
this.ballViewArr[i] = new BallView(this, this.ballArr[i]);
this.ballArr[i].setMode(Const.NOT_ACTIVE);
}
this.ballArr[0].setMode(Const.ACTIVE);
synchronized (Const.mPauseLock) {
Const.toStop = false;
Const.mPauseLock.notifyAll();
}
super.onResume();
}
}
AND THE THREAD:
package com.example.newpingpong;
import android.content.res.Resources;
import android.graphics.Color;
public class PingPongGame extends Thread {
private PingPongView gameView;
private Tray gamePaddle;
private GameLevel gameLevel;
private Messages message;
private Ball [] ballArr;
private GameSounds gameSounds;
private Resources res;
//private int screenWidth;
//private int screenHight;
public PingPongGame(Resources res ,Tray theTray , PingPongView mainView , GameLevel gameLevel ,
Messages message , Ball ballArr[] ,GameSounds gameSounds)
{
this.gameView = mainView;
this.ballArr = ballArr;
this.gamePaddle = theTray;
this.gameLevel=gameLevel;
this.message = message;
this.gameSounds=gameSounds;
this.res=res;
//this.screenWidth = screenWidth;
//this.screenHight = screenHight;
//for stop and resume threads
}
//main method of the current thread
#Override
public void run()
{
try {
PingPongGame.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
this.message.setMessage(res.getString(R.string.lives) + gameLevel.getLives());
gameView.postInvalidate();
//infinitely loop, and move the balls accordingly
while ((Const.isLose == false) && (Const.isCompleteThisLevel==false))
{
for(int i=0; i<Const.MAX_BALLS ; i++){
if(ballArr[i].getMode() != Const.NOT_ACTIVE){
//move the ball one step
if(ballArr[i].move()) //hit kir
gameSounds.play(Const.HIT_KIR,false);
gameView.postInvalidate();
//check if the ball hit some block
if(gameLevel.updateIfHit(ballArr[i])){
ballArr[i].down();
gameSounds.play(Const.HIT_BLOCK,false);
}
gameView.postInvalidate();
if(gameLevel.getLives() == 0){
//gameLevel.restart();
this.message.setMessage(res.getString(R.string.game_over));
gameView.postInvalidate();
gameSounds.play(Const.GAME_OVER,false);
Const.isLose = true;
try
{
PingPongGame.sleep(2000);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
//check if the ball has reached the paddle
if(checkHitPaddle(i,ballArr[i]) == true){ //משתמש פספס and lives --
this.message.setMessage(res.getString(R.string.lives)+"" + gameLevel.getLives());
//send a request to refresh the display
gameView.postInvalidate();
}
}
}
try
{
PingPongGame.sleep(5);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
//public static boolean toStop = false;
//public static boolean toStopMessageHasRecieved = false;
//for stop and resume
//for stop and resume
synchronized (Const.mPauseLock) {
while (Const.toStop) {
Const.toStopMessageHasRecieved = true;
try {
Const.mPauseLock.wait();
} catch (InterruptedException e) {
}
}
}
}
// Const.toStopMessageHasRecieved = true;
}
//This method is responsible for handling the situation in which the ball has reached the height of the paddle
//return true if lives--
private boolean checkHitPaddle(int i , Ball ball)
{
// Checking if the ball had hit the paddle
if (ball.getBottom() >= gamePaddle.getTop())
{
// check if the paddle is in the right place
if ((ball.getRight() > gamePaddle.getLeft()) &&
(ball.getLeft() < gamePaddle.getRight()))
{
//The ball had hit the paddle - so it should be bounced
gameSounds.play(Const.HIT_PADDLE,false);
ball.bounceUp();
}
else
{
//The ball had missed the paddle - player looses - restart the game
gameSounds.play(Const.GAME_OVER,false); //not really game over - just lose live
boolean isMoreBall = false;
for(int j=0 ; j< Const.MAX_BALLS ; j++)
if( (j != i) && (ballArr[j].getMode() == Const.ACTIVE)){
isMoreBall = true;
break;
}
if(isMoreBall){
ball.setMode(Const.NOT_ACTIVE);
//for(int k=0; k<Const.MAX_BALLS ; k++){
//ballArr[k].downSpeed(0.25);
//}
}
else{
gameLevel.setLives(gameLevel.getLives()-1);
ball.restart();
return true;
}
}
}
return false;
}
}
Everytime the Activity is started you create a new gameThread in onCreate since the Activity is a new object then and gameThread is null. But you never stop them. You should probably insert something that ends your threads in onDestroy.
At the moment both Threads modify your Const object. Which leads me to the main problem:
I think the main problem is that all your communication is handled by some 'Global Variable Collection' in the Const object which can be very confusing the bigger the game gets. For now a solution could be to let the Const object handle the Threads too, but that would possibly only make it worse on the long run.
Try using
Timer rather than Thread.
And cancel timer using timer.cancel() in onBackPressed() method.
I'm creating a face recognition app which draws rectangle when it detects a face. Currently, all the computations are done on the main thread because of which it's using up a lot of CPU.
I've decided to run the computations on a different thread. Now the question is, is it possible to draw the canvas from the background thread itself? Or do I have to send the parameters to the main thread and then draw canvas from there?
Yes, do render your canvas from another thread and not from the UI thread! This is will improve and optimize performance.
Here is some code I wrote that does just this:
package com.webstorms.framework;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.view.SurfaceView;
public class RenderView extends WSObject implements Runnable {
Bitmap gameScreen;
SurfaceView surfaceView;
Thread gameloop;
boolean running;
int sleepTime;
int numberOfFramesSkipped;
int maxFrameSkips;
long beginTime;
long endTime;
long lastTime;
int differenceTime;
int framePeriod;
Canvas frameBuffer;
int frameCount;
int realFPS;
int setFPS;
/**
* This class is the game loop that will update and render the game.
*
*/
public RenderView(Game game, Bitmap gameScreen, int fps, int maxFrameSkips) {
super(game);
this.gameScreen = gameScreen;
surfaceView = new SurfaceView(game);
this.setFPS = fps;
this.framePeriod = 1000/this.setFPS;
this.maxFrameSkips = maxFrameSkips;
lastTime = System.currentTimeMillis();
beginTime = System.currentTimeMillis();
}
public SurfaceView getView() {
return this.surfaceView;
}
#Override
public void run() {
while(running) {
if(this.surfaceView.getHolder().getSurface().isValid()) {
beginTime = System.currentTimeMillis();
this.getGame().getInput().update(); // Synchronize input and call all attached listeneres
this.getGame().getCurrentScreen().update();
this.renderFrameBuffer();
// Frame Per Second Count
frameCount++;
if(lastTime + 1000 < System.currentTimeMillis()) {
WSLog.d(Game.GAME_ENGINE_TAG, this, "REAL FPS: " + frameCount);
this.realFPS = frameCount;
lastTime = System.currentTimeMillis();
frameCount = 0;
}
endTime = System.currentTimeMillis();
differenceTime = (int) (endTime - beginTime);
sleepTime = (int) (framePeriod - differenceTime);
if(sleepTime > 0) {
try {
Thread.sleep(sleepTime);
}
catch (InterruptedException exception) {
exception.printStackTrace();
}
}
else {
while(sleepTime < 0 && numberOfFramesSkipped < this.maxFrameSkips) {
WSLog.d(Game.GAME_ENGINE_TAG, this, "Game thread is only updating the update method and is not rendering anything");
this.getGame().getCurrentScreen().update();
sleepTime += framePeriod;
numberOfFramesSkipped++;
}
}
}
}
}
public int getRealFPS() {
return this.realFPS;
}
public int getSetFPS() {
return this.setFPS;
}
private void renderFrameBuffer() {
// Update the current virtual screen image
this.getGame().getCurrentScreen().render();
// Render the current virtual screen to the real phone screen
frameBuffer = this.surfaceView.getHolder().lockCanvas();
if(frameBuffer != null) { // Fix for mysterious bug ( FATAL EXCEPTION: Thread)
frameBuffer.drawBitmap(this.gameScreen, null, this.getGame().getWSScreen().getGameScreendst(), null);
this.surfaceView.getHolder().unlockCanvasAndPost(frameBuffer);
}
else {
WSLog.e(Game.GAME_ENGINE_TAG, this, "Surface has not been created or otherwise cannot be edited");
}
}
public void resume() {
this.running = true;
gameloop = new Thread(this);
gameloop.start();
}
public void pause() {
this.running = false;
running = false;
while(true) {
try {
gameloop.join();
break;
}
catch (InterruptedException e) {
// retry
}
}
}
}
When creating a renderview object, pass a reference of our activity for the first parameter.
Also do this in your activity:
this.setContentView(this.renderView.getView());
It is, no problem with that. And it may help you with optimising the rendering.
I have a SurfaceView in an Activity, and I want to open up a new Activity when something occurs in the SurfaceView(when you run out of lives - lives == 0). I've tried different things, but I keep having problems with it. If I stop my UIThread first, then of course it won't keep running and so won't be able to execute the startActivity statement. If I do start the activity, it freezes up on me and force closes - having to do with my UIThread I believe. Has anybody run into this problem before - and if so, do you have any idea how I might go about achieving this. At the very least, if I can't open up a new Activity, how could I CLOSE this current Activity (from inside the SurfaceView).
public class BoardView extends SurfaceView implements SurfaceHolder.Callback{
Context mContext;
// thread initialization
private BoardThread thread;
Thread timer;
Thread timer2;
// box variables
Bitmap box =
(BitmapFactory.decodeResource
(getResources(), R.drawable.box));
private int box_x = 140;
private int box_y = 378;
private int boxWidth = box.getWidth();
private int boxHeight = box.getHeight();
// storage
private Vector<Blossom> blossomVector = new Vector<Blossom>();
Iterator<Blossom> dataIterator = blossomVector.iterator();
// counters
private int blossomNum = 0;
private String score;
private int currentScore = 0;
private int lives = 3;
boolean mode = false;
boolean game = false;
OutputStreamWriter out = null;
FileOutputStream fOut = null;
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);
//set up read/write data
File root = Environment.getExternalStorageDirectory();
File highscoresFile = new File(root, "highscores.txt");
// controls drawings
thread = new BoardThread(getHolder(),this, blossomVector, dataIterator, box_x, box_y,
boxWidth, boxHeight);
timer2 = new Thread(){
public void run(){
while(game == false){
uiCallback.sendEmptyMessage(0);
try{
Thread.sleep(5000); // change to be random
}
catch (InterruptedException e){
e.printStackTrace();
}
}
}
};
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();
timer2.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);
// uses a synchronized method to prevent concurrent modification
DrawBlossoms(canvas);
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");
try {
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private Handler uiCallback = new Handler(){
public synchronized void handleMessage(Message msg){
//add a new blossom to the blossom Vector!!
blossomVector.add(new Blossom(
(BitmapFactory.decodeResource
(getResources(), R.drawable.blossom))));
dataIterator = blossomVector.iterator();
blossomNum++;
Log.v(TAG, "Number of Blossoms =" + blossomNum);
}
};
private synchronized void DrawBlossoms(Canvas c) // method to draw flowers on screen and test for collision
{
Canvas canvas = c;
dataIterator = blossomVector.iterator();
while (dataIterator.hasNext())
{
Blossom tempBlossom = dataIterator.next();
tempBlossom.Draw(canvas);
if (tempBlossom.hit(box_x,box_y, box_x + boxWidth, box_y + boxHeight, blossomVector) == true)
{
Log.v(TAG, "ITERATOR WORKS!");
dataIterator.remove();
currentScore += 100;
}
if (tempBlossom.dropped() == true)
{
dataIterator.remove();
Log.v(TAG, "Blossom dropped");
lives--;
}
if (lives == 0)
{
// stop the thread that makes blossoms
game = true;
//save the highscore
try {
File root = Environment.getExternalStorageDirectory();
if(root.canWrite()){
File highscoresFile = new File(root, "highscores.txt");
FileWriter writer = new FileWriter(highscoresFile);
BufferedWriter out = new BufferedWriter(writer);
//out.newLine();
out.write(score);
out.close();
}
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
File root = Environment.getExternalStorageDirectory();
File highscoresFile = new File(root, "highscores.txt");
FileReader reader = new FileReader(highscoresFile);
BufferedReader in = new BufferedReader(reader);
try {
String scoreTest = in.readLine();
Log.v(TAG, "Score: " + scoreTest);
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
Board Thread`public class BoardThread extends Thread {
private SurfaceHolder surfaceHolder;
private BoardView boardView;
private Vector<Blossom> blossomVector;
private int boxX;
private int boxY;
private int boxWidth;
private int boxHeight;
private boolean mrun =false;
private Iterator<Blossom> iterator;
private static final String TAG = "Debug";
public BoardThread(SurfaceHolder holder, BoardView boardView2,
Vector<Blossom> blossomVector1, Iterator<Blossom> dataIterator,
int box_x, int box_y, int boxW, int boxH) {
surfaceHolder = holder;
boardView=boardView2;
blossomVector = blossomVector1;
iterator = dataIterator;
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) {
//update position
//Position(blossomVector, boxX, boxY, boxWidth, boxHeight);
// draw flowers
boardView.onDraw(canvas);
}
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
private synchronized void Position(Vector<Blossom> blossomVector,int box_x, int box_y,
int boxWidth, int boxHeight)
{
//for(Blossom blossom: blossomVector)
iterator = blossomVector.iterator();
while (iterator.hasNext())
{
Blossom tempBlossom = iterator.next();
tempBlossom.UpdatePosition();
}
}
}
`
Sounds like the SurfaceView is running in a different thread than the main UI thread and can't modify the View objects. You'll want to create a Handler in your activity and make it accessible to your SurfaceView. Then you can send a message to the handler and it can update the UI (or launch a new activity) in the main UI thread.