Плавное движение Змеи в Свинге - PullRequest
0 голосов
/ 07 февраля 2019

Я занимаюсь разработкой простой игры Snake на Java с использованием Swing.До сих пор я делал анимацию змей, которая беспокоит меня: когда змеи двигаются, кажется, что они начинают отставать без всякой причины, но когда я перемещаю их с помощью клавиш со стрелками, все идет гладко.

Как я могзаставить его двигаться плавно, когда он идет по прямой?Вот код:

GameFrame

public class GameFrame extends JFrame {
    private GamePanel gamePanel;
    private JLabel scoreLabel;

    public GameFrame() throws HeadlessException {
        this.setTitle("Snake");
        this.setBounds(100, 100, 400, 400);
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.setResizable(false);
        getContentPane().setLayout(null);
        getContentPane().setBackground(Color.BLACK);

        scoreLabel = new JLabel("0000");
        scoreLabel.setBounds(0, 0, 70, 20);
        scoreLabel.setForeground(Color.WHITE);
        getContentPane().add(scoreLabel);

        gamePanel = new GamePanel();
        gamePanel.setBounds(20, 20, 360, 350);
        this.add(gamePanel);

        this.setVisible(true);
    }
}

GamePanel

public class GamePanel extends JPanel implements KeyListener, ActionListener {
    private GameManager gameManager;
    private Timer timer;
    private final int DELAY = 150;

    GamePanel() {
        this.setOpaque(true);
        this.setBackground(new Color(51, 51, 51));
        this.addKeyListener(this);
        this.setFocusable(true);
        this.requestFocus();
        gameManager = new GameManager(this);
        timer = new Timer(DELAY, this);
        timer.start();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        gameManager.renderSnake(g);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        gameManager.gameLoop();
    }

    @Override
    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_UP:
                gameManager.moveSnakeUp();
                break;
            case KeyEvent.VK_DOWN:
                gameManager.moveSnakeDown();
                break;
            case KeyEvent.VK_RIGHT:
                gameManager.moveSnakeRight();
                break;
            case KeyEvent.VK_LEFT:
                gameManager.moveSnakeLeft();
                break;
        }
    }

    @Override
    public void keyTyped(KeyEvent e) { }

    @Override
    public void keyReleased(KeyEvent e) { }
}

GameManager

public class GameManager {
    private Color snakeColor;
    private Snake snake;
    private GamePanel gamePanel;
    private boolean running;

    public GameManager(GamePanel gamePanel) {
        this.gamePanel = gamePanel;
        snakeColor = Color.GREEN;
        snake = new Snake();
        running = true;
    }

    public void gameLoop() {
        update();
        draw();
    }

    public void renderSnake(Graphics graphics) {
        Graphics2D graphics2D = (Graphics2D) graphics;
        graphics2D.setColor(snakeColor);
        for (Rectangle tile : snake.getBody())
            graphics2D.fillRect(tile.x, tile.y, tile.width, tile.height);
    }

    public void update() {
        snake.move();
    }

    public boolean isRunning() {
        return running;
    }

    public void draw() {
        gamePanel.repaint();
    }

    public void moveSnakeUp() {
        snake.setUpDirection();
    }

    public void moveSnakeDown() {
        snake.setDownDirection();
    }

    public void moveSnakeRight() {
        snake.setRightDirection();
    }

    public void moveSnakeLeft() {
        snake.setLeftDirection();
    }
}

Змея

public class Snake {
    private final int SNAKE_SPEED = 10;
    private final int BODY_WIDTH = 10;

    private LinkedList<Rectangle> body;
    private Directions direction;

    public Snake() {
        direction = Directions.RIGHT;
        body = new LinkedList<>();
        body.add(new Rectangle(50, 20, BODY_WIDTH, BODY_WIDTH));
        body.add(new Rectangle(40, 20, BODY_WIDTH, BODY_WIDTH));
        body.add(new Rectangle(30, 20, BODY_WIDTH, BODY_WIDTH));
        body.add(new Rectangle(20, 20, BODY_WIDTH, BODY_WIDTH));
        body.add(new Rectangle(10, 20, BODY_WIDTH, BODY_WIDTH));
    }

    public LinkedList<Rectangle> getBody() {
        return body;
    }

    public void setUpDirection() {
        if (direction != Directions.UP)
            direction = Directions.UP;
    }

