【问题标题】:Inconsistent movement in JFrameJFrame 中的移动不一致
【发布时间】:2022-01-21 21:21:13
【问题描述】:

我正在尝试构建 psuedo-galaga,并且我希望我的 JComponents 能够始终如一地移动。它们在自定义 JFrame 容器中以空布局、自定义 JPanel 布局。当我移动我的角色时,子弹的速度会发生变化 - 通过使用计时器,我试图限制帧速率,以便它们始终如一地移动,但没有奏效。

为什么当用户移动时我的代码会变慢?我觉得这是一个焦点子系统问题,或者我应该使用多个线程?

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Frame extends JFrame {
    private Dimension dimension;
    private final int WIDTH, HEIGHT;
    private JPanel screen;
    public Frame(int width, int height) {
        WIDTH = width;
        HEIGHT = height;
        dimension = new Dimension(WIDTH, HEIGHT);
        this.setPreferredSize(dimension);
        this.setResizable(false);
        this.setMinimumSize(dimension);
        this.setMaximumSize(dimension);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.pack();
        this.setVisible(true);
        this.setTitle("Galaga");
        this.setBackground(Color.black);
        this.setForeground(Color.white);
        screen = new LevelOneScreen(dimension);
        this.getContentPane().add(screen);
        screen.requestFocus();
        screen.requestFocusInWindow();
    }

    public void display() {
        this.pack();
        this.setVisible(true);
        this.repaint();
        if(screen instanceof LevelOneScreen && ((LevelOneScreen) screen).isDone()) {
            this.dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
        }
    }
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class LevelOneScreen extends JPanel {
    public static ArrayList<LevelOneBullet> enemyBullets;
    private ArrayList<LevelOneEnemy> enemies;
    private boolean direction;
    private Dimension dimension;
    private User user;
    private int score;
    private boolean isDone;

    public LevelOneScreen(Dimension dimension) {
        this.dimension = dimension;
        isDone = false;
        enemies = new ArrayList<LevelOneEnemy>();
        enemyBullets = new ArrayList<LevelOneBullet>();
        direction = true;
        this.setLayout(null);
        setBackground(Color.BLACK);
        createEnemies();
        createUser();
        user.requestFocusInWindow();
        user.requestFocus();
        score = 0;
        this.setSize(dimension);
        this.setVisible(true);
    }

    private void createUser() {
        user = new User((int) (dimension.getWidth() / 2), (int) (dimension.getHeight() - 100));
        user.setVisible(true);
        this.add(user);
    }

    private void createEnemies() {
        BufferedImage img = null;
        try {
            img = ImageIO.read(new File("Enemy1.png"));
        } catch (IOException e) {
            System.out.println("Error Reading \"Enemy1.png\"");
        }
        // dimension.width
        for (int i = 0; i < 15; i++) {
            enemies.add(new LevelOneEnemy(i * 40 + 5, 30, img));
            enemies.get(i).setVisible(true);
        }
        for (LevelOneEnemy e : enemies) {
            this.add(e);
        }
    }

    private void paintEnemies(Graphics g) {
        for (LevelOneEnemy e : enemies) {
            e.paint(g);
            if (!direction)
                e.setLocation(e.getX() - 1, e.getY());
            else
                e.setLocation(e.getX() + 1, e.getY());
        }
        if (enemies.get(enemies.size() - 1).getX() + 45 > dimension.getWidth() && direction) {
            direction = false;
        } else if (enemies.get(0).getX() < 5 && !direction) {
            direction = true;
        }
    }

    private void paintCollisionObjects(Graphics g) {
        if (!enemies.isEmpty()) {
            paintEnemies(g);
            // check for bullet collision
            if (!user.getBullets().isEmpty()) {
                for (int i = enemies.size() - 1; i >= 0; i--) {
                    for (int j = user.getBullets().size() - 1; j >= 0; j--) {
                        if (enemies.get(i).getBounds().intersects(user.getBullets().get(j).getBounds())) {
                            this.remove(enemies.get(i));
                            enemies.remove(i);
                            user.getBullets().remove(j);
                            score += 100;
                            // To prevent ArrayOutOfBoundsException when
                            // Enemies are destroyed faster than they're removed
                            if (enemies.size() == 0)
                                break;
                        }
                    }
                }
            }
            // check for user collision
            if (!enemies.isEmpty()) {
                for (int i = enemies.size() - 1; i >= 0; i--) {
                    if (enemies.get(i).getBounds().intersects(user.getBounds())) {
                        enemies.get(i).setLocation(0, getParent().getHeight() + 100);
                        this.remove(enemies.get(i));
                        enemies.remove(i);
                        user.decrementHealth();
                        score += 100;
                    }
                }
            }
            if (!enemyBullets.isEmpty()) {
                for (int i = enemyBullets.size() - 1; i >= 0; i--) {
                    enemyBullets.get(i).paint(g);
                    if (enemyBullets.get(i).getY() > getParent().getHeight() + 50) {
                        enemyBullets.remove(i);
                    } else if (enemyBullets.get(i).getBounds().intersects(user.getBounds())) {
                        enemyBullets.remove(i);
                        user.decrementHealth();
                    }
                }
            }
        }
    }

    public void paintComponent(Graphics g) {
        this.requestFocusInWindow();
        super.paintComponent(g);
        user.paintComponent(g);
        paintCollisionObjects(g);
        if(!isDone && enemies.isEmpty())
            isDone = true;
    }

    public boolean isDone() {
        return isDone;
    }
    
    public boolean isDead() {
        return user.healthPercent() < .1;
    }
    public int getScore() {
        return score;
    }
    
    public double getHealth() {
        return user.healthPercent();
    }
    
    public String toString() {
        String s = "Level One Screen\n";
        for (int i = 0; i < this.getComponentCount(); i++) {
            s = s + this.getComponent(i) + "\n";
        }
        return s;
    }
}
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JComponent;

@SuppressWarnings("serial")
public class LevelOneBullet extends JComponent {
    private Image img;
    private int dy;
    public LevelOneBullet(int x, int y, boolean isEnemy) {
        BufferedImage img = null;
        try {
            if(isEnemy)
                img = ImageIO.read(new File("EnemyLaserShot.png"));
            else
                img = ImageIO.read(new File("UserLaserShot.png"));
        } catch (IOException e) {
            if(isEnemy)
                System.out.println("Error Reading \"EnemyLaserShot.png\"");
            else
                System.out.println("Error Reading \"UserLaserShot.png\"");
        }
        this.img = img;
        super.setLocation(x, y);
        this.setVisible(true);
        this.setBounds(x, y, 16, 30);
        dy = isEnemy ? 3 : -3;
    }

    public void paint(Graphics g) {
        g.drawImage(img, super.getX(), super.getY(), 16, 30, null);
        //super.setLocation(super.getX(), super.getY() + dy);
        this.setBounds(super.getX(), super.getY()+dy, 16, 30);
    }
    
    public String toString() {
        return "LevelOneBullet: @" + super.getX() + ", " +super.getY();
    }
}
import java.awt.Graphics;
import java.awt.Image;
import java.util.Random;

import javax.swing.JComponent;

@SuppressWarnings("serial")
public class LevelOneEnemy extends JComponent {
    private Image im;
    private int health;
    private int shootSeed;
    private long time;
    private long lastTimeFired;
    public LevelOneEnemy(int x, int y, Image im, int health) {
        Random rand = new Random();
        super.setBounds(x, y, 30, 30);
        super.setLocation(x, y);
        this.im = im;
        this.health = health;
        shootSeed = rand.nextInt(1000)+5000;
        time = System.currentTimeMillis();
        lastTimeFired = 0;
    }

    public LevelOneEnemy(int x, int y, Image im) {
        Random rand = new Random();
        super.setBounds(x, y, 30, 30);
        super.setLocation(x, y);
        this.im = im;
        this.health = 100;
        shootSeed = rand.nextInt(1000)+6000;
        time = System.currentTimeMillis();
        lastTimeFired = 0;
    }
    public LevelOneEnemy(int x, int y, Image im, boolean isLevelOne) {
        Random rand = new Random();
        super.setBounds(x, y, 30, 30);
        super.setLocation(x, y);
        this.im = im;
        this.health = 100;
        shootSeed = rand.nextInt(1000)+6000;
        time = System.currentTimeMillis();
        lastTimeFired = 0;
    }

    public void paint(Graphics g) {
        if((System.currentTimeMillis()-time) % shootSeed < (shootSeed/30) &&
                System.currentTimeMillis() - lastTimeFired > 5000) {
                LevelOneScreen.enemyBullets.add(new LevelOneBullet(this.getX()+15, this.getY()+10, true));
            lastTimeFired = System.currentTimeMillis();
        }
        g.drawImage(im, super.getX(), super.getY(), 30, 30, null);
    }

    public int getHealth() {
        return health;
    }

    public String toString() {
        return "LevelOneEnemy @(" + this.getX() + ", " + this.getY() + ")";
    }
}
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.Timer;

public class Runner {
    public final static int SCREENHEIGHT = 1000;
    public final static int SCREENWIDTH = 800;
    private static Frame frame;
    public static void main(String[] args) {
        frame = new Frame(SCREENWIDTH, SCREENHEIGHT);
        FrameRateListener listen = new FrameRateListener();
        Timer timer = new Timer(34, listen);
        timer.start();
    }
    private static class FrameRateListener implements ActionListener{
        @Override
        public void actionPerformed(ActionEvent e) {
            frame.display();
        }
        
    }
}
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;

public class UserKeyboardListener implements KeyListener {
    private int dx, dy;
    private int shoot;
    private int speed;
    private ArrayList<Integer> keysPressed;

    public UserKeyboardListener() {
        keysPressed = new ArrayList<Integer>();
        shoot = 0;
        speed = 1;
    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    public int getdx() {
        return dx;
    }

    public int getdy() {
        return dy;
    }

    public int getShoot() {
        return shoot;
    }

    public boolean decrementShoot() {
        if (shoot - 1 < 0) {
            return false;
        } else {
            shoot = shoot - 1;
            return true;
        }
    }

    @Override
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_SPACE) {
        } else {
            if (!keysPressed.contains(key))
                keysPressed.add(key);
            if(keysPressed.contains(KeyEvent.VK_C))
                speed = 2;
            else
                speed = 1;
            if (keysPressed.contains(KeyEvent.VK_RIGHT) && keysPressed.contains(KeyEvent.VK_LEFT)) {
                dx = 0;
            } else if (keysPressed.contains(KeyEvent.VK_RIGHT)) {
                dx = 1*speed;
            } else if (keysPressed.contains(KeyEvent.VK_LEFT)) {
                dx = -1*speed;
            } else {
                dx = 0;
            }
            if (keysPressed.contains(KeyEvent.VK_UP) && keysPressed.contains(KeyEvent.VK_DOWN)) {
                dy = 0;
            } else if (keysPressed.contains(KeyEvent.VK_UP)) {
                dy = -1*speed;
            } else if (keysPressed.contains(KeyEvent.VK_DOWN)) {
                dy = 1*speed;
            } else {
                dy = 0;
            }
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_SPACE) {
            shoot++;
        } else {
            if (keysPressed.contains(key))
                keysPressed.remove(keysPressed.indexOf(key));
            if(keysPressed.contains(KeyEvent.VK_C))
                speed = 2;
            else
                speed = 1;
            if (keysPressed.contains(KeyEvent.VK_RIGHT) && keysPressed.contains(KeyEvent.VK_LEFT)) {
                dx = 0;
            } else if (keysPressed.contains(KeyEvent.VK_RIGHT)) {
                dx = 1*speed;
            } else if (keysPressed.contains(KeyEvent.VK_LEFT)) {
                dx = -1*speed;
            } else {
                dx = 0;
            }
            if (keysPressed.contains(KeyEvent.VK_UP) && keysPressed.contains(KeyEvent.VK_DOWN)) {
                dy = 0;
            } else if (keysPressed.contains(KeyEvent.VK_UP)) {
                dy = -1*speed;
            } else if (keysPressed.contains(KeyEvent.VK_DOWN)) {
                dy = 1*speed;
            } else {
                dy = 0;
            }
        }
    }

    public String toString() {
        return "UserKeyListener: (" + dx + ", " + dy + ")";
    }
}

