0

I'm making a small Android game in which I need two ImageViews (obstacles) to move, at the same time, from the top of the screen to the bottom of the screen; starting and ending OUTSIDE of the screen. Doing this would be a start.

I've got a timer, and each time the timer ends, it runs again and I would also want two other ImageViews (same as two previous ones) to move in the same way described above.

In other words:

  • Some random event triggers a timer
  • Timer hits threshold -> timer resets and two ImageViews start their way down

What I've got kind of works, but with Bitmaps and Y incrementation. The problem is, because the phone increments as fast as it can, the game can get laggy and the speed of the two images going down won't be the same on two different phones.

I know I've got to use Animations, but I can't get what I want each time I try something with them. They usually don't start where I want them to be and weird stuff happens.

Here are some links I already visited:

Here's some code to show you what I've got:

GAMEVIEW

public class GameView extends SurfaceView implements SurfaceHolder.Callback {
    // [...]

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        // [...]
        this.obstacles = new ArrayList <> ();

        this.handleObstacleTimer = null;
        this.runnableObstacleTimer = null;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // [...]
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                this.startObstacleTimer();
                GameLogic.gameState = GameState.RUNNING;
                // [...]
        }

        return result;
    }


    @Override
    public void draw (Canvas canvas) {
        super.draw(canvas);

        if (canvas != null) {
            this.background.draw(canvas);
            if (! this.obstacles.isEmpty()) {
                for (Obstacle obstacle : this.obstacles) {
                    obstacle.draw(canvas);
                }
            }
            // [...]
        }
    }


    public void createObstacle () {
        Bitmap imageObstacle = BitmapFactory.decodeResource(this.getResources(), R.drawable.obstacle);
        this.obstacles.add(new Obstacle(imageObstacle, this, this.displayMetrics.widthPixels, this.displayMetrics.heightPixels));
    }

    public void removeObstacle (Obstacle obstacle) {
        this.obstacles.remove(obstacle);
    }

    private void stopObstacleTimer () {
        if (this.handleObstacleTimer != null  &&  this.runnableObstacleTimer != null) {
            this.handleObstacleTimer.removeCallbacks(this.runnableObstacleTimer);
            this.handleObstacleTimer = null;
        }
    }

    private void startObstacleTimer () {
        if (this.handleObstacleTimer == null) {
            final Handler handler = new Handler();
            this.runnableObstacleTimer = new Runnable() {
                @Override
                public void run() {
                    if (GameLogic.gameState == GameState.RUNNING) {
                        createObstacle();
                        handler.postDelayed(this, 2700);
                    }
                }
            };

            handler.postDelayed(this.runnableObstacleTimer, 2700);

            this.handleObstacleTimer = handler;
        }
    }

    // [...]

    // Called by a custom Thread class
    public void update () {
        if (! this.obstacles.isEmpty()) {
            for (Obstacle obstacle : this.obstacles) {
                obstacle.update();
            }
        }
        // [...]
    }
}

MAINTHREAD (custom Thread class)

public class MainThread extends Thread {

    private SurfaceHolder surfaceHolder;
    private GameView gameView;
    private GameLogic gameLogic;

    private boolean running;

    public static Canvas canvas;


    public MainThread (SurfaceHolder surfaceHolder, GameView gameView, GameLogic gameLogic) {
        super();

        this.surfaceHolder = surfaceHolder;
        this.gameView = gameView;
        this.gameLogic = gameLogic;
    }


