Java 控制2d游戏的动画速度

Java 控制2d游戏的动画速度,java,animation,game-physics,2d-games,Java,Animation,Game Physics,2d Games,我正在制作一个小的小行星游戏,我在控制动画速度方面遇到了一些问题 例如,假设我在游戏中有20颗小行星,当我摧毁一颗小行星时,小行星的数量会下降(很明显)。因为游戏中的对象越来越少,fps会上升,小行星的动画速度也越来越快 我通过根据游戏中小行星的数量调整动画速度来修复它,但我也面临着另一个问题,那就是当我摧毁一颗小行星时会发生爆炸。我想我也可以对小行星做同样的事情,但我只是认为这不是一个非常明智的“解决”问题的方法,而且对我来说似乎是一个糟糕的做法 我曾想过给fps设置上限,但我真的不知道该怎么

我正在制作一个小的小行星游戏,我在控制动画速度方面遇到了一些问题

例如,假设我在游戏中有20颗小行星,当我摧毁一颗小行星时,小行星的数量会下降(很明显)。因为游戏中的对象越来越少,fps会上升,小行星的动画速度也越来越快

我通过根据游戏中小行星的数量调整动画速度来修复它,但我也面临着另一个问题,那就是当我摧毁一颗小行星时会发生爆炸。我想我也可以对小行星做同样的事情,但我只是认为这不是一个非常明智的“解决”问题的方法,而且对我来说似乎是一个糟糕的做法

我曾想过给fps设置上限,但我真的不知道该怎么做。我想得到一些建议,以及处理这种情况的最佳方法

我将在这里发布我的主要游戏类,包括游戏循环,以及爆炸类的一个示例,以便您了解代码的总体概念

游戏类和循环:

import com.asteroids.view.*;

