文章目录
坦克大战0.3
陆游曾说:纸上得来终觉浅,绝知此事要躬行。前面已经了解Java线程基础知识,现在我们来实际运用一下。
在坦克大战游戏(0.2版)基础上添加如下功能:当玩家按一下j键,就发射一颗子弹。
- 为了能射出子弹,需要把每个子弹做成一个线程,并根据KeyListener事件判断用户输入,之后循环判断子弹位置,repaint()子弹,并要根据坦克的每个方向调整x,y轴
新建Shot类
package tankgame3;
/**
* Create By 刘鸿涛
* 2022/1/17 5:19
*/
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){
//子弹休眠
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
//根据方向来改变x,y坐标
switch (direct){
case 0://上
y -= speed;
break;
case 1://右
x += speed;
break;
case 2://下
y += speed;
break;
case 3://左
x -= speed;
break;
}
//测试,这里我们输出x,y坐标
System.out.println("子弹 x = " + x + " y= " + y);
//当子弹移动到面板
if (!(x >= 0 && x <= 1000 && y >= 0 && y <= 750)){
isLive = false;
break;
}
}
}
}
坦克大战0.4版
- 增加功能
- 让敌人的坦克也能够发射子弹(可以有多颗子弹)
- 当我方坦克击中敌人坦克时,敌人的坦克就消失,如果能做出爆炸效果更好
- 让敌人的坦克也可以*随机的上下左右移动
- 控制我方的坦克和敌人的坦克在规定的范围移动
- 特别说明
- 只要能实现就行,方法的好坏我们后面评讲
- 完成上面的任务,不会有没有讲过的知识点,这里主要是锻炼大伙灵活运用技术点能力
- 一定要自己先动脑筋想想,试着做做。再听老师的评讲才有意义,时间自己掌控(再次提醒,一定要先自己思考,再听老师讲收获会很大)
1.让敌人坦克发射子弹(多颗子弹)
思路
- 为了使敌方坦克射出子弹,需要把敌方坦克的所有子弹做成一个线程集合里,这里的集合我们用的vector(为了线程安全),敌人坦克是一个独立的集合,敌人子弹也是一个独立的集合,每个子弹线程都被Thread代理,并执行start()。根据判断敌人坦克子弹的状态,判断是否需要移出此线程集合元素,比如是否碰到边缘、是否碰到玩家、是否碰到障碍物
EnemyTank类
Vector<Shot> shots = new Vector<>();
MyPanel类无参构造器
public MyPanel(){
hero = new Hero(500,500); //初始化自己的坦克
hero.setSpeed(50); //初始化坦克速度
//初始化敌人坦克
for (int i = 0; i < enemyTanksSize; i++) {
//创建一个敌人的坦克
EnemyTank enemyTank = new EnemyTank((100 * (i + 1)), 0);
//设置方向
enemyTank.setDirect(2);
//给该enemyTank 加入一颗子弹
Shot shot = new Shot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirect());
//加入enemyTank的Vector 成员
enemyTank.shots.add(shot);
//启动 shot 对象
new Thread(shot).start();
//加入坦克
enemyTanks.add(enemyTank);
}
}
MyPanel类paint()方法
@Override
public void paint(Graphics g){
super.paint(g);
g.fillRect(0,0,1000,750); //填充矩形,默认黑色
//画出坦克 - 封装方法
//自己的坦克
drawTank(hero.getX(),hero.getY(),g,hero.getDirect(),1); //type坦克类型
//画出hero射击的子弹
if (hero.shot != null && hero.shot.isLive == true){
g.draw3DRect(hero.shot.x,hero.shot.y,5 ,5,false);
}
//画出敌人的坦克,遍历Vector
for (int i = 0; i < enemyTanks.size(); i++) {
//取出坦克
EnemyTank enemyTank = enemyTanks.get(i);
drawTank(enemyTank.getX(),enemyTank.getY(),g,enemyTank.getDirect(),0);
//画出 enemyTank 所有子弹
for (int j = 0; j < enemyTank.shots.size(); j++){
//取出子弹
Shot shot = enemyTank.shots.get(j);
//绘制
if (shot.isLive){ //isLive 为 true
g.draw3DRect(shot.x,shot.y,2,2,false);
}else {
//从Vector 移除
enemyTank.shots.remove(shot);
}
}
}
}
2.当我方坦克击中敌人坦克时,敌人的坦克就消失
实现子弹击中敌方坦克消失,我们需要写一个方法,判断我方子弹是否碰到敌方坦克,需要考虑敌方坦克面向问题。我们在paint方法中也加入判断语句,如果上面条件为真,则返回敌方坦克isLive属性为false,根据情况paint敌方坦克是否执行。而我们的线程集合每隔100毫秒遍历一次敌方坦克集合,刷新整个游戏区域,实现敌方坦克被击中后消失
hitTank()方法
public static 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;
}
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;
}
break;
}
}
3.实现敌人坦克爆炸后消失
当敌方坦克被击中后出现爆炸效果,我们新建一个爆炸效果类,这个类需要有x,y大小,需要一个生命周期的方法,来让爆炸效果动态化。当然了,我们的爆炸效果也在一个集合中,也有自己的isLive属性,判断存活时间,我们用Image来初始化三张图片,并路径赋值,在坦克被击中方法里,我们添加执行效果-> 坦克被击中消失后,我们开始执行爆炸类的“生命周期方法”,按条件执行图片放映顺序
新建Bomb类
package tankgame4;
/**
* Create By 刘鸿涛
* 2022/1/19 4:56
*/
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;
}
}
}
注意图片位置
当敌方坦克被击中后出现爆炸效果,我们新建一个爆炸效果类,这个类需要有x,y大小,需要一个生命周期的方法,来让爆炸效果动态化。当然了,我们的爆炸效果也在一个集合中,也有自己的isLive属性,判断存活时间,我们用Image来初始化三张图片,并路径赋值,在坦克被击中方法里,我们添加执行效果-> 坦克被击中消失后,我们开始执行爆炸类的“生命周期方法”,按条件执行图片放映顺序
4.实现敌人坦克*移动
- 因为要求敌人的坦克,可以*移动,因此需要将敌人坦克当做线程使用
- 因此,我们需要EnemyTank implements Runnable
- 在run方法写上我们相应的业务代码
- 在创建敌人坦克对象时,启动线程
为了使敌方坦克*活动,我们需要为敌方每个坦克做成一个线程,因此,我们的敌人坦克类要实现Runnable接口,我们的run实现逻辑,通过setDirect封装方法,我们用switch循环加上Math.Random随机生成数,随机生成坦克方向,并按for循环来进行移动,每次朝一个方向移动后休眠50毫秒,体现缓冲效果,当然了,如果此线程坦克被击中,那么我们让此线程while循环break,不再执行
5.实现坦克站规定范围内移动
为了能使敌方坦克在游戏范围内移动,我们只需要在敌方坦克随机循环移动时,加入一个if条件即可,当超过范围,则不执行此次循环
package tankgame4;
import java.util.Vector;
/**
* Create By 刘鸿涛
* 2022/1/13 2:16
*/
public class EnemyTank extends Tank implements Runnable {
//在敌人坦克类,使用Vector 保存多个Shot
Vector<Shot> shots = new Vector<>();
boolean isLive = true;
public EnemyTank(int x, int y) {
super(x, y);
}
@Override
public void run() {
//然后随机的改变坦克方向
while (true) {
//根据坦克的方向来继续移动
switch (getDirect()) {
case 0://向上
//让坦克保存一个方向,走30步
for (int i = 0; i < 30; i++) {
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() < 902) {
moveRight();
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
break;
case 2://下
for (int i = 0; i < 30; i++) {
if (getY() < 620) {
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;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
//随机改变坦克方向0 - 3
setDirect((int) (Math.random() * 4));
//如果坦克被击中线程终止
if (!isLive) {
break;
}
}
}
}