import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JComponent;

@SuppressWarnings("serial")
public class User extends JComponent {
    private Image im;
    private double health;
    private double initialHealth;
    private double healthDecrement;
    private double stamina;
    private int staminaDecrement;
    private long lastBulletFired;
    private ArrayList<LevelOneBullet> bullets;

    public User(int x, int y, double health) {
        BufferedImage img = null;
        try {
            img = ImageIO.read(new File("UserShip.png"));
        } catch (IOException e) {
            System.out.println("Error Reading \"UserShip.png\"");
        }
        bullets = new ArrayList<LevelOneBullet>();
        super.setLocation(x, y);
        super.setBounds(x, y, 50, 50);
        this.addKeyListener(new UserKeyboardListener());
        this.im = img;
        this.health = 100 * health;
        this.initialHealth = 100;
        this.healthDecrement = 100/5.0;
        this.stamina = 100;
        this.staminaDecrement = 10;
        this.setFocusable(true);
    }

    public User(int x, int y) {
        BufferedImage img = null;
        try {
            img = ImageIO.read(new File("UserShip.png"));
        } catch (IOException e) {
            System.out.println("Error Reading \"UserShip.png\"");
        }
        bullets = new ArrayList<LevelOneBullet>();
        super.setLocation(x, y);
        super.setBounds(x, y, 50, 50);
        this.addKeyListener(new UserKeyboardListener());
        this.im = img;
        this.health = 100;
        this.initialHealth = 100;
        this.healthDecrement = health/5.0;
        this.stamina = 100;
        this.staminaDecrement = 10;
        this.setFocusable(true);
    }

