Android - Creating a Timer loop for a pong game - android

I have a Pong game that I wrote in Java and I'm trying to port it to Android. (It's my first Android game ;).
In Java I used a Timer object which basically updated the Game values (ball/paddles position) and then redrew the screen.
I'm trying to implement the same functionality in Android but I'm getting a number of errors.
My program consists of a PongView class which is the visual part of the game, and a PongDriverActivity class which uses PongView for its view. If I have a looping thread invalidating PongView I get an error because the thread can't touch a view spawned on another thread.
I assume I need to do some sort of AsyncTask, but I'm not sure how to loop that.
Any suggestions on what would be the best way to implement this?

There's many ways to do this. Android does support normal Java Thread objects, and you can use those. You can search (SO) for android game loop and probably find lots of examples. But, if you'd like to try AsyncTask, here's a sample implementation:
public class PongView extends View {
public PongView(Context context) {
super(context);
}
public void setBallPosition(int x, int y) {
// relocate the ball graphic
}
}
Then, your Activity:
public class PongDriverActivity extends Activity implements OnSeekBarChangeListener {
private enum GameSpeed { SLOW, MEDIUM, FAST };
private PongView mGameView;
private PongDriverTask mWorker;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGameView = new PongView(this);
mWorker = new PongDriverTask(GameSpeed.MEDIUM);
// if you need to pass some data to the worker:
mWorker.execute("one", "two", "three");
// else, declare the AsyncTask's first generic param as Void, and do:
//mWorker.execute();
setContentView(mGameView);
}
// you would connect this up to a button, to allow the user to stop
public void onStopClicked(View sender) {
if (mWorker.isRunning()) {
mWorker.stopRunning();
}
}
// you could connect this up to a slider, to allow the user to control the paddle
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// you may need to convert progress to a y-coordinate
mWorker.setPaddlePosition(progress);
}
And, then (maybe inside PongDriverActivity.java, an inner class):
private class PongDriverTask extends AsyncTask<String, Integer, Integer> {
private int mDelay;
private boolean mRunning = true;
private int mPaddleY = 0;
public PongDriverTask(GameSpeed speed) {
if (speed == GameSpeed.SLOW) {
mDelay = 100;
} else if (speed == GameSpeed.MEDIUM) {
mDelay = 50;
} else if (speed == GameSpeed.FAST) {
mDelay = 10;
}
}
public synchronized boolean isRunning() {
return mRunning;
}
public synchronized void stopRunning() {
mRunning = false;
}
public synchronized void setPaddlePosition(int y) {
mPaddleY = 0;
}
private synchronized int getPaddlePosition() {
return mPaddleY;
}
#Override
protected void onPostExecute(Integer score) {
super.onPostExecute(score);
// here you could show a UI that shows the final score
}
#Override
protected void onPreExecute() {
super.onPreExecute();
mGameView.setBallPosition(0, 0);
// here you could throw up some UI that shows just
// before the game really starts
}
#Override
protected void onProgressUpdate(Integer... params) {
super.onProgressUpdate(params);
// retrieve updated game coordinates from params
mGameView.setBallPosition(params[0], params[1]); // x,y
}
#Override
protected Integer doInBackground(String... args) {
// If you have some information to pass to the background worker,
// it would be passed in args[0], args[1], etc.
// It doesn't have to be String data. you could change the generic
// to take something other than String as the first param, in which
// case, doInBackground() would take a variable length list of that
// other data type.
int ballX = 0;
int ballY = 0;
int score = 0;
while (isRunning()) {
// use your game engine to recalculate the ball position
// on this background thread
int paddleY = getPaddlePosition();
ballX += 1;
ballY += 2;
score++;
// now, update the UI, which must happen on the UI thread
publishProgress(ballX, ballY);
try {
Thread.sleep(mDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return score;
}
}
The task's doInBackground() method will be run on a background thread, so you must not directly modify the UI from there. But, all the other AsyncTask methods I override above are called on the UI thread, so it's safe to perform UI work in any of them.
This code assumes the paddle is controlled by a seek bar, which you'd make your Activity a change listener for. Lots of other ways to do it, too.
I just implemented a crude game stop mechanism, which you could hook up to a button. If you want to allow the user to pause and resume, you can search for implementations to pause/resume threads, which should be applicable here, too. For example:
How to Pause and Resume a Thread in Java from another Thread
More Reading ...
See this writeup for some discussion of game loop speeds ...
and maybe take a look at this discussion, too

Related

While loop causes UI to go blank

I'm writing a simple Whack a Mole clone, and I've got my UI elements declared in a GridLayout in a layout.xml, then assigned to ImageView variables in an array programmatically. I've got a startGame() method that simply takes a random int, pulls it from the array and causes it to go visible for a second, then repeats. For some reason, when I put this code in a while() loop, it causes my UI to go blank as soon as it's launched.
I know it's the while() loop because I tried taking the code out of the while() loop, and it ran correctly (once), but turns everything white when placed in a while loop.
Here's the method causing the problem:
public void startGame() {
gameStarted = true;
while(gameStarted) {
randomInt = rand.nextInt(11);
mole[randomInt].setVisibility(View.VISIBLE);
handler.postDelayed(new Runnable() {
#Override
public void run() {
mole[randomInt].setVisibility(View.INVISIBLE);
}
}, 5000);
}
}
All the other relevant code is in onCreate, it's otherwise just a skeleton Activity subclass.
public class WAM_Activity extends Activity {
private ImageView[] mole = new ImageView[11];
private int[] moleId = {R.id.mole1, R.id.mole3, R.id.mole4, R.id.mole5, R.id.mole6, R.id.mole7, R.id.mole8, R.id.mole9, R.id.mole10, R.id.mole11, R.id.mole12};
private boolean gameStarted;
private int randomInt = 0;
private Random rand = new Random();
Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.wam_view_layout);
for (int i = 0; i < 11; i++) {
mole[i] = (ImageView) findViewById(moleId[i]);
mole[i].setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//do stuff eventually
}
});
}
gameStarted = true;
startGame();
}
Any idea why this isn't working? I've been staring at it for hours and I'm quite stumped.
Android doesn't work that way, when onCreate is called, it need to be finished in order for the app to keep responding, I'm surprised you are not getting any "App not respopnding" error.
If you want to create a "game loop" you can simply by creating a new Thread and putting the while in there.
Activity's lifecycle must be executed without blocking them for the app to operate correctly, for more info check here.
Do you know about threads? if you want i can post an example of how to do that with threads but it might be long and if you don't know what a Thread is it will be too confusing for you.
Edit: Ok I'll make an example of a Thread
When I create my games I usually have only one Activity that the only thing it does is creating a custom SurfaceView and nothing else.
public class GameActivity extends Activity
{
//This is a custom class that extends SurfaceView - I will write it below
private GameSurface game;
#Override
protected void onCreate(Bundle bundle)
{
super.onCreate(bundle);
//Create a new instance of the game
game = new GameSurface(this);
//Set the View of the game to the activity
setContentView(game);
}
}
You can also add extra stuff like onSaveInstanceState to save game data and restore them later but I don't want to add them now so the code looks simple.
This class was very simple, let's move on to our SurfaceView. The reason I picked a SurfaceView to do that it's because it is made to allow custom graphics to be drawn on it - exactly what we want on a video game. I will try to make the class as simple as possible:
/*SurfaceHolder.Callback will run some functions in our class when
our surface is completed - at that point we can initialize data
that have to do with the View's width/height.
I don't know if you've noticed that on a View's onCreate()
when you call getWidth() or getHeight() you get 0, that's because
the surface is not initialized yet, this is a way to fix that.
Also we need a Runnable to run the Thread inside this class,
no need to make more classes and make it more complicated*/
public class GameSurface extends SurfaceView
implements SurfaceHolder.Callback, Runnable
{
//This is our thread - we need the "running" variable to be
//able to stop the Thread manually, this will go inside our "while" loop
private Thread thread;
private boolean running;
//Right here you can add more variables that draw graphics
//For example you can create a new class that has a function that
//takes Canvas as a parameter and draws stuff into it, I will add
//a Rect in this case which is a class already made by android
//but you can create your own class that draws images or more
//complicated stuff
private Rect myRect;
//Rect needs a paint to give it color
private Paint myPaint;
//Constructor
public GameSurface(Context context)
{
super(context);
//This is the callback to let us know when surface is completed
getHolder().addCallback(this);
}
//When a class implements SurfaceHolder.Callback you are forced to
//create three functions "surfaceCreated", "surfaceChanged" and
//"surfaceDestroyed" these are called when the surface is created,
//when some settings are changed (like the orientation) and when
//it is about to be destroyed
#Override
public void surfaceCreated(Surface holder)
{
//Let's initialize our Rect, lets assume we want it to have 40
//pixels height and fill the screen's width
myRect = new Rect(0, 0, getWidth(), 40);
//Give color to the rect
myPaint = new Paint();
myPaint.setARGB(0, 255, 0, 0);
//In case you are not familiar with the Rect class, as
//parameters it gets Rect(left, top, right, bottom)
//Time to start our Thread - nothing much to explain here if
//you know how threads work, remember this class implements
//Runnable so the Thread's constructor gets "this" as parameter
running = true;
thread = new Thread(this);
thread.start();
}
//We won't use this one for now, but we are forced to type it
//Even if we leave it empty
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
//When the surface is destroyed we just want the Thread to
//terminate - we don't want threads running when our app is not visible!
#Override
public void surfaceDestroyed(SurfaceHolder holder)
//We will type this function later
{destroyThread();}
//Time for the interesting stuff! let's start with input
#Override
public boolean onTouchEvent(MotionEvent event)
{
//The logic is as follows: when our Rect is touched, we want
//it to become smaller
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (myRect.contains((int) event.getX(), (int) event.getY())
{
myRect.right -= 5;
//Return true - we did something with the input
return true;
}
}
return super.onTouchEvent(event);
}
//This is our update, it will run once per frame
private void update()
{
//Let's assume we want our rect to move 1 pixel downwards
//on every frame
myRect.offset(0, 1);
}
//Now for our draw function
public void draw(Canvas canvas)
{
//Here we want to draw a background and our rect
canvas.drawARGB(0, 0, 0, 255);
canvas.drawRect(myRect, myPaint);
}
//The only thing left is our run() function for the Thread
#Override
public void run()
{
//Screen
Canvas canvas;
//Our game cycle (the famous while)
while(running)
{
//Count start time so we can calculate frames
int startTime = System.currentTimeMillis();
//Update our game
update();
//Empty screen so it can obtain new instance
canvas = null;
//Try locking the canvas for pixel editing on surface
try
{
//Try getting screen
canvas = getHolder().lockCanvas();
//Succeeded
if (canvas != null) synchronized (getHolder())
{
//Actual drawing - our draw function
draw(canvas);
}
} finally
{
//Draw changes
if (canvas != null) getHolder().unlockCanvasAndPost(canvas);
}
//End Frame - 1000/30 means 30 frames per second
int frameTime = System.currentTimeMillis() -startTime;
if (frameTime < 1000/30)
try { Thread.sleep(1000/30 -frameTime); } catch (InterruptedException e){}
}
}
//Last but not least, our function for closing the thread
private void destroyThread()
{
//Stop thread's loop
running = false;
//Try to join thread with UI thread
boolean retry = true;
while (retry)
{
try {thread.join(); retry = false;}
catch (InterruptedException e) {}
}
}
}
I may have made some minor mistakes (probably with case sensitive letters) so feel free to correct these, I wrote the code at once so I didn't have time to test it, it should work flawlessly though.
If you have any more questions, need more explanation or something is not working right let me know!

