I'm developing a 2d Game using Canvas/Surfaceview and have a problem with thread.
So what I want to accomplish is something in the background that is for example:
for each second SpawnEnemy() or ticking seconds or attacking.
I tried Thread.wait but that just cause pain and make my game 2fps.
here is my gameLoop:
import android.graphics.Canvas;
public class GameLoopThread extends Thread {
static final long FPS = 20;
private GameView view;
private boolean running = false;
public GameLoopThread(GameView view) {
this.view = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running) {
Canvas c = null;
startTime = System.currentTimeMillis();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
} catch (Exception e) {}
}
}
}
So I want something that is ticking in the background (seconds) that doesn't thread.wait.
You should make your thread run the game normally about 60fps, see this example: How can I use the animation framework inside the canvas?
If you want for each second to do something then you either count frames in onDraw(), on each 60th frame do it, or if you need a greater precision then in each onDraw() check the system time and do something when a second has elapsed.
You should also consider using two methods one for drawing onDraw() and another for the game logic which are called from your thread.
you could use AsyncTask for something to run in background
Related
I'm trying to do an FPS counter for any Android app. That means I don't have the source code for that app (I can't modify it or anything like that, I just have the .apk).
I've researched a lot into this and I've found only one app that does this (it's called Game Bench, you can find it on Google Play), so it is possible somehow. When the app starts, it has a list with all the games on your phone, you choose one and Game Bench automatically starts it and calculates the FPS. I need a similar behaviour.
Now, what I am asking is, if any of you has at least an idea of how I could calculate the FPS of an app (without writing code in it). Doing research I found a few vague ones, like record the screen and calculate FPS of the video, or somehow calculate the FPS using data collected with systrace. But on both these "ideas" there is very few info on the internet.
So please, if you guys have any information about this matter/ ideas/ opinions, I'll be happy to hear them. Thanks!
This is an example of how I would bench FPS using a Surface View.
Look at the Run method to see how the FPS works.
1: We get the time before update and render to the screen.
2: After all work is done we get the time again.
3: 1000 milliseconds is a second and 40 FPS is the norm.
4: elaspedTime = startTime - endTime;
5: So if elaspedTime is under 25 then its at-least doing 40 FPS.
6: If elaspedTime = 5 milliseconds then its doing 1000 / 5 = 200 FPS
7: You can sleep the thread for a couple milliseconds if you are running the updater and renderer on the same thread. That way you're not updating to many times.
8: Hope it helps this class is a basic game class that keeps the game running and 40 fps even if they are running it on a Galaxy S 6. You would want to make your own necessary changes to tweak it more.
public class MySurfaceView extends SurfaceView implements Runnable {
long time = 0, nextGameTick = 0;
SurfaceHolder myHolder;
Thread myThread = null;
boolean myRunning = false;
public MySurfaceView(Context context) {
super(context);
myHolder = getHolder();
}
#Override
public void run() {
while (myRunning) {
nextGameTick = System.currentTimeMillis();
if (!myHolder.getSurface().isValid())
continue;
Update();
Render();
time = nextGameTick - System.currentTimeMillis();
time = (time <= 0) ? 1 : time;
if (time <= 25)
sleepThread(25 - time);
Log.d("FrameRate", String.valueOf(1000 / time));
}
}
public void pause() {
myRunning = false;
while (true) {
try {
myThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
myThread = null;
}
public void resume() {
myRunning = true;
myThread = new Thread(this);
myThread.start();
}
public void sleepThread(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void Render() {
// TODO Render to Screen
}
private void Update() {
// TODO Update
}
}
My project is to create a flip card game in Android like http://partyhatmy.blogspot.kr/2012/08/angry-bird-matching-card-game.html. The differences are that there will be 3 x 4 total 12 cards on the screen, and the game will have a timer. So if the timer expires or if the user finds all pairs, the new stage begins.
My problem is that I do know how to implement this using SurfaceView, but since all cards are at fixed positions, I think it might be possible to implement the game using layouts in xml, but I don't know how. Is there any starting point resource available on the web?
Edition 1
My Code is like this: I just first want to print the remaining time to one TextView to the screen. The problem is that the screen is all black (without runOnUiThread() invocation, the activity draws the given layout activity_game flawlessly.
public class GameActivity extends Activity {
private TextView mTimerTextView;
private int mRemainingTime = 30;
#Override
protected void onCreate(Bundled savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_game);
mTimerTextView = (TextView)findViewById(R.id.remaining_time);
this.runOnUiThread(new Runnable() {
public void run() {
long lastSystemTime = 0;
mTimeTextView.setText(String.valueOf(mRemainingTime));
while (mRemainingTime > 0) {
if (lastSystemTime == 0) { // initial run
lastSystemTime = System.currentTimeMillis();
continue;
}
long currentTime = System.currentTimeMillis();
long elapsedTime = currentTime - lastSystemTime;
lastSystemTime = currentTime;
if (elapsedTime > 1000) {
mRemainingTime--;
mTimeTextView.setText(String.valueOf(mRemainingTime));
}
// To avoid excessive loop
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
}
see this link it will help you with flip animation
Displaying card flip animation on old android
or
http://www.techrepublic.com/blog/software-engineer/use-androids-scale-animation-to-simulate-a-3d-flip/
use this coode to do changes in ui thread
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
//do ui changes here
}
});
I'm still pretty confused as to when / how to end threads in my Surfaceview application and was hoping someone could explain.
Currently, I am using this code:
Log.v("Destroyed","Surface Destroyed");
preThread.setRunning(false);
boolean retry = true;
while (retry) {
try {
preThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
The above code sits in my surfaceDestroyed method - firstly, is this correct?
In my surfaceCreated method I have the following code which should check to see if the thread still exists or has been stopped and if it's been stopped, then re-start it:
if (runthread==false){
if (preThread.getState()==Thread.State.TERMINATED){
preThread = new MainThread(thisholder, thiscontext, thishandler);}
else {}
preThread.setRunning(true);
preThread.start();
}
It seems to act really strange. Here is what I get:
*) When I first install the game and run it, through my logging, it says that the thread already exists, If I then press the back key, surfaceDestroyed is run but when I go back into the activity, again it says that the thread already exists.
*) If I press the home key, then surfaceDestroyed is run and when I go back into the activity it says that the thread was previously destroyed and starts a new one.
*) If I kill the activity using DDMS, surfaceDestroyed isn't run and when I go back into the activity it says that the thread already exists.
If I'm thinking straight, then the third scenario is the only one that seems to make sense.
I'm clearly doing something drastically wrong. The main problem is this:
If I hit the home key during the game and then end the app via DDMS in Eclipse, restart the app and hit the back key twice in quick succession (once, to go back to the previous activity, then again to get back to the splash screen) - the app force-closes and I get a "Fatal exception: Thread 12" in logcat. I have to assume this is because my thread is never ending and is trying to be-restarted? I'm not sure.
I've been trying to figure this out for what seems like an age so I really hope someone can explain what I'm doing wrong!
Many thanks!
Edit. Logcat output.
my Run() method:
public void run(){
//Main Loop
while (runthread){
Log.v("tracking","runthread is: "+runthread); //This should only be logged while this loop is running
timestart = System.currentTimeMillis(); //Get time at start of loop for FPS calc
try{c=mySurfaceHolder.lockCanvas(); //Set Canvas to locked
synchronized(mySurfaceHolder){
if (c==null){Log.v("Stop","Canvas is null for some reason - exiting, "+c+" - see?!!!");}
framesskipped = 0; // resetting frames skipped
doDraw(c); //Draw to the screen
updateMenu();
}
}
finally{
if (c != null){
mySurfaceHolder.unlockCanvasAndPost(c); //Post canvas
}
}
//work out timings
timeend = System.currentTimeMillis(); //get end time for current frame (for FPS)
frametime = timeend-timestart; //Set the frametime variable to the time the frame took to render & update (end time - start time)
sleepfor = (int) (33-frametime); // this is the time that the thread will sleep for if <target time
if (sleepfor>0){ // If the 'sleepfor' variable is >0 then set the thread to sleep for it's value (expressed in ms)
try {
OptionsThread.sleep(sleepfor); //send thread to sleep for value of sleepfor (determined above).
} catch (InterruptedException e) {} //in case of exception
} //close if statement
while (sleepfor<0 && framesskipped<maxframesskipped){ //if sleepfor is < 0 (ie, frame took longer to render than target time and the maxframesskipped has not reached it's limit)
updateMenu(); //Update animation variables without rendering to the screen while these conditions are met
sleepfor+=33; //time to sleep plus the time frame took to render
framesskipped++; //add one to framesskipped variable so this only skips a certain number of frames
}
}
}
New Logcat output showing nullPointerException and output of logging. runThread is never logged as false so I'm not sure how the line that logs canvas as null is reached!
Thanks
Edit:
OK, I've completely started from scratch and re-written the whole class- it's very much a stripped-down version of what I had before and here is the whole class:
import android.content.res.Resources;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class OptionsScreen extends SurfaceView implements
SurfaceHolder.Callback {
//Create Variables
private SurfaceHolder thisHolder;
private Context thisContext;
private Handler thisHandler;
private preThread thread;
private Bitmap background;
private Resources res;
private Context myContext;
private Handler myHandler;
private Canvas c;
// thisholder = getHolder();
public OptionsScreen(Context context) {
super(context);
myContext=context; //This is the context passed into this constructor (this)
thisHolder = getHolder(); //Get surface holder
thisHandler=getHandler(); //Get Handler
thisContext = getContext(); //Get context
res=getResources(); //Get resource
//add the callback surface holder
getHolder().addCallback(this);
//make focusable
setFocusable(true);
//create new thread
thread = new preThread(thisHolder, thisContext, thisHandler);
//create bitmaps from resources
background = BitmapFactory.decodeResource(res, R.drawable.sky);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.v("check","surfaceChanged run");
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("check","surfaceCreated run"+thread.getState());
int height = this.getHeight();
int width = this.getWidth();
if(thread.getState()==Thread.State.TERMINATED){ //Has thread been stopped previously? could happen if the home key is pressed
Log.v("check","Thread still exists!!!! - Starting a new one. "+thread.getState());
thread = new preThread(thisHolder, thisContext, thisHandler);
}
thread.setRunning(true);
thread.start();
Log.v("check","Thread - "+thread.getState());
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("check","surfaceDestroyed run"+thread.getState());
thread.setRunning(false); //Set to false to exit run() method
boolean retry = true; //Shut off rendering thread
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.v("check","Surface Touched");
Log.v("check","Thread - "+thread.getState());
// System.exit(0);
return super.onTouchEvent(event);
}
#Override
protected void onDraw(Canvas canvas) {
// if (canvas!=null){
canvas.drawBitmap(background, 0, 0, null);
Log.v("Stop","Canvas is "+canvas);
}
}
//*******************************************************************
//** run loop **
//*******************************************************************
protected class preThread extends Thread {
private SurfaceHolder mySurfaceHolder;
private Context myContext;
public preThread(SurfaceHolder surfaceholder, Context context, Handler handler) { //Constructor
mySurfaceHolder=surfaceholder;
myContext=context;
res = myContext.getResources();
}
// flag
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
#Override
public void run() {
while (running) {
try{c=mySurfaceHolder.lockCanvas();
synchronized(mySurfaceHolder){
Log.v("check","Drawing!!");
onDraw(c);
}
}
finally{
if (c != null){
mySurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
Threads are pretty difficult to manage, but after some hit and trial i think i have come up with a scheme which works correctly most of the time.
To End a Thread
if(m_hThread != null)
{
try
{
m_bThread = false; // m_bThread is the while condition of the thread
m_hThread.interrupt(); // incase the thread is in sleep
m_Thread.join(); // This call blocks and waits for thread to end
m_hThread = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
For re-creating thread
if(m_hThread == null)
{
m_bThread = true; //while condition of thread
StartNewThread();
}
In your implementation, No need to retry Thread.join, either it will join in the first try or it will block until the thread joins. As for your cases only the first case seems to be weird, where you find the thread is already running, this can't possibly be true. Second and third make complete sense to me, and work like they should. When the user clicks Home button surfaceDestroyed is invoked and thread is Terminated.
thread continues as long as m_bThread is true,
while(m_bThread) //b
{
// Continuous Thread operations...
}
m_hThread is simply preThread in your code, and m_Thread is a also m_hThread just a typing mistake here.
I am making a board game. The board doesn't ever move, but pieces on top of it sometimes do depending on user interaction. There are also UI elements which may update periodically.
Right now the way I set it up is by overwriting the onDraw() method of a SurfaceView subclass. I have a drawing thread that constantly calls postInvalidate() in a while loop:
class PanelThread extends Thread
{
//...
long sleepTime = 0;
long nextGameTick = System.currentTimeMillis();
#Override
public void run()
{
Canvas c;
while (_run)
{ // When setRunning(false) occurs, _run is
c = null; // set to false and loop ends, stopping thread
try
{
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder)
{
// Insert methods to modify positions of items in onDraw()
_panel.postInvalidate();
}
} finally
{
if (c != null)
{
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
nextGameTick += MILLISECONDS_PER_FRAME;
sleepTime = nextGameTick - System.currentTimeMillis();
if(sleepTime >= 0)
{
try
{
sleep(sleepTime, 0);
} catch (InterruptedException e)
{
continue;
}
}
else
{
//we're behind, oh well.
System.out.println("behind!");
nextGameTick = System.currentTimeMillis();
}
}
}
This is not efficient and is taking a lot of CPU. Is there a easy way to get android to only update when something changes?
You have the right idea, but it needs a bit of refinement.
You definitely do not want to loop as fast as the CPU can handle it though.
You should be sleeping your Thread in every loop for a little while. You most certainly do not need to do everything in your loop every millisecond.
I found this guide to FPS control to be incredible helpful in designing a game loop.
This Android-specific game loop guide also provides a lot of great sample code and an in-depth explanation.
To use a SurfaceView for drawing a 2D game in Android, I use this in the main activity's onCreate():
setContentView(new GameView(this));
Which is a reference to this class:
public class GameView extends SurfaceView implements SurfaceHolder.Callback
Additionally, I have a thread with its run() function:
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_panel.updatePhysics();
_panel.onDraw(c);
}
}
finally { // when exception is thrown above we may not leave the surface in an inconsistent state
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
In updatePhysics() I do some calculations. They are more complex than this simple example, of course, but work the same way:
public void updatePhysics() {
GraphicObject.Coordinates coord;
GraphicObject.Speed speed;
for (GraphicObject graphic : _allElements) {
coord = graphic.getCoordinates();
speed = graphic.getSpeed();
coord.setX(coord.getX() + speed.getX());
coord.setY(coord.getY() + speed.getY());
...
}
}
And in onDraw(), I draw everything to the canvas:
#Override
public void onDraw(Canvas canvas) {
canvas.drawBitmap(BITMAP, xPos, yPos, null);
...
}
This works fine - everything. And when I tested it on my device, it looked pretty good. But when I gave it to someone else and he did a test game, the objects were moving much faster! Why is this so? Because the thread calls updatePhysics() as often as possible which means that fast devices call this function more often?
How can I prevent this and make the game equally fast on all devices? Something like this?
private long lastRun = System.currentTimeMillis();
public void updatePhysics() {
long millisPassed = System.currentTimeMillis()-lastRun;
...
float newCoord = (coord.getX() + speed.getX()) * millisPassed / 33;
coord.setX(newCoord);
...
}
Thanks for your help!
If you can, use the time directly to calculate all your physics. That would usually work best.
If you have no way to calculate based on time because what you are doing that is just step based and you know that generating the next step does not take much time then you have another option.
You create two threads. The first one advances the state at a fixed rate (and you have to be sure that this works on slow devices at that rate too). The second one takes the current state is sees and draws that. Now the second thread can be as slow as it wants because it simply skips some states (or draws the same state several times if it is faster).
Small example below has one thread that advances some state object and replaces the reference each time so the consuming thread does not need to worry that it's state object gets modified
class GameState {
private int state = 0;
public GameState advanceState() {
GameState result = new GameState();
result.state = this.state + 1;
return result;
}
}
class SurfaceViewImplementation extends SurfaceView {
// the current state
volatile GameState mState = new GameState();
void somewhere() {
Thread fastProducer = new Thread(new Runnable() {
private static final long MAX_WAIT = 1000 / 60;
#Override
public void run() {
while (!Thread.interrupted()) {
long timeBefore = SystemClock.currentThreadTimeMillis();
GameState newState = mState.advanceState();
mState = newState;
long timeAfter = SystemClock.currentThreadTimeMillis();
long timeSpent = timeAfter - timeBefore;
SystemClock.sleep(Math.max(0, MAX_WAIT - timeSpent));
}
}
});
fastProducer.start();
Thread slowConsumer = new Thread(new Runnable() {
#Override
public void run() {
while (!Thread.interrupted()) {
GameState currentState = mState;
longRunningDraw(currentState);
}
}
});
slowConsumer.start();
}
}
That will still fail to give you a speed independant result if the producing thread can't run at the desired rate.
I would save the time when I start rendering the frame(in your case is updatePhysics()), and then next time I would got to this point, I know how much time pass, if it's to fast you can use Thread.Sleep();