1.子弹发射原理分析
1.首先想到,子弹应该是一个单独的线程,因为发射后就和坦克的移动无关了,自己会一直朝前走。
2.子弹程序内部:(1)子弹要朝前走,所以他应该是一个循环一直改变位置的值,应该有发射方向,初始位置,发射速度这三个基本参数需要初始化。(2)子弹应该沿着初始方向一直匀速前进,直到出界才销毁,所以应该定义一个子弹位置x,y不断变化的循环。
3.子弹是某个坦克的子弹,所以他可以在坦克的类中使用并创建,包装在坦克类的方法里,调用这个方法即可发射一枚子弹(线程生成)。
4.很明显发射子弹需要按键触发,所以在画布的监听器里加一个触发器,如果按了某个键就调用某个坦克的发射子弹方法。
5.现在子弹其实已经发射了,只需要把子弹画出来就行了,由于子弹一直在动,所以我们要不停的重绘,才有动态效果,画布是一个单独的类,想要不断重绘就要把它也线程化,在run里写repaint,然后用while(true)包裹住。
(这里要注意,对于监听器来说,会自动监视passkey方法,对于new 画布来说,会自动调用paint方法,对于Runnable这个线程接口来说,会自动调用run方法,这三者不是谁包括谁的关系,而是并列的,都会自动调用。而且,重写方法不能再方法里,所以不可能包互相包括)
6.关于画子弹,就在paint里面直接画圆就行,坐标传的是坦克对象里的子弹对象的x和y,因为x,y会自动更新,然后画图线程会不断重绘,就会出来动态效果了。
(这里有个注意点的点,就是我们调坦克对象肯定没问题, 但是调坦克对象里的子弹对象,存在两个问:
1).作用域问题,要把子弹这个对象先在属性那定义出来,也就是
Shot shot = null;
然后再再方法里new他
public void shoot(){
shot = new Shot(x, y, direct);
shot.start();
}
这样做的目的是能够直接调用,不然作用域不够找不到shot这个子弹对象
2).由于一开始shou是null,所以画图的时候要先判断是否以及存在了shot对象,不然直接用的话肯定会报错(因为我们要用对象里面的x,y)
2.升级->连发
上述方法可以实现发射一枚子弹,但是如果再按一次,子弹会消失,重新发射。
我一开始以为原因是每次生成子弹的时候都是重新覆盖了,所以原来的子弹对象消失了,这其实是个很愚蠢且外行的误解。对象生成了他就存在那里不会覆盖,对象名就像指针一样,重新给他赋了对象他只不过指向新的对象了而已,原来的对象只不过失去了名字。更何况他作为一个线程,肯定还在自己运行的呀,举例:
for (int i = 0; i<2 ; i++) {
cc cc = new cc();
cc.start();
}
像这个循环(与本题问题极为相似),他会生成两个线程,互不相干,也就是两个对象。
这么愚蠢的问题不应该画这么大的篇幅解释,不过作为编程成长路上的问题,遇到这种问题也应该认真对待。
所以回归正题
想要实现连发,首先先明白目前这种现象的原因是什么(现象是每次只能发一枚子弹,发第二枚,第一枚就会消失),很明显是因为再绘图那里,始终在画新的子弹,有了新的线程,新的对象,就会画新的,因为新的才叫shot…
绘画的语句如下:
g.fillOval(hero.shot.x, hero.shot.y, 10, 10);
解决方法:
因为每次按下j就会创建新的子弹对象,所以在按键那里把新的子弹对象加到集合里就好,然后绘图的时候就不断循环遍历集合即可(注意集合要用Vector,线程安全),代码如下:
if(e.getKeyCode() == KeyEvent.VK_J){
hero.shoot(); //这里shoot方法里会创建Shot的对象,赋给shot这个对象名字
zidan.add(hero.shot); //把新的shot加进去
}
if(!(zidan.isEmpty())){
for (Shot s : zidan) {
g.fillOval(s.x, s.y, 10, 10); //这里zidan集合记得用泛型,不然还要向下转型
}
}
下面附上完整代码:
DrawCircle类:
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Map;
import java.util.Vector;
public class DrawCircle extends JFrame { //这是画板(框)(展示器)
private MyPanel mp = null;
public static void main(String[] args) {
new DrawCircle();
}
public DrawCircle(){
mp = new MyPanel(); //这是画布
Thread thread = new Thread(mp);
thread.start();
this.add(mp); //调的本对象的父类JFrame的方法 //在框架里加入画布
this.addKeyListener(mp);
this.setSize(1000,750); //设置框的大小
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}
class MyPanel extends JPanel implements KeyListener,Runnable { //画布,在这里画东西
Hero hero = null;
Vector<Enemy> enemies = new Vector<>(); //用一个集合存储enemy,用Vector的原因是可以多线程
Vector<Shot> zidan = new Vector<>(); //用一个集合存储enemy,用Vector的原因是可以多线程
int enemySize = 4;
public MyPanel(){
hero = new Hero(10,20,0);
for (int i = 0; i<enemySize; i++){
enemies.add(new Enemy(100*i,0,0));
}
}
@Override
public void paint(Graphics g) { //g是画笔 //这个paint函数是自动调用的,只要new了MyPanel这个类就会调,相当于画画的工作台
super.paint(g); //要先调用父类初始化
g.fillRect(0,0,1000,750); //画一个和框架大小一样的和黑色背景
drawTank(hero.getX(),hero.getY(),g,hero.getDirect(),0);//调用画坦克的方法
if(!(zidan.isEmpty())){
for (Shot s : zidan) {
g.fillOval(s.x, s.y, 10, 10);
}
}
for(Enemy e : enemies){
drawTank(e.getX(),e.getY(),g,e.getDirect(),1);//调用画坦克的方法
}
}
/*
x,y是坦克的坐标
g是画笔
direct是坦克面朝的方向
type是坦克的类型
*/
public void drawTank(int x, int y , Graphics g, int direct, int type){
//绘制不同类型的坦克
switch (type){
case 0: //我们的坦克
g.setColor(Color.cyan); //把g这个画笔设置成某种颜色
break;
case 1: //敌人的坦克
g.setColor(Color.yellow);
break;
}
//绘制不同方向的坦克
switch (direct){
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+60,y+20);//画炮
break;
case 2://下
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);//画炮
break;
case 3: //左
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;
default:
System.out.println("输错了");
break;
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()){
case KeyEvent.VK_DOWN:
hero.setDirect(2);
hero.setY(hero.getY()+3);
break;
case KeyEvent.VK_UP:
hero.setDirect(0);
hero.setY(hero.getY()-3);
break;
case KeyEvent.VK_LEFT:
hero.setDirect(3);
hero.setX(hero.getX()-3);
break;
case KeyEvent.VK_RIGHT:
hero.setDirect(1);
hero.setX(hero.getX()+3);
break;
}
if(e.getKeyCode() == KeyEvent.VK_J){
hero.shoot();
zidan.add(hero.shot);
}
this.repaint(); //一直在监视键盘,有了就重绘
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void run() {
while (true) this.repaint();
}
}
Tank类:
import java.util.Vector;
public class Tank {
private int x;
private int y;
private int direct;
Shot shot = null;
Vector<Shot> zidan = new Vector<>();
public Tank(int x, int y, int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getDirect() {
return direct;
}
public void setDirect(int direct) {
this.direct = direct;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public void shoot(){
shot = new Shot(x, y, direct);
shot.start();
}
}
Hero类
public class Hero extends Tank{
public Hero(int x, int y,int direct) {
super(x, y, direct);
}
}
Shot类
public class Shot extends Thread{
int x,y; //子弹当前的坐标
int direct = 0; //坦克当前的方向
int speed = 3; //子弹的飞行速度
boolean isLive = true;
public Shot(int x, int y, int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
@Override
public void run() {
super.run();
switch (direct){
case 0:
x = x+20-5;
break;
case 1:
x = x+60;
y = y+20-5;
break;
case 2:
x = x+20-5;
y = y+60;
break;
case 3:
y = y+20-5;
break;
}
while (x>0&& x<1000&&y>0&&y<750){
switch (direct){
case 0:
y-=3;
break;
case 1:
x+=3;
break;
case 2:
y+=3;
break;
case 3:
x-=3;
break;
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
isLive = false;
}
}