1. 线程-应用到坦克大战04版 599
1.1 增加功能 599-601
java线程基本知识,现在我们来实际运用一下。添加如下功能:当玩家按一下 j键,就发射颗子弹.
分析如何实现当用户按下J键,我们的坦克就发射一颗子弹.
1.1.1 思路
1.当发射一颗子弹后,就相当于启动一个线程
2. Hero有子弹的对象,当按下J时,我们就启动一个发射行为(线程),让子弹不停的移动,形成一 个射击的效果
3.我们MyPanel需要不停的重绘子弹,才能出现该效果.
4.当子弹移动到面板的边界时,就应该销毁(把启动的子弹的线程销毁)
代码在com.stulzl.tankgame04.包中
测试TankGame04
package com.stulzl.tankgame04;
import javax.swing.*;
//主方法 572
public class TankGame04 extends JFrame {
//定义我们的MyPanel
MyPanel mp = null;
public static void main(String[] args) {
TankGame04 tankGame01 = new TankGame04();//调用显示结果
}
//构造器设置窗口信息
public TankGame04(){
mp = new MyPanel();//初始化面板
//将mp放入到Thread 并启动
Thread thread = new Thread(mp);
thread.start();//启动MyPanel线程
this.add(mp);//将面板添加到窗口
this.setSize(1000,750);//设置窗口大小
this.addKeyListener(mp);//让JFrame监听mp
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭程序
this.setVisible(true);//显示窗口
}
}
父类Tank
package com.stulzl.tankgame04;
public class Tank {//父类坦克
private int x;//坦克的横坐标
private int y;//坦克的纵坐标
private int direct;//坦克方向0上 1右 2下 3左 577
private int speed = 1;//控制坦克速度
//坦克上右下左移动方法 577
public void moveUp(){//向上移动
y-=speed;
}
public void moveRight(){//向右移动
x+=speed;
}
public void moveDown(){//向下移动
y+=speed;
}
public void moveLeft(){//向左移动
x-=speed;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getDirect() {
return direct;
}
public void setDirect(int direct) {
this.direct = direct;
}
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;
}
}
我们的坦克Hero
package com.stulzl.tankgame04;
public class Hero extends Tank {//我们的坦克,设置坦克坐标并且开启Shot射击线程
//定义一个Shot对象,表示射击(线程)
Shot shot = null;
public Hero(int x, int y) {
super(x, y);
}
//射击方法 600
public void shortEnemyTank(){
//创建一个Shot对象,根据Hero坦克的位置 方向
switch(getDirect()){
case 0://向上
shot = new Shot(getX()+20,getY(),0);//因为射击Shot线程有有参构造器,所以要传值
break;
case 1://向右
shot = new Shot(getX()+60,getY()+20,1);
break;
case 2://向下
shot = new Shot(getX()+20,getY()+60,2);
break;
case 3://向左
shot = new Shot(getX(),getY()+20,3);
break;
}
//启动Shot线程,因为Shot实现了Runnable接口,所以直接调用即可
Thread thread = new Thread(shot);
thread.start();//启动
}
}
射击线程Shot
package com.stulzl.tankgame04;
//发射子弹线程 600
public class Shot implements Runnable{
int x;//子弹x坐标
int y;//子弹y坐标
int direct=0;//子弹的方向
int speed = 2;//子弹的速度
boolean isLive = true;//子弹是否存活
//构造器,接收传来的值
public Shot(int x, int y, int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
@Override
public void run() {//射击行为
while(true){
//休息50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
switch(direct){//根据方向值选择相应的子弹方向路线
case 0://向上
y-=speed;
break;
case 1://向右
x+=speed;
break;
case 2://向下
y+=speed;
break;
case 3://向左
x-=speed;
break;
}
System.out.println("子弹x= "+x+" y= "+y);
//当子弹移动到面板的边界时,就应该销毁(把启动的子弹的线程销毁)
if(!(x>=0 && x<=1000 && y>=0 && y<=750)){//子弹越界出窗口范围
isLive = false;//子弹死了
break;
}
}
}
}
敌人坦克EnemyTank
package com.stulzl.tankgame04;
public class EnemyTank extends Tank{//敌人的坦克
public EnemyTank(int x, int y) {
super(x, y);
}
}
画板MyPanel
package com.stulzl.tankgame04;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
//坦克的绘图区
//为了响应键盘事件,实现KeyListener
//为了让Panel 不停的重绘子弹,需要将 MyPanel 实现Runnable ,当做一个线程使用 601
public class MyPanel extends JPanel implements KeyListener,Runnable{
//定义我们的坦克
Hero hero = null;
//定义敌人坦克,放入到Vector中 578
Vector enemyTanks = new Vector<>();
int enemyTanksSize = 3;//敌人坦克数量
//构造器设置坦克信息
public MyPanel(){
hero = new Hero(100,100);//创建并初始化自己的坦克坐标
hero.setSpeed(2);//改变坦克速度
//初始化敌人坦克
for (int i = 0; i <enemyTanksSize; i++) {
//创建并设置敌人坦克坐标,每隔100像素放一辆坦克
EnemyTank enemyTank = new EnemyTank((100 * (i + 1)), 0);
//更改敌人坦克方向
enemyTank.setDirect(2);
//舔加敌人坦克到Vector
enemyTanks.add(enemyTank);
}
}
//绘图方法
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0,0,1000,750);//填充矩形,默认黑色,这个是坦克的游戏区域
//画出自己坦克,我们把方法封装到一个方法,调用即可 573
drawTank(hero.getX(), hero.getY(), g,hero.getDirect(),1);
//画出hero射击的子弹
if(hero.shot!=null && hero.shot.isLive){//如果子弹存在且存活就画,如果子弹运动到边界就不画了
g.draw3DRect(hero.shot.x,hero.shot.y,1,1,false);//子弹大小 长1 高1
//this.repaint();//重绘
}
//画出敌人坦克,遍历Vector集合
for (int i = 0; i <enemyTanks.size(); i++) {
//取出敌人坦克
EnemyTank enemyTank = enemyTanks.get(i);
//画敌人坦克
drawTank(enemyTank.getX(),enemyTank.getY(),g,enemyTank.getDirect(),0);
}
}
/**
* @param x 坦克的左上角 x 坐标
* @param y 坦克的左上角 y 坐标
* @param g 画笔
* @param direct 坦克方向(上下左右)
* @param type 坦克类型
*/
//编写方法画出坦克
public void drawTank(int x,int y,Graphics g,int direct,int type){
//根基不同的类型,设置不同的颜色
switch(type){
case 0://敌人的坦克
g.setColor(Color.cyan);
break;
case 1://我们的坦克
g.setColor(Color.yellow);
break;
}
//根据坦克方向,来绘制对应方向的坦克 577
//direct 表示方向(0: 向上 1 向右 2 向下 3 向左 )
switch(direct){
case 0://表示坦克向上
g.fill3DRect(x,y,10,60,false);//左边轮子//绘制3D的矩形看起来更像 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);//左边轮子//绘制3D的矩形看起来更像 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);//左边轮子//绘制3D的矩形看起来更像 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);//左边轮子//绘制3D的矩形看起来更像 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("暂时没有别的操作");
}
}
//三个方法
//有字符输出,触发
public void keyTyped(KeyEvent e) {
}
//按下键盘,触发
//处理wdsa键按下的情况 577
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_W){//方向向上
//改变坦克的方向
hero.setDirect(0);
hero.moveUp();
}else if(e.getKeyCode()==KeyEvent.VK_D){//方向向右
hero.setDirect(1);
hero.moveRight();
}else if(e.getKeyCode()==KeyEvent.VK_S){//方向向下
hero.setDirect(2);
hero.moveDown();
}else if(e.getKeyCode()==KeyEvent.VK_A){//方向向左
hero.setDirect(3);
hero.moveLeft();
}
//如果用户按下J键,就发射子弹
if(e.getKeyCode()==KeyEvent.VK_J){
hero.shortEnemyTank();//调用射击方法
}
this.repaint();//面板重绘
}
//松开键盘,触发
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.repaint();
}
}
}
2. 坦克大战 05 版 602-606
2.1 增加功能
1.让敌人的坦克也能够发射子弹(可以有多颗子弹)
2.当我方坦克击中敌人坦克时,敌人的坦克就消失,如果能做出爆炸效果更好.
3.让敌人的坦克也可以自由随机的上下左右移动
4.控制我方的坦克和敌人的坦克在规定的范围移动分析--->解决
2.1.2 思路
代码在com.stulzl.tankgame05.包中
测试TankGame05
package com.stulzl.tankgame05;
import javax.swing.*;
//主方法 572
public class TankGame05 extends JFrame {
//定义我们的MyPanel
MyPanel mp = null;
public static void main(String[] args) {
TankGame05 tankGame01 = new TankGame05();//调用显示结果
}
//构造器设置窗口信息
public TankGame05(){
mp = new MyPanel();//初始化面板
//将mp放入到Thread 并启动
Thread thread = new Thread(mp);
thread.start();//启动MyPanel线程
this.add(mp);//将面板添加到窗口
this.setSize(1000,750);//设置窗口大小
this.addKeyListener(mp);//让JFrame监听mp
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭程序
this.setVisible(true);//显示窗口
}
}
父类Tank
package com.stulzl.tankgame05;
public class Tank {//父类坦克
private int x;//坦克的横坐标
private int y;//坦克的纵坐标
private int direct;//坦克方向0上 1右 2下 3左 577
private int speed = 1;//控制坦克速度
//坦克上右下左移动方法 577
public void moveUp(){//向上移动
y-=speed;
}
public void moveRight(){//向右移动
x+=speed;
}
public void moveDown(){//向下移动
y+=speed;
}
public void moveLeft(){//向左移动
x-=speed;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getDirect() {
return direct;
}
public void setDirect(int direct) {
this.direct = direct;
}
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;
}
}
我们的坦克Hero
package com.stulzl.tankgame05;
public class Hero extends Tank {//我们的坦克,设置坦克坐标并且开启Shot射击线程
//定义一个Shot对象,表示射击(线程)
Shot shot = null;
public Hero(int x, int y) {
super(x, y);
}
//射击方法 600
public void shortEnemyTank(){
//创建一个Shot对象,根据Hero坦克的位置 方向
switch(getDirect()){
case 0://向上
shot = new Shot(getX()+20,getY(),0);//因为射击Shot线程有有参构造器,所以要传值
break;
case 1://向右
shot = new Shot(getX()+60,getY()+20,1);
break;
case 2://向下
shot = new Shot(getX()+20,getY()+60,2);
break;
case 3://向左
shot = new Shot(getX(),getY()+20,3);
break;
}
//启动Shot线程,因为Shot实现了Runnable接口,所以直接调用即可
Thread thread = new Thread(shot);
thread.start();//启动
}
}
敌人的坦克EnemyTank
package com.stulzl.tankgame05;
import java.util.Vector;
public class EnemyTank extends Tank implements Runnable{//敌人的坦克
//在敌人坦克类,使用Vector 保存多个Shot
Vector shots = new Vector<>();
boolean isLive = true;//敌人坦克存活
public EnemyTank(int x, int y) {
super(x, y);
}
@Override
public void run() {
while(true){
//根据坦克的方向来继续移动 605
switch(getDirect()){
case 0://向上
//让坦克保持一个方向走30步
for(int i=0;i<30;i++){
//控制坦克移动范围不能超过边界,只能在边界内移动 606
if(getY()>0) {
moveUp();
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1://向右
for(int i=0;i<30;i++){
if(getX()+60<1000){
moveRight();
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2://向下
for(int i=0;i<30;i++){
if(getY()+60<750){
moveDown();
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3://向左
for(int i=0;i<30;i++){
if(getX()>0){
moveLeft();
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//然后判断随机的改变坦克方向0-3
setDirect((int)(Math.random()*4));//Math.random() [0-1)
//Math.random()*4) [0-4)
//结束线程
if(isLive==false){
break;
}
}
}
}
射击线程Shot
package com.stulzl.tankgame05;
//发射子弹线程 600
public class Shot implements Runnable{
int x;//子弹x坐标
int y;//子弹y坐标
int direct=0;//子弹的方向
int speed = 2;//子弹的速度
boolean isLive = true;//子弹是否存活
//构造器,接收传来的值
public Shot(int x, int y, int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
@Override
public void run() {//射击行为
while(true){
//休息50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
switch(direct){//根据方向值选择相应的子弹方向路线
case 0://向上
y-=speed;
break;
case 1://向右
x+=speed;
break;
case 2://向下
y+=speed;
break;
case 3://向左
x-=speed;
break;
}
System.out.println("子弹x= "+x+" y= "+y);
//当子弹移动到面板的边界时,就应该销毁(把启动的子弹的线程销毁)
//当子弹击中敌人坦克时也应该结束次线程
if(!(x>=0 && x<=1000 && y>=0 && y<=750 && isLive)){//子弹越界出窗口范围
isLive = false;//子弹死了
break;
}
}
}
}
炸弹Bomb
package com.stulzl.tankgame05;
//炸弹 604
public class Bomb {
int x,y;//炸弹坐标
int life = 9;//炸弹生命周期,用来控制后面炸弹爆炸瞬间的大图还是小图
boolean isLive = true;//是否存活
public Bomb(int x, int y) {
this.x = x;
this.y = y;
}
//减少生命值
public void lifeDown(){
if(life>0){
life--;
}else{
isLive = false;
}
}
}
画板MyPanel
package com.stulzl.tankgame05;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
//坦克的绘图区
//为了响应键盘事件,实现KeyListener
//为了让Panel 不停的重绘子弹,需要将 MyPanel 实现Runnable ,当做一个线程使用 601
public class MyPanel extends JPanel implements KeyListener,Runnable{
//定义我们的坦克
Hero hero = null;
//定义敌人坦克,放入到Vector中 578
Vector enemyTanks = new Vector<>();
int enemyTanksSize = 3;//敌人坦克数量
//定义一个Vector,用于存放炸弹
Vector bombs = new Vector<>();
//定义三张炸弹图片,用于显示爆炸效果
//说明,当子弹击中坦克时,加入一个Bomb对象到bombs
Image image1 = null;
Image image2 = null;
Image image3 = null;
//构造器设置坦克信息
public MyPanel(){
//我们的坦克设置
hero = new Hero(100,100);//创建并初始化自己的坦克坐标
hero.setSpeed(2);//改变坦克速度
//遍历 初始化敌人坦克
for (int i = 0; i <enemyTanksSize; i++) {
//创建并设置敌人坦克坐标,每隔100像素放一辆坦克
EnemyTank enemyTank = new EnemyTank((100 * (i + 1)), 0);//(100 * (i + 1)), 0敌人坦克坐标
//更改敌人坦克方向
enemyTank.setDirect(2);
//启动敌人坦克随机动线程
Thread thread1 = new Thread(enemyTank);
thread1.start();
//给该enemyTank 加入一颗子弹 602
Shot shot = new Shot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirect());
//加入enemyTank的Vector 成员
enemyTank.shots.add(shot);
//启动 shot 对象
Thread thread = new Thread(shot);
thread.start();
//添加敌人坦克到Vector
enemyTanks.add(enemyTank);
}
//初始化图片对象
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_1.gif"));
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_2.gif"));
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_3.gif"));
}
//绘图方法
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0,0,1000,750);//填充矩形,默认黑色,这个是坦克的游戏区域
//画出自己坦克,我们把方法封装到一个方法,调用即可 573
drawTank(hero.getX(), hero.getY(), g,hero.getDirect(),1);
//画出hero射击的子弹
if(hero.shot!=null && hero.shot.isLive==true){//如果子弹存在且存活就画,如果子弹运动到边界就不画了
g.draw3DRect(hero.shot.x,hero.shot.y,1,1,false);//子弹大小 长1 高1
//this.repaint();//重绘
}
//如果bombs集合中有对象,就画出 604
for (int i = 0; i < bombs.size(); i++) {
//取出一个炸弹
Bomb bomb = bombs.get(i);
//根据当前这个bomb对象的life值去判断对应的图片
if(bomb.life>6){
g.drawImage(image1,bomb.x, bomb.y, 60,60,this);//画出大图片
}else if(bomb.life>3){
g.drawImage(image2,bomb.x, bomb.y, 60,60,this);//画出中图片
}else{
g.drawImage(image3,bomb.x, bomb.y, 60,60,this);//画出小图片
}
//让炸弹的生命值减少
bomb.lifeDown();
//如果bomb的life为0,就从bombs的集合中删除
if(bomb.life==0){
bombs.remove(bomb);
}
}
//画出敌人坦克,遍历Vector集合
for (int i = 0; i <enemyTanks.size(); i++) {
//从Vector中 取出敌人坦克
EnemyTank enemyTank = enemyTanks.get(i);
if(enemyTank.isLive) {//当前敌人坦克是活的,我们才画
//画敌人坦克
drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirect(), 0);
//画出 enemyTank 所有子弹 602
for (int j = 0; j < enemyTank.shots.size(); j++) {
//取出子弹
Shot shot = enemyTank.shots.get(j);
//绘制
if (shot.isLive == true) { //子弹存活
g.draw3DRect(shot.x, shot.y, 1, 1, false);//子弹大小 长1 高1
} else {//子弹不存活
//从Vector 移除
enemyTank.shots.remove(shot);
}
}
}
}
}
/**
* @param x 坦克的左上角 x 坐标
* @param y 坦克的左上角 y 坐标
* @param g 画笔
* @param direct 坦克方向(上下左右)
* @param type 坦克类型
*/
//编写方法画出坦克
public void drawTank(int x,int y,Graphics g,int direct,int type){
//根基不同的类型,设置不同的颜色
switch(type){
case 0://敌人的坦克
g.setColor(Color.cyan);
break;
case 1://我们的坦克
g.setColor(Color.yellow);
break;
}
//根据坦克方向,来绘制对应方向的坦克 577
//direct 表示方向(0: 向上 1 向右 2 向下 3 向左 )
switch(direct){
case 0://表示坦克向上
g.fill3DRect(x,y,10,60,false);//左边轮子//绘制3D的矩形看起来更像 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);//左边轮子//绘制3D的矩形看起来更像 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);//左边轮子//绘制3D的矩形看起来更像 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);//左边轮子//绘制3D的矩形看起来更像 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("暂时没有别的操作");
}
}
//编写一个方法判断我方子弹是否击中敌人坦克 603
public void hitTank(Shot s,EnemyTank enemyTank){
//判断子弹s是否击中坦克
switch(enemyTank.getDirect()){
case 0://向上
case 2://向下//如果子弹在坦克区域内,说明击中坦克
if(s.x>enemyTank.getX() && s.x<enemyTank.getX()+40
&& s.y>enemyTank.getY() && s.y<enemyTank.getY()+60){
s.isLive = false;
enemyTank.isLive = false;
//当我们的子弹击中敌人坦克后,就将enemyTank从Vector中去掉
enemyTanks.remove(enemyTank);
//创建Bomb对象,加入到bombs集合
Bomb bomb = new Bomb(enemyTank.getX(),enemyTank.getY());
bombs.add(bomb);//将炸弹添加金bombs的Vector集合
}
break;
case 1:
case 3:
if(s.x>enemyTank.getX() && s.x<enemyTank.getX()+60
&& s.y>enemyTank.getY() && s.y<enemyTank.getY()+40){
s.isLive = false;
enemyTank.isLive = false;
//当我们的子弹击中敌人坦克后,就将enemyTank从Vector中去掉 605
enemyTanks.remove(enemyTank);
//创建Bomb对象,加入到bombs集合
Bomb bomb = new Bomb(enemyTank.getX(),enemyTank.getY());
bombs.add(bomb);//将炸弹添加金bombs的Vector集合
}
break;
}
}
//三个方法
//有字符输出,触发
public void keyTyped(KeyEvent e) {
}
//按下键盘,触发
//处理wdsa键按下的情况 577
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_W){//方向向上
//改变坦克的方向
hero.setDirect(0);
if(hero.getY()>0){//判断我方坦克,在边界范围内移动
hero.moveUp();
}
}else if(e.getKeyCode()==KeyEvent.VK_D){//方向向右
hero.setDirect(1);
if(hero.getX()+60<1000){
hero.moveRight();
}
}else if(e.getKeyCode()==KeyEvent.VK_S){//方向向下
hero.setDirect(2);
if(hero.getY()+60<750){
hero.moveDown();
}
}else if(e.getKeyCode()==KeyEvent.VK_A){//方向向左
hero.setDirect(3);
if(hero.getX()>0){
hero.moveLeft();
}
}
//如果用户按下J键,就发射子弹
if(e.getKeyCode()==KeyEvent.VK_J){
hero.shortEnemyTank();//调用射击方法
}
this.repaint();//面板重绘
}
//松开键盘,触发
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断是否击中了敌人坦克 603
if(hero.shot!=null && hero.shot.isLive){//当我的子弹射出(即不为空)且 存活
//遍历敌人所有坦克,看看哪一辆敌人坦克被击中
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTank enemyTank = enemyTanks.get(i);//取出一辆敌人坦克
hitTank(hero.shot,enemyTank);
}
}
this.repaint();
}
}
}
3. 坦克大战06版 607-609
3.1 增加功能
1.我方坦克在发射的子弹消亡后,才能发射新的子弹. =>扩展(发多颗子弹怎么办,
控制在我们的面板上,最多只有5颗)
2.让敌人坦克发射的子弹消亡后,可以再发射子弹.
3.当敌人的坦克击中我方坦克时,我方坦克消失,并出现爆炸效果.
课后练习:让敌人坦克可以最多发射3颗(在面板 上),我们的坦克可以发射3颗.
并且能够出现正常的爆炸效果即可.
3.1.1 思路:
代码在com.stulzl.tankgame06.包中
测试TankGame06
package com.stulzl.tankgame06;
import javax.swing.*;
//主方法 572
public class TankGame06 extends JFrame {
//定义我们的MyPanel
MyPanel mp = null;
public static void main(String[] args) {
TankGame06 tankGame01 = new TankGame06();//调用显示结果
}
//构造器设置窗口信息
public TankGame06(){
mp = new MyPanel();//初始化面板
//将mp放入到Thread 并启动
Thread thread = new Thread(mp);
thread.start();//启动MyPanel线程
this.add(mp);//将面板添加到窗口
this.setSize(1000,750);//设置窗口大小
this.addKeyListener(mp);//让JFrame监听mp
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭程序
this.setVisible(true);//显示窗口
}
}
父类Tank
package com.stulzl.tankgame06;
public class Tank {//父类坦克
private int x;//坦克的横坐标
private int y;//坦克的纵坐标
private int direct;//坦克方向0上 1右 2下 3左 577
private int speed = 1;//控制坦克速度
boolean isLive = true;//我们坦克存活标志
//坦克上右下左移动方法 577
public void moveUp(){//向上移动
y-=speed;
}
public void moveRight(){//向右移动
x+=speed;
}
public void moveDown(){//向下移动
y+=speed;
}
public void moveLeft(){//向左移动
x-=speed;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getDirect() {
return direct;
}
public void setDirect(int direct) {
this.direct = direct;
}
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;
}
}
我们的坦克Hero
package com.stulzl.tankgame06;
import java.util.Vector;
public class Hero extends Tank {//我们的坦克,设置坦克坐标并且开启Shot射击线程
//我们坦克存活标志isLive直接写到父类中,方便
//定义一个Shot对象,表示射击(线程)
Shot shot = null;
//可以发射多颗子弹 607
Vector shots = new Vector<>();
//构造器
public Hero(int x, int y) {
super(x, y);
}
//射击方法 600
public void shortEnemyTank(){
//最多发射5颗子弹 607
// if(shots.size()==5){
// return;
// }
//创建一个Shot对象,根据Hero坦克的位置 方向
switch(getDirect()){
case 0://向上
shot = new Shot(getX()+20,getY(),0);//因为射击Shot线程有有参构造器,所以要传值
break;
case 1://向右
shot = new Shot(getX()+60,getY()+20,1);
break;
case 2://向下
shot = new Shot(getX()+20,getY()+60,2);
break;
case 3://向左
shot = new Shot(getX(),getY()+20,3);
break;
}
//把新创建的shot放入到shots的Vector集合中
shots.add(shot);
//启动Shot线程,因为Shot实现了Runnable接口,所以直接调用即可
Thread thread = new Thread(shot);
thread.start();//启动
}
}
敌人的坦克EnemyTank
package com.stulzl.tankgame06;
import java.util.Vector;
public class EnemyTank extends Tank implements Runnable{//敌人的坦克
//在敌人坦克类,使用Vector 保存多个Shot子弹
Vector shots = new Vector<>();
boolean isLive = true;//敌人坦克存活
public EnemyTank(int x, int y) {
super(x, y);
}
@Override
public void run() {
while(true){
//这里我们判断如果shots size() = 0就创建一颗子弹,放入到shots集合中,并启动 608
if(isLive==true && shots.size()==0){//敌人的坦克存活且敌人子弹集合Vector为空
Shot s = null;//临时子弹变量
//判断坦克方向,创建对应子弹
switch(getDirect()){
case 0://向上
s = new Shot(getX()+20,getY(),0);
break;
case 1://向右
s = new Shot(getX()+60,getY()+20,1);
break;
case 2://向下
s = new Shot(getX()+20,getY()+60,2);
break;
case 3://向左
s = new Shot(getX(),getY()+20,3);
break;
}
shots.add(s);//将子弹加入shots的Vector集合
Thread thread = new Thread(s);
thread.start();//启动子弹线程
}
//根据坦克的方向来继续移动 605
switch(getDirect()){
case 0://向上
//让坦克保持一个方向走30步
for(int i=0;i<30;i++){
//控制坦克移动范围不能超过边界,只能在边界内移动 606
if(getY()>0) {
moveUp();
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1://向右
for(int i=0;i<30;i++){
if(getX()+60<1000){
moveRight();
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2://向下
for(int i=0;i<30;i++){
if(getY()+60<750){
moveDown();
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3://向左
for(int i=0;i<30;i++){
if(getX()>0){
moveLeft();
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//然后判断随机的改变坦克方向0-3
setDirect((int)(Math.random()*4));//Math.random() [0-1)
//Math.random()*4) [0-4)
//结束线程
if(isLive==false){
break;
}
}
}
}
射击线程Shot
package com.stulzl.tankgame06;
//发射子弹线程 600
public class Shot implements Runnable{
int x;//子弹x坐标
int y;//子弹y坐标
int direct=0;//子弹的方向
int speed = 2;//子弹的速度
boolean isLive = true;//子弹是否存活
//构造器,接收传来的值
public Shot(int x, int y, int direct) {
this.x = x;
this.y = y;
this.direct = direct;
}
@Override
public void run() {//射击行为
while(true){
//休息50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
switch(direct){//根据方向值选择相应的子弹方向路线
case 0://向上
y-=speed;
break;
case 1://向右
x+=speed;
break;
case 2://向下
y+=speed;
break;
case 3://向左
x-=speed;
break;
}
System.out.println("子弹x= "+x+" y= "+y);
//当子弹移动到面板的边界时,就应该销毁(把启动的子弹的线程销毁)
//当子弹击中敌人坦克时也应该结束次线程
if(!(x>=0 && x<=1000 && y>=0 && y<=750 && isLive)){//子弹越界出窗口范围
isLive = false;//子弹死了
break;
}
}
}
}
炸弹Bomb
package com.stulzl.tankgame06;
//炸弹 604
public class Bomb {
int x,y;//炸弹坐标
int life = 9;//炸弹生命周期,用来控制后面炸弹爆炸瞬间的大图还是小图
boolean isLive = true;//是否存活
public Bomb(int x, int y) {
this.x = x;
this.y = y;
}
//减少生命值
public void lifeDown(){
if(life>0){
life--;
}else{
isLive = false;
}
}
}
画板MyPanel
package com.stulzl.tankgame06;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
//坦克的绘图区
//为了响应键盘事件,实现KeyListener
//为了让Panel 不停的重绘子弹,需要将 MyPanel 实现Runnable ,当做一个线程使用 601
public class MyPanel extends JPanel implements KeyListener,Runnable{
//定义我们的坦克
Hero hero = null;
//定义敌人坦克,放入到Vector中 578
Vector enemyTanks = new Vector<>();
int enemyTanksSize = 3;//敌人坦克数量
//定义一个Vector,用于存放炸弹
Vector bombs = new Vector<>();
//定义三张炸弹图片,用于显示爆炸效果
//说明,当子弹击中坦克时,加入一个Bomb对象到bombs
Image image1 = null;
Image image2 = null;
Image image3 = null;
//构造器设置坦克信息
public MyPanel(){
//我们的坦克设置
hero = new Hero(500, 100);//创建并初始化自己的坦克坐标
hero.setSpeed(2);//改变坦克速度
//遍历 初始化敌人坦克
for (int i = 0; i <enemyTanksSize; i++) {
//创建并设置敌人坦克坐标,每隔100像素放一辆坦克
EnemyTank enemyTank = new EnemyTank((100 * (i + 1)), 0);//(100 * (i + 1)), 0敌人坦克坐标
//更改敌人坦克方向
enemyTank.setDirect(2);
//启动敌人坦克随机动线程
Thread thread1 = new Thread(enemyTank);
thread1.start();
//给该enemyTank 加入一颗子弹shot 602
Shot shot = new Shot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirect());
//加入enemyTank的Vector 成员
enemyTank.shots.add(shot);
//启动 shot 子弹对象
Thread thread = new Thread(shot);
thread.start();
//添加敌人坦克到Vector
enemyTanks.add(enemyTank);
}
//初始化图片对象
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_1.gif"));
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_2.gif"));
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_3.gif"));
}
//绘图方法
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0,0,1000,750);//填充矩形,默认黑色,这个是坦克的游戏区域
//画出自己坦克,我们把方法封装到一个方法,调用即可 573
if(hero !=null && hero.isLive==true) {//我们的坦克不为空且存活
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 1);
}
//画出hero射击的子弹
// if(hero.shot!=null && hero.shot.isLive==true){//如果子弹存在且存活就画,如果子弹运动到边界就不画了
// g.draw3DRect(hero.shot.x,hero.shot.y,1,1,false);//子弹大小 长1 高1
// //this.repaint();//重绘
// }
//画出hero的子弹集合Vector 607
//遍历绘制
for (int i = 0; i < hero.shots.size(); i++) {
Shot shot = hero.shots.get(i);//取出一颗子弹
//如果子弹存在且存活就画,如果子弹运动到边界就不画了
if(shot!=null && shot.isLive==true) {
g.draw3DRect(shot.x,shot.y, 1, 1, false);//子弹大小 长1 高1
}else{//如果子弹为空或者已经销毁,就从shots的Vector集合中去掉
hero.shots.remove(shot);
}
}
//如果bombs集合中有对象,就画出 604
for (int i = 0; i < bombs.size(); i++) {
//取出一个炸弹
Bomb bomb = bombs.get(i);
//根据当前这个bomb对象的life值去判断对应的图片
if(bomb.life>6){
g.drawImage(image1,bomb.x, bomb.y, 60,60,this);//画出大图片
}else if(bomb.life>3){
g.drawImage(image2,bomb.x, bomb.y, 60,60,this);//画出中图片
}else{
g.drawImage(image3,bomb.x, bomb.y, 60,60,this);//画出小图片
}
//让炸弹的生命值减少
bomb.lifeDown();
//如果bomb的life为0,就从bombs的集合中删除
if(bomb.life==0){
bombs.remove(bomb);
}
}
//画出敌人坦克,遍历Vector集合
for (int i = 0; i <enemyTanks.size(); i++) {
//从Vector中 取出敌人坦克
EnemyTank enemyTank = enemyTanks.get(i);
if(enemyTank.isLive) {//当前敌人坦克是活的,我们才画
//画敌人坦克
drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirect(), 0);
//画出 enemyTank 所有子弹 602
for (int j = 0; j < enemyTank.shots.size(); j++) {
//取出敌人子弹
Shot shot = enemyTank.shots.get(j);
//绘制
if (shot.isLive == true) { //子弹存活
g.draw3DRect(shot.x, shot.y, 1, 1, false);//子弹大小 长1 高1
} else {//敌人子弹不存活
//从Vector 移除
enemyTank.shots.remove(shot);
}
}
}
}
}
/**
* @param x 坦克的左上角 x 坐标
* @param y 坦克的左上角 y 坐标
* @param g 画笔
* @param direct 坦克方向(上下左右)
* @param type 坦克类型
*/
//编写方法画出坦克
public void drawTank(int x,int y,Graphics g,int direct,int type){
//根基不同的类型,设置不同的颜色
switch(type){
case 0://敌人的坦克
g.setColor(Color.cyan);
break;
case 1://我们的坦克
g.setColor(Color.yellow);
break;
}
//根据坦克方向,来绘制对应方向的坦克 577
//direct 表示方向(0: 向上 1 向右 2 向下 3 向左 )
switch(direct){
case 0://表示坦克向上
g.fill3DRect(x,y,10,60,false);//左边轮子//绘制3D的矩形看起来更像 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);//左边轮子//绘制3D的矩形看起来更像 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);//左边轮子//绘制3D的矩形看起来更像 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);//左边轮子//绘制3D的矩形看起来更像 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("暂时没有别的操作");
}
}
//编写一个方法判断子弹击中坦克 方法 603
public void hitTank(Shot s, Tank tank){//Tank tank这里直接用父类Tank接收涉及多态,更方便
//判断子弹s是否击中坦克
switch(tank.getDirect()){
case 0://向上
case 2://向下//如果子弹在坦克区域内,说明击中坦克
if(s.x>tank.getX() && s.x<tank.getX()+40
&& s.y>tank.getY() && s.y<tank.getY()+60){
s.isLive = false;
tank.isLive = false;
//当我们的子弹击中敌人坦克后,就将enemyTank从Vector中去掉
enemyTanks.remove(tank);
//创建Bomb对象,加入到bombs集合
Bomb bomb = new Bomb(tank.getX(),tank.getY());
bombs.add(bomb);//将炸弹添加进bombs的Vector集合
}
break;
case 1:
case 3:
if(s.x>tank.getX() && s.x<tank.getX()+60
&& s.y>tank.getY() && s.y<tank.getY()+40){
s.isLive = false;
tank.isLive = false;
//当我们的子弹击中敌人坦克后,就将enemyTank从Vector中去掉 605
enemyTanks.remove(tank);
//创建Bomb对象,加入到bombs集合
Bomb bomb = new Bomb(tank.getX(),tank.getY());
bombs.add(bomb);//将炸弹添加金bombs的Vector集合
}
break;
}
}
//三个方法
//有字符输出,触发
public void keyTyped(KeyEvent e) {
}
//按下键盘,触发
//处理wdsa键按下的情况 577
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_W){//方向向上
//改变坦克的方向
hero.setDirect(0);
if(hero.getY()>0){//判断我方坦克,在边界范围内移动 606
hero.moveUp();
}
}else if(e.getKeyCode()==KeyEvent.VK_D){//方向向右
hero.setDirect(1);
if(hero.getX()+60<1000){
hero.moveRight();
}
}else if(e.getKeyCode()==KeyEvent.VK_S){//方向向下
hero.setDirect(2);
if(hero.getY()+60<750){
hero.moveDown();
}
}else if(e.getKeyCode()==KeyEvent.VK_A){//方向向左
hero.setDirect(3);
if(hero.getX()>0){
hero.moveLeft();
}
}
//如果用户按下J键,就发射子弹
if(e.getKeyCode()==KeyEvent.VK_J){
//如果子弹为空或者子弹销毁才发射子弹 ,一次只能发射一颗子弹 607
// if(hero.shot==null || hero.shot.isLive==false){
// hero.shortEnemyTank();//调用射击方法
// }
//发射多颗子弹
if(hero.shots.size()<5){//最多发射5颗子弹 607
hero.shortEnemyTank();//调用射击方法
}
}
this.repaint();//面板重绘
}
//松开键盘,触发
@Override
public void keyReleased(KeyEvent e) {
}
//当我们的坦克可以连续发射子弹后,判断每一颗子弹是否击中敌人坦克 607
public void hitEnemyTank(){
for (int j = 0; j < hero.shots.size(); j++) {//遍历子弹集合
Shot shot = hero.shots.get(j);//取出集合中的子弹
if(shot!=null && shot.isLive){//当我们的子弹射出(即不为空)且 存活
//遍历敌人所有坦克集合,看看哪一辆敌人坦克被击中
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTank enemyTank = enemyTanks.get(i);//取出一辆敌人坦克
hitTank(shot,enemyTank);//调用判断是否击中
}
}
}
}
//编写方法判断敌人坦克子弹是否击中我方坦克 609
public void hitHero(){
//遍历所有的敌人坦克enemyTanks的Vector集合
for (int i = 0; i < enemyTanks.size(); i++) {
//取出一辆敌人坦克
EnemyTank enemyTank = enemyTanks.get(i);
//遍历这辆敌人坦克的所有子弹shots的集合Vector
for(int j=0;j<enemyTank.shots.size();j++){
//取出子弹
Shot shot = enemyTank.shots.get(j);
//判断敌人坦克子弹shot是否击中我们的Hero坦克
if(hero.isLive && shot.isLive){//我们的坦克存活且敌人子弹也存活
hitTank(shot,hero);
}
}
}
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断是否击中了敌人坦克 603
//这个方法只能判断当前射出的子弹击中敌人,适用于单颗子弹
// if(hero.shot!=null && hero.shot.isLive){//当我的子弹射出(即不为空)且 存活
// //遍历敌人所有坦克,看看哪一辆敌人坦克被击中
// for (int i = 0; i < enemyTanks.size(); i++) {
// EnemyTank enemyTank = enemyTanks.get(i);//取出一辆敌人坦克
// hitTank(hero.shot,enemyTank);
// }
// }
//调用多颗子弹击中敌人坦克方法
hitEnemyTank();
//调用敌人坦克击中我们的Hero坦克方法
hitHero();
this.repaint();
}
}
}