Android Development: App is lagging when ontouch listener is inactive

I haven't been able to find a question online similar to this, so I thought I would submit the question. In most cases it seems people have the opposite problem were lag may be occurring during a touch event, but I am seeing the exact opposite. I am creating an air hockey game and each frame the pieces are moved based on their current parameters, and the game is redrawn while in the background the override ontouch listener is looking for motion events. The thing is there is noticeable lag when there is no fingers touching the screen and just watching the animated pieces, but as long as there is any form of motion event happening, if ontouch is being called, then the animation is smooth. I really can not figure why this is, my best educated guess is the interrupt that is checking if ontouch should be called is consuming more resources than it should, but I know nothing about how to modify or check the ontouch interrupt behavior at all so hopefully someone might know.
A little more background on how everything is organized. The overall class is located in Main.java, it is created here and requests the Context view, the game class is an extension of ImageView. The game class has a #override OnTouchEvent, which has all the defintions for what to do depending on what state and what motion event. The class also has a draw method, and at the end of the method it calls h.postDelayed(r, FRAME_RATE); where r is a Runnable with #Override public void run() which just calls invalidate();. h is just a handler that is initialized in the class. So each time the game is drawn invalidate will be called after the FRAME_RATE elapse time of 10ms, which tells the game to redraw. All the move functions for the game are also called from the draw method. The OnTouch is happening all along side this process so why would it be smoother if on OnTouch is being called rather than checking if its true, it all seems counter intuitive but I'm sure there is a logical reason. Lastly the lag time was measurable using the system clock and was time dependent based on where exactly it was called. It just showed an increase in passing time between the games move function when a touch event was not occurring.
Sorry for the long response without much actual code context, I hope it is descriptive enough. The code its self is rather long so I didn't think it would help to post that, but if needed I can do better at re posting some pseudo code with the visualized hierarchy. Thank you for any help and suggestions for this problem. It would be great to be able to understand why it is happening.
Here is the code, I was trying to fit it into a comment didn't realize you had to post it by editing the original post.
public class Main extends Activity {
Game game;
private Menus menus; // contains menu info
int menu; //keep track of what menu you are at
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
game = new Game(this);
setContentView(game);
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
..some code here to process photo files
}
//get the orientation of loaded pictures
public static int getOrientation(Context context, Uri photoUri) {
/* it's on the external media. */
Cursor cursor = context.getContentResolver().query(photoUri,
new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null);
if (cursor.getCount() != 1) {
return -1;
}
cursor.moveToFirst();
return cursor.getInt(0);
}
public void Keyboard()
{
use keyboard listener and perform some actions if...
}
public class Game extends ImageView{
private Game game;
//Touch Events
boolean pressed;
private Context mContext;
private Handler h;
private final int FRAME_RATE = 10;
boolean initDimension = false;
//constant for defining the time duration between the click that can be considered as double-tap
static final int MAX_DURATION = 500;
Bitmap background;
int HEIGHT;
int WIDTH;
public Game(Context context) {
super(context);
mContext = context;
h = new Handler();
//ModeInit(); // load up puck game mode
menu = 0; //after tap from ontouch changes to menu = 1 for game
}
private Runnable r = new Runnable() {
#Override
public void run() {
invalidate();
}
};
#Override
public boolean onTouchEvent(MotionEvent e)
{
Switch Statement...
CHECK MOTION EVENT TYPE
CHECK menu state.. perform action
}
public void ModeInit()
{
game = new Game( mContext, WIDTH, HEIGHT );
}
protected void onDraw(Canvas c)
{
if(!initDimension)
{
WIDTH = this.getWidth();
HEIGHT = this.getHeight();
initDimension = true;
}
else
{
//logo screen
if(menu == 0)
{
menu = menus.DisplayIntro(c);
}
//game mode
else if(menu == 1)
{
game.paint_game(c);
long run_test = System.nanoTime()/1000;
game.Move();
Log.d("run time: ",": "+(System.nanoTime()/1000-run_test)); //Ontouch LAG TEST
}
... other menu items here
}
h.postDelayed(r, FRAME_RATE);
}
}
}