    public void paintComponent(Graphics g) {
        this.requestFocus();
        this.requestFocusInWindow();
        
        if (this.getKeyListeners().length > 0 &&
                this.getKeyListeners()[0] instanceof UserKeyboardListener) {
            UserKeyboardListener listen = (UserKeyboardListener) this.getKeyListeners()[0];
            if(listen.getdx() != 0) {
                if(this.getX()+listen.getdx() + this.getWidth() < this.getParent().getWidth() &&
                        this.getX()+listen.getdx() > 5)
                    this.setLocation(this.getX() + listen.getdx(), this.getY());
                if(this.getX() < 10) {
                    this.setLocation(10, this.getY());
                }
                else if(this.getX()+this.getWidth() > this.getParent().getWidth() - 10)
                    this.setLocation(this.getParent().getWidth()-10-this.getWidth(), this.getY());
            }
            if(listen.getdy() != 0) {
                if(this.getY() + listen.getdy() > 30 && 
                        this.getY()+this.getHeight()+listen.getdy() < this.getParent().getHeight()-10)
                    this.setLocation(this.getX(), this.getY()+listen.getdy());
                if(this.getY() < 30) {
                    this.setLocation(30, this.getY());
                }
                else if(this.getY()+this.getHeight() > this.getParent().getHeight()-10)
                    this.setLocation(this.getX(), this.getParent().getHeight()-10-this.getHeight());
            }
            
            
            if (listen.getShoot() > 0 && stamina > 10) {
                bullets.add(new LevelOneBullet(super.getX() + 17, super.getY() - 5, false));
                decrementStamina();
                listen.decrementShoot();
                lastBulletFired = System.currentTimeMillis();
            }
        }
        for (int i = bullets.size() - 1; i >= 0; i--) {
            bullets.get(i).paint(g);
            if (bullets.get(i).getY() < -50)
                bullets.remove(i);
        }
        if(System.currentTimeMillis() - lastBulletFired > 1000 && stamina < 100) {
            stamina += .5;
        }
        g.drawImage(im, super.getX(), super.getY(), this.getWidth(), this.getHeight(), null);
    }
    
