原文: https://zetcode.com/tutorials/javagamestutorial/movingsprites/

在 Java 2D 游戏教程的这一部分中,我们将使用精灵。

术语精灵具有多种含义。 它用于表示场景中的图像或动画。

它也用于表示游戏中的任何可移动对象。 含义之一也是在游戏中封装角色的代码。 在我们的教程中,通过使用精灵,我们引用了一个可移动对象或其 Java 类。

移动精灵

在第一个示例中,我们有一个太空飞船。 我们可以使用光标键在板上移动宇宙飞船。

SpaceShip.java

  1. package com.zetcode;
  2. import java.awt.Image;
  3. import java.awt.event.KeyEvent;
  4. import javax.swing.ImageIcon;
  5. public class SpaceShip {
  6. private int dx;
  7. private int dy;
  8. private int x = 40;
  9. private int y = 60;
  10. private int w;
  11. private int h;
  12. private Image image;
  13. public SpaceShip() {
  14. loadImage();
  15. }
  16. private void loadImage() {
  17. ImageIcon ii = new ImageIcon("src/resources/spaceship.png");
  18. image = ii.getImage();
  19. w = image.getWidth(null);
  20. h = image.getHeight(null);
  21. }
  22. public void move() {
  23. x += dx;
  24. y += dy;
  25. }
  26. public int getX() {
  27. return x;
  28. }
  29. public int getY() {
  30. return y;
  31. }
  32. public int getWidth() {
  33. return w;
  34. }
  35. public int getHeight() {
  36. return h;
  37. }
  38. public Image getImage() {
  39. return image;
  40. }
  41. public void keyPressed(KeyEvent e) {
  42. int key = e.getKeyCode();
  43. if (key == KeyEvent.VK_LEFT) {
  44. dx = -2;
  45. }
  46. if (key == KeyEvent.VK_RIGHT) {
  47. dx = 2;
  48. }
  49. if (key == KeyEvent.VK_UP) {
  50. dy = -2;
  51. }
  52. if (key == KeyEvent.VK_DOWN) {
  53. dy = 2;
  54. }
  55. }
  56. public void keyReleased(KeyEvent e) {
  57. int key = e.getKeyCode();
  58. if (key == KeyEvent.VK_LEFT) {
  59. dx = 0;
  60. }
  61. if (key == KeyEvent.VK_RIGHT) {
  62. dx = 0;
  63. }
  64. if (key == KeyEvent.VK_UP) {
  65. dy = 0;
  66. }
  67. if (key == KeyEvent.VK_DOWN) {
  68. dy = 0;
  69. }
  70. }
  71. }

此类代表一艘太空飞船。 在此类中,我们保留子画面的图像和子画面的坐标。 keyPressed()keyReleased()方法控制精灵是否在移动。

  1. public void move() {
  2. x += dx;
  3. y += dy;
  4. }

move()方法更改子画面的坐标。 这些 x 和 y 值在paintComponent()方法中用于绘制子画面的图像。

  1. if (key == KeyEvent.VK_LEFT) {
  2. dx = 0;
  3. }

释放左光标键时,将dx变量设置为零。 航天器将停止移动。

Board.java

  1. package com.zetcode;
  2. import java.awt.Color;
  3. import java.awt.Graphics;
  4. import java.awt.Graphics2D;
  5. import java.awt.Toolkit;
  6. import java.awt.event.ActionEvent;
  7. import java.awt.event.ActionListener;
  8. import java.awt.event.KeyAdapter;
  9. import java.awt.event.KeyEvent;
  10. import javax.swing.JPanel;
  11. import javax.swing.Timer;
  12. public class Board extends JPanel implements ActionListener {
  13. private Timer timer;
  14. private SpaceShip spaceShip;
  15. private final int DELAY = 10;
  16. public Board() {
  17. initBoard();
  18. }
  19. private void initBoard() {
  20. addKeyListener(new TAdapter());
  21. setBackground(Color.black);
  22. setFocusable(true);
  23. spaceShip = new SpaceShip();
  24. timer = new Timer(DELAY, this);
  25. timer.start();
  26. }
  27. @Override
  28. public void paintComponent(Graphics g) {
  29. super.paintComponent(g);
  30. doDrawing(g);
  31. Toolkit.getDefaultToolkit().sync();
  32. }
  33. private void doDrawing(Graphics g) {
  34. Graphics2D g2d = (Graphics2D) g;
  35. g2d.drawImage(spaceShip.getImage(), spaceShip.getX(),
  36. spaceShip.getY(), this);
  37. }
  38. @Override
  39. public void actionPerformed(ActionEvent e) {
  40. step();
  41. }
  42. private void step() {
  43. spaceShip.move();
  44. repaint(spaceShip.getX()-1, spaceShip.getY()-1,
  45. spaceShip.getWidth()+2, spaceShip.getHeight()+2);
  46. }
  47. private class TAdapter extends KeyAdapter {
  48. @Override
  49. public void keyReleased(KeyEvent e) {
  50. spaceShip.keyReleased(e);
  51. }
  52. @Override
  53. public void keyPressed(KeyEvent e) {
  54. spaceShip.keyPressed(e);
  55. }
  56. }
  57. }

