1、java绘图技术-原理
/*
java绘图原理
*/
package tank_Fighting;
import javax.swing.*;
import java.awt.*;
public class demo9_1 extends JFrame{
Mypanel mp = null;
public demo9_1(){
mp = new Mypanel();
this.add(mp);
this.setSize(400, 300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
public static void main(String[] args) {
demo9_1 d = new demo9_1();
}
}
//定义自己的面板,用于绘图和显示绘图的一个区域
class Mypanel extends JPanel{
//需要重写Jpanel的paint方法
//Graphics是绘图的重要类,可以理解为一个画笔
public void paint(Graphics g){
//1、调用父类函数完成初始化任务
super.paint(g);
//2、先画一个圆圈
g.drawOval(10,10,66,66);
}
}
1、paint(Graphics g)绘制组建的外观
2、repaint()刷新组件的外观
当组件第一次在屏幕显示的时候,程序会自动调用paint方法来绘制组件。
在以下情况paint()方法将会被再次调用:
1、窗口最小化,再最大化。
2、窗口的大小发生变化。
3、repaint函数被调用。
Graphics类:
可以理解就是画笔,为我们提供了各种绘制图形的方法。
/*
java绘图原理
*/
package tank_Fighting;
import javax.swing.*;
import java.awt.*;
public class demo9_1 extends JFrame{
Mypanel mp = null;
public demo9_1(){
mp = new Mypanel();
this.add(mp);
this.setSize(400, 300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
public static void main(String[] args) {
demo9_1 d = new demo9_1();
}
}
//定义自己的面板,用于绘图和显示绘图的一个区域
class Mypanel extends JPanel{
//需要重写Jpanel的paint方法
//Graphics是绘图的重要类,可以理解为一个画笔
public void paint(Graphics g){
//1、调用父类函数完成初始化任务
super.paint(g);
//2、先画一个圆圈
g.drawOval(10,10,66,66);
//3、画出直线
g.drawLine(10,10,40,40);
//4、矩形边框
g.drawRect(10, 10, 66, 66);
//设置颜色
g.setColor(Color.red);
//填充矩形
g.fillRect(10, 10, 33, 33);
}
}
2、画出我们的坦克
/*
1、画出坦克
*/
package tank_Fighting;
import javax.swing.*;
import java.awt.*;
public class MyTankGame1 extends JFrame{
Mypanel mp = null;
//构造函数
public MyTankGame1(){
mp = new Mypanel();
this.add(mp);
this.setSize(400, 300);
this.setVisible(true);
}
public static void main(String[] args) {
MyTankGame1 mt = new MyTankGame1();
}
}
//我的面板
class Mypanel extends JPanel{
//定义一个我的坦克
MyTank myTank = null;
//构造函数
public Mypanel(){
myTank = new MyTank(10,10);
}
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0,0,400,300);
drawTank(myTank.getX(),myTank.getY(),g,0,0);
}
//画出坦克
public void drawTank(int x,int y,Graphics g,int direction,int type){
//判断是什么类型的坦克
switch (type) {
case 0:
g.setColor(Color.CYAN);
break;
case 1:
g.setColor(Color.yellow);
break;
}
//判断方向
switch (direction) {
//方向向上
case 0:
g.fill3DRect(x, y, 5, 30,false);
g.fill3DRect(x + 15, y, 5, 30,false);
g.fill3DRect(x+5,y+5,10,20,false);
g.fillOval(x+5,y+10,10,10);
g.drawLine(x+10,y+15,x+10,y);
}
}
}
//坦克类
class Tank{
int x = 0; //横坐标
int y = 0; //纵坐标
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 Tank(int x, int y) {
this.x = x;
this.y = y;
}
}
//我的坦克
class MyTank extends Tank{
public MyTank(int x, int y) {
super(x, y);
}
}
3、事件处理机制
当事件发生时,产生事件的对象(事件源),会把信息传递给事件的监听者。
信息就是java.awt.event事件类库里某个类所创建的对象。
事件源-----监听addactionlistener、setActioncommand-----事件处理
案例:通过点击不同的按钮,让面板的背景色发生相应的变化。
package tank_fighting_test;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
//事件处理机制
public class clickDemo extends JFrame implements ActionListener {
Panel mp = null;
JButton jb1 = null;
JButton jb2 = null;
public clickDemo() {
mp = new Panel();
jb1 = new JButton("黑色");
jb2 = new JButton("红色");
//添加组件
this.add(jb1, BorderLayout.NORTH);
mp.setBackground(Color.black);
this.add(mp);
this.add(jb2, BorderLayout.SOUTH);
//注册监听
jb1.addActionListener(this);
jb2.addActionListener(this);
//指定action命令
jb1.setActionCommand("jb1");
jb2.setActionCommand("jb2");
//设置frame
this.setSize(200, 150);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
public static void main(String[] args) {
clickDemo cd = new clickDemo();
}
//对事件处理的方法
@Override
public void actionPerformed(ActionEvent e) {
//判断是哪个按钮被点击
if (e.getActionCommand().equals("jb1")) {
System.out.println("点击了黑色按钮");
mp.setBackground(Color.black);
} else if (e.getActionCommand().equals("jb2")) {
System.out.println("点击了红色按钮");
mp.setBackground(Color.red);
}
}
}
案例:让小球受键盘控制,上下左右移动。
package tank_fighting_test;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class listenner extends JFrame {
Mypanel mp = null;
public listenner() {
mp = new Mypanel();
this.add(mp);
this.addKeyListener(mp);
this.setSize(400, 300);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
listenner li = new listenner();
}
}
class Mypanel extends Panel implements KeyListener {
int x =10;
int y = 10;
public void paint(Graphics g) {
super.paint(g);
g.fillOval(x, y, 20, 20);
}
//键的一个值被输出
@Override
public void keyTyped(KeyEvent e) {
}
//键被按下
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_UP){
System.out.println("按下了↑");
y--;
//调用repaint来重绘界面
super.repaint();
}
if(e.getKeyCode() == KeyEvent.VK_DOWN){
System.out.println("按下了↓");
y++;
super.repaint();
}
if(e.getKeyCode() == KeyEvent.VK_LEFT){
System.out.println("按下了←");
x--;
super.repaint();
}
if(e.getKeyCode() == KeyEvent.VK_RIGHT){
System.out.println("按下了→");
x++;
super.repaint();
}
}
//键被释放
@Override
public void keyReleased(KeyEvent e) {
}
}
任何一个类,只要它实现了相应了接口,就可以去监听某个事件源。
事件编程步骤:
1、编写事件处理类(事件监听者)
2、根据需求给事件处理类实现监听器接口
3、在事件处理类中重写(实现)其事件处理的函数
4、在事件源类中指定该事件的监听器(响应者)是谁,即注册监听
MytankGame1.java
/*
1、画出坦克
*/
package tank_Fighting;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
public class MyTankGame1 extends JFrame {
Mypanel mp = null;
//构造函数
public MyTankGame1() {
mp = new Mypanel();
this.addKeyListener(mp);
this.add(mp);
this.setSize(400, 300);
this.setVisible(true);
}
public static void main(String[] args) {
MyTankGame1 mt = new MyTankGame1();
}
}
//我的面板
class Mypanel extends JPanel implements KeyListener {
//定义一个我的坦克
MyTank myTank = null;
//定义敌人的坦克组
Vector<EnemyTank> ets = new Vector<EnemyTank>();
int ensize = 3;
//构造函数
public Mypanel() {
myTank = new MyTank(10, 10);
//创建敌人的坦克组
for(int i =0;i<ensize;i++){
EnemyTank et = new EnemyTank((i + 1) * 50, 0);
et.setColor(1);
et.setDirection(2);
ets.add(et);
}
}
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0, 0, 400, 300);
//画出自己的坦克(扩展)
drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 0);
//画出敌人的坦克
for(int i = 0;i<ensize;i++){
drawTank(ets.get(i).getX(),ets.get(i).getY(),g,ets.get(i).getDirection(),ets.get(i).getColor());
}
}
//画出坦克
public void drawTank(int x, int y, Graphics g, int direction, int type) {
//判断是什么类型的坦克
switch (type) {
case 0:
g.setColor(Color.CYAN);
break;
case 1:
g.setColor(Color.yellow);
break;
}
//判断方向
switch (direction) {
//方向向上
case 0: //炮筒向上
g.fill3DRect(x, y, 5, 30, false);
g.fill3DRect(x + 15, y, 5, 30, false);
g.fill3DRect(x + 5, y + 5, 10, 20, false);
g.fillOval(x + 5, y + 10, 10, 10);
g.drawLine(x + 10, y + 15, x + 10, y);
break;
case 1: //炮筒向右
g.fill3DRect(x, y, 30, 5, false);
g.fill3DRect(x, y + 15, 30, 5, false);
g.fill3DRect(x + 5, y + 5, 20, 10, false);
g.fillOval(x + 10, y + 5, 10, 10);
g.drawLine(x + 15, y + 10, x + 30, y + 10);
break;
case 2: //炮筒向下
g.fill3DRect(x, y, 5, 30, false);
g.fill3DRect(x + 15, y, 5, 30, false);
g.fill3DRect(x + 5, y + 5, 10, 20, false);
g.fillOval(x + 5, y + 10, 10, 10);
g.drawLine(x + 10, y + 15, x + 10, y + 30);
break;
case 3: //炮筒向左
g.fill3DRect(x, y, 30, 5, false);
g.fill3DRect(x, y + 15, 30, 5, false);
g.fill3DRect(x + 5, y + 5, 20, 10, false);
g.fillOval(x + 10, y + 5, 10, 10);
g.drawLine(x + 15, y + 10, x, y + 10);
break;
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
myTank.setDirection(0);
myTank.moveUp();
} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
myTank.setDirection(2);
myTank.movedown();
} else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
myTank.setDirection(3);
myTank.moveLeft();
} else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
myTank.setDirection(1);
myTank.moveRight();
}
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
}
member.java
package tank_Fighting;
//坦克类
class Tank {
int speed = 5;
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;
}
int x = 0; //横坐标
int y = 0; //纵坐标
private int direction = 0; //tank方向 1、右 2、下 3、左
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
int color ;
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
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;
}
}
//敌人的坦克
class EnemyTank extends Tank{
public EnemyTank(int x, int y) {
super(x, y);
}
}
//我的坦克
class MyTank extends Tank {
public MyTank(int x, int y) {
super(x, y);
}
//坦克向上移动
public void moveUp(){
y-=speed;
}
//坦克向右移动
public void moveRight(){
x+=speed;
}
//坦克向左移动
public void moveLeft() {
x-=speed;
}
//坦克向下移动
public void movedown() {
y+=speed;
}
}
4、2.0版本,加入线程
当玩家按一下j键,就发射一颗子弹。
子弹会自动往前面跑,所以需要使用线程。
package tank_Fighting;
//子弹类
class shot implements Runnable {
int x, y, diret;
int speed = 5;
//是否还或者
boolean isalive = true;
public shot(int x, int y, int diret) {
this.x = x;
this.y = y;
this.diret = diret;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
switch (diret) {
case 0://上
y -= speed;
break;
case 1://右
x += speed;
break;
case 2://下
y += speed;
break;
case 3://左
x -= speed;
break;
}
System.out.println(x+" "+y);
//子弹何时死亡
//判断该子弹是否碰到边缘
if (x < 0 || x > 400 || y < 0 || y > 300) {
this.isalive = false;
break;
}
}
}
}
//坦克类
class Tank {
int speed = 5;
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;
}
int x = 0; //横坐标
int y = 0; //纵坐标
private int direction = 0; //tank方向 1、右 2、下 3、左
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
int color;
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
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;
}
}
//敌人的坦克
class EnemyTank extends Tank {
public EnemyTank(int x, int y) {
super(x, y);
}
}
//我的坦克
class MyTank extends Tank {
//子弹
shot s = null;
public MyTank(int x, int y) {
super(x, y);
}
//开火
public void fire() {
switch (this.getDirection()) {
case 0:
s = new shot(x + 10, y, 0);
break;
case 1:
s = new shot(x + 30, y + 10, 1);
break;
case 2:
s = new shot(x + 10, y + 30, 2);
break;
case 3:
s = new shot(x, y + 10, 3);
break;
}
//启动子弹线程
Thread t = new Thread(s);
t.start();
}
//坦克向上移动
public void moveUp() {
y -= speed;
}
//坦克向右移动
public void moveRight() {
x += speed;
}
//坦克向左移动
public void moveLeft() {
x -= speed;
}
//坦克向下移动
public void movedown() {
y += speed;
}
}
/*
1、画出坦克
*/
package tank_Fighting;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
public class MyTankGame1 extends JFrame {
Mypanel mp = null;
//构造函数
public MyTankGame1() {
mp = new Mypanel();
//启动mp线程
Thread t = new Thread(mp);
t.start();
this.addKeyListener(mp);
this.add(mp);
this.setSize(400, 300);
this.setVisible(true);
}
public static void main(String[] args) {
MyTankGame1 mt = new MyTankGame1();
}
}
//我的面板
class Mypanel extends JPanel implements KeyListener, Runnable {
//定义一个我的坦克
MyTank myTank = null;
//定义敌人的坦克组
Vector<EnemyTank> ets = new Vector<EnemyTank>();
int ensize = 3;
//构造函数
public Mypanel() {
myTank = new MyTank(10, 10);
//创建敌人的坦克组
for (int i = 0; i < ensize; i++) {
EnemyTank et = new EnemyTank((i + 1) * 50, 0);
et.setColor(1);
et.setDirection(2);
ets.add(et);
}
}
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0, 0, 400, 300);
//画出自己的坦克(扩展)
drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 0);
//画出子弹
if (myTank.s != null && myTank.s.isalive == true) {
g.draw3DRect(myTank.s.x, myTank.s.y, 1, 1, false);
}
//画出敌人的坦克
for (int i = 0; i < ensize; i++) {
drawTank(ets.get(i).getX(), ets.get(i).getY(), g, ets.get(i).getDirection(), ets.get(i).getColor());
}
}
//画出坦克
public void drawTank(int x, int y, Graphics g, int direction, int type) {
//判断是什么类型的坦克
switch (type) {
case 0:
g.setColor(Color.CYAN);
break;
case 1:
g.setColor(Color.yellow);
break;
}
//判断方向
switch (direction) {
//方向向上
case 0: //炮筒向上
g.fill3DRect(x, y, 5, 30, false);
g.fill3DRect(x + 15, y, 5, 30, false);
g.fill3DRect(x + 5, y + 5, 10, 20, false);
g.fillOval(x + 5, y + 10, 10, 10);
g.drawLine(x + 10, y + 15, x + 10, y);
break;
case 1: //炮筒向右
g.fill3DRect(x, y, 30, 5, false);
g.fill3DRect(x, y + 15, 30, 5, false);
g.fill3DRect(x + 5, y + 5, 20, 10, false);
g.fillOval(x + 10, y + 5, 10, 10);
g.drawLine(x + 15, y + 10, x + 30, y + 10);
break;
case 2: //炮筒向下
g.fill3DRect(x, y, 5, 30, false);
g.fill3DRect(x + 15, y, 5, 30, false);
g.fill3DRect(x + 5, y + 5, 10, 20, false);
g.fillOval(x + 5, y + 10, 10, 10);
g.drawLine(x + 10, y + 15, x + 10, y + 30);
break;
case 3: //炮筒向左
g.fill3DRect(x, y, 30, 5, false);
g.fill3DRect(x, y + 15, 30, 5, false);
g.fill3DRect(x + 5, y + 5, 20, 10, false);
g.fillOval(x + 10, y + 5, 10, 10);
g.drawLine(x + 15, y + 10, x, y + 10);
break;
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
myTank.setDirection(0);
myTank.moveUp();
} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
myTank.setDirection(2);
myTank.movedown();
} else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
myTank.setDirection(3);
myTank.moveLeft();
} else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
myTank.setDirection(1);
myTank.moveRight();
}
//判断玩家是否按下J键
if (e.getKeyCode() == KeyEvent.VK_J) {
myTank.fire();
}
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void run() {
//每隔100毫秒进行刷新
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
}
}
}
5、子弹连发
使用vector集合,可以保证线程安全。
通过控制J键的判断来控制子弹的数量。
//判断玩家是否按下J键(最多连发五颗)
if (e.getKeyCode() == KeyEvent.VK_J) {
if (this.myTank.shots.size() < 5) {
myTank.fire();
}
}
但是这个有一个问题,就是子弹在全局只有五颗。所以要想办法在paint函数中进行控制,使用isAlive属性。
//从子弹集合中,取出每一颗子弹并绘制
for (int i = 0; i < myTank.shots.size(); i++) {
//画出子弹
if (myTank.shots.get(i) != null && myTank.shots.get(i).isalive == true) {
g.draw3DRect(myTank.shots.get(i).x, myTank.shots.get(i).y, 1, 1, false);
}
if (myTank.shots.get(i).isalive == false) {
myTank.shots.remove(myTank.shots.get(i));
}
}
6、当我方坦克击中敌人坦克时,敌人的坦克就消失
分析:
炮弹的坐标只要到达敌人的坦克范围内即可,但是有一个问题,就是敌人的坦克也会有方向。
1、写一个专门判断子弹是否击中敌人坦克的函数。(可以直接放在panel中)
//写一个函数专门判断是否子弹击中敌人坦克
public void isHitTank(shot s, EnemyTank et) {
//判断该坦克的方向
switch (et.getDirection()) {
//如果敌人坦克方向是上或者是下
case 0:
case 2:
if (s.x > et.x && s.x < et.x + 20 && s.y > et.y && s.y < et.y + 30) {
//击中
//子弹死亡
s.isalive = false;
//敌人坦克死亡
et.islive = false;
}
case 1:
case 3:
if (s.x > et.x && s.x < et.x + 30 && s.y > et.y && s.y < et.y + 20) {
//击中
//子弹死亡
s.isalive = false;
//敌人坦克死亡
et.islive = false;
}
}
2、什么地方调用该函数
要随时检测,所以放在run函数当中。
注意问题:需要判断每一颗子弹。
//判断是否击中
for (int i = 0; i < myTank.shots.size(); i++) {
//取出子弹
shot myshot = myTank.shots.get(i);
//判断子弹是否有效
if (myshot.isalive) {
//取出每一个敌人坦克,与之匹配
for (int j = 0; j < ets.size(); j++) {
EnemyTank et = ets.get(j);
if (et.islive) {
this.isHitTank(myshot,et);
}
}
}
}
3、在画出敌人坦克的地方进行if判断
//画出敌人的坦克
for (int i = 0; i < ensize; i++) {
EnemyTank et = ets.get(i);
if (et.islive) {
drawTank(et.getX(),et.getY(),g,et.getDirection(),et.getColor());
}
}
7、爆炸效果
1、先准备三张图片
//定义三张图片
Image image1 = null;
Image image2 = null;
Image image3 = null;
//初始化图片
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/素材/bomb_1.gif"));
image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/素材/bomb_2.gif"));
image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/素材/bomb_3.gif"));
考虑到可能不止一处炸弹,所以炸弹也需要为集合。
要在members里面新建炸弹类
//炸弹类
class boom {
//定义炸弹的坐标
int x, y;
//定义炸弹的生命
int life = 9;
boolean isAlive = true;
//构造器
public boom(int x, int y) {
this.x = x;
this.y = y;
}
//减少生命值
public void life_dowm() {
if (life > 0) {
life--;
} else {
isAlive = false;
}
}
}
在击中敌人坦克时,把炸弹放入Vector中。
在ishittank函数中进行判断
//判断该坦克的方向
switch (et.getDirection()) {
//如果敌人坦克方向是上或者是下
case 0:
case 2:
if (s.x > et.x && s.x < et.x + 20 && s.y > et.y && s.y < et.y + 30) {
//击中
//子弹死亡
s.isalive = false;
//敌人坦克死亡
et.islive = false;
//创建一颗炸弹,放入booms中
boom b = new boom(et.x, et.y);
booms.add(b);
}
case 1:
case 3:
if (s.x > et.x && s.x < et.x + 30 && s.y > et.y && s.y < et.y + 20) {
//击中
//子弹死亡
s.isalive = false;
//敌人坦克死亡
et.islive = false;
//创建一颗炸弹,放入vector中
boom b = new boom(et.x, et.y);
booms.add(b);
}
}
然后想办法进行绘制。
//画出炸弹
for (int i = 0; i < booms.size(); i++) {
//取出炸弹
boom b = booms.get(i);
if (b.life > 6) {
g.drawImage(image1, b.x, b.y, 30, 30, this);
} else if (b.life > 4) {
g.drawImage(image2, b.x, b.y, 30, 30, this);
} else {
g.drawImage(image3, b.x, b.y, 30, 30, this);
}
b.life_down();
//如果炸弹生命值为0,就把该炸弹从向量中去除
if (b.life == 0) {
booms.remove(b);
}
}
有一个问题没有解决:
当第一次碰到一个敌人的坦克的时候,敌人的坦克并不会爆炸。
8、让敌人的坦克也可以自由随机的上下左右移动
1、因为敌人的坦克坐标要单独更改,所以考虑给敌人的坦克增加线程。
//敌人的坦克,把敌人的坦克做成线程类
class EnemyTank extends Tank implements Runnable {
boolean islive = true;
public EnemyTank(int x, int y) {
super(x, y);
}
@Override
public void run() {
while (true) {
switch (this.getDirection()) {
case 0:
//说明坦克在向上走
for (int i = 0; i < 30; i++) {
y -= speed;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1:
for (int i = 0; i < 30; i++) {
x += speed;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2:
for (int i = 0; i < 30; i++) {
y += speed;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3:
for (int i = 0; i < 30; i++) {
x -= speed;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//让坦克随机产生一个新的方向
this.setDirection((int) (Math.random() * 4));
//判断敌人是否死亡
if (this.islive == false) {
//让坦克死亡后退出线程
break;
}
}
}
}
2、控制我方坦克和敌人的坦克在规定的范围移动
在第一部分的代码中加入控制条件即可,也就是,如果范围小于边界,才可以让其自增或者自减少。
3、让敌人的坦克也发射子弹
应该与我的坦克的发射子弹的原理一样
首先在敌人的坦克的类中加入子弹向量
//定义一个向量,可以存放敌人的子弹
Vector<shot> ss_e = new Vector<shot>();
然后在Mypanel的构造函数中给敌人坦克添加一颗子弹,让敌人的坦克一生成就发射一颗子弹。
//给敌人坦克加一颗子弹
shot s = new shot(et.x+10, et.y+30,2);
et.ss_e.add(s);
Thread t2 = new Thread(s);
t2.start();
在paint函数中,画出子弹。
//画出敌人的坦克
for (int i = 0; i < ensize; i++) {
EnemyTank et = ets.get(i);
if (et.islive) {
drawTank(et.getX(), et.getY(), g, et.getDirection(), et.getColor());
//画出敌人的子弹
for(int j = 0;j<et.ss_e.size();j++) {
shot enemyshot = et.ss_e.get(j);
if (enemyshot.isalive) {
g.draw3DRect(enemyshot.x,enemyshot.y,1,1,false);
}else {
//如果敌人的坦克死亡,就从向量里删除掉子弹
et.ss_e.remove(enemyshot);
}
}
}
}
让敌人的坦克不止可以发一颗子弹
在敌人的坦克的类中
this.times++;
if (times % 2 == 0) {
if (this.islive) {
if (ss_e.size() < 5) {
shot s= null;
//没有子弹
//添加
switch (getDirection()) {
case 0:
s = new shot(x + 10, y, 0);
ss_e.add(s);
break;
case 1:
s = new shot(x + 30, y + 10, 1);
ss_e.add(s);
break;
case 2:
s = new shot(x + 10, y + 30, 2);
ss_e.add(s);
break;
case 3:
s = new shot(x, y + 10, 3);
ss_e.add(s);
break;
}
Thread t = new Thread(s);
t.start();
}
}
}
9、当敌人坦克击中我的坦克时,我的坦克就爆炸
和我们的坦克击中敌人坦克一样,可以在panel面板的run函数中进行判断
进行函数的封装
//写一个函数专门判断敌人坦克是否击中我
public void hitMyTank() {
//取出每一个敌人的坦克
for (int i = 0; i < ets.size(); i++) {
EnemyTank et = ets.get(i);
//取出每一颗子弹
for(int j =0;j<et.ss_e.size();j++) {
shot s = et.ss_e.get(j);
this.isHitTank(s,myTank);
}
}
}
这需要修改原来的isHitTank函数的第二个输入参数。
修改之后,要在member的tank类中加入islive属性。
//写一个函数专门判断是否子弹击中敌人坦克
public void isHitTank(shot s, Tank et) {
//判断该坦克的方向
if (et.getDirection() == 0 || et.getDirection() == 2) {
if (s.x > et.getX() && s.x < et.getX() + 20 && s.y > et.getY() && s.y < et.getY() + 30) {
//击中
//子弹死亡
s.isalive = false;
//敌人坦克死亡
et.islive = false;
//创建一颗炸弹,放入booms中
boom b = new boom(et.x, et.y);
booms.add(b);
}
}
if (et.getDirection() == 1 || et.getDirection() == 3) {
if (s.x > et.getX() && s.x < et.getX() + 30 && s.y > et.getY() && s.y < et.getY() + 20) {
//击中
//子弹死亡
s.isalive = false;
//敌人坦克死亡
et.islive = false;
//创建一颗炸弹,放入vector中
boom b = new boom(et.x, et.y);
booms.add(b);
}
}
}
现在还有一个问题,就是敌人的坦克可以重叠起来。
解决思路:
1、代码的位置,应该在每个地方坦克移动的位置。
2、判断条件,不管两个坦克的方向,默认为最大矩形框。
3、如果在敌方某一个坦克的移动方向上,获取到了另一个坦克的矩形框,那么就不能再往前走。
日后解决
决定把判断是否碰撞的函数写到EnemyTank类
首先需要拿到mypanel里面的敌人坦克
//得到mypannel敌人的坦克向量
public void getEts(Vector enemy) {
this.ets = enemy;
}
判断是否撞到别的地方坦克
//判断是否碰到了别的坦克
public boolean isTouchOther() {
boolean b = false;
switch (this.getDirection()) {
case 0:
for (int i = 0; i < ets.size(); i++) {
EnemyTank et = ets.get(i);
if (et != this) {
if (this.x >= et.x && this.x <= et.x + 30 & this.y >= et.y && this.y <= et.y + 30) {
return true;
}
if (this.x + 20 >= et.x && this.x + 20 <= et.x + 30 && this.y >= et.y && this.y >= et.y + 30) {
return true;
}
}
}
break;
case 1://向右
for (int i = 0; i < ets.size(); i++) {
EnemyTank et = ets.get(i);
if (et != this) {
if (this.x + 30 >= et.x && this.x + 30 <= et.x + 30 & this.y >= et.y && this.y <= et.y + 30) {
return true;
}
if (this.x + 30 >= et.x && this.x + 30 <= et.x + 30 && this.y + 30 >= et.y && this.y + 30 <= et.y + 30) {
return true;
}
}
}
break;
case 2://向下
for (int i = 0; i < ets.size(); i++) {
EnemyTank et = ets.get(i);
if (et != this) {
if (this.x >= et.x && this.x <= et.x + 30 & this.y + 30 >= et.y && this.y + 30 <= et.y + 30) {
return true;
}
if (this.x + 30 >= et.x && this.x + 30 <= et.x + 30 && this.y + 30 >= et.y && this.y + 30 <= et.y + 30) {
return true;
}
}
}
break;
case 3://向左
for (int i = 0; i < ets.size(); i++) {
EnemyTank et = ets.get(i);
if (et != this) {
if (this.x >= et.x && this.x <= et.x + 30 & this.y >= et.y && this.y <= et.y + 30) {
return true;
}
if (this.x + 30 >= et.x && this.x + 30 <= et.x + 30 && this.y >= et.y && this.y >= et.y + 30) {
return true;
}
}
}
break;
}
return b;
}
10、可以分关
写一个提示关卡的panel
class MyStartPanel extends JPanel implements Runnable {
int times = 0;
public void paint(Graphics graphics) {
super.paint(graphics);
graphics.fillRect(0, 0, 400, 300);
if (times % 2 == 0) {
graphics.setColor(Color.yellow);
Font myfont = new Font("微软雅黑", Font.BOLD, 30);
graphics.setFont(myfont);
graphics.drawString("stage 1", 150, 150);
}
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
times++;
this.repaint();
}
}
}
//作出我需要的菜单
JMenuBar jmb = null;
//开始游戏
JMenu jm = null;
JMenuItem jm1 = null;
//创建菜单及菜单选项
jmb = new JMenuBar();
jm = new JMenu("游戏");
//设置快捷方式
jm.setMnemonic('G');
jm1 = new JMenuItem("开始游戏");
jm1.addActionListener(this);
jm1.setActionCommand("newgame");
jm.add(jm1);
jmb.add(jm);
this.setJMenuBar(jmb);
//开始界面
msp = new MyStartPanel();
Thread t = new Thread(msp);
this.add(msp);
t.start();
把游戏的panel放在关卡panel线程的run方法中,并进行事件监听,如果按到meuaItem中的开始游戏,那么就启动游戏画面的panel线程。
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("newgame")) {
mp = new Mypanel();
//启动mp线程
Thread t = new Thread(mp);
t.start();
this.remove(msp);
this.add(mp);
this.addKeyListener(mp);
//刷新Jframe
this.setVisible(true);
}
11、保存玩家的游戏情况
//记录类,同时也可以保存玩家的设置
class Recorder{
public static int getEnNum() {
return enNum;
}
public static void setEnNum(int enNum) {
Recorder.enNum = enNum;
}
public static int getMylife() {
return mylife;
}
public static void setMylife(int mylife) {
Recorder.mylife = mylife;
}
private static int enNum = 20;
//设置命数
private static int mylife = 3;
}
private void drawInfo(Graphics g) {
//画出提示信息坦克(该坦克不参与战斗)
this.drawTank(0,330,g,0,0);
g.setColor(Color.black);
g.drawString(":"+String.valueOf(Recorder.getEnNum()),35,340);
this.drawTank(100, 330, g, 0, 1);
g.setColor(Color.black);
g.drawString(":"+String.valueOf(Recorder.getMylife()),135,340);
}
当我打到敌人坦克时候,面板下面的提示信息中,敌人的坦克数量就减少一。
在RECORDER类里面使用文件流来保存玩家击毁的数量
//把玩家击毁敌坦克的数量进行保存,保存到文件中
public static void keepRecorder() throws IOException {
//创建
fw = new FileWriter("F:\\javatest\\recorder.txt");
bfw = new BufferedWriter(fw);
bfw.write(destroy_en+"\r\n");
//后开的流先关闭
bfw.close();
fw.close();
}
现在希望开局的时候右上角显示的您的总成绩是上面文件保存的数量。
//从文件中读取记录
public static void readRecorder() throws IOException {
fr = new FileReader("F:\\javatest\\recorder.txt");
bfr = new BufferedReader(fr);
String text = bfr.readLine();
destroy_en = Integer.parseInt(text);
bfr.close();
fr.close();
}
然后在Mypanel的构造函数中调用这个函数,来进行读取。
下一步做出存盘退出游戏,目标:可以记录当时的敌人的坦克坐标并可以恢复。
//保存击毁敌人的数量和敌人坦克的坐标,方向
public static void keepRecorderAndEnemy() throws IOException {
//创建
fw = new FileWriter("F:\\javatest\\recorder.txt");
bfw = new BufferedWriter(fw);
//第一行为击毁敌人坦克的数量
bfw.write(destroy_en+"\r\n");
//保存当前还活着的坦克的坐标和方向
for(int i = 0;i<ets.size();i++) {
//取出第一个坦克
EnemyTank et = ets.get(i);
if (et.islive) {
//活着就保存
String isliveEnemyTank= et.x+" "+et.y+" "+et.getDirection();
bfw.write(isliveEnemyTank);
bfw.flush();
}
}
//后开的流先关闭
bfw.close();
fw.close();
}
要做到上一步首先需要访问到Mypanel中地方坦克的坐标,所以在需要member类中写一个函数
private static Vector<EnemyTank> ets = new Vector<>();
public Vector<EnemyTank> getEts() {
return ets;
}
public static void setEts(Vector<EnemyTank> ets) {
Recorder.ets = ets;
}
下一步进行恢复
class Node{
int x;
int y;
int direct;
public Node(int x, int y, int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
}
在Recorder类中,返回读取后的点的集合。
//完成读取任务
public Vector<Node> getNodeAndNums() throws IOException {
fr = new FileReader("F:\\javatest\\recorder.txt");
bfr = new BufferedReader(fr);
String text = null;
//先读取第一行
text = bfr.readLine();
destroy_en = Integer.parseInt(text);
while ((text = bfr.readLine()) != null) {
String[] xyd = text.split(" ");
Node node = new Node(Integer.parseInt(xyd[0]),Integer.parseInt(xyd[1]),Integer.parseInt(xyd[2]));
nodes.add(node);
}
bfr.close();
fr.close();
return nodes;
}
因为敌人坦克的初始化是在mypanel中初始化的,所以在reload的时候代码要放在mypanel中。
可以在Mypanel的构造函数中加入参数来判断是不是新游戏还是继续游戏,然后再根据情况来进行读取或者新建游戏。
public Mypanel(String flag) throws IOException {
//恢复记录
Recorder.readRecorder();
myTank = new MyTank(10, 10);
//创建敌人的坦克组
if(flag == "newgame") {
for (int i = 0; i < ensize; i++) {
EnemyTank et = new EnemyTank((i + 1) * 50, 0);
et.setColor(1);
et.setDirection(2);
et.getEts(this.ets); //给member里面的敌人坦克传入其他敌人坦克的坐标,放置覆盖
//启动敌人坦克的线程
Thread t = new Thread(et);
t.start();
//给敌人坦克加一颗子弹
shot s = new shot(et.x + 10, et.y + 30, 2);
et.ss_e.add(s);
Thread t2 = new Thread(s);
t2.start();
ets.add(et);
}
}else {
for (int i = 0; i < nodes.size(); i++) {
Node n = nodes.get(i);
EnemyTank et = new EnemyTank(n.x, n.y);
et.setColor(1);
et.setDirection(n.direct);
et.getEts(this.ets); //给member里面的敌人坦克传入其他敌人坦克的坐标,放置覆盖
//启动敌人坦克的线程
Thread t = new Thread(et);
t.start();
//给敌人坦克加一颗子弹
shot s = new shot(et.x + 10, et.y + 30, 2);
et.ss_e.add(s);
Thread t2 = new Thread(s);
t2.start();
ets.add(et);
}
}
//初始化图片
image1 = Toolkit.getDefaultToolkit().getImage(MyTankGame1.class.getResource("素材/bomb_1.gif"));
image2 = Toolkit.getDefaultToolkit().getImage(MyTankGame1.class.getResource("素材/bomb_2.gif"));
image3 = Toolkit.getDefaultToolkit().getImage(MyTankGame1.class.getResource("素材/bomb_3.gif"));
}
然后在actionperform中对按钮监听的结果进行处理
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("newgame")) {
try {
mp = new Mypanel("newGame");
} catch (IOException e1) {
e1.printStackTrace();
}
//启动mp线程
Thread t = new Thread(mp);
t.start();
this.remove(msp);
this.add(mp);
this.addKeyListener(mp);
//刷新Jframe
this.setVisible(true);
}
else if (e.getActionCommand().equals("exit")) {
//保存玩家的击毁的数量
try {
Recorder.keepRecorder();
} catch (IOException e1) {
e1.printStackTrace();
}
//退出系统
System.exit(0);
}
//存盘退出
else if (e.getActionCommand().equals("save")) {
Recorder.setEts(mp.ets);
//保存击毁敌人的数量和敌人的坐标
try {
Recorder.keepRecorderAndEnemy();
} catch (IOException e1) {
e1.printStackTrace();
}
//退出
System.exit(0);
}
//存盘恢复
else if (e.getActionCommand().equals("reload")) {
try {
mp = new Mypanel("reload");
} catch (IOException e1) {
e1.printStackTrace();
}
try {
mp.nodes = new Recorder().getNodeAndNums();
} catch (IOException e1) {
e1.printStackTrace();
}
Thread t = new Thread(mp);
t.start();
this.remove(msp);
this.addKeyListener(mp);
this.setVisible(true);
}
}