public class Game extends Canvas implements Runnable {

private static final long serialVersionUID = -8921419424614180143L;
public static final int WIDTH = 1152, HEIGHT = WIDTH / 8 * 5;

private Thread thread;
private boolean isRunning;
private LoadImages loadImages = new LoadImages();
private Player player = new Player();
private AllObjects objects;
private KeyInput keyInput;
private long delay = 80;
private long currentTime = System.currentTimeMillis();
private long expectedTime = currentTime + delay;
public static BufferedImage test;

public Game() {
    new Window(WIDTH, HEIGHT, "Asteroids!", this);
    objects = new AllObjects();
    objects.addObject(player);
    for (int i = 0; i < 20; i++) {
        objects.addObject(new Rock((int) (Math.random() * (Game.WIDTH - 64) + 1),
                (int) (Math.random() * (Game.HEIGHT - 64) + 1)));
    }
    keyInput = new KeyInput(player);
    this.addKeyListener(keyInput);
}

public void run() {
    this.requestFocus();
    long lastTime = System.nanoTime();
    double amountOfTicks = 60.0;
    double ns = 1000000000 / amountOfTicks;
    double delta = 0;
    long timer = System.currentTimeMillis();
    int frames = 0;

    // main game loop.
    while (isRunning) {
        adjustAsteroidsSpeed();
        destroyAsteroids();
        collisionLoop();

        // used to set delay between every bullet(milliseconds)
        currentTime = System.currentTimeMillis();
        if (KeyInput.shoot && currentTime >= expectedTime) {

            // calculates the accurate position of the x,y on the "circumference" of the
            // player
            float matchedX = player.getX() + 1 + (float) ((player.getRadius() + 32) * Math.cos(player.getRadian()));
            float matchedY = player.getY() - 7 + (float) ((player.getRadius() + 32) * Math.sin(player.getRadian()));
            objects.addObject(new Bullet(matchedX, matchedY, player));
            expectedTime = currentTime + delay;
        }
        destroyBullets();
        long now = System.nanoTime();
        delta += (now - lastTime) / ns;
        lastTime = now;
        while (delta >= 1) {
            tick();
            delta--;
        }
        if (isRunning)
            render();
        frames++;
        if (System.currentTimeMillis() - timer > 1000) {
            timer += 1000;
            System.out.println("FPS: " + frames);
            frames = 0;
        }
    }

    render();

    stop();
    System.exit(1);

}

private void stop() {
    try {
        thread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.exit(1);

}

private void render() {
    BufferStrategy bs = this.getBufferStrategy();
    if (bs == null) {
        this.createBufferStrategy(3);
        return;
    }

    Graphics g = bs.getDrawGraphics();
    g.drawImage(LoadImages.getbackground(), 0, 0, getWidth(), getHeight(), this);
    objects.render(g);
    player.render(g);
    g.dispose();
    bs.show();

}

private void tick() {
    player.tick();
    objects.tick();
}

// starting thread and game loop.
public void start() {
    thread = new Thread(this);
    thread.start();
    isRunning = true;
}

// minimum and maximum possible position for object.
public static float Bounds(float value, float min, float max) {
    if (value >= max) {
        return value = max;
    }
    if (value <= min) {
        return value = min;
    } else {
        return value;
    }

}

// detects collision between two objects
public boolean collision(GameObject a, GameObject b) {
    return (b.getX() - a.getX() + 10) * (b.getX() - a.getX() + 10)
            + (b.getY() - a.getY() + 10) * (b.getY() - a.getY() + 10) < (a.getRadius() + b.getRadius())
                    * (a.getRadius() + b.getRadius());
}

// destroys bullets once they go out of the screen
public void destroyBullets() {
    for (int i = 0; i < objects.getSize(); i++) {
        if (objects.get(i).getId() == ID.BULLET) {
            GameObject bullet = objects.get(i);
            if (bullet.getX() > Game.WIDTH || bullet.getX() < 0 || bullet.getY() > Game.HEIGHT
                    || bullet.getY() < 0) {
                objects.removeObject(bullet);
            }
        }
    }
}

// whenever a collision between an asteroid and a bullet occurs, the asteroid and the bullets are destroyed
public void destroyAsteroids() {
    GameObject bullet = null;
    GameObject bigRock = null;
    for (int i = 0; i < objects.getSize(); i++) {
        if (objects.get(i).getId() == ID.BULLET) {
            bullet = (Bullet) objects.get(i);
            for (int q = 0; q < objects.getSize(); q++) {
                if (objects.get(q).getId() == ID.BIGROCK) {
                    bigRock = objects.get(q);
                    if (bullet != null && bigRock != null) {
                        if (collision(bigRock, bullet)) {
                            objects.addObject(new Explosion(bigRock.getX(), bigRock.getY(), objects));
                            objects.removeObject(bigRock);
                            objects.removeObject(bullet);
                        }
                    }
                }
            }
        }
    }
}

// calculates the amount of asteroids in the game and adjust the asteroids speed
public void adjustAsteroidsSpeed() {
    int rocksCount = 0;
    Rock rock;
    for (GameObject object : objects.link()) {
        if (object.getId() == ID.BIGROCK) {
            rocksCount++;
        }
    }
    for (GameObject object : objects.link()) {
        if (object.getId() == ID.BIGROCK) {
            rock = (Rock) object;
            rock.setAnimSpeed(rocksCount * 0.002f);
        }
    }
 }

您的主循环正在生成不均匀的更新。如果我什么也不做,我会在
7799913
8284754
fps之间,但是,如果我加上
8
毫秒延迟(模拟一些工作),它会下降到
115
-
120
fps左右

您的目的是尝试使帧速率尽可能均匀,这将确保动画速度保持不变

就我个人而言,我不喜欢游戏循环的“随心所欲”风格,这意味着循环可以在不做任何事情的情况下消耗CPU周期,而这些周期可以用来做更重要的工作,比如更新UI

在大多数情况下,我只是使用一个Swing
定时器
设置为类似
5
毫秒的间隔,然后使用日期/时间API来计算现在和上次更新之间的差异,并选择要执行的操作,但是,这假设您使用的是基于Swing的绘制路径。如果您正在进行直接绘制路径(即
BufferStrategy
),则可以使用类似的“循环”来代替

public void run() throws InterruptedException {

    int frames = 0;
    Duration threashold = Duration.ofMillis(1000 / 59);
    Duration cycle = Duration.ofSeconds(1);

    Instant cycleStart = Instant.now();

    // main game loop.
    while (isRunning) {
        Instant start = Instant.now();
        // Some update function...

        Thread.sleep(rnd.nextInt(32));

        Duration processTime = Duration.between(start, Instant.now());
        Duration remainingTime = threashold.minusMillis(processTime.toMillis());
        long delay = remainingTime.toMillis();
        if (delay > 0) {
            Thread.sleep(delay);
        } else {
            System.out.println("Dropped frame");
        }

        frames++;
        // Render the output

        Duration cycleTime = Duration.between(cycleStart, Instant.now());
        if (cycleTime.compareTo(cycle) >= 0) {
            cycleStart = Instant.now();
            System.out.println(frames);
            frames = 0;
        }
    }

}
在本例中,更新和绘制调度代码只有16毫秒的时间来完成任务,否则它将丢弃帧。如果工作时间少于16毫秒,则循环将“等待”剩余时间,以便为CPU提供喘息空间,以便为其他线程留出时间(而不是占用CPU上不必要的更新时间)

在上面的示例中,我为测试生成了高达32毫秒的“随机”延迟。把它设置回16,你应该得到(大约)60帧每秒

现在,我知道人们对这些事情非常感兴趣,所以如果使用
Thread.sleep
Duration
让你的皮肤爬行,你“可以”使用一个“自由旋转”的循环,就像

下面是一个示例实现,我已将每秒更新和帧数设置为60,但您可以更改这些值以满足您的需要

public void run() throws InterruptedException {

    double ups = 60;
    double fps = 60;

    long initialTime = System.nanoTime();
    final double timeU = 1000000000 / ups;
    final double timeF = 1000000000 / fps;
    double deltaU = 0, deltaF = 0;
    int frames = 0, ticks = 0;
    long timer = System.currentTimeMillis();

    while (isRunning) {

        long currentTime = System.nanoTime();
        deltaU += (currentTime - initialTime) / timeU;
        deltaF += (currentTime - initialTime) / timeF;
        initialTime = currentTime;

        if (deltaU >= 1) {
            Thread.sleep(rnd.nextInt(32));
            //getInput();   
            //update();
            ticks++;
            deltaU--;
        }

        if (deltaF >= 1) {
            Thread.sleep(rnd.nextInt(32));
            //render();
            frames++;
            deltaF--;
        }

        if (System.currentTimeMillis() - timer > 1000) {
            System.out.println(String.format("UPS: %s, FPS: %s", ticks, frames));
            frames = 0;
            ticks = 0;
            timer += 1000;
        }
    }
}

同样,这里的
线程.sleep
只是为了注入随机数量的“工作”。因为它允许超过16ms的延迟,所以您也会发现它“丢弃”帧。你的工作将是将你的每次传球时间降低到16毫秒以下

你的游戏循环计时似乎是错误的(我很确定它不会每秒产生10到数千帧),这会产生不均衡的更新量。你需要设计一个解决方案,试图达到每秒特定数量的“渲染”,并尽可能做到这一点。我只是在这篇文章中读到了关于timestep的内容:但我真的不确定在我的情况下我想要哪一个,以及为什么。你知道这件事吗?你能告诉我该找哪一件吗?谢谢。首先非常感谢您的详细回答,非常感谢!我意识到问题其实是游戏循环产生不稳定的fps,我开始寻找一些游戏循环等等,我一定会尝试使用你提供的,看看它是如何工作的,一般来说,我现在在这里读一篇关于游戏循环的文章:他说使用计时器和线程。睡眠不好,但我想每个游戏都有不同的需求,所以我会尝试一下,看看它是如何工作的,再次感谢。计时器和
线程的问题在于睡眠
只能保证至少指定的时间量,也就是说,他们可以比你要求的睡眠/时间更长,但自由循环也是一个问题,所以这是一个交易,我试着使用你的gameloop和我之前发布的文章中的另一个gameloop,两个都很好,我觉得它们之间没有任何区别,所以首先感谢你的帮助,它起了作用,解决了我的问题,我的问题是你是否可以看看另一个gameloop,告诉我你认为哪一个更好,为什么。这是游戏循环:,谢谢:]如果FPS保持不变,那么游戏循环运行良好,它很可能在更新/渲染过程中。您还可能受到GC开销的影响(尤其是在创建/销毁丢失的对象时)。我有一个类似的问题,通过使用对象缓存来缓存可重用的对象来解决。如果这没有帮助,那么您需要调查您的更新/碰撞/渲染代码,以进一步了解当您有一群或小行星来来往往时,速度可能会减慢。你只需从“活动”(屏幕上)列表中删除小行星并将其放入“缓存”列表中,而不是在每次需要时创建新的小行星。当你需要另一个星体时,你从这个缓存中取出它并把它放回活动列表中——如果缓存中没有任何东西,你就创建一个新的。这样做的目的是减少GC(垃圾收集)开销,这可能会降低系统的运行速度
public void run() throws InterruptedException {

    double ups = 60;
    double fps = 60;

    long initialTime = System.nanoTime();
    final double timeU = 1000000000 / ups;
    final double timeF = 1000000000 / fps;
    double deltaU = 0, deltaF = 0;
    int frames = 0, ticks = 0;
    long timer = System.currentTimeMillis();

    while (isRunning) {

        long currentTime = System.nanoTime();
        deltaU += (currentTime - initialTime) / timeU;
        deltaF += (currentTime - initialTime) / timeF;
        initialTime = currentTime;

        if (deltaU >= 1) {
            Thread.sleep(rnd.nextInt(32));
            //getInput();   
            //update();
            ticks++;
            deltaU--;
        }

        if (deltaF >= 1) {
            Thread.sleep(rnd.nextInt(32));
            //render();
            frames++;
            deltaF--;
        }

        if (System.currentTimeMillis() - timer > 1000) {
            System.out.println(String.format("UPS: %s, FPS: %s", ticks, frames));
            frames = 0;
            ticks = 0;
            timer += 1000;
        }
    }
}