这是Board类。

  1. private void doDrawing(Graphics g) {
  2. Graphics2D g2d = (Graphics2D) g;
  3. g2d.drawImage(ship.getImage(), ship.getX(), ship.getY(), this);
  4. }

doDrawing()方法中,我们使用drawImage()方法绘制宇宙飞船。 我们从精灵类中获得图像和坐标。

  1. @Override
  2. public void actionPerformed(ActionEvent e) {
  3. step();
  4. }

actionPerformed()方法每DELAY ms 调用一次。 我们称为step()方法。

  1. private void step() {
  2. ship.move();
  3. repaint(ship.getX()-1, ship.getY()-1,
  4. ship.getWidth()+2, ship.getHeight()+2);
  5. }

我们移动精灵并重新粉刷已更改的电路板部分。 我们使用一种小的优化技术,该技术仅重新绘制实际更改的窗口的小区域。

  1. private class TAdapter extends KeyAdapter {
  2. @Override
  3. public void keyReleased(KeyEvent e) {
  4. craft.keyReleased(e);
  5. }
  6. @Override
  7. public void keyPressed(KeyEvent e) {
  8. craft.keyPressed(e);
  9. }
  10. }

Board类中,我们监听关键事件。 KeyAdapter类的重写方法将处理委托给Craft类的方法。

MovingSpriteEx.java

  1. package com.zetcode;
  2. import java.awt.EventQueue;
  3. import javax.swing.JFrame;
  4. public class MovingSpriteEx extends JFrame {
  5. public MovingSpriteEx() {
  6. initUI();
  7. }
  8. private void initUI() {
  9. add(new Board());
  10. setTitle("Moving sprite");
  11. setSize(400, 300);
  12. setLocationRelativeTo(null);
  13. setResizable(false);
  14. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  15. }
  16. public static void main(String[] args) {
  17. EventQueue.invokeLater(() -> {
  18. MovingSpriteEx ex = new MovingSpriteEx();
  19. ex.setVisible(true);
  20. });
  21. }
  22. }

这是主要的类。

移动精灵 - 图1

图:移动精灵

射击导弹

在下一个示例中,我们在示例中添加了另一个精灵类型-导弹。 用 Space 键发射导弹。

Sprite.java

  1. package com.zetcode;
  2. import java.awt.Image;
  3. import javax.swing.ImageIcon;
  4. public class Sprite {
  5. protected int x;
  6. protected int y;
  7. protected int width;
  8. protected int height;
  9. protected boolean visible;
  10. protected Image image;
  11. public Sprite(int x, int y) {
  12. this.x = x;
  13. this.y = y;
  14. visible = true;
  15. }
  16. protected void loadImage(String imageName) {
  17. ImageIcon ii = new ImageIcon(imageName);
  18. image = ii.getImage();
  19. }
  20. protected void getImageDimensions() {
  21. width = image.getWidth(null);
  22. height = image.getHeight(null);
  23. }
  24. public Image getImage() {
  25. return image;
  26. }
  27. public int getX() {
  28. return x;
  29. }
  30. public int getY() {
  31. return y;
  32. }
  33. public boolean isVisible() {
  34. return visible;
  35. }
  36. public void setVisible(Boolean visible) {
  37. this.visible = visible;
  38. }
  39. }

