画图基础
先定义画板类, 用于画图
// 定义画板类画板,用于画图
class MyPanel extends JPanel{// Graphics g是一只画笔// Graphics 提供了绘画方法public void paint(Graphics g){super.paint(g);g.drawOval(0,0,100,100);}}
定义窗口类,用于显示画板
// public类继承JFrame,用于定义窗口
public class Painting extends JFrame {public static void main(String[] args) {new Painting();}private MyPanel myPanel = null;public Painting(){myPanel = new MyPanel();// 窗口加入画布this.add(myPanel);this.setSize(500,500);// 设置窗口退出方式this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 显示窗口this.setVisible(true);}
}
paint方法被调用情况
- 组件第一次在屏幕显示的时候
- 窗口大小改变的时候
- repaint()方法被调用的时候
Graphics类常用方法
// 画直线
drawLine(int x1, int y1, int x2, int y2)
// 画矩形边框
drawRect(int x, int y, int width, int height)
// 画椭圆边框
drawOval(int x, int y, int width, int height)
// 填充矩形
fillRect(int x, int y, int width, int height)
// 填充椭圆
fillOval(int x, int y, int width, int height)
// 画图片
drawImage(Image img, int x, int y, ImageObserver observer)
g.drawImage(imgae, 0 , 0,889,500,this);
// 获得图片资源
Image image = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/vertin.png"));
// 画字符串
drawString(String str, int x, int y)
// 设置画笔颜色
setColor(Color c)
// 设置字体
setFont(Font font)
setFont(new Font("宋体",Font.BOLD,20))
// 设置背景色
setBackground(Color c)
Java事件处理
事件源:产生事件的对象,比如按钮,窗口,文本框等
事件监听器:负责监听事件源所发生的事件,当事件发生时,事件监听器会自动调用相应的方法来处理事件
事件:事件源上发生的事情,比如单击,双击,键盘按下,鼠标移动等
事件发生时,事件源创建对象交给事件监听器,事件监听器调用相应的方法处理事件
事件处理流程:
- 组件.addXXXListener(监听器对象)
- 监听器对象实现接口
- 重写接口中的方法
事件监听器接口
- 鼠标事件监听器接口:MouseListener
- 键盘事件监听器接口:KeyListener
- 窗口事件监听器接口:WindowListener
事件监听器适配器
- 鼠标事件监听器适配器:MouseAdapter
- 键盘事件监听器适配器:KeyAdapter
坦克大战
MyPanel类,用于完成游戏界面绘制
package TankGame;import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;public class MyPanel extends JPanel implements KeyListener {public Player player = null;static Vector<RedEnemy> enemies = new Vector<>();private int enemiesNum = 6;private int points = 0;private int startMode = 0;private String saveAddress = "d:\\save.dat";public int getPoints() {return points;}public void setPoints(int points) {this.points = points;}public static Vector<RedEnemy> getEnemies() {return enemies;}public static void setEnemies(Vector<RedEnemy> enemies) {MyPanel.enemies = enemies;}public MyPanel(){player = new Player(500, 500);for (int i = 0; i < enemiesNum; ++i) {RedEnemy e = new RedEnemy(10 + 100 * i, 10);enemies.add(e);}}public MyPanel(int startMode){File file = new File(saveAddress);if(startMode == 1 && file.exists()){try {ObjectInputStream oi = new ObjectInputStream(new FileInputStream(saveAddress));this.points = oi.readInt();player = (Player)oi.readObject();int N = oi.readInt();for(int i = 0;i < N;++i){RedEnemy e = (RedEnemy) oi.readObject();enemies.add(e);}} catch (IOException | ClassNotFoundException e) {throw new RuntimeException(e);} finally {}}else if(startMode == 0){player = new Player(500, 500);for (int i = 0; i < enemiesNum; ++i) {RedEnemy e = new RedEnemy(10 + 100 * i, 10);enemies.add(e);}}}@Overridepublic void paint(Graphics g) {super.paint(g);g.fillRect(0,0,1000,750);g.setColor(Color.CYAN);g.setFont(new Font("宋体",Font.BOLD,20));g.drawString("当前得分:" + points, 800, 700);// 绘制坦克if(player.isAlived())drawTank(player.getX(),player.getY(),g,player.getDir(),0);synchronized (player.getBullets()){// 绘制子弹Vector<Bullet> bullets = player.getBullets();for(Bullet b : bullets){switch (b.getType()){case 0:g.setColor(Color.yellow);break;case 1:g.setColor(Color.RED);break;}g.fillOval(b.getX(),b.getY(),10,10);}}synchronized (enemies) {for (Tank e : enemies) {drawTank(e.getX(), e.getY(), g, e.getDir(), 1);}}}public void drawTank(int x,int y,Graphics g,int dir, int type){// type决定颜色switch (type){case 0:// 玩家坦克g.setColor(Color.cyan);break;case 1:// 敌人坦克g.setColor(Color.magenta);break;default:g.setColor(Color.GRAY);}// dir决定方向 0上,1左,2右,3下switch (dir){case 0:g.fill3DRect(x,y,10,60,false);g.fill3DRect(x + 30,y,10,60,false);g.fill3DRect(x + 10,y + 10,20,40,false);g.fillOval(x + 10,y + 20,20,20);g.drawLine(x + 20,y + 30,x + 20,y);break;case 1:g.fill3DRect(x,y,60,10,false);g.fill3DRect(x,y + 30,60,10,false);g.fill3DRect(x + 10,y + 10,40,20,false);g.fillOval(x + 20,y + 10,20,20);g.drawLine(x + 30,y + 20,x ,y + 20);break;case 2:g.fill3DRect(x,y,60,10,false);g.fill3DRect(x,y + 30,60,10,false);g.fill3DRect(x + 10,y + 10,40,20,false);g.fillOval(x + 20,y + 10,20,20);g.drawLine(x + 30,y + 20,x + 60,y + 20);break;case 3:g.fill3DRect(x,y,10,60,false);g.fill3DRect(x + 30,y,10,60,false);g.fill3DRect(x + 10,y + 10,20,40,false);g.fillOval(x + 10,y + 20,20,20);g.drawLine(x + 20,y + 30,x + 20,y + 60);}}@Overridepublic void keyTyped(KeyEvent e) {}@Overridepublic void keyPressed(KeyEvent e) {if(!player.isAlived())return;if(e.getKeyCode() == KeyEvent.VK_J){//System.out.println("射击!");player.shoot();}//dir决定方向 0上,1左,2右,3下if(e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_A){player.setDir(1);player.moveLeft();}if(e.getKeyCode() == KeyEvent.VK_RIGHT || e.getKeyCode() == KeyEvent.VK_D){player.setDir(2);player.moveRight();}if(e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W){player.setDir(0);player.moveUp();}if(e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_S){player.setDir(3);player.moveDown();}this.repaint();}@Overridepublic void keyReleased(KeyEvent e) {}
}
TankGame类,用于创建坦克大战游戏的窗口,并让用户在控制台选择是新游戏还是继续上一局游戏
package TankGame;import javax.swing.*;
import java.util.Scanner;public class TankGame extends JFrame {MyPanel myPanel = null;Timer timer = null;Checker checker = null;Saver saver = null;int startMode = 0;public TankGame(int startMode){myPanel = new MyPanel(startMode);timer = new Timer(myPanel.player.getBullets(),myPanel);checker = new Checker(myPanel.player.getBullets(),myPanel);saver = new Saver(myPanel);saver.setDaemon(true);timer.setDaemon(true);checker.setDaemon(true);timer.start();checker.start();saver.start();// 窗口加入画布this.add(myPanel);this.setSize(1000,750);// 设置窗口退出方式this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.addKeyListener(myPanel);// 显示窗口this.setVisible(true);}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);//while(true){System.out.println("选择你想要开始的方式:");System.out.println("1. 新游戏");System.out.println("2. 上一次自动存档游戏");int input = scanner.nextInt();switch (input){case 1:new TankGame();break;case 2:new TankGame(1);break;default:new TankGame();break;}//}}public TankGame(){myPanel = new MyPanel();timer = new Timer(myPanel.player.getBullets(),myPanel);checker = new Checker(myPanel.player.getBullets(),myPanel);saver = new Saver(myPanel);saver.setDaemon(true);timer.setDaemon(true);checker.setDaemon(true);timer.start();checker.start();saver.start();// 窗口加入画布this.add(myPanel);this.setSize(1000,750);// 设置窗口退出方式this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.addKeyListener(myPanel);// 显示窗口this.setVisible(true);}
}
Tank类,用于实现坦克有关的所有行为,包括坦克的移动、发射子弹、判断是否碰撞等
package TankGame;import java.io.Serializable;
import java.util.Vector;public class Tank implements Collied , Serializable {private int x;private int y;private int dir = 3; dir决定方向 0上,1左,2右,3下private int speed = 3;private int shootType = 0;private boolean isAlived = true;private boolean restricted = false;public boolean isRestricted() {return restricted;}public void setRestrict(boolean restricted) {this.restricted = restricted;}public boolean isAlived() {return isAlived;}public void setAlived(boolean alived) {isAlived = alived;}public boolean AinB(Tank t1, Tank t2){if(t1.getX() < t2.getX() || t1.getY() < t2.getY()){return false;}int t1x = t1.getX() + (t1.dir == 1 || t1.dir == 2 ? 0 : 20);int t1y = t1.getY() + (t1.dir == 1 || t1.dir == 2 ? 20 : 0);if(t2.dir == 0 || t2.dir == 3){return ((t1x - t2.getX() < 40 && t1y - t2.getY() < 60));}else{return t1x - t2.getX() < 60 && t1y - t2.getY() < 40;}}@Overridepublic boolean onCollision(Object o1, Object o2) {if(o1 instanceof Tank && o2 instanceof Tank){Tank t1 = (Tank) o1;Tank t2 = (Tank) o2;return AinB(t1,t2) || AinB(t2,t1);}return false;}public int getShootType() {return shootType;}public void setShootType(int shootType) {this.shootType = shootType;}private static Vector<Bullet> bullets= new Vector<>();public static Vector<Bullet> getBullets() {return bullets;}public void shoot(){//bullets.add(new Bullet(x,y,0,dir,10));switch (dir){case 0:bullets.add(new Bullet(x + 15,y,shootType,dir,10));break;case 1:bullets.add(new Bullet(x ,y + 15,shootType,dir,10));break;case 2:bullets.add(new Bullet(x + 60,y + 15,shootType,dir,10));break;case 3:bullets.add(new Bullet(x + 15,y + 60,shootType,dir,10));break;}} dir决定方向 0上,1左,2右,3下public void moveLeft(){x -= speed;if(x < 0 ){dir = 2;x += speed;}if(restricted){x += (int)(1.5 * speed);}}public void moveRight(){x += speed;if(x >= 930){dir = 1;x -= speed;}if(restricted){x -= (int)(1.5 * speed);}}public void moveUp(){y -= speed;if(y < 0 ){dir = 3;y += speed;}if(restricted){y += (int)(1.5 * speed);}}public void moveDown(){y += speed;if(y >= 680 ){dir = 0;y -= speed;}if(restricted){y -= (int)(1.5 * speed);}}public int getDir() {return dir;}public void setDir(int dir) {this.dir = dir;}public int getSpeed() {return speed;}public void setSpeed(int speed) {this.speed = speed;}public Tank(int x, int y) {this.x = x;this.y = y;}public int getX() {return x;}public void setX(int x) {this.x = x;}public int getY() {return y;}public void setY(int y) {this.y = y;}
}
Player类,作为Tank类的子类,用于定义一些玩家坦克特有的属性和方法
package TankGame;public class Player extends Tank{public Player(int x,int y){super(x,y);setSpeed(5);}
}
RedEnemy类,作为Tank类的子类,用于定义一些敌方坦克特有的属性和方法
package TankGame;public class RedEnemy extends Tank{public RedEnemy(int x, int y) {super(x, y);setShootType(1);}public int moveTime = 500;public int shootTime = 1000;public final int MOVETIME = 500;public final int SHOOTTIME = 1000;
}
Bullet类,用于定义坦克发射的子弹的相关属性和方法
package TankGame;public class Bullet implements Collied{private int x;private int y;private int type;private int dir;private int speed;@Overridepublic boolean onCollision(Object o1, Object o2) {if(o2 instanceof Tank){Tank t = (Tank) o2;Bullet b = (Bullet) o1;if(b.getX() < t.getX() || b.getY() < t.getY()) return false;if(t.getDir() == 0 || t.getDir() == 3){return (b.getX() - t.getX() < 40 && b.getY() - t.getY() < 60);}else{return b.getX() - t.getX() < 60 && b.getY() - t.getY() < 40;}}return false;}public int getSpeed() {return speed;}public void setSpeed(int speed) {this.speed = speed;}public Bullet(int x, int y, int type, int dir, int speed) {this.x = x;this.y = y;this.type = type;this.dir = dir; // dir决定方向 0上,1左,2右,3下this.speed = speed;}public Bullet(int x, int y) {this.x = x;this.y = y;}public int getDir() {return dir;}public void setDir(int dir) {this.dir = dir;}public Bullet(int x, int y, int type, int dir) {this.dir = dir;this.type = type;this.y = y;this.x = x;}public int getX() {return x;}public void setX(int x) {this.x = x;}public int getY() {return y;}public void setY(int y) {this.y = y;}public int getType() {return type;}public void setType(int type) {this.type = type;}public void moveLeft(){x -= speed;}public void moveRight(){x += speed;}public void moveUp(){y -= speed;}public void moveDown(){y += speed;}
}
Checker类,用于实现碰撞检测、游戏资源回收等功能
package TankGame;import java.util.Vector;public class Checker extends Thread{private Vector<Bullet> bullets = null;private MyPanel panel = null;private Vector<Bullet> removeBulletArr = new Vector<>();private Vector<RedEnemy> removeEnemiesArr = new Vector<>();private Vector<RedEnemy> enemies;public Checker(Vector<Bullet> bullets) {this.bullets = bullets;}public Checker(Vector<Bullet> bullets,MyPanel panel) {this.bullets = bullets;this.panel = panel;this.enemies = panel.getEnemies();}@Overridepublic void run() {while(true){try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}//碰撞判断synchronized (bullets){//System.out.print("A");synchronized (enemies){//System.out.println("B");//检查碰撞for(Bullet b : bullets){if(b.getType() != 0 && b.onCollision(b,panel.player)){panel.player.setAlived(false);//panel.setPoints(panel.getPoints());}if(b.getType() == 1) continue;for(RedEnemy e : enemies) {if (!e.isAlived()) continue;if (b.onCollision(b, e)) {removeBulletArr.add(b);e.setAlived(false);removeEnemiesArr.add(e);panel.setPoints(panel.getPoints() + 10);}}}}}synchronized (enemies){boolean plCanMove = true;for(int i = 0;i < enemies.size();++i){RedEnemy e1 = enemies.get(i);boolean e1CanMove = true;for(int j = 0;j < enemies.size();++j) {if(j == i) continue;RedEnemy e2 = enemies.get(j);if (e1.onCollision(e1, e2)) {e1.setRestrict(true);e2.setRestrict(true);e1CanMove = false;}}Tank t = panel.player;if (e1.onCollision(e1, t)) {e1.setRestrict(true);t.setRestrict(true);e1CanMove = false;plCanMove = false;}if(e1CanMove){e1.setRestrict(false);}}if(plCanMove){panel.player.setRestrict(false);}}synchronized (bullets){//System.out.println(bullets.size());for(Bullet b : removeBulletArr){bullets.remove(b);}removeBulletArr.clear();}synchronized (enemies){//System.out.println(bullets.size());for(RedEnemy e : removeEnemiesArr){enemies.remove(e);}removeEnemiesArr.clear();}//无敌//panel.player.setAlived(true);//检查子弹是否超出边界synchronized (bullets){//System.out.println(bullets.size());for(Bullet b : bullets){if(b.getY() <= 0 || b.getY() >= 750 || b.getX() <= 0 || b.getX() >= 1000){removeBulletArr.add(b);}}for(Bullet b : removeBulletArr){bullets.remove(b);}removeBulletArr.clear();}panel.repaint();}}
}
Collied类,用于定义一个碰撞检测的类,其中包含一个方法,可以检测两个对象是否碰撞
package TankGame;public interface Collied {// 判断是否碰撞的方法boolean onCollision(Object o1,Object o2);
}
Timer类,用于模拟游戏时间流逝,然后根据时间的流逝更新子弹和坦克的位置
package TankGame;import java.awt.*;
import java.util.Vector;public class Timer extends Thread{private Vector<Bullet> bullets= new Vector<>();private MyPanel panel = null;private int deltaTime = 25;Vector<RedEnemy> enemies = null;public Timer(Vector<Bullet> bullets) {this.bullets = bullets;}public Timer(Vector<Bullet> bullets,MyPanel panel) {this.bullets = bullets;this.panel = panel;enemies = panel.enemies;}public int getDeltaTime() {return deltaTime;}public void setDeltaTime(int deltaTime) {this.deltaTime = deltaTime;}@Overridepublic void run() {while(true){// 40帧左右try {Thread.sleep(deltaTime);} catch (InterruptedException e) {throw new RuntimeException(e);}// 子弹运动synchronized (bullets){for(Bullet b : bullets){switch (b.getDir()){ dir决定方向 0上,1左,2右,3下case 0:b.moveUp();break;case 1:b.moveLeft();break;case 2:b.moveRight();break;case 3:b.moveDown();break;}}}// 敌人运动int nextDir = 0;synchronized (bullets) {synchronized (enemies) {for (RedEnemy t : enemies) {if (!t.isAlived()) {continue;}t.moveTime -= deltaTime;t.shootTime -= deltaTime;if (t.shootTime < 0) {t.shoot();t.shootTime = t.SHOOTTIME;}if (t.moveTime < 0) {t.moveTime = t.MOVETIME;nextDir = (int) (Math.random() * 4);} else {nextDir = t.getDir();}t.setDir(nextDir);switch (nextDir) {case 0:t.moveUp();break;case 1:t.moveLeft();break;case 2:t.moveRight();break;case 3:t.moveDown();break;}}//System.out.println("release e");}}panel.repaint();}}
}
Saver类,用于保存游戏状态到指定文件中去
package TankGame;import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.util.Vector;public class Saver extends Thread{private MyPanel panel = null;private int saveInterval = 10000;private String saveAddress = "d:\\save.dat";private Tank player = null;private Vector<RedEnemy> enemies = null;public Saver(MyPanel panel) {this.panel = panel;this.player = panel.player;this.enemies = panel.getEnemies();}@Overridepublic void run() {while(true){try {Thread.sleep(saveInterval);} catch (InterruptedException e) {throw new RuntimeException(e);}try {ObjectOutputStream op = new ObjectOutputStream(new FileOutputStream(saveAddress));op.writeInt(panel.getPoints());op.writeObject(player);op.writeInt(enemies.size());synchronized (enemies ){for(int i = 0;i < enemies.size();++i){op.writeObject(enemies.get(i));}}op.close();} catch (IOException e) {throw new RuntimeException(e);} finally {}}}
}
项目源于视频:https://www.bilibili.com/video/BV1fh411y7R8?spm_id_from=333.788.videopod.episodes&vd_source=16bf0c507e4a78c3ca31a05dff1bee4e&p=569