java植物大战僵尸,我家ADC直呼内行,甚至喊出辅助牛逼666
Posted 编程界明世隐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java植物大战僵尸,我家ADC直呼内行,甚至喊出辅助牛逼666相关的知识,希望对你有一定的参考价值。
引言:
作为辅助,带领我家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天时间开发一个简版植物大战僵尸
以上是关于java植物大战僵尸,我家ADC直呼内行,甚至喊出辅助牛逼666的主要内容,如果未能解决你的问题,请参考以下文章