Sprite类共享MissileSpaceShip类的通用代码。

  1. public Sprite(int x, int y) {
  2. this.x = x;
  3. this.y = y;
  4. visible = true;
  5. }

构造器初始化 x 和 y 坐标以及visible变量。

Missile.java

  1. package com.zetcode;
  2. public class Missile extends Sprite {
  3. private final int BOARD_WIDTH = 390;
  4. private final int MISSILE_SPEED = 2;
  5. public Missile(int x, int y) {
  6. super(x, y);
  7. initMissile();
  8. }
  9. private void initMissile() {
  10. loadImage("src/resources/missile.png");
  11. getImageDimensions();
  12. }
  13. public void move() {
  14. x += MISSILE_SPEED;
  15. if (x > BOARD_WIDTH) {
  16. visible = false;
  17. }
  18. }
  19. }

在这里,我们有一个名为Missile的新精灵。

  1. public void move() {
  2. x += MISSILE_SPEED;
  3. if (x > BOARD_WIDTH) {
  4. vis = false;
  5. }
  6. }

导弹以恒定速度运动。 当它碰到Board的右边界时,它变得不可见。 然后将其从导弹列表中删除。

SpaceShip.java

  1. package com.zetcode;
  2. import java.awt.event.KeyEvent;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. public class SpaceShip extends Sprite {
  6. private int dx;
  7. private int dy;
  8. private List<Missile> missiles;
  9. public SpaceShip(int x, int y) {
  10. super(x, y);
  11. initSpaceShip();
  12. }
  13. private void initSpaceShip() {
  14. missiles = new ArrayList<>();
  15. loadImage("src/resources/spaceship.png");
  16. getImageDimensions();
  17. }
  18. public void move() {
  19. x += dx;
  20. y += dy;
  21. }
  22. public List<Missile> getMissiles() {
  23. return missiles;
  24. }
  25. public void keyPressed(KeyEvent e) {
  26. int key = e.getKeyCode();
  27. if (key == KeyEvent.VK_SPACE) {
  28. fire();
  29. }
  30. if (key == KeyEvent.VK_LEFT) {
  31. dx = -1;
  32. }
  33. if (key == KeyEvent.VK_RIGHT) {
  34. dx = 1;
  35. }
  36. if (key == KeyEvent.VK_UP) {
  37. dy = -1;
  38. }
  39. if (key == KeyEvent.VK_DOWN) {
  40. dy = 1;
  41. }
  42. }
  43. public void fire() {
  44. missiles.add(new Missile(x + width, y + height / 2));
  45. }
  46. public void keyReleased(KeyEvent e) {
  47. int key = e.getKeyCode();
  48. if (key == KeyEvent.VK_LEFT) {
  49. dx = 0;
  50. }
  51. if (key == KeyEvent.VK_RIGHT) {
  52. dx = 0;
  53. }
  54. if (key == KeyEvent.VK_UP) {
  55. dy = 0;
  56. }
  57. if (key == KeyEvent.VK_DOWN) {
  58. dy = 0;
  59. }
  60. }
  61. }

这是SpaceShip类。

  1. if (key == KeyEvent.VK_SPACE) {
  2. fire();
  3. }

如果按空格键,则会触发。

  1. public void fire() {
  2. missiles.add(new Missile(x + width, y + height / 2));
  3. }

fire()方法创建一个新的Missile对象并将其添加到导弹列表中。

  1. public List<Missile> getMissiles() {
  2. return missiles;
  3. }

getMissiles()方法返回导弹列表。 从Board类调用它。

