I'm working on a game that at some point needs to run for x steps to update certain objects on the screen. As of now, all that's seen by the player is step 0, and step x. How do i show each step in-between for a set time?
I've tried adding a check to make sure onDraw() is called in-between, as well as sleeps, making sure to call PostInvalidate after each step is done in hopes of forcing onDraw() to be called, but haven't had any luck.
(ex. In step function, have a bool "nextStep" that is set to false at the end of the function, onDraw() sets this to true at the end. If it's false, step function will wait to update the 2d array of objects that gets drawn to the screen until "ondraw" sets the bool to true.)
This works
for (int i = 0; i < steps; i++) {
// postDelayed fixes this because it tells the GPU to draw on next cycle otherwise, it has already passed the draw by now.
postDelayed(new Runnable() {
#Override
public void run() {
Canvas c = null;
step(CellType.PLAYER);
invalidate();
try {
c = getHolder().lockCanvas();
synchronized (getHolder()) {
draw(c);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (c != null) {
getHolder().unlockCanvasAndPost(c);
}
}
}
}, stepDelay*i);
}
Related
I am experimenting with SurfaceView. My requirement is to simply render a node (simple drawable) first. Then, render more nodes at a later point in time.
The snippets of my thread's run method & my doDraw method are below. I am just trying to render 2 different drawables in subsequent passes while retaining both. The problem is it wipes away whatever gets written in 1st pass (see comment in code). How to retain the previously drawn object?
public void run() {
Canvas canvas;
while (_running) {
canvas = null;
try {
canvas = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
doDraw(canvas, isUpdate);
}
} finally {
if (canvas != null) {
_surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
public void doDraw(Canvas canvas, boolean update){
if(update){
//goes here 2nd pass & wipes away things done in 1st pass
//I want to retain whatever was drawn in 1st pass
Bitmap thumb = BitmapFactory.decodeResource(getResources(),R.drawable.icon);
//canvas.drawColor(Color.RED);
canvas.drawBitmap(thumb, 0, 0, null);
} else{
//goes here 1st pass
Bitmap thumb = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
//canvas.drawColor(Color.BLACK);
canvas.drawBitmap(thumb, 300, 300, null);
this.isUpdate = true;
}
}
UPDATE 1:
Still does not seem to work. I changed the run code to this passing a non:
public void run() {
Canvas canvas;
while (_running) {
canvas = null;
try {
Rect dty = null;
if(isUpdate == true){
//--> In 2nd pass, I was hoping that only these co-ordinates will be updated
dty = new Rect(0,0,100,100);
canvas = _surfaceHolder.lockCanvas(dty);
}else{
canvas = _surfaceHolder.lockCanvas(null);
}
synchronized (_surfaceHolder) {
doDraw(canvas, isUpdate);
}
} finally {
if (canvas != null) {
_surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
Later I tried passing 0,0,1,1 to dirty rectangle. Could not get it to work yet...
The SurfaceView is double- or triple-buffered. The previous contents are "preserved" in the sense that the system doesn't go out of its way to clear older buffers, but you can't rely on that behavior.
If you specify a dirty rect, the framework will render whatever you ask, then copy the non-dirty region from the previous buffer on top of the new buffer.
The system is allowed to expand the dirty rectangle -- the Rect you pass to lockCanvas() may be updated. You're required to redraw every pixel inside it.
For a (somewhat eye-searing) example of this in action, see "Simple Canvas in TextureView" in Grafika.
For more details on how the system works, see this article.
I found this interesting note in the Android documentation:
The content of the Surface is never preserved between unlockCanvas()
and lockCanvas(), for this reason, every pixel within the Surface area
must be written. The only exception to this rule is when a dirty
rectangle is specified, in which case, non-dirty pixels will be
preserved.
So to do what you are trying to do, it looks like you need to provide a non-null dirty rectangle in your lockCanvas call. Also, this will only work as long as none of your node pixels intersect.
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.
I have an android game on the market and I've been getting crash reports of NullPointers when trying to use the canvas. I can assume it's because SurfaceHolder.lockCanvas() is returning null, however it does this mid gameplay because based on where it crashes, SurfaceHolder.lockCanvas() returned a valid canvas at least once.
This is difficult to debug because I can't re-create it on my own device, which makes me wonder if it has to do with specific devices. The only hint I have is that one of the devices it occurred on was a Nexus 7.
NOTE: This is not the same problem as the similar-named question by me. The other question was due to trying to use the canvas before it was available, whereas here it was available.
Below is a sample of my code:
public class GameView extends SurfaceView implements SurfaceHolder.Callback
{
class GameThread extends Thread
{
#Override
public void run()
{
while (running)
{
Canvas c = null;
try
{
c = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder)
{
long start = System.currentTimeMillis();
doDraw(c);
long diff = System.currentTimeMillis() - start;
if (diff < frameRate)
Thread.sleep(frameRate - diff);
}
} catch (InterruptedException e)
{
}
finally
{
if (c != null)
{
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
public void surfaceCreated(SurfaceHolder holder)
{
if (gThread.getState() == Thread.State.TERMINATED)
{
gThread = new GameThread(getHolder(), getContext(), getHandler());
gThread.start();
}
else
{
gThread.start();
}
}
}
I also faced that problem. It happens sporadically when the surface is already destroyed (and surfaceDestroyed was already called), but the looper thread is already inside the while loop (and after checking the running variable). Then lockCanvas returns null (as the surface was destroyed). This may happen for example on screen orientation changes or activity changes. It is a "simple" race condition threading problem.
A simple fix is to add if (canvas == null) continue; after lockCanvas (or just break the loop). Then running will be checked again and the loop ends.
having some trouble with a Thread (CanvasThread) that is intermittently pausing at random points within my application. Everything else in the app continues to function as necessary, it's simply this thread that randomly blocks out for some reason and doesn't draw anything new to the screen. I noticed that Surface.lockCanvasNative() seems to be the last function called before the block, and the first one returned after. In a pattern as such:
Surface.lockCanvasNative (Landroid/graphics/Rect)Landroid/graphics/Canvas; # 26,560 msec ____
Surface.lockCanvasNative (Landroid/graphics/Rect)Landroid/graphics/Canvas; # 40,471 msec ____|
Surface.lockCanvasNative (Landroid/graphics/Rect)Landroid/graphics/Canvas; # 40,629 msec ____
Surface.lockCanvasNative (Landroid/graphics/Rect)Landroid/graphics/Canvas; # 54,516 msec ____|
This is evident with the traceview below:
I have been using the CanvasThread.run() below if it helps:
#Override
public void run() {
boolean tellRendererSurfaceChanged = true;
/*
* This is our main activity thread's loop, we go until
* asked to quit.
*/
while (!mDone) {
/*
* Update the asynchronous state (window size)
*/
int w;
int h;
synchronized (this) {
// If the user has set a runnable to run in this thread,
// execute it and record the amount of time it takes to
// run.
if (mEvent != null) {
mEvent.run();
}
if(needToWait()) {
while (needToWait()) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
if (mDone) {
break;
}
tellRendererSurfaceChanged = mSizeChanged;
w = mWidth;
h = mHeight;
mSizeChanged = false;
}
if (tellRendererSurfaceChanged) {
mRenderer.sizeChanged(w, h);
tellRendererSurfaceChanged = false;
}
if ((w > 0) && (h > 0)) {
// Get ready to draw.
// We record both lockCanvas() and unlockCanvasAndPost()
// as part of "page flip" time because either may block
// until the previous frame is complete.
Canvas canvas = mSurfaceHolder.lockCanvas();
if (canvas != null) {
// Draw a frame!
mRenderer.drawFrame(canvas);
mSurfaceHolder.unlockCanvasAndPost(canvas);
//CanvasTestActivity._isAsyncGoTime = true;
}
else{
Log.v("CanvasSurfaceView.CanvasThread", "canvas == null");
}
}
}
}
Just let me know if I can provide any other useful information. I'm simply looking for clues as to why my thread might be blocking at this point? Thanks for any help in advance!
I've since narrowed the block down to mSurfaceHolder.unlockCanvasAndPost(canvas); I inserted a log before and after this call and the one after is not logged after app is frozen; but the log before is last logged event on this thread. It's not pausing or using a null canvas either, because I threw in logs for those instances as well; which are not logged even once until app is done.
I'm not sure if this could be the reason, but under SurfaceHolder.lockCanvas(), it warns that,
If you call this repeatedly when the
Surface is not ready (before
Callback.surfaceCreated or after
Callback.surfaceDestroyed), your calls
will be throttled to a slow rate in
order to avoid consuming CPU.
If null is not returned, this function
internally holds a lock until the
corresponding
unlockCanvasAndPost(Canvas) call,
preventing SurfaceView from creating,
destroying, or modifying the surface
while it is being drawn. This can be
more convenient than accessing the
Surface directly, as you do not need
to do special synchronization with a
drawing thread in
Callback.surfaceDestroyed.
I'm not sure what the threshold is when the CPU starts throttling. How many threads are refreshing the canvas?
btw,
if(needToWait()) {
while (needToWait()) {
is redundant
I have since figured out my problem. I'm not sure why but because I had accidentally forgot to fully comment out an earlier asyncTask(), thus had two doing roughly the same tasks and obviously struggling to do so with the same variables and such. Thanks for your pointers, but simply another careless mistake on my part I guess.
I based my game off of the lunar lander demo, although heavily modified, and I can get around 40-50fps but the problem is it fluctuates between 40-50fps so much that it causes the moving graphics to jitter! Its very annoying and makes my game look really shitty when in fact its running at a good frame rate.
I tried setting the thread priority higher but that just made it worse... now it will fluctuate between 40-60fps...
I was thinking of limiting the FPS to about 30 so that it will be constant. Is this a good idea and does anyone else have experience or a different solution?
Thanks!
This is my run loop
#Override
public void run() {
while (mRun) {
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if(mMode == STATE_RUNNING){
updatePhysics();
}
doDraw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
private void updatePhysics() {
now = android.os.SystemClock.uptimeMillis();
elapsed = (now - mLastTime) / 1000.0;
posistionY += elapsed * speed;
mLastTime = now;
}
Don't base your game's logic (object movement, etc.) updating rate on the framerate. In other words, put your drawing and logic updating code in two separate components/threads. This way your game logic is completely independent from your framerate.
Logic updating should be based on how much time has passed since the last update (let's call it delta). Therefore, if you have an object moving at 1px/millisecond, then during each update your object should do something like this:
public void update(int delta) {
this.x += this.speed * delta;
}
So now even if your FPS lags, it won't affect your object's movement speed, since the delta will just be larger, making the object move farther to compensate (there are complications in some cases, but that's the gist of it).
And this is one way of calculating delta within your logic updating object (running in some thread loop):
private long lastUpdateTime;
private long currentTime;
public void update() {
currentTime = System.currentTimeMillis();
int delta = (int) (currentTime - lastUpdateTime);
lastUpdateTime = currentTime;
myGameObject.update(delta); // This would call something like the update method above.
}
Hope that helps! Please ask if you have any other questions; I've been making Android games myself. :)
Sample code:
Copy these two snippets (1 activity and 1 view) and run the code. The result should be a white dot smoothly falling down your screen, no matter what your FPS is. The code looks kinda complicated and long, but it's actually quite simple; the comments should explain everything.
This activity class isn't too important. You can ignore most of the code in it.
public class TestActivity extends Activity {
private TestView view;
public void onCreate(Bundle savedInstanceState) {
// These lines just add the view we're using.
super.onCreate(savedInstanceState);
setContentView(R.layout.randomimage);
RelativeLayout rl = (RelativeLayout) findViewById(R.id.relative_layout);
view = new TestView(this);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
10000, 10000);
rl.addView(view, params);
// This starts our view's logic thread
view.startMyLogicThread();
}
public void onPause() {
super.onPause();
// When our activity pauses, we want our view to stop updating its logic.
// This prevents your application from running in the background, which eats up the battery.
view.setActive(false);
}
}
This class is where the exciting stuff is!
public class TestView extends View {
// Of course, this stuff should be in its own object, but just for this example..
private float position; // Where our dot is
private float velocity; // How fast the dot's moving
private Paint p; // Used during onDraw()
private boolean active; // If our logic is still active
public TestView(Context context) {
super(context);
// Set some initial arbitrary values
position = 10f;
velocity = .05f;
p = new Paint();
p.setColor(Color.WHITE);
active = true;
}
// We draw everything here. This is by default in its own thread (the UI thread).
// Let's just call this thread THREAD_A.
public void onDraw(Canvas c) {
c.drawCircle(150, position, 1, p);
}
// This just updates our position based on a delta that's given.
public void update(int delta) {
position += delta * velocity;
postInvalidate(); // Tells our view to redraw itself, since our position changed.
}
// The important part!
// This starts another thread (let's call this THREAD_B). THREAD_B will run completely
// independent from THREAD_A (above); therefore, FPS changes will not affect how
// our velocity increases our position.
public void startMyLogicThread() {
new Thread() {
public void run() {
// Store the current time values.
long time1 = System.currentTimeMillis();
long time2;
// Once active is false, this loop (and thread) terminates.
while (active) {
try {
// This is your target delta. 25ms = 40fps
Thread.sleep(25);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
time2 = System.currentTimeMillis(); // Get current time
int delta = (int) (time2 - time1); // Calculate how long it's been since last update
update(delta); // Call update with our delta
time1 = time2; // Update our time variables.
}
}
}.start(); // Start THREAD_B
}
// Method that's called by the activity
public void setActive(boolean active) {
this.active = active;
}
}
I am thinking there might be, not really something wrong with some of the above code, but rather an inefficiency. I am talking about this code...
// The important part!
// This starts another thread (let's call this THREAD_B). THREAD_B will run completely
// independent from THREAD_A (above); therefore, FPS changes will not affect how
// our velocity increases our position.
public void startMyLogicThread() {
new Thread() {
public void run() {
// Store the current time values.
long time1 = System.currentTimeMillis();
long time2;
// Once active is false, this loop (and thread) terminates.
while (active) {
try {
// This is your target delta. 25ms = 40fps
Thread.sleep(25);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
time2 = System.currentTimeMillis(); // Get current time
int delta = (int) (time2 - time1); // Calculate how long it's been since last update
update(delta); // Call update with our delta
time1 = time2; // Update our time variables.
}
}
}.start(); // Start THREAD_B
}
Specifically, I am thinking about the following lines...
// This is your target delta. 25ms = 40fps
Thread.sleep(25);
It seems to me that just having the thread hang out doing nothing is a waste of valuable processing time, when in fact what you want to be doing is performing the updates, then, if the updates have taken less time than the 25 millis, then sleep the thread for the difference of what was used during the update and 25 millis (or whatever your chosen frame rate is). In this way the update will happen while the current frame is being rendered, and will be completed so the next frame update uses the updated values.
The only problem I can think of here is that some kind of syncronization will need to occur so that the current frame render does not use partially updated values. Perhaps update into a new instance of your set of values, and then make the new instance the current instance just before rendering.
I think I remember reading something in a graphics book about the goal being to perform as many updates as you can while staying within your desired frame rate, then, and only them, perform a screen update.
This of course will require one thread to drive the updates - if you use a SurfaceView, the render is controlled by this thread when you lock the canvas (in theory, according to my understanding anyway).
So, in code, it would be more like...
// Calculate next render time
nextRender = System.currentTimeInMillis() + 25;
while (System.currentTimeInMillis() < nextRender)
{
// All objects must be updated here
update();
// I could see maintaining a pointer to the next object to be updated,
// such that you update as many objects as you can before the next render, and
// then continue the update from where you left off in the next render...
}
// Perform a render (if using a surface view)
c = lockCanvas() blah, blah...
// Paint and unlock
// If using a standard view
postInvalidate();
Good luck and any feedback from anyone using this would surely help us all learn something...
rpbarbati
I think it's about Garbage collector
I would use SurfaceView instead of View if your game is action heavy. If you don't need to update the GUI rapidly then View is fine but for 2D games it's always better to use SurfaceView.
I have a similar issue, the jitter makes large object moves look uneven. Even though the "speed" is the same, different lengths of steps make the movements look jumpy.
Broody - You say a SurfaceView is beter, however, this is not true after Android 3.0 as the View is HW accelerated but the canvas returned by .lockCanvas is not.
Steven - Yes, this is likely causing poroblems, but is easy to detect.
/Jacob