首页 > 软件开发 > 软件开发

软件开发java植物大战僵尸,我家ADC直呼内行,甚至喊出辅助牛逼、666

admin 软件开发 2021-05-25 09:26:04 Java swing 植物大战僵尸 
后台-系统设置-扩展变量-手机广告位-内容正文底部

引言:

作为辅助,带领我家ADC获取胜利是我的第一目标,虽然AD有可能是新手、也有可能是手残党,但是丝毫对我没有影响,因为我会在游戏里面对AD给与无微不至的关怀,正所谓我武能抢人头,文能回首与打野对喷,兵线是你的人头是我的,哈哈!

老java程序员用2天时间开发一个简版植物大战僵尸

对话

正好今天的AD是个新手,我就先带她打打怪、升升级,给他好好上一课!

小AD:明哥,带我上分,我要上最强王者。

明世隐:哇,有志向,你只要会喊明哥牛逼,明哥666就行。

小AD:我最擅长了,666。

明世隐:今天先打打怪升升级,让你提升一下操作水平。

小AD:做什么呀?

明世隐:做个小游戏,植物大战hello kitty,贼好玩。

小AD:没听过,不过听起来有点奇怪!

明世隐:明哥什么时候骗过你呀,你不是前段时候学习了java吗,来我教你。

小AD:可是我学的不咋样呀,肯定做不来。

明世隐:放心有我在,保证万无一失,就做个简单的,你也可以轻松做出来。

小AD:那好吧。

游戏窗体

明世隐:首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗口。

小AD:是不是跟王者打开时候弹出来的游戏窗口一个意思?

明世隐:对的!我们来个GameFrame 添加一个无参构造,里面设置好窗口标题、尺寸、布局等就可以了,看下面的代码。

import java.awt.BorderLayout;
import javax.swing.JFrame;
/*
 * 游戏窗体类
 */
public class GameFrame extends JFrame {
	
	public GameFrame() {
		setTitle("植物大战僵尸");//设置标题
		setSize(906, 655);//设定尺寸
		setLayout(new BorderLayout());
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击关闭按钮是关闭程序
        setLocationRelativeTo(null);   //设置居中
    	setResizable(false); //不允许修改界面大小
	}
}

明世隐:再创建一个Main类,来启动这个窗口(只要一行设定显示这个窗口的代码即可)。

public class Main {
	//主类
	public static void main(String[] args) {
		GameFrame frame = new GameFrame();
		frame.setVisible(true);//设定显示
	}
}

明世隐:右键运行这个Main类,窗口被打开。

明世隐:怎么样,简单不简单,就这么几句话。

面板容器

小AD:这个好简单,我一听就会了,可是要怎么写游戏呢?

明世隐:这个窗体就好比手机的外壳体,我们需要加上手机屏幕才能显示内容。这里创建一个类GamePanel继承至JPanel,JPanel就是一个面板容器类,我们把这个GamePanel添加到窗体中,然后在GamePanel里面添加上我们要的内容,就会显示出来。当然别忘记了把GamePanel添加到窗体中,这其中GamePanel可以理解为手机的屏幕。

小AD:那要怎么添加呢?

明世隐:简单,只要在Main加入两句话即可(1创建实例,2添加到窗体中)

public class Main {
	//主类
	public static void main(String[] args) {
		GameFrame frame = new GameFrame();
		GamePanel panel = new GamePanel(frame);//创建实例
		frame.add(panel);//添加到窗体中
		frame.setVisible(true);//设定显示
	}
}

小AD:可是运行看起来和刚才一样啊,没有区别。

游戏背景图

明世隐:那是我没给屏幕上画东西,所以你看不到。

小AD:就跟在白纸上画画一样?

明世隐:是的,明哥来举个例子,首先重写paint(Graphics g)方法,这个方法可以进行2D画图,要做游戏这个方法就特别的重要,而且这个方法不需要我们自己来调用,swing框架会自动调用,只要我们重写这个方法就行,我们在这个方法写了什么内容,就会绘制什么内容出来。

明世隐:先绘制一个背景图吧,怎么用呢,用 g.drawImage(img, x, y, width, height, null); 第一个参数是BufferedImage对象,是根据图片路径加载出来的图片对象,x,y就是绘制的左坐标,width宽度,height高度,其实width, height这两个参数也可以省略,这样就会根据图片的长宽来绘制,如果设定了就有可能会缩放。

小AD:如何获取图片对象呢?

明世隐:明哥写好了一个方法,只要传入图片的路径就可以了。

