Java 2d игра - Управление скоростью анимации - PullRequest
0 голосов
/ 11 июня 2018

Я играю в маленькую астероидную игру, и у меня возникают некоторые проблемы с управлением скоростью анимации, например, скажем, у меня в игре 20 астероидов, когда я уничтожаю астероид, количество астероидов уменьшается (очевидно), потому чтов игре меньше объектов, число кадров в секунду увеличивается, а скорость анимации астероидов становится все быстрее и быстрее, я исправил это, настроив скорость анимации в соответствии с количеством астероидов, которые у меня есть в игре, но я тоже столкнулся с другимпроблема со взрывами, когда я уничтожаю астероид, я могу сделать то же самое, что я сделал с астероидами, я полагаю, но я просто думаю, что это не очень мудрый способ "решить" это и кажется мне плохой практикой.Я думал об ограничении 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);
        }
    }
 }

Класс взрыва:

package com.asteroids.model;

import java.awt.Graphics;
import java.awt.Image;

import com.asteroids.controller.*;
import com.asteroids.view.LoadImages;


public class Explosion extends GameObject {

private AllObjects objects;
private Image explosion;
private float frame = 0;
private float animSpeed = 0.09f;
private int frameCount = 48;

public Explosion(float x, float y, AllObjects objects) {
    super(x, y, ID.EXPLOSION, 1);
    this.objects = objects;
}

public void render(Graphics g) {
    explosion(g);
}

public void explosion(Graphics g) {
    frame += animSpeed;
    if (frame > frameCount) {
        frame -= frameCount;
    }
    explosion = LoadImages.getExplosion().getSubimage((int) frame * 256, 0, 256, 256);
    g.drawImage(explosion, (int) x, (int) y, 110, 110, null);
    if (frame >= 47.8f) {
        objects.removeObject(this);
    }
}

public void tick() {

}

public void setAnimSpeed(float animSpeed) {
    this.animSpeed = animSpeed;
}
}

Один маленький вопрос: в моей игре я использую Enum для идентификатора объектов, я пытаюсь реализовать MVC в своей игре, и я не совсем уверен вэто, все идет внутри одного из них (модель, контроллер, вид)?если нет, я просто помещаю Enum в отдельный пакет?Заранее спасибо за помощь!

1 Ответ

0 голосов
/ 11 июня 2018

Ваш основной цикл генерирует неравные обновления.Если я ничего не делаю, я получаю где-то между 7799913 и 8284754 кадров в секунду, однако, если я добавлю задержку 8 миллисекунды (для имитации некоторой работы), она упадет примерно до 115 - 120 кадров в секунду.

Ваше намерение состоит в том, чтобы попытаться сделать частоту кадров настолько равномерной, насколько это возможно, это обеспечит неизменность скорости анимации

Лично мне не нравится«вращая» стиль игрового цикла, это означает, что циклу разрешено потреблять циклы ЦП, фактически ничего не делая, где эти циклы могут использоваться для выполнения более важной работы, такой как обновление пользовательского интерфейса.

В большинстве случаев я просто использую Swing Timer, настроенный на что-то вроде 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 миллисекунд, цикл «подождет» оставшееся время, чтобы обеспечить некоторое пространство для ЦП, чтобы дать время другим потокам (и не занимать обновление ненужного времени на ЦП)

В приведенном выше примере я генерирую «случайную» задержку до 32 миллисекунд для тестирования.Установите его на 16, и вы должны получить (примерно) 60 кадров в секунду.

Теперь я знаю, что люди чрезвычайно увлечены этими вещами, поэтому, если вы используете Thread.sleep и Duration, чтобы ваша кожа ползла, вы "могли быиспользовать цикл «free wheeling», что-то вроде того, что представлено в цикле основной игры Java

Ниже приведен пример реализации, я установил количество обновлений и кадров в секунду для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;
        }
    }
}

Опять же, Thread.sleep здесь просто для того, чтобы ввести случайное количество "работы".Поскольку он допускает задержку более 16 мс, вы также обнаружите, что он «сбрасывает» кадры.Ваша работа будет сводиться к тому, чтобы вы работали до 16 мс за проход

...