Board.java

  1. package com.zetcode;
  2. import java.awt.Color;
  3. import java.awt.Graphics;
  4. import java.awt.Graphics2D;
  5. import java.awt.Toolkit;
  6. import java.awt.event.ActionEvent;
  7. import java.awt.event.ActionListener;
  8. import java.awt.event.KeyAdapter;
  9. import java.awt.event.KeyEvent;
  10. import java.util.List;
  11. import javax.swing.JPanel;
  12. import javax.swing.Timer;
  13. public class Board extends JPanel implements ActionListener {
  14. private final int ICRAFT_X = 40;
  15. private final int ICRAFT_Y = 60;
  16. private final int DELAY = 10;
  17. private Timer timer;
  18. private SpaceShip spaceShip;
  19. public Board() {
  20. initBoard();
  21. }
  22. private void initBoard() {
  23. addKeyListener(new TAdapter());
  24. setBackground(Color.BLACK);
  25. setFocusable(true);
  26. spaceShip = new SpaceShip(ICRAFT_X, ICRAFT_Y);
  27. timer = new Timer(DELAY, this);
  28. timer.start();
  29. }
  30. @Override
  31. public void paintComponent(Graphics g) {
  32. super.paintComponent(g);
  33. doDrawing(g);
  34. Toolkit.getDefaultToolkit().sync();
  35. }
  36. private void doDrawing(Graphics g) {
  37. Graphics2D g2d = (Graphics2D) g;
  38. g2d.drawImage(spaceShip.getImage(), spaceShip.getX(),
  39. spaceShip.getY(), this);
  40. List<Missile> missiles = spaceShip.getMissiles();
  41. for (Missile missile : missiles) {
  42. g2d.drawImage(missile.getImage(), missile.getX(),
  43. missile.getY(), this);
  44. }
  45. }
  46. @Override
  47. public void actionPerformed(ActionEvent e) {
  48. updateMissiles();
  49. updateSpaceShip();
  50. repaint();
  51. }
  52. private void updateMissiles() {
  53. List<Missile> missiles = spaceShip.getMissiles();
  54. for (int i = 0; i < missiles.size(); i++) {
  55. Missile missile = missiles.get(i);
  56. if (missile.isVisible()) {
  57. missile.move();
  58. } else {
  59. missiles.remove(i);
  60. }
  61. }
  62. }
  63. private void updateSpaceShip() {
  64. spaceShip.move();
  65. }
  66. private class TAdapter extends KeyAdapter {
  67. @Override
  68. public void keyReleased(KeyEvent e) {
  69. spaceShip.keyReleased(e);
  70. }
  71. @Override
  72. public void keyPressed(KeyEvent e) {
  73. spaceShip.keyPressed(e);
  74. }
  75. }
  76. }

This is the Board class.

  1. private void doDrawing(Graphics g) {
  2. Graphics2D g2d = (Graphics2D) g;
  3. g2d.drawImage(spaceShip.getImage(), spaceShip.getX(),
  4. spaceShip.getY(), this);
  5. List<Missile> missiles = spaceShip.getMissiles();
  6. for (Missile missile : missiles) {
  7. g2d.drawImage(missile.getImage(), missile.getX(),
  8. missile.getY(), this);
  9. }
  10. }

doDrawing()方法中,我们绘制飞行器和所有可用的导弹。

  1. private void updateMissiles() {
  2. List<Missile> missiles = spaceShip.getMissiles();
  3. for (int i = 0; i < missiles.size(); i++) {
  4. Missile missile = missiles.get(i);
  5. if (missile.isVisible()) {
  6. missile.move();
  7. } else {
  8. missiles.remove(i);
  9. }
  10. }
  11. }

updateMissiles()方法中,我们解析missiles列表中的所有导弹。 根据isVisible()方法返回的内容,我们要么移动导弹,要么将其从容器中取出。

ShootingMissilesEx.java

  1. package com.zetcode;
  2. import java.awt.EventQueue;
  3. import javax.swing.JFrame;
  4. public class ShootingMissilesEx extends JFrame {
  5. public ShootingMissilesEx() {
  6. initUI();
  7. }
  8. private void initUI() {
  9. add(new Board());
  10. setSize(400, 300);
  11. setResizable(false);
  12. setTitle("Shooting missiles");
  13. setLocationRelativeTo(null);
  14. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  15. }
  16. public static void main(String[] args) {
  17. EventQueue.invokeLater(() -> {
  18. ShootingMissilesEx ex = new ShootingMissilesEx();
  19. ex.setVisible(true);
  20. });
  21. }
  22. }

最后,这是主要类。

移动精灵 - 图2

图:发射导弹

在本章中,我们介绍了精灵。