    public void setDownDirection() {
        if (direction != Directions.DOWN)
            direction = Directions.DOWN;
    }

    public void setRightDirection() {
        if (direction != Directions.RIGHT)
            direction = Directions.RIGHT;
    }

    public void setLeftDirection() {
        if (direction != Directions.LEFT)
            direction = Directions.LEFT;
    }

    public void move() {
        Rectangle newHead = body.removeLast();
        newHead.x = body.peek().x;
        newHead.y = body.peek().y;

        switch (direction) {
            case UP:
                newHead.y -= SNAKE_SPEED;
                break;
            case DOWN:
                newHead.y += SNAKE_SPEED;
                break;
            case RIGHT:
                newHead.x += SNAKE_SPEED;
                break;
            case LEFT:
                newHead.x -= SNAKE_SPEED;
                break;
        }

        body.addFirst(newHead);
    }
}

Я пытался использовать Thread и различные значения времени задержки, но безрезультатно.Как это можно улучшить?

Большое спасибо.

EDIT

Я нашел решение с использованием BufferedImage, которое действительно имеет большое значение,просто, но эффективно:

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    BufferedImage bufferedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics bufferedGraphics = bufferedImage.getGraphics();

    gameManager.renderSnake(bufferedGraphics);
    gameManager.renderFruit(bufferedGraphics);

    g.drawImage(bufferedImage, 0, 0, this);
}

РЕДАКТИРОВАТЬ 2

Мне наконец-то удалось получить приличную анимацию даже без использования Timer из java.Swing, но с использованием Thread вместо.Это хорошо работает для меня.

Для тех, кто заинтересован, вот код .

1 Ответ

0 голосов
/ 08 февраля 2019

Это увеличение на плитки, которое, кажется, доставляет вам неприятности.Я изменил это на 1 пиксель.С этим это гладко.Я также добавил что-то, что увеличивает размер случайно.Вы можете пропустить подход плитки и использовать один объект для линии в одном направлении.И вы можете работать с цветами и смешиванием, чтобы сделать его еще более плавным.Много места для улучшения - но, возможно, это продвинет вас на шаг дальше.Также я использовал Netbeans для редактирования Swing, чтобы не тратить слишком много времени.

import static Snake.GameFrame.SnakeBodyTile.BODYWIDTH;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import javax.swing.Timer;

/**
 *
 * @author Kai
 */
public class GameFrame extends javax.swing.JFrame implements KeyListener {

    public enum DIRECTION {
        LEFT,
        RIGHT,
        UP,
        DOWN
    }

    public enum STATE {
        SELFBITTEN,
        WALLHIT,
        RUNNING,
        WAITING,
        STOPPED
    }

    public static class SnakeBodyTile {

        public static final int BODYWIDTH = 10;
        public final Rectangle rect;
        public final DIRECTION direction;

        public SnakeBodyTile(Rectangle rect, DIRECTION direction) {
            this.direction = direction;
            this.rect = rect;
        }

        private boolean update() {
            switch (this.direction) {
                case UP:
                    return (1 >= rect.height--);
                case DOWN:
                    rect.y++;
                    return (1 >= rect.height--);
                case LEFT:
                    return (1 >= rect.width--);
                case RIGHT:
                    rect.x++;
                    return (1 >= rect.width--);
            }
            return false;//dummy
        }

    }

    LinkedList<SnakeBodyTile> snake = new LinkedList();
    Color snakeColor;
    Random random = new Random();
    DIRECTION direction = DIRECTION.LEFT;
    STATE state = STATE.STOPPED;
    Timer timer;

