【问题标题】:Android game programming - onDraw() stutteringAndroid游戏编程——onDraw()口吃
【发布时间】:2016-06-24 14:51:59
【问题描述】:

我正在创建一个动画,基本上一个球在屏幕上移动。但是,我遇到了口吃,使游戏的可玩性降低。请看一下我的代码,非常欢迎任何指向正确方向的人!

GameActivity.java -> 启动游戏循环,并处理更新。

Thread gameThread = new Thread(this);

public boolean running = true;

private final static int MAX_FPS = 60;
private final static int MAX_FRAME_SKIPS = 5;
private final static int FRAME_PERIOD = 1000000000/MAX_FPS;

private final static int FPS_COUNTER_REPORTTIME = 1000000000;
long lastFPSReport;
int UPS; //updates per second
public static int DPS; //draws per second

public SurfaceView gameView;

public void init() {
    WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();

    Point screenDim = new Point();
    display.getRealSize(screenDim);

    width = screenDim.x;
    height = screenDim.y;

    initGame();

    gameThread.start();
}

public void initGame() {
    fingerX = (width / 2);
    fingerY = height * 0.90f;

    GamePlatform platform = new GamePlatform(fingerX, fingerY, width * 0.3f, height * 0.025f, 0, 0);
    GameBall ball = new GameBall(fingerX - 50, height * 0.8f - 50, 100, 100, 20, 20);
}