public static BufferedImage getImg(String path){
    //用try方法捕获异常
    try {
        //io流,输送数据的管道
        BufferedImage img = ImageIO.read(GameApp.class.getResource(path));
        return img;
    }
    //异常处理,打印异常
    catch (IOException e) {
        e.printStackTrace();
    }
    //没找到则返回空
    return null;
}

明世隐:绘制一下背景图,只要在paint写入下面的代码既可

BufferedImage bufferedImage = GameApp.getImg("/images/1.png");
g.drawImage((BufferedImage)imageMap.get("1"), -150, 0, null);

运行一下看看:

小AD:这么简单就出来了呀!好厉害!

卡牌、积分等辅助框

明世隐:那必须的,我们先来把放卡牌的框、积分牌什么的绘制一下。跟刚才一样,就是找到对应的图片,设定好位置就行。

//绘制背景
g.drawImage((BufferedImage)imageMap.get("1"), -150, 0, null);

//方形卡片盘
g.drawImage((BufferedImage)imageMap.get("2"), 0, 0,446,80, null);

//铲子背景
g.drawImage((BufferedImage)imageMap.get("17"), 446, 2,80,35, null);

//积分盘
g.drawImage((BufferedImage)imageMap.get("12"), 530, -4,128,40, null);

小AD:嗯这个明白了。

卡牌绘制

明世隐:再来把卡牌绘制一下,有了卡牌我们才知道绘制什么植物的,这时候我来创建一个 Card 类,属性有 x、y坐标,width、height长宽,cost需要花费的阳光数(比如创建太阳植物需要50阳光,创建豌豆射手需要100阳光),type表示类型(sun 表示太阳,wandou表示射手植物)。构造函数直接传进去,同时创建draw方法,用来绘制卡牌。

public class Card {
	private int x = 0;//x坐标
	private int y = 0;//y坐标
	private int width = 0;//宽
	private int height = 0;//高
	private BufferedImage image = null;//图片对象
	private int cost = 0;//阳光花费
	private String type = "";//类型,可以通过类型来判断创建什么植物
	private GamePanel panel=null;
	private int index=0;

	public Card(int x, int y, int width, int height, BufferedImage image,String type,int cost, int index ,GamePanel panel) {
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
		this.image = image;
		this.type=type;
		this.cost=cost;
		this.index=index;
		this.panel=panel;
	}

	public void draw(Graphics g) {
		g.drawImage(image, x, y, width, height, null);
	}
}

小AD:明哥我不是特别明白,为什么要这么搞呢?

明世隐:这就是面向对象编程的应用,如果我要创建不同的卡牌,只要传入不同的参数既可以做到。

private void initCard() {
	int x=0,y=0;
	Card card=null;
	card = new Card(76,5,50,68,(BufferedImage)imageMap.get("3"),"sun",50,1,this);//向日葵卡牌
	plantsCard.add(card);
	
	card = new Card(127,4,50,70,(BufferedImage)imageMap.get("4"),"wandou",100,2,this);//豌豆射手卡牌
	plantsCard.add(card);
	
	card = new Card(177,4,50,70,(BufferedImage)imageMap.get("5"),"wallNut",50,3,this);//胡桃卡牌
	plantsCard.add(card);
}

明世隐:你看上面的3小块代码分别创建了3个卡牌,传入了不同的x坐标,不同的图片,不同的type类型,不同的阳光花费等,但是我们只需要创建同一个Card的实例就行。我们把这些实例对象都添加到定义好的集合 plantsCard 中,然后在paint方法里面循环这个集合,对每个元素执行前面Card类里面draw方法进行绘制。

明世隐:铲子工具一样的道理,只不过我没有用集合来存,而是单独写,因为跟卡牌稍微有点区别。

shovelCard = new ShovelCard(454, 4,68,28,(BufferedImage)imageMap.get("16"), this);	

小AD:好像有点明白了。

抽象小例

明世隐:来把植物做出来,因为考虑到植物很多种,有写方法需要公共去调用,我们可以封装抽象类!

小AD:抽象类?是不是形容一个人一样,“你长得很抽象”!

明世隐:哎呀我去,你说我干嘛,我可是英俊潇洒、风流倜傥的辅助,小心我抢你人头。

小AD:明哥我错了!

明世隐:就比如以前军训的时候,教官喊一句报数,我们就一个个都报过去,因为我们每一个都有一个嘴巴,都会报数,我们就可以理解为创建一个抽象类Student,有个抽象方法叫 sayNum, 然后每个我们每个人都继承至Student,然后在具体去实现这个sayNum 方法;

public abstract class Student {
	abstract void sayNum();
}

再创建子类


public class StudentC extends Student{
	int count =0;
	public StudentC(int count) {
		this.count=count;
	}
	@Override
	void sayNum() {
		System.out.println("报数:"+count);
	}
}