Lazy fetching of paginated objects using RxJava

I'm almost sold to RxJava, which is a perfect companion to Retrofit, but I'm struggling into a common pattern while migrating my code: to save bandwidth, I'd like to lazily fetch (paginated) objects from my webservice as needed, while my listview (or recyclerview) is scrolling using reactive programming.
My previous code was doing the job perfectly, but reactive programming seems worth the try.
Listening to listview/recyclerview scrolling (and other boring stuffs) isn't the concern and getting an Observable is easy using Retrofit:
#GET("/api/messages")
Observable<List<Message>> getMessages(#Path("offset") int offset, #Path("limit") int limit);
I just can't figure out the pattern to use in reactive programming.
The Concat operator seems a good starting point, along with ConnectableObservable at some point to defer emission and maybe flatMap, but how ?
EDIT:
Here's my current (naive) solution:
public interface Paged<T> {
boolean isLoading();
void cancel();
void next(int count);
void next(int count, Scheduler scheduler);
Observable<List<T>> asObservable();
boolean hasCompleted();
int position();
}
And my implementation using a subject:
public abstract class SimplePaged<T> implements Paged<T> {
final PublishSubject<List<T>> subject = PublishSubject.create();
private volatile boolean loading;
private volatile int offset;
private Subscription subscription;
#Override
public boolean isLoading() {
return loading;
}
#Override
public synchronized void cancel() {
if(subscription != null && !subscription.isUnsubscribed())
subscription.unsubscribe();
if(!hasCompleted())
subject.onCompleted();
subscription = null;
loading = false;
}
#Override
public void next(int count) {
next(count, null);
}
#Override
public synchronized void next(int count, Scheduler scheduler) {
if (isLoading())
throw new IllegalStateException("you can't call next() before onNext()");
if(hasCompleted())
throw new IllegalStateException("you can't call next() after onCompleted()");
loading = true;
Observable<List<T>> obs = onNextPage(offset, count).single();
if(scheduler != null)
obs = obs.subscribeOn(scheduler); // BEWARE! onNext/onError/onComplete will happens on that scheduler!
subscription = obs.subscribe(this::onNext, this::onError, this::onComplete);
}
#Override
public Observable<List<T>> asObservable() {
return subject.asObservable();
}
#Override
public boolean hasCompleted() {
return subject.hasCompleted();
}
#Override
public int position() {
return offset;
}
/* Warning: functions below may be called from another thread */
protected synchronized void onNext(List<T> items) {
if (items != null)
offset += items.size();
loading = false;
if (items == null || items.size() == 0)
subject.onCompleted();
else
subject.onNext(items);
}
protected synchronized void onError(Throwable t) {
loading = false;
subject.onError(t);
}
protected synchronized void onComplete() {
loading = false;
}
abstract protected Observable<List<T>> onNextPage(int offset, int count);
}
Here's one out of a few potential ways to handle reactive paging. Let's assume we have a method getNextPageTrigger which returns an Observable emits some event object when the scroll listener (or whatever input) wants a new page to be loaded. In real life it would probably have the debounce operator, but in addition to that we'll make sure we only trigger it after the latest page has loaded.
We also define a method to unwrap the messages from their list:
Observable<Message> getPage(final int page) {
return service.getMessages(page * PAGE_SIZE, PAGE_SIZE)
.flatMap(messageList -> Observable.from(messageList));
}
Then we can make the actual fetching logic:
// Start with the first page.
getPage(0)
// Add on each incremental future page.
.concatWith(Observable.range(1, Integer.MAX_VALUE)
// Uses a little trick to get the next page to wait for a signal to load.
// By ignoring all actual elements emitted and casting, the trigger must
// complete before the actual page request will be made.
.concatMap(page -> getNextPageTrigger().limit(1)
.ignoreElements()
.cast(Message.class)
.concatWith(getPage(page))) // Then subscribe, etc..
This is still missing a couple potentially important things:
1 - This obviously doesn't know when to stop fetching additional pages, which means once it hits the end, depending on what the server returns, it could either keep hitting errors or empty results every time scroll is triggered. Approaches to solving this depend on how you signal to the client that there are no more pages to load.
2 - If you need error retries, I would suggest looking into the retryWhen operator. Otherwise, common network errors could cause an error in a page load to propagate.

In android Eclipse - I'm unable to upload new screen(layout) at the moment the progress bar finished

The Error that I receive is "Unfortunately XXXXXX has stopped".Probably something wrong in the onContinue function.
When the progress bar finished it's upload I want him to view the next layout MainScreen.class
Any help will be highly appreciated.
Here is my code:
public class MainActivity extends Activity {
protected static final int TIMER_RUNTIME = 10000; // in ms --> 10s
protected boolean mbActive;
protected ProgressBar mProgressBar;
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.apploading);
mProgressBar = (ProgressBar)findViewById(R.id.adprogress_progressBar);
final Thread timerThread = new Thread() {
#Override
public void run() {
mbActive = true;
try {
int waited = 0;
while(mbActive && (waited < TIMER_RUNTIME)) {
sleep(200);
if(mbActive) {
waited += 200;
updateProgress(waited);
}
}
} catch(InterruptedException e) {
// do nothing
} finally {
onContinue();
}
}
};
timerThread.start();
}
#Override
public void onDestroy() {
super.onDestroy();
}
public void updateProgress(final int timePassed) {
if(null != mProgressBar) {
// Ignore rounding error here
final int progress = mProgressBar.getMax() * timePassed / TIMER_RUNTIME;
mProgressBar.setProgress(progress);
}
}
public void onContinue() {
// Moved to the Application to the Main Screen
Intent intent = new Intent(this, MainScreen.class);
startActivity(intent);
}
}
You are sleeping in the UI thread, which will cause an ANR. I'm not sure exactly what you are trying to do but if you wish to execute long-running tasks have a look at Asynctask or a Handler.
Also have a read here on keeping your application responsive and avoiding ANRS. Keeping Your App Responsive
Android applications normally run entirely on a single thread by default the "UI thread" or "main thread"). This means anything your application is doing in the UI thread that takes a long time to complete can trigger the ANR dialog because your application is not giving itself a chance to handle the input event or intent broadcasts.
Therefore, any method that runs in the UI thread should do as little work as possible on that thread. In particular, activities should do as little as possible to set up in key life-cycle methods such as onCreate() and onResume(). Potentially long running operations such as network or database operations, or computationally expensive calculations such as resizing bitmaps should be done in a worker thread (or in the case of databases operations, via an asynchronous request).
updateProgress(waited);
can not be executed on your timerThread. All the operations that modify the UI have to be executed on the UI Thread. Use an Handler or runOnTheUiThread
final finalWaited = waited;
runOnUiThread(new Runnable() {
public void run() {
updateProgress(finalWaited);
}
});

Make calculations and drawing independent of CPU speed in Android's SurfaceView

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();

Categories

Resources