    public double healthPercent() {
        return health/initialHealth;
    }
    
    public void decrementHealth() {
        health -= healthDecrement;
    }
    
    public double staminaPercent() {
        return stamina / 100.0;
    }
    
    public void decrementStamina() {
        stamina -= staminaDecrement;
    }

    public String toString() {
        return "User: " + "(" + super.getX() + ", " + super.getY() + ")";
    }

    public ArrayList<LevelOneBullet> getBullets() {
        return bullets;
    }
}

【问题讨论】:

  • 否则,我可以使用 javax.Swing.Timer 来控制敌人和子弹的重新定位吗?
  • 相关代码需要直接在你的问题中。如果那个 github 项目消失或发生变化,这个问题对未来的读者将毫无用处。
  • 我建议您绘制一张JPanel 的绘图,您可以在其中将所有精灵和子弹绘制为图像。 Oracle 有一个有用的教程,Creating a GUI With Swing。跳过使用 NetBeans IDE 学习摇摆部分。密切关注Concurrency in SwingPerforming Custom Painting 部分。
  • 1) “我把它放进去。” Edit 添加一个minimal reproducible example。 2) implements KeyListener 在 Swing 中,我们通常使用键绑定。 3) new File("UserShip.png") 这在部署时不起作用,请使用getResource("..") 作为 URL。 4)Frame extends JFrame AWT 中有一个Frame 类。所以这个名字很混乱。相反,给它一个描述性的名称,如GameFrame。 5) “当我查看 SwingWorker 时..” 不,@GilbertLeBlanc 指的是 Swing Timer,本教程的该部分也提到了这一点。 6) 尽早并经常测试。你不应该带着..来这里
  • .. 如此大量的极差代码。 7) paintComponent(Graphics g) 任何被覆盖的paint 方法都应该立即调用super(..) 方法。 8) catch (IOException e) { System.out.println(.. 更短更有用(通常)是e.printStackTrace(); 9) new Frame(SCREENWIDTH, SCREENHEIGHT); 这是错误的尺寸,因为它没有考虑框架装饰。取而代之的是让“游戏”组件返回一个合适的首选大小作为布局管理器的提示(代码应该使用)然后pack()它周围的框架。

标签: java swing awt


【解决方案1】:

问题来自@VGR 所说的更新paintComponents(..) 方法中的组件位置。这应该通过计时器动作发生时调用的另一个方法来完成。然后,这会根据应该从计时器刷新它们的时间而不是调用 paintComponents(..) 的时间来更新 JComponents 的位置 - 我们无法控制。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 2021-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多