@Override
public void run() {
    Canvas canvas = null;

    Log.d("testlog", "Starting game loop");

    lastFPSReport = System.nanoTime();

    while(running) {
        beginTime = System.nanoTime();
        framesSkipped = 0;

        try{
            canvas = gameView.getHolder().lockCanvas();

            update();

            synchronized (gameView.getHolder()) {
                gameView.postInvalidate();
            }

            timeDiff = System.nanoTime() - beginTime;
            sleeptime = (int)(FRAME_PERIOD - timeDiff);

            long sleeptimeMillis = (long) (Math.floor(sleeptime / 1000000));
            int sleeptimeNanos = (int)(sleeptime - (sleeptimeMillis * 1000000));

            if(sleeptime > 0) {
                try{
                    Thread.sleep(sleeptimeMillis, sleeptimeNanos);
                }catch(InterruptedException e) {}
            }

            while (sleeptime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
                //only update

                update();

                sleeptime += FRAME_PERIOD;
                framesSkipped++;
            }
        }finally {
            if(canvas != null) {
                gameView.getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }
}

public void update() {
    UPS++;

    //FPS counter
    if(System.nanoTime() - lastFPSReport > FPS_COUNTER_REPORTTIME) {
        Log.d("testlog", "UPS: " + UPS + " | DPS: " + DPS);
        lastFPSReport = System.nanoTime();
        UPS = 0;
        DPS = 0;
    }

    //Entities
    GameEntity.updateAllEntities();
}

GameBall.java -> 处理更新和绘制球,扩展 GameEntity.java(见下文)

public class GameBall extends GameEntity {

public GameBall(float x, float y, float width, float height, float dx, float dy) {
    super(x, y, width, height, dx, dy, "ballMain");
}

@Override
public void update() {
    ArrayList<GameEntity> entityList = getEntityList();

    //Collision

    for(int i = 0; i < entityList.size(); i++) {
        GameEntity entity = entityList.get(i);

        if(entity.getType() != "ballMain") {
            if(this.getX() < entity.getX() + entity.getWidth() && this.getX() + this.getWidth() > entity.getX() && this.getY() < entity.getY() + entity.getHeight() && this.getY() + this.getHeight() > entity.getY()) {
                if(Math.abs(this.getDx()) < Math.abs(entity.getDx())) this.setDx(entity.getDx());
                if(Math.abs(this.getDy()) < Math.abs(entity.getDy())) this.setDy(entity.getDy());

                if(this.getDx() > 20) entity.setDx(20);
                else if(this.getDx() < -20) entity.setDx(-20);
                if(this.getDy() > 20) entity.setDy(20);
                else if(this.getDy() < -20) entity.setDy(-20);
            }
        }
    }

    this.setX(this.getX() + this.getDx());
    this.setY(this.getY() + this.getDy());

    //walls

    if(this.getX() + this.getWidth() >= GameActivity.width || this.getX() <= 0) {
        this.setDx(-this.getDx());
    }

    if(this.getY() <= 0) {
        this.setDy(-this.getDy());
    }else if(this.getY() + this.getHeight() >= GameActivity.height) {
        Log.d("testlog", "game over");
        this.setDy(-this.getDy());
    }
}

@Override
public void draw(Canvas canvas) {
    Paint paint = new Paint();
    paint.setColor(Color.BLUE);

    canvas.drawOval(new RectF(this.getX(), this.getY(), this.getX() +      this.getWidth(), this.getY() + this.getHeight()), paint);
}
}

GameEntity.java:

public abstract class GameEntity {

float x = 0, y = 0, width = 0, height = 0;
float dx = 0, dy = 0;
String type;

static ArrayList<GameEntity> entityList = new ArrayList();

public GameEntity(float x, float y, float width, float height, float dx, float dy, String type) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.dx = dx;
    this.dy = dy;
    this.type = type;

    entityList.add(this);
}

public static void updateAllEntities() {
    for(int i = 0; i < entityList.size(); i++) {
        entityList.get(i).update();
    }
}

public void update() {
    this.x += this.dx;
    this.y += this.dy;
}

public static void drawAllEntities(Canvas canvas) {
    for(int i = 0; i < entityList.size(); i++) {
        entityList.get(i).draw(canvas);
    }
}

public abstract void draw(Canvas canvas);

//Getters and Setters

public ArrayList<GameEntity> getEntityList() {
    return entityList;
}

public float getX() {
    return this.x;
}

public void setX(float x) {
    this.x = x;
}

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

public void setY(float y) {
    this.y = y;
}

public float getWidth() {
    return this.width;
}

public void setWidth(float width) {
    this.width = width;
}

public float getHeight() {
    return this.height;
}

public void setHeight(float height) {
    this.height = height;
}

public Point getAbsCenter() {
    return new Point((int)(this.x + (this.width / 2)), (int)(this.y + (height / 2)));
}

public Point getSurfaceCenter() {
    return new Point((int)(this.x + (this.width / 2)), (int)(this.y));
}

public float getDx() {
    return this.dx;
}

public void setDx(float speed) {
    this.dx = speed;
}

public float getDy() {
    return this.dy;
}

public void setDy(float speed) {
    this.dy = speed;
}

public String getType() {
    return this.type;
}

public void setType(String type) {
    this.type = type;
}
}

FPS 计数器日志示例:

06-24 16:40:43.304 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 51 | DPS: 46
06-24 16:40:44.308 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 55 | DPS: 55
06-24 16:40:44.786 30689-30793/com.zenodhaene.keepitup D/testlog: game over
06-24 16:40:45.324 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 49 | DPS: 49
06-24 16:40:46.330 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 53 | DPS: 53
06-24 16:40:46.566 30689-30793/com.zenodhaene.keepitup D/testlog: game over
06-24 16:40:47.345 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 54 | DPS: 54
06-24 16:40:48.344 30689-30793/com.zenodhaene.keepitup D/testlog: game over
06-24 16:40:48.363 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 56 | DPS: 56
06-24 16:40:49.490 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 50 | DPS:  19
06-24 16:40:50.213 30689-30793/com.zenodhaene.keepitup D/testlog: game over
06-24 16:40:50.514 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 60 | DPS: 0
06-24 16:40:51.516 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 63 | DPS: 1
06-24 16:40:51.855 30689-30793/com.zenodhaene.keepitup D/testlog: game over
06-24 16:40:52.522 30689-30793/com.zenodhaene.keepitup D/testlog: UPS: 62 | DPS: 44

【问题讨论】:

    标签: java android android-canvas surfaceview game-physics


    【解决方案1】:

    根据经验,您应该绝对避免在 onDraw() 中分配/释放内存。

    您正在使用它来创建Paint 对象。由于 onDraw() 每秒被调用多达 60 次,因此创建方法的性能影响巨大。只需将您的 Paint 对象移动到类的一个字段并初始化一次。这会给你一个很好的保存初学者。

    Yan 对更新非常了解。使用某种插值会给你一个更平滑的动画。有一个很棒的article,你可以看看以优化你的游戏线程。

    【讨论】:

    • 首先感谢两位的回答。但是,当我测量 timeDifference 并将其除以理论帧长度 (1.000.000.000 ns/60) 时,我发现某些帧占用的帧长度是正常帧长度的 6 倍,而其他帧则在该限制之下执行得很好.所以你会认为每一帧的长度大致相等,但事实并非如此......
    • 主要目标是计算插值 - 两次渲染之间的时间差与您所需的帧速率之间的比率。然后你在你的更新方法中传递它,所以你的对象可以在需要时访问它(几乎总是需要它,因为所有设备之间的恒定帧速率是一个神话)。因此,如果您的球在每次更新之间必须以 10 的速度移动,并且您的插值为 0.2,那么您在下一次渲染时将其移动 2 个点。这样,您的更新率是恒定的,并且您的动画是流畅的。
    • 所以你是说我不应该使用框架帽?现在,帧数上限为 60 帧,线程休眠的剩余时间。因此,只有帧时间高于允许的帧才会在轮询中使用 deltatime。否则,interpollat​​ion 为 1。只有几帧的帧时间差异很大(例如 1/10 秒,而不是正常的 1/60 秒)
    • 我会推荐它,是的。限制帧是确保您在多个设备上获得一致帧速率的最佳方式。还有一种检查跳过更新的机制,因此,如果您开始删除,您将能够相应地重新调整您的对象。
    【解决方案2】:

    在视频游戏中常用的一种解决方案是将每个运动因子乘以最后一帧之间的时间(也称为增量时间),您应该尝试看看这个

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-17
      • 1970-01-01
      • 1970-01-01
      • 2011-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多