        @Override
        public void run() {
            while (this.running) {
                this.canvas = null;

            try {
                this.canvas = this.surfaceHolder.lockCanvas();
                synchronized (this.surfaceHolder) {
                    this.gameView.update();
                    this.gameView.draw(this.canvas);
                    this.gameLogic.update();
                }
            } catch (Exception e) {

            } finally {
                if (this.canvas != null) {
                    try {
                        this.surfaceHolder.unlockCanvasAndPost(this.canvas);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }


    public void setRunning (boolean isRunning) {
        this.running = isRunning;
    }
}

OBSTACLE

public class Obstacle {

    private static final int GAP_MIN_X = 300;
    private static int GAP_MAX_X;
    private static final int GAP_WIDTH = 250;

    private GameView gameView;

    private int displayHeight;

    private Bitmap obstacleLeft;
    private Bitmap obstacleRight;
    private int leftX;
    private int rightX;
    private int y;

    private boolean passed;


    public Obstacle (Bitmap image, GameView gameView, int displayWidth, int displayHeight) {
        this.gameView = gameView;

        this.displayHeight = displayHeight;

        this.obstacleLeft = this.flip(image);
        this.obstacleRight = image;

        GAP_MAX_X = displayWidth - GAP_MIN_X;
        final int randomX = new Random().nextInt((GAP_MAX_X - GAP_MIN_X) + 1) + GAP_MIN_X;

        this.leftX = randomX - this.obstacleLeft.getWidth() - (GAP_WIDTH / 2);
        this.rightX = randomX + (GAP_WIDTH / 2);

        this.y = 0 - this.obstacleLeft.getHeight();

        this.passed = false;
    }


    private Bitmap flip (Bitmap image) {
        Matrix m = new Matrix();
        m.preScale(-1, 1);
        Bitmap result = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), m, false);
        result.setDensity(DisplayMetrics.DENSITY_DEFAULT);
        return result;
    }


    /**
     * Those getters are used for collision
     */
    public int getLeftX () {
        return this.leftX;
    }

    public int getRightX () {
        return this.rightX;
    }

    public int getY () {
        return this.y;
    }

    public int getWidth () {
        return this.obstacleLeft.getWidth();
    }

    public int getHeight () {
        return this.obstacleLeft.getHeight();
    }


    public boolean hasPassed () {
        return this.passed;
    }

    public void setAsPassed () {
        this.passed = true;
    }


    public void draw (Canvas canvas) {
        canvas.drawBitmap(this.obstacleLeft, this.leftX, this.y, null);
        canvas.drawBitmap(this.obstacleRight, this.rightX, this.y, null);
    }


    public void update () {
        if (! (GameLogic.gameState == GameState.GAMEOVER)) {
            this.y += 8;

            if (this.y > this.displayHeight) {
                this.gameView.removeObstacle(this);
            }
        }
    }
}

Basically, what I want is to have the same result. But without lag and with obstacles going at the same speed no matter which phone the game is running on.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Calvin M.T.
  • 51
  • 1
  • 8
  • If inside your Obstacle.update() function, you change your Y incrementation from the constant integer 8 to something that is proportional to the time interval that the update function is called, it will be the same speed on any device. You could, for example, calculate the variation based on the difference between the time on the last update() call and the current update call – Rander Gabriel Sep 15 '19 at 16:31
  • @RanderGabriel Thanks for your fast response! I thought doing something alike, but I have no idea how code-wise. Would you be possible for you to write it out or link me to related answer? – Calvin M.T. Sep 15 '19 at 16:37
  • i've posted an answer – Rander Gabriel Sep 15 '19 at 16:49

1 Answers1

0

If inside your Obstacle.update() function, you change your Y incrementation from the constant integer 8 to something that is proportional to the time interval that the update function is called, it will be the same speed on any device. You could, for example, calculate the variation based on the difference between the time on the last update() call and the current update call

long speed = 100; //you should add this to adjust the speed of the incrementing
long delta = System.currentTimeMillis();
public void update () {
        delta = speed*(System.currentTimeMillis() - delta);
        if (! (GameLogic.gameState == GameState.GAMEOVER)) {
            this.y += delta;

        if (this.y > this.displayHeight) {
            this.gameView.removeObstacle(this);
        }
    }
}

This way, you delta variable will ever be something that varies with the time interval between the last call and the current call beacause System.currentTimeMillis() returns the current time in miliseconds

Rander Gabriel
  • 637
  • 4
  • 14
  • The code doesn't work as is, so I had to change some small things around (i.e.: `static` not possible inside method if not `final`), but that isn't an issue for me. So I tried replacing the constant with `delta`, but upon running the game, the obstacles aren't coming down. Don't know if they're just moving very, very slowly or if nothing's happening. – Calvin M.T. Sep 15 '19 at 17:24
  • Sorry, I Made a mistake. I've Made an edit to the answer. Could you put a log inside the Update function That Shows the Delta value? And give US the result? – Rander Gabriel Sep 15 '19 at 18:26
  • Note that for this to work you may need to adjust the delta value (by multipliyng a constant) to achieve the desired speed. I'm gonna edit It to show how. – Rander Gabriel Sep 15 '19 at 19:27
  • I managed to make the code work by adding a variable to differentiate current time from previous time. I also managed to get a good speed. The only problem now is that it lags more than before. When I say lag it's more like a lot of jittering. How can I make it smoother? – Calvin M.T. Sep 15 '19 at 20:58
  • It jitters even with the Same speed AS before? – Rander Gabriel Sep 15 '19 at 21:16
  • I can't make it 'exactly' the same speed because the difference between current and previous times aren't always the same. They vary between something like 5 and 17. – Calvin M.T. Sep 15 '19 at 21:22
  • In your game implementation is It possible to call update more times per Second? You commented in your GameView.update() that It is called from a custom thread class. Could you provide it's code – Rander Gabriel Sep 15 '19 at 21:50
  • I just updated the original post with the custom thread class: `MainThread`. – Calvin M.T. Sep 15 '19 at 22:02