    /**
     * Creates new form GameFrame
     */
    @SuppressWarnings("LeakingThisInConstructor")
    public GameFrame() {
        initComponents();
        snakeColor = Color.GREEN;
        direction = DIRECTION.LEFT;
        state = STATE.STOPPED;

        snake.add(new SnakeBodyTile(new Rectangle(100, 100, 10, 10), direction));
        timer = new Timer(15, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                timerAction(e);
            }
        });
        super.addKeyListener(this);
        state = STATE.WAITING;
        gamePanel.repaint();
        timer.start();
    }

    public void timerAction(ActionEvent e) {
        moveSnake();
        gamePanel.repaint();
    }

    @Override
    public void keyTyped(KeyEvent e) {
    }

    @Override
    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_UP:
                direction = DIRECTION.UP;
                break;
            case KeyEvent.VK_DOWN:
                direction = DIRECTION.DOWN;
                break;
            case KeyEvent.VK_RIGHT:
                direction = DIRECTION.RIGHT;
                break;
            case KeyEvent.VK_LEFT:
                direction = DIRECTION.LEFT;
                break;
        }
        if (state == STATE.WAITING) {
            snake.clear();
            snake.add(new SnakeBodyTile(new Rectangle(100, 100, 10, 10), direction));
            state = STATE.RUNNING;
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    public void moveSnake() {
        if (state != STATE.RUNNING) {
            return;
        }
        final SnakeBodyTile first = snake.getFirst();
        final SnakeBodyTile last = snake.getLast();
        switch (direction) {
            case DOWN:
                if (first.rect.height >= BODYWIDTH) {
                    Rectangle rect = new Rectangle(first.rect.x + (first.direction == DIRECTION.LEFT ? 0 : first.rect.width - BODYWIDTH), first.rect.y + BODYWIDTH, BODYWIDTH, 1);
                    snake.addFirst(new SnakeBodyTile(rect, direction));
                } else {
                    first.rect.height++;
                }
                break;
            case UP:
                if (first.rect.height >= BODYWIDTH) {
                    Rectangle rect = new Rectangle(first.rect.x + (first.direction == DIRECTION.LEFT ? 0 : first.rect.width - BODYWIDTH), first.rect.y, BODYWIDTH, 1);
                    snake.addFirst(new SnakeBodyTile(rect, direction));
                } else {
                    first.rect.y--;
                    first.rect.height++;
                }
                break;
            case LEFT:
                if (first.rect.width >= BODYWIDTH) {
                    Rectangle rect = new Rectangle(first.rect.x, first.rect.y + (first.direction == DIRECTION.UP ? 0 : first.rect.height - BODYWIDTH), 1, BODYWIDTH);
                    snake.addFirst(new SnakeBodyTile(rect, direction));
                } else {
                    first.rect.x--;
                    first.rect.width++;
                }
                break;
            case RIGHT:
                if (first.rect.width >= BODYWIDTH) {
                    Rectangle rect = new Rectangle(first.rect.x + first.rect.width, first.rect.y + (first.direction == DIRECTION.UP ? 0 : first.rect.height - BODYWIDTH), 1, BODYWIDTH);
                    snake.addFirst(new SnakeBodyTile(rect, direction));
                } else {
                    first.rect.width++;
                }
                break;
        }
        if (last.update() && random.nextInt(4) != 1) {
            snake.removeLast();
        }
    }

    void updateGamePanel(Graphics graphics) {
        final Graphics2D graphics2D = (Graphics2D) graphics;
        graphics2D.setColor(snakeColor);
        for (Iterator<SnakeBodyTile> it = snake.iterator(); it.hasNext();) {
            final Rectangle tile = it.next().rect;
            graphics2D.fillRect(tile.x, tile.y, tile.width, tile.height);
        }
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        ScoreLable = new javax.swing.JLabel();
        gamePanel = new javax.swing.JPanel(){
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                updateGamePanel(g);
            };
        };
        jLabel2 = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Snake");
        setBackground(java.awt.Color.black);
        setBounds(new java.awt.Rectangle(100, 100, 400, 400));
        setResizable(false);
        getContentPane().setLayout(new org.netbeans.lib.awtextra.AbsoluteLayout());

        ScoreLable.setText("0000");
        getContentPane().add(ScoreLable, new org.netbeans.lib.awtextra.AbsoluteConstraints(50, 0, 50, -1));

        gamePanel.setLayout(null);
        getContentPane().add(gamePanel, new org.netbeans.lib.awtextra.AbsoluteConstraints(0, 30, 400, 240));

        jLabel2.setText("Score:");
        getContentPane().add(jLabel2, new org.netbeans.lib.awtextra.AbsoluteConstraints(0, 0, -1, -1));

        pack();
    }// </editor-fold>                        

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(GameFrame.class
                    .getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(GameFrame.class
                    .getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(GameFrame.class
                    .getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(GameFrame.class
                    .getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new GameFrame().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify                     
    private javax.swing.JLabel ScoreLable;
    private javax.swing.JPanel gamePanel;
    private javax.swing.JLabel jLabel2;
    // End of variables declaration                   
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...