创建main方法测试

public static void main(String[] args) {
	List list = new ArrayList();
	for (int i = 1; i <= 5; i++) {
		list.add(new StudentC(i));
	}
	
	Student student=null;
	for (int i = 0; i < list.size(); i++) {
		student = (Student)list.get(i);
		student.sayNum();
	}
	
}

明世隐:上例中我们创建了5个子对象,最后遍历调用方法的时候,可以直接向上转型至父类,然后直接调用,根本不用关心子类具体是什么,就能实现通用。

小AD:有点懵。

植物抽象化

明世隐:那在本例中,要创建 太阳植物、豌豆植物、胡桃果,我们都可以存到一个集合中去处理,给他们定义一个父类,这样处理起来就很方便。

明世隐:创建Plant抽象类,包含绘制、摇摆、射击、清除、种植等抽象方法,然后子类中去进行不同的实现就好。

public abstract class Plant {
	public abstract void draw(Graphics g);
	abstract void waggle();
	abstract void shoot();
	public abstract void clear();
	public abstract void plant(PlantsRect rect);
}

豌豆植物实现

明世隐:来说说豌豆植物类,它继承至Plant类。同样的给它设置 x\y坐标,宽高等常用属性,还需要加入阳光花费、生命值等属性。draw方法的实现和上面讲过的卡牌类是一样的。

private int x = 0;
private int y = 0;
private int width = 0;
private int height = 0;
private int index=0;
private int cost = 0;
private BufferedImage image = null;
private GamePanel panel=null;
private HashMap imageMap=null;
private List zombies = new ArrayList();
private int key=1;
private boolean alive=false;
private int hp=10;
private PlantsRect plantsRect=null;
MusicPlayer musicShoot= null;
public WandouPlant(int x,int y,int width,int height,GamePanel panel) {
	this.x=x;
	this.y=y;
	this.width=width;
	this.height=height;
	this.panel=panel;
	this.imageMap=panel.wandouPlantHashMap;
	this.image=(BufferedImage)imageMap.get("("+key+")");
}
@Override
public void draw(Graphics g) {
	g.drawImage(image, x, y, width,height, null);
}

明世隐:我们来创建一个到图中的格子中去,设置好坐标生命的就可以了。比如我们在100、100坐标的位置创建一个豌豆植物(代码同样放在paint方法里面)。

Plant plant = new WandouPlant(100, 100, 63,70, this);
plant.draw(g);

植物本身动画

小AD:看到了,这个小树苗干嘛用的。

明世隐:它有两个功能,第一会来回摇晃,第二发射子弹进行攻击,但是他没有别的技能不像王者有好几个技能,我们现在来让它动起来,只要在WandouPlant类重写摇晃方法,开启线程让它不停的切换图片即可,能明白吗?

//摇晃动画
	@Override
	void waggle() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(alive){
					changeImage();
					try {
						Thread.sleep(110);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
	}
	//更换图片
	void changeImage(){
		key++;
		if(key>13){
			key=1;
		}
		this.image=(BufferedImage)imageMap.get("("+key+")");
		width = this.image.getWidth();
		height = this.image.getHeight();
	}

小AD:这个我知道,就是图片的不停切换,达到动画的效果呗。

植物射击

明世隐:再来写shoot射击方法,先创建一个Wandou类,属性什么的都差不多,有坐标、宽高这些,就是给他添加一个move方法,只要Wandou实例对象被创建出来就开启线程,一直更改x的位置就行,因为是向右飞行,只要设置x递增就可以,当然如果跑到最右边去了,就清除这对象。

//移动
void move(){
	new Thread(new Runnable() {
		@Override
		public void run() {
			while(alive){
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				x+=speed;
				//超过范围,豌豆消失
				if(x>=panel.gameWidth){
					clear();
				}
			
			}
		
		}
	}).start();
}

回到WandouPlant类,射击的话也是采取线程的方式,执行完后睡眠2000毫秒,也就是2秒发射一个子弹(豌豆)

@Override
void shoot() {
	//Sun
	new Thread(new Runnable() {
		@Override
		public void run() {
			while(alive){
				try {
					//每2秒发射一个豌豆
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if(alive){//防止被吃了还能发射一次子弹
					createWandou();
					musicShoot=new MusicPlayer("/music/shoot.wav");
					musicShoot.play();
				}
			}
		}

		private void createWandou() {
			Wandou wandou = new Wandou(x+50, y, 28, 28, panel,index);
			panel.wandous.add(wandou);
		}
	}).start();
}

卡牌创建植物

 

小AD:那明哥,不是应该点击卡牌来创建植物的吗?

明世隐:是的,要先给容器添加事件监听,我采用MouseAdapter 的方式,根据键的值来判断是左键还是右键,如果是左键,则依次去遍历卡牌对象,如果在卡牌的范围内就判断是哪种卡牌被点击了,需要创建一个对应的植物出来,并且跟随鼠标移动。

小AD:怎么判断呢?

明世隐:很简单,就是根据鼠标的坐标,与图片的x\y坐标 、长度、宽度进行比较,当鼠标的x大于卡牌的x,并且鼠标的y大于卡牌的y,并且鼠标的x小于卡牌的x+width,并且鼠标的y小于卡牌的y+height,4个条件都满足就表示在这个卡牌飞范围内,就判断这个卡牌被点击了,然后就根据这个卡牌来创建植物。

小AD:我听不太明白,能说清楚些吗?

明世隐:我画个图你就明白了 

从上图我们可以看到,鼠标的位置要是在范围内的话,X1肯定大于X,Y1肯定大于Y,X1<X+WIDTH ,Y1<Y+HEIGHT,加入代码如下:

//检查坐标是否图形范围内
public boolean isPoint(int x,int y){
	if(x>this.x && y>this.y && x<this.x+this.width && y < this.y + this.height){
		return true;
	}
	return false;
}

当鼠标点击的时候,调用用 isPoint方法传入鼠标的坐标,就可以判断是否在某个卡牌内,如在就创建对应的植物,根据鼠标的位置,这个能理解不?

小AD:可以的。

明世隐:我们可以在Card类中,加入新方法,根据不同的卡牌类型创建不同的植物,当卡牌被鼠标点击的时候,调用此方法。

//在对应的坐标出创建对应的植物
public void createPlant(int x, int y,int cost) {
	Plant plant = null;
	if("sun".equals(type)){//创建太阳植物
		plant = new SunPlant(x-31, y-36, 62,72, panel);
	}else  if("wandou".equals(type)){//创建豌豆植物
		plant = new WandouPlant(x-31, y-35, 63,70, panel);
	}else  if("wallNut".equals(type)){//创建胡桃植物
		plant = new wallNutPlant(x-31, y-35, 61,71, panel);
	}
	plant.setCost(cost);
	panel.curPlant=plant;
	panel.plants.add(plant);
}

小AD:如何调用呢?

明世隐:这里有几个事情要做

1.判断阳光数量,如果不足,则不能创建。

2.开启创建创建音效。

3.在鼠标坐标处创建对应的植物

4.扣除花费的阳光数

5.判断阳光总数值够不够创建这些卡牌,如果不够的,给他遮罩起来

Card card=null;
for (int i = 0; i < plantsCard.size(); i++) {
	card = (Card)plantsCard.get(i);
	if(card.isPoint(x, y)){
		if(sunCount<card.getCost()){//阳光不足,则不能创建
			continue;
		}
		musicPlant = new MusicPlayer("/music/plant.wav");
		musicPlant.play();
		
		isCatch=true;
		//在鼠标坐标处创建对应的植物
		card.createPlant(x, y,card.getCost());
		//扣除对应的阳光
		sunCount-=card.getCost();
		//处理遮罩是否显示
		cardCanUse();
		break;
	}
}

小AD:怎么把它移动到田里面呢?

明世隐:然后加入鼠标移动监听  在MouseAdapter里面重写 mouseMoved方法,当鼠标移动就改变新创建的植物的x,y坐标就会跟随鼠标移动。

明世隐:这里要开启一个主线程,用来重绘页面,重绘的线程只有这一个,重绘全部交给主线程,主线程里面只要调用 repaint方法就会重绘了,如果不执行重绘更换图片,改动坐标也是不会动的,因为页面没有刷新。

//刷新线程,用来重新绘制页面
private class RefreshThread implements Runnable {
	@Override
	public void run() {
		while (true) {
			repaint();
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
	
		}
	}
}

在mouseMoved方法里面,一直把鼠标的X,Y坐标更新给树苗对象,树苗就这样跟随鼠标移动了。

植物种植

小AD:明哥,你的草地怎么会变颜色,还有那我要怎么种植物下去呢?

明世隐:这里需要给田划分好一块块的,每一块绘制一个小方形来区域,这个方形区域很重要,每个植物就是对应种在这个方形上的,怎么来划分呢,就是根据图片的尺寸。

当然这样画的不好看,我用代码计算并绘制出来就清晰了。创建一个PlantsRect 植物背景方形类,跟之前的差不多,只不过不是绘制的图片,而是方形

public class PlantsRect {
	private int x=0;
	private int y=0;
	private int width=0;
	private int height=0;
	private int index=0;
	private Color color=null;
	private Plant plant=null;

	public PlantsRect(int x,int y,int width,int height,int index) {
		this.x=x;
		this.y=y;
		this.width=width;
		this.height=height;
		this.index=index;
		this.color = RandomColor.getRandomColor();;//随机颜色
		//this.color = new Color(0,250,154, 0);//绘制成透明的颜色
	}
	
	public void draw(Graphics g) {
		Color oColor = g.getColor();
		g.setColor(color);
		g.fillRect(x, y, width, height);
		g.setColor(oColor);
	}
	//检查坐标是否图形范围内
	public boolean isPoint(int x,int y){
		if(x>this.x && y>this.y && x<this.x+this.width && y < this.y + this.height){
			return true;
		}
		return false;
	}
}

执行创建

private void initPlantsRect() {
	int x=0,y=0;
	PlantsRect pRect=null;
	for(int i=1;i<=5;i++){//5行
		y = 75+(i-1)*100;
		for(int j=1;j<=9;j++){//9列
			x = 105+(j-1)*80;
			pRect = new PlantsRect(x,y,80,100,i);
			plantsRect.add(pRect);
		}
	}
	
}

明世隐:AD你看,田被我分成一块块的,植物就种植在每一个方块里面,这样就非常好控制, 当然这里我等会要绘制成透明的,种植时鼠标在小方形点击的时候,植物的坐标更改过去,就完成了种植了。而且可以设置成鼠标移入的时候,变成绿色,如果已经有植物了则变成红色,无法种植。

PlantsRect pRect = null;
for (int i = 0; i < plantsRect.size(); i++) {
	pRect = (PlantsRect)plantsRect.get(i);
	if(pRect.isPoint(x, y)){
		if(pRect.getPlant()!=null){//如果已经有植物了 
			if(curPlant!=null){//不能种植
				pRect.setColor(new Color(255,23,13, 190));
			}
		}else{
			if(curPlant!=null){//可以种植
				pRect.setColor(new Color(0,250,154, 120));
			}
		}
	}else{
		pRect.setColor(new Color(0,250,154, 0));
	}
}

   

左图是可以种植的,右图则不能种植,因为已经有植物了。

实现僵尸

小AD:明哥你的hello kitty呢?

明世隐:马上来做,同样以抽象类的方式来做,因为kitty也有多种(但我目前就做了普通的那种)。

public class GeneralZombie extends Zombie{

	private int x = 0;
	private int y = 0;
	private int width = 0;
	private int height = 0;
	private BufferedImage image = null;
	private GamePanel panel=null;
	private HashMap zombieMoveHashMap=null;
	private HashMap zombieEatHashMap=null;
	private HashMap zombieDeadHashMap=null;
	private int key=1;
	private boolean alive=false;
	private boolean moveFlag=true;
	private boolean eatFlag=false;
	private boolean deadFlag=false;
	private int speed=1;
	
	private int action = 1;//1 移动,2 吃  3 死亡
	private int index=0 ;//下标
	private int hp=0 ;//血量
	private Plant eatPlant=null;//正在吃的植物
	
	MusicPlayer musicEat=null;
	MusicPlayer musicDead=null;
	
	public GeneralZombie(int x,int y,int width,int height,GamePanel panel) {
		this.x=x;
		this.y=y;
		this.width=width;
		this.height=height;
		this.panel=panel;
		this.zombieMoveHashMap=panel.zombieMoveHashMap;
		this.zombieEatHashMap=panel.zombieEatHashMap;
		this.zombieDeadHashMap=panel.zombieDeadHashMap;
		this.image=(BufferedImage)zombieMoveHashMap.get("("+key+")");
		
		alive = true;
		move();
	}
	
	@Override
	public void draw(Graphics g) {
		g.drawImage(image, x, y, width,height, null);
	}
}

这里要注意,是分了5行的,一行行都要有下标,计算的时候就不会出错(下面代码是在1、2、3、4、5随机的行创建)。

private void createZombie(Random random){
	int x=825;
	int y=0;
	int index = random.nextInt(5)+1;//随机获取1\2\3\4\5 行数
	if(index==1){
		y=60;
	}else if(index==2){
		y=160;
	}else if(index==3){
		y=260;
	}else if(index==4){
		y=355;
	}else if(index==5){
		y=460;
	}
	
	Zombie zombie = new GeneralZombie(x, y, 75,119, this);
	zombie.setIndex(index);//设置是第几行的僵尸
	zombie.setHp(10);//设置血量
	zombies.add(zombie);
}

僵尸移动

然后实现移动方法,让他坐标x递减,同时切换图片,可以达到移动的动画

//移动
@Override
void move(){
	new Thread(new Runnable() {
		@Override
		public void run() {
			while(alive && moveFlag && !deadFlag){
				try {
					Thread.sleep(80);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				x-=speed;
				
				changeImage();
				
				eat();
				
				if(x<100){
					System.out.println("结束了");
					panel.gameOver();
				}
			}
		
		}
	}).start();
}

于是,hello kitty一号粉墨登场了。

小AD:挖槽,你这明明是僵尸。

明世隐:在我眼里这就是hello kitty ,呆萌呆萌的,跟你一样萌,这不是都是金币吗?你在王者里面见到的野怪不都这个德性,这个有比暴君凶猛?

小AD:你。。。。。

僵尸吃和死亡动画

再加入 eat(吃) dead(死亡) 的方法,因为移动,吃、死亡这3个事情都是独立做的,不会在一起,所以每次都要停止之前的线程,启动新的线程。

@Override
void dead() {
	musicDead=new MusicPlayer("/music/dead.wav");
	musicDead.play();
	
	alive=false;
	
	//moveFlag =false;//停止移动动画
	
	action=3;//死亡动作
	
	key=1;//key要重置
	
	deadFlag = true;
	
	//僵尸死亡动画
	new Thread(new Runnable() {
		@Override
		public void run() {
			while(deadFlag){
				try {
					Thread.sleep(80);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				changeImage();//这个里面会处理僵尸消失
			}
		
		}
	}).start();
}

@Override
void eat() {
	//因为僵尸是向左移动的,判断僵尸的x 小于植物的 x+width 就表示要吃(同时还需要判断僵尸的屁股要大于植物的x,否则植物放到僵尸后面也被吃)
	List plants = panel.plants;
	Plant plant=null;
	for (int i = 0; i < plants.size(); i++) {
		plant = (Plant)plants.get(i);
		if(x<plant.getX()+plant.getWidth() && index==plant.getIndex()
			&& x+width >plant.getX()+plant.getWidth()/2){//走过了植物的一半就不让吃了
			//吃
			eatPlant=plant;
			doEat(); 
		}
	}

}

void doEat(){
	musicEat=new MusicPlayer("/music/eat.wav");
	musicEat.loop(-1);
	
	//将僵尸对象添加到植物中,存储为一个list,当植物被吃完后,需要主动通知每一个僵尸对象,否则会发生植物吃完,仍然有僵尸执行吃的延时动作
	eatPlant.addZombie(this);
	
	action=2;
	key=1;
	moveFlag=false;
	eatFlag=true;
	//僵尸吃动画
	new Thread(new Runnable() {
		@Override
		public void run() {
			while(alive && eatFlag){
				changeImage();
				try {
					Thread.sleep(80);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}).start();
}

小AD:是不是他在吃的时候,就鞋带掉了呀,在系鞋带的,所以不能走。

明世隐:对啊,你在王者里面被杨玉环勾住了,你可不得石乐志的往她身边走 ?

豌豆攻击

下面来加入豌豆攻击僵尸的判断,因为子弹和僵尸是相向而行的,只要子弹的X坐标,大于或者等于僵尸的坐标,就判断为击中,击中的时候,僵尸血量减去1,子弹消失,当僵尸的血量减完归零的时候,僵尸触发dead方法,进行死亡的动画,然后执行完动画,从屏幕中清除它。

//击中
private void hit() {
	//因为豌豆是向右飞行的,所以只需判断豌豆的 x_width  大于僵尸的x 就表示击中
	List zombies = panel.zombies;
	Zombie zombie=null;
	for (int i = 0; i < zombies.size(); i++) {
		zombie = (Zombie)zombies.get(i);
		if(x+width>zombie.getX() && index==zombie.getIndex()){//同一行,且子弹的x+width大于僵尸的X坐标,判断我击中
			//僵尸掉血
			 hitZombie(zombie); 
		}
	}
}



private void hitZombie(Zombie zombie) {
	musicHit=new MusicPlayer("/music/hit.wav");
	musicHit.play();
	//僵尸血量减少
	int hp = zombie.getHp();
	hp--;

	zombie.setHp(hp);
	if(hp==0){
		//执行僵尸死亡动画
		zombie.dead();
		
		panel.curCount+=10;
		
		/*if(panel.curCount>=panel.winCount){
			panel.gameWin();
		}*/
	}
	//子弹爆炸
	boom();
}
//爆炸
private void boom() {
	this.alive=false;
	this.image=(BufferedImage)imageMap.get("7");
	width=image.getWidth();
	height=image.getHeight();
	
	new Thread(new Runnable() {
		@Override
		public void run() {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//子弹消失
			clear();
		}
	}).start();
}

//清除豌豆
void clear(){
	this.alive=false;
	panel.wandous.remove(this);
}

创建阳光植物

太阳植物的写法和豌豆植物的写法很类似,就是发射子弹变成了发射太阳光,就不重复了。

阳光植物类

package main.plants;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import main.GamePanel;

public class SunPlant extends Plant {
	private int x = 0;
	private int y = 0;
	private int width = 0;
	private int height = 0;
	private int index=0;
	private int cost = 0;
	private BufferedImage image = null;
	private GamePanel panel=null;
	private HashMap imageMap=null;
	private int key=1;
	private boolean alive=false;
	private int hp=6;
	private List zombies = new ArrayList();
	private PlantsRect plantsRect=null;
	
	public SunPlant(int x,int y,int width,int height,GamePanel panel) {
		this.x=x;
		this.y=y;
		this.width=width;
		this.height=height;
		this.panel=panel;
		this.imageMap=panel.sunPlantImageMap;
		this.image=(BufferedImage)imageMap.get("("+key+")");
	}
	@Override
	public void draw(Graphics g) {
		g.drawImage(image, x, y, width,height, null);
	}

	@Override
	void shoot() {
		//Sun
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(alive){
					try {
						//每6秒发射一个阳光
						Thread.sleep(6000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if(alive){//防止被吃了还能发射一次阳光
						createSun();
					}
				}
			}

			private void createSun() {
				Sun sun = new Sun(x+width/2-22, y-44, 45, 44, panel);
				panel.suns.add(sun);
			}
		}).start();
	}
	
	

	//摇晃动画
	@Override
	void waggle() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(alive){
					changeImage();
					try {
						Thread.sleep(110);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
	}
	//更换图片
	void changeImage(){
		key++;
		if(key>18){
			key=1;
		}
		this.image=(BufferedImage)imageMap.get("("+key+")");
		width = this.image.getWidth();
		height = this.image.getHeight();
	}
	//种下植物
	@Override
	public void plant(PlantsRect rect) {
		this.x=rect.getX()+10;
		this.y=rect.getY()+12;
		this.index=rect.getIndex();
		this.alive=true;
		plantsRect = rect;
		//植物摇晃的动画
		waggle();
		
		shoot();//定时发射阳光
	}
	
	@Override
	public void clear() {
		alive=false;
		//植物占的位置去除
		if(plantsRect!=null){
			plantsRect.setPlant(null);
			plantsRect=null;	
		}
		//移除植物
		panel.plants.remove(this);
	}
	//添加僵尸对象,等植物吃完要通知每一个对象
	@Override
	public void addZombie(Zombie z) {
		zombies.add(z);
	}
	//通知所有僵尸对象
	@Override
	public void noteZombie() {
		Zombie zombie = null;
		//执行通知
		for (int i = 0; i < zombies.size(); i++) {
			zombie = (Zombie)zombies.get(i);
			zombie.noteZombie();
		}
		//通知完清除这个对象
		zombies.clear();
	}
	
	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 getWidth() {
		return width;
	}
	public void setWidth(int width) {
		this.width = width;
	}
	public int getHeight() {
		return height;
	}
	public void setHeight(int height) {
		this.height = height;
	}
	
	public int getIndex() {
		return index;
	}

	public void setIndex(int index) {
		this.index = index;
	}
	
	public int getHp() {
		return hp;
	}
	public void setHp(int hp) {
		this.hp = hp;
	}
	public boolean isAlive() {
		return alive;
	}
	public void setAlive(boolean alive) {
		this.alive = alive;
	}
	
	public int getCost() {
		return cost;
	}

	public void setCost(int cost) {
		this.cost = cost;
	}

}

阳光类

package main.plants;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.HashMap;

import main.GamePanel;
import main.common.MusicPlayer;

public class Sun {
	private int x = 0;
	private int y = 0;
	private int width = 0;
	private int height = 0;
	private BufferedImage image = null;
	private GamePanel panel=null;
	private HashMap imageMap=null;
	private int key=1;
	//是否存活
	private boolean alive=true;
	//向下移动的量
	private boolean moveDownFlag = false;
	private int moveDown = 0;
	//正在向目标移动标示
	private boolean moveTarget = false;
	//向目标移动的x、y速度
	private double mx=0;
	private double my=0;
	
	private int downMax = 100;//向下移动的距离

	private int count=20;//收集一个得到的分数
	
	MusicPlayer musicPoints=null;
	MusicPlayer musicMoneyfalls=null;
	
	public Sun(int x,int y,int width,int height,GamePanel panel) {
		this.x=x;
		this.y=y;
		this.width=width;
		this.height=height;
		this.panel=panel;
		this.imageMap=panel.sunImageMap;
		this.image=(BufferedImage)imageMap.get("("+key+")");
		
		//执行动画
		waggle();
		
		move();//默认向下移动
	}
	
	public void draw(Graphics g) {
		g.drawImage(image, x, y, width,height, null);
	}
	
	//摇晃动画
	void waggle() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(alive){
					changeImage();
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
	}
	
	//更换图片
	void changeImage(){
		key++;
		if(key>22){
			key=1;
		}
		this.image=(BufferedImage)imageMap.get("("+key+")");
	}
	//移动
	void move(){//往下移动100
		moveDownFlag=true;
		new Thread(new Runnable() {
			int speed=4;
			@Override
			public void run() {
				while(moveDownFlag){
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					moveDown+=speed;
					y+=speed;
					
					//阳光静止10秒
					if(moveDown>downMax){
						moveDownFlag=false;
						stop();
					}
				}
			
			}
		}).start();
	}
	
	//停止8秒
	void stop(){//
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(8000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//阳光消失
				
				clear();
			}
		}).start();
	}
	//阳光清除
	void clear(){
		this.alive=false;
		panel.suns.remove(this);
	}
	
	//被点击
	public void click(){
		musicPoints=new MusicPlayer("/music/points.wav");
		musicPoints.play();
		//向左上角移动
		int cx=20,
			cy=20;//收集点的X\Y坐标
		
		double angle = Math.atan2((y-cy), (x-cx));  //弧度 
		//计算出X\Y每一帧移动的距离
	    mx = Math.cos(angle)*20;
	    my = Math.sin(angle)*20;
		
		moveTarget = true ;
		moveDown=downMax+1;//设定不再向下运动
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(moveTarget){
					x-=mx;
					y-=my;
					
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					//停止移动,到达目标位置
					if(y<=20||x<=20){
					    musicMoneyfalls=new MusicPlayer("/music/moneyfalls.wav");
						musicMoneyfalls.play();
						moveTarget=false;
						panel.sunCount+=count;
						panel.cardCanUse();
						clear();
					}
				}
			}
		}).start();
	}
	
	//检查坐标是否图形范围内
	public boolean isPoint(int x,int y){
		if(x>=this.x && y>=this.y && x<=this.x+this.width && y <= this.y + this.height){
			return true;
		}
		return false;
	}
	
	public boolean isAlive() {
		return alive;
	}
	public void setAlive(boolean alive) {
		this.alive = alive;
	}
	
	public int getDownMax() {
		return downMax;
	}

	public void setDownMax(int downMax) {
		this.downMax = downMax;
	}
}

胡桃的就不说了,再把一些细节处理一些就基本完成了。

小AD:挺好玩的,这些呆萌呆萌的hello kitty,嘻嘻嘻嘻。

给AD上课

新涉世的ADC,怕是没有经历过时间的险恶,于是我决定给他上一课。

(现在僵尸是每5秒出一批,每批随机1-5只,还是蛮好守的,于是我在5波之后 一次性创建20-50只僵尸)

//创建一大波僵尸  5秒后
new Thread(new Runnable() {
	@Override
	public void run() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Random random = new Random();
		int count = random.nextInt(30)+20;//随机数量的僵尸 20-50
		for(int i=0;i<count;i++){
			createZombie(random);
		}
		//僵尸全部创建完成后,要开始计算胜利了
		winComputed=true;
	}
}).start();

于是屏幕出现了这样的画面

紧接着,一大波僵尸冲了过去

小AD失败了。

代码目录

总结

不好意思,小AD战绩0-10,我10-0,AD直呼有套路,然后大喊辅助牛b666,但是小AD的编程知识却是长进了不少。

我希望每一位AD经过明世隐的辅助,都能够从0-10,变成10-0,成长为一个优秀的编程者,编程界的一流ADC。

代码下载

代码下载:老java程序员用2天时间开发一个简版植物大战僵尸

 

 

 

文章来源:https://blog.csdn.net/dkm123456/article/details/117047116

后台-系统设置-扩展变量-手机广告位-内容正文底部
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。
本文地址:https://www.jcdi.cn/ruanjiankaifa/30787.html

留言与评论(共有 0 条评论)
   
验证码:
后台-系统设置-扩展变量-手机广告位-评论底部广告位

教程弟

https://www.jcdi.cn/

统计代码 | 京ICP1234567-2号

Powered By 教程弟 教程弟

使用手机软件扫描微信二维码