Android - Creating a Timer loop for a pong game

2019-03-02 04:54发布

问题:

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?

回答1:

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