Таким образом, существует целый ряд вопросов, которые выскакивают сразу ...
- апплетов тупика, большинство браузеров будут активно блокировать их и/или прекратили поддержку плагина
- Вы добавляете в апплет
JPanel
, но переопределяя метод апплета paint
, из-за того, что картина может работать, панель может быть нарисована независимо от апплета, заставляя ее рисовать все, что вы могли бы нарисовать.
- Вам не нужно несколько таймеров, вам просто нужно иметь лучшие значения дельта (меньше было быстрее)
KeyListener
- это плохой выбор для обнаружения ввода на клавиатуре, он довольно низкий и имеет проблемы с фокусировкой, которые легко преодолеть, используя клавиатурных комбинации API
Я бы начать иметь взгляд на Performing Custom Painting, Painting in AWT and Swing и How to Use Key Bindings для получения более подробной информации.
Итак, как бы вы начали его исправлять? Начните с использования JPanel
в качестве основного контейнера, переопределите его paintComponent
и поместите всю свою логику рисования.
Используйте один Timer
для управления обновлениями
Есть любое количество подходов, которые можно предпринять, но я хотел бы начать с определения некоторых договоров, которые определяют, что может пойти в игре
public interface GameSpace extends ImageObserver {
public Dimension getGameSpace();
public boolean hasInput(Input input);
}
public interface Entity {
public void paint(GameSpace gameSpace, Graphics2D g2d);
public boolean update(GameSpace gameSpace);
public Rectangle getBounds();
}
public abstract class AbstractEntity implements Entity {
private int x;
private int y;
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
protected abstract int getWidth();
protected abstract int getHeight();
public Rectangle getBounds() {
return new Rectangle(getX(), getY(), getWidth(), getHeight());
}
}
public abstract class AbstractImageEntity extends AbstractEntity {
protected abstract BufferedImage getImage();
@Override
protected int getWidth() {
return getImage().getWidth();
}
@Override
protected int getHeight() {
return getImage().getHeight();
}
}
Это отделяет некоторые элементы управления ключевыми элементами, что позволяет использовать более гибкий дизайн. У вас может быть намного больше сущностей, которые могут перемещаться, те, которые не могут, некоторые из них окрашены, некоторые из которых не являются, но все, что обеспечивает поддержку ядра для выполнения работы.
После того, как вы есть, что вы можете начать определять некоторые ключевые объекты вам нужно
public class ShipEntity extends AbstractImageEntity {
private BufferedImage ship;
public ShipEntity(GameSpace gameSpace) throws IOException {
ship = ImageIO.read(getClass().getResource("/resources/ship.png"));
setY(gameSpace.getGameSpace().height - getBounds().height);
setX((gameSpace.getGameSpace().width - getBounds().width)/2);
}
@Override
public BufferedImage getImage() {
return ship;
}
@Override
public void paint(GameSpace gameSpace, Graphics2D g2d) {
g2d.drawImage(ship, getX(), getY(), gameSpace);
}
@Override
public boolean update(GameSpace gameSpace) {
int x = getX();
if (gameSpace.hasInput(Input.LEFT)) {
x -= 2;
}
if (gameSpace.hasInput(Input.RIGHT)) {
x += 2;
}
if (x < 0) {
x = 0;
} else if (x + getWidth() > gameSpace.getGameSpace().width) {
x = gameSpace.getGameSpace().width - getWidth();
}
setX(x);
return true;
}
}
public class InvaderEntity extends AbstractImageEntity {
private BufferedImage invader;
public InvaderEntity() throws IOException {
invader = ImageIO.read(getClass().getResource("/resources/Invader.png"));
}
@Override
protected BufferedImage getImage() {
return invader;
}
@Override
public void paint(GameSpace gameSpace, Graphics2D g2d) {
g2d.drawImage(invader, getX(), getY(), gameSpace);
}
@Override
public boolean update(GameSpace gameSpace) {
return true;
}
}
public class ProjectileEntity extends AbstractEntity {
private int delta;
public ProjectileEntity(int delta) {
this.delta = delta;
}
@Override
protected int getWidth() {
return 10;
}
@Override
protected int getHeight() {
return 10;
}
@Override
public void paint(GameSpace gameSpace, Graphics2D g2d) {
g2d.setColor(Color.RED);
int width = getWidth();
int height = getHeight();
g2d.fillOval(getX() - width/2, getY() - height/2, width, height);
}
@Override
public boolean update(GameSpace gameSpace) {
int y = getY() + delta;
setY(getY() + delta);
return y + getHeight() >= 0 && y + getHeight() <= gameSpace.getGameSpace().height;
}
}
В основном, они содержат логику, необходимую для получения выполнять свою работу.
Наконец, вам нужно настроить фактическую игру UI
public class GamePane extends JPanel implements GameSpace {
private Set<Input> inputs;
private Entity playerEntity;
private List<Entity> projectileEntities;
private List<Entity> invaderEntities;
private long timeOfLastProjectile = -1;
public GamePane() throws IOException {
setBackground(Color.BLACK);
inputs = new HashSet<>(2);
playerEntity = new ShipEntity(this);
projectileEntities = new ArrayList<>(25);
invaderEntities = new ArrayList<>(25);
InvaderEntity invader = new InvaderEntity();
invader.setX((getGameSpace().width - invader.getBounds().width)/2);
invader.setY((getGameSpace().height - invader.getBounds().height)/2);
invaderEntities.add(invader);
addKeyBinding(Input.LEFT, "left", KeyEvent.VK_LEFT);
addKeyBinding(Input.RIGHT, "right", KeyEvent.VK_RIGHT);
addKeyBinding(Input.SPACE, "space", KeyEvent.VK_SPACE);
Timer timer = new Timer(15, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateState();
processCollisions();
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
protected void updateState() {
playerEntity.update(this);
if (hasInput(Input.SPACE)) {
long time = System.currentTimeMillis() - timeOfLastProjectile;
if (time < 0 || time > 1000) {
timeOfLastProjectile = System.currentTimeMillis();
Rectangle bounds = playerEntity.getBounds();
ProjectileEntity projectile = new ProjectileEntity(-1);
int x = bounds.x + ((bounds.width - projectile.getWidth())/2);
int y = bounds.y - projectile.getHeight();
projectile.setX(x);
projectile.setY(y);
projectileEntities.add(projectile);
}
}
for (Entity entity : invaderEntities) {
entity.update(this);
}
List<Entity> outOfBounds = new ArrayList<>(25);
for (Entity entity : projectileEntities) {
if (!entity.update(this)) {
outOfBounds.add(entity);
}
}
projectileEntities.removeAll(outOfBounds);
}
protected void processCollisions() {
Set<Entity> hitInvaders = new HashSet<>(25);
Set<Entity> hitProjectiles = new HashSet<>(25);
for (Entity invader : invaderEntities) {
for (Entity projectile : projectileEntities) {
if (projectile.getBounds().intersects(invader.getBounds())) {
// Maybe lots of cool explosiions
hitInvaders.add(invader);
hitProjectiles.add(projectile);
}
}
}
invaderEntities.removeAll(hitInvaders);
projectileEntities.removeAll(hitProjectiles);
}
protected void addKeyBinding(Input input, String name, int virtualKey) {
ActionMap am = getActionMap();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(virtualKey, 0, false), name + ".pressed");
im.put(KeyStroke.getKeyStroke(virtualKey, 0, true), name + ".released");
am.put(name + ".pressed", new KeyAction(inputs, input, true));
am.put(name + ".released", new KeyAction(inputs, input, false));
}
@Override
public Dimension getGameSpace() {
return getPreferredSize();
}
@Override
public boolean hasInput(Input input) {
return inputs.contains(input);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
playerEntity.paint(this, g2d);
g2d.dispose();
for (Entity entity : invaderEntities) {
g2d = (Graphics2D) g.create();
entity.paint(this, g2d);
g2d.dispose();
}
for (Entity entity : projectileEntities) {
g2d = (Graphics2D) g.create();
entity.paint(this, g2d);
g2d.dispose();
}
}
}
public class KeyAction extends AbstractAction {
private Input input;
private Set<Input> inputs;
private boolean pressed;
public KeyAction(Set<Input> inputs, Input input, boolean pressed) {
this.input = input;
this.inputs = inputs;
this.pressed = pressed;
}
@Override
public void actionPerformed(ActionEvent e) {
if (pressed) {
inputs.add(input);
} else {
inputs.remove(input);
}
}
}
Теперь вы можете пойти немного дальше и создать класс «двигатель», который контролирует объекты и выполняет все необходимые обновления, но я ленивая;)
это действительно грубое представление о некоторых основных понятий, которые необходимо развивать, чтобы иметь возможность двигаться вперед, надеюсь, что это помогает
Вам не нужно три Таймеры все связаны с тем же ActionListener, используйте один и позволяют модели принимать решения о том, что на каждом ти ск. Измените дельта объектов, чтобы изменить скорость.Предпочитайте переопределить paintComponent вместо краски – MadProgrammer
Переместите свою картину на пользовательский компонент, mainPanel и метод переопределенной краски могут мешать друг другу – MadProgrammer
BufferedImage может быть лучше, чем ImageIcon – MadProgrammer