飞道的博客

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

201人阅读  评论(0)

引言:

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

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

对话

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

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

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

小AD:我最擅长了,666。

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

小AD:做什么呀?

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

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

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

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

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

小AD:那好吧。

游戏窗体

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

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

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


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

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


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

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

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

面板容器

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

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

小AD:那要怎么添加呢?

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


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

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

游戏背景图

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

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

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

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

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

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


  
  1. public static BufferedImage getImg( String path){
  2. //用try方法捕获异常
  3. try {
  4. //io流,输送数据的管道
  5. BufferedImage img = ImageIO.read(GameApp.class.getResource(path));
  6. return img;
  7. }
  8. //异常处理,打印异常
  9. catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. //没找到则返回空
  13. return null;
  14. }

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


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

运行一下看看:

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

卡牌、积分等辅助框

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


  
  1. //绘制背景
  2. g.drawImage((BufferedImage)imageMap. get( "1"), - 150, 0, null);
  3. //方形卡片盘
  4. g.drawImage((BufferedImage)imageMap. get( "2"), 0, 0, 446, 80, null);
  5. //铲子背景
  6. g.drawImage((BufferedImage)imageMap. get( "17"), 446, 2, 80, 35, null);
  7. //积分盘
  8. g.drawImage((BufferedImage)imageMap. get( "12"), 530, - 4, 128, 40, null);

小AD:嗯这个明白了。

卡牌绘制

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


  
  1. public class Card {
  2. private int x = 0; //x坐标
  3. private int y = 0; //y坐标
  4. private int width = 0; //宽
  5. private int height = 0; //高
  6. private BufferedImage image = null; //图片对象
  7. private int cost = 0; //阳光花费
  8. private String type = ""; //类型,可以通过类型来判断创建什么植物
  9. private GamePanel panel= null;
  10. private int index= 0;
  11. public Card(int x, int y, int width, int height, BufferedImage image,String type,int cost, int index ,GamePanel panel) {
  12. this.x = x;
  13. this.y = y;
  14. this.width = width;
  15. this.height = height;
  16. this.image = image;
  17. this.type=type;
  18. this.cost=cost;
  19. this.index=index;
  20. this.panel=panel;
  21. }
  22. public void draw(Graphics g) {
  23. g.drawImage(image, x, y, width, height, null);
  24. }
  25. }

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

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


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

明世隐:你看上面的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 方法;


  
  1. public abstract class Student {
  2. abstract void sayNum();
  3. }

再创建子类


  
  1. public class StudentC extends Student{
  2. int count = 0;
  3. public StudentC(int count) {
  4. this.count=count;
  5. }
  6. @Override
  7. void sayNum() {
  8. System.out.println( "报数:"+count);
  9. }
  10. }

创建main方法测试


  
  1. public static void main( String[] args) {
  2. List list = new ArrayList();
  3. for ( int i = 1; i <= 5; i++) {
  4. list.add( new StudentC(i));
  5. }
  6. Student student= null;
  7. for ( int i = 0; i < list.size(); i++) {
  8. student = (Student) list.get(i);
  9. student.sayNum();
  10. }
  11. }

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

小AD:有点懵。

植物抽象化

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

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


  
  1. public abstract class Plant {
  2. public abstract void draw(Graphics g);
  3. abstract void waggle();
  4. abstract void shoot();
  5. public abstract void clear();
  6. public abstract void plant(PlantsRect rect);
  7. }

豌豆植物实现

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


  
  1. private int x = 0;
  2. private int y = 0;
  3. private int width = 0;
  4. private int height = 0;
  5. private int index= 0;
  6. private int cost = 0;
  7. private BufferedImage image = null;
  8. private GamePanel panel= null;
  9. private HashMap imageMap= null;
  10. private List zombies = new ArrayList();
  11. private int key= 1;
  12. private boolean alive= false;
  13. private int hp= 10;
  14. private PlantsRect plantsRect= null;
  15. MusicPlayer musicShoot= null;
  16. public WandouPlant(int x,int y,int width,int height,GamePanel panel) {
  17. this.x=x;
  18. this.y=y;
  19. this.width=width;
  20. this.height=height;
  21. this.panel=panel;
  22. this.imageMap=panel.wandouPlantHashMap;
  23. this.image=(BufferedImage)imageMap.get( "("+key+ ")");
  24. }
  25. @Override
  26. public void draw(Graphics g) {
  27. g.drawImage(image, x, y, width,height, null);
  28. }

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


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

植物本身动画

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

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


  
  1. //摇晃动画
  2. @Override
  3. void waggle() {
  4. new Thread( new Runnable() {
  5. @Override
  6. public void run() {
  7. while(alive){
  8. changeImage();
  9. try {
  10. Thread.sleep( 110);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. }).start();
  17. }
  18. //更换图片
  19. void changeImage(){
  20. key++;
  21. if(key> 13){
  22. key= 1;
  23. }
  24. this.image=(BufferedImage)imageMap.get( "("+key+ ")");
  25. width = this.image.getWidth();
  26. height = this.image.getHeight();
  27. }

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

植物射击

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


  
  1. //移动
  2. void move(){
  3. new Thread( new Runnable() {
  4. @Override
  5. public void run() {
  6. while(alive){
  7. try {
  8. Thread.sleep( 50);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. x+=speed;
  13. //超过范围,豌豆消失
  14. if(x>=panel.gameWidth){
  15. clear();
  16. }
  17. }
  18. }
  19. }).start();
  20. }

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


  
  1. @Override
  2. void shoot() {
  3. //Sun
  4. new Thread( new Runnable() {
  5. @Override
  6. public void run() {
  7. while(alive){
  8. try {
  9. //每2秒发射一个豌豆
  10. Thread.sleep( 2000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. if(alive){ //防止被吃了还能发射一次子弹
  15. createWandou();
  16. musicShoot= new MusicPlayer( "/music/shoot.wav");
  17. musicShoot.play();
  18. }
  19. }
  20. }
  21. private void createWandou() {
  22. Wandou wandou = new Wandou(x+ 50, y, 28, 28, panel,index);
  23. panel.wandous.add(wandou);
  24. }
  25. }).start();
  26. }

卡牌创建植物

 

小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,加入代码如下:


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

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

小AD:可以的。

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


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

小AD:如何调用呢?

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

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

2.开启创建创建音效。

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

4.扣除花费的阳光数

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


  
  1. Card card=null ;
  2. for ( int i = 0 ; i < plantsCard.size(); i++) {
  3. card = ( Card)plantsCard.get( i) ;
  4. if( card.isPoint( x, y)){
  5. if( sunCount<card.getCost()){//阳光不足,则不能创建
  6. continue ;
  7. }
  8. musicPlant = new MusicPlayer( "/music/plant.wav") ;
  9. musicPlant.play() ;
  10. isCatch=true ;
  11. //在鼠标坐标处创建对应的植物
  12. card.createPlant( x, y,card.getCost()) ;
  13. //扣除对应的阳光
  14. sunCount-=card.getCost() ;
  15. //处理遮罩是否显示
  16. cardCanUse() ;
  17. break ;
  18. }
  19. }

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

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

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


  
  1. //刷新线程,用来重新绘制页面
  2. private class RefreshThread implements Runnable {
  3. @Override
  4. public void run() {
  5. while ( true) {
  6. repaint();
  7. try {
  8. Thread.sleep( 50);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }
  14. }

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

植物种植

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

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

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


  
  1. public class PlantsRect {
  2. private int x= 0;
  3. private int y= 0;
  4. private int width= 0;
  5. private int height= 0;
  6. private int index= 0;
  7. private Color color= null;
  8. private Plant plant= null;
  9. public PlantsRect(int x,int y,int width,int height,int index) {
  10. this.x=x;
  11. this.y=y;
  12. this.width=width;
  13. this.height=height;
  14. this.index=index;
  15. this.color = RandomColor.getRandomColor();; //随机颜色
  16. //this.color = new Color(0,250,154, 0);//绘制成透明的颜色
  17. }
  18. public void draw(Graphics g) {
  19. Color oColor = g.getColor();
  20. g.setColor(color);
  21. g.fillRect(x, y, width, height);
  22. g.setColor(oColor);
  23. }
  24. //检查坐标是否图形范围内
  25. public boolean isPoint(int x,int y){
  26. if(x> this.x && y> this.y && x< this.x+ this.width && y < this.y + this.height){
  27. return true;
  28. }
  29. return false;
  30. }
  31. }

执行创建


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

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


  
  1. PlantsRect pRect = null ;
  2. for ( int i = 0 ; i < plantsRect.size(); i++) {
  3. pRect = ( PlantsRect)plantsRect.get( i) ;
  4. if( pRect.isPoint( x, y)){
  5. if( pRect.getPlant()!=null){//如果已经有植物了
  6. if( curPlant!=null){//不能种植
  7. pRect.setColor( new Color( 255, 23, 13, 190)) ;
  8. }
  9. }else{
  10. if( curPlant!=null){//可以种植
  11. pRect.setColor( new Color( 0, 250, 154, 120)) ;
  12. }
  13. }
  14. }else{
  15. pRect.setColor( new Color( 0, 250, 154, 0)) ;
  16. }
  17. }

   

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

实现僵尸

小AD:明哥你的hello kitty呢?

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


  
  1. public class GeneralZombie extends Zombie{
  2. private int x = 0;
  3. private int y = 0;
  4. private int width = 0;
  5. private int height = 0;
  6. private BufferedImage image = null;
  7. private GamePanel panel= null;
  8. private HashMap zombieMoveHashMap= null;
  9. private HashMap zombieEatHashMap= null;
  10. private HashMap zombieDeadHashMap= null;
  11. private int key= 1;
  12. private boolean alive= false;
  13. private boolean moveFlag= true;
  14. private boolean eatFlag= false;
  15. private boolean deadFlag= false;
  16. private int speed= 1;
  17. private int action = 1; //1 移动,2 吃 3 死亡
  18. private int index= 0 ; //下标
  19. private int hp= 0 ; //血量
  20. private Plant eatPlant= null; //正在吃的植物
  21. MusicPlayer musicEat= null;
  22. MusicPlayer musicDead= null;
  23. public GeneralZombie(int x,int y,int width,int height,GamePanel panel) {
  24. this.x=x;
  25. this.y=y;
  26. this.width=width;
  27. this.height=height;
  28. this.panel=panel;
  29. this.zombieMoveHashMap=panel.zombieMoveHashMap;
  30. this.zombieEatHashMap=panel.zombieEatHashMap;
  31. this.zombieDeadHashMap=panel.zombieDeadHashMap;
  32. this.image=(BufferedImage)zombieMoveHashMap.get( "("+key+ ")");
  33. alive = true;
  34. move();
  35. }
  36. @Override
  37. public void draw(Graphics g) {
  38. g.drawImage(image, x, y, width,height, null);
  39. }
  40. }

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


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

僵尸移动

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


  
  1. //移动
  2. @Override
  3. void move(){
  4. new Thread( new Runnable() {
  5. @Override
  6. public void run() {
  7. while(alive && moveFlag && !deadFlag){
  8. try {
  9. Thread.sleep( 80);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. x-=speed;
  14. changeImage();
  15. eat();
  16. if(x< 100){
  17. System.out.println( "结束了");
  18. panel.gameOver();
  19. }
  20. }
  21. }
  22. }).start();
  23. }

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

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

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

小AD:你。。。。。

僵尸吃和死亡动画

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


  
  1. @Override
  2. void dead() {
  3. musicDead= new MusicPlayer( "/music/dead.wav");
  4. musicDead.play();
  5. alive= false;
  6. //moveFlag =false;//停止移动动画
  7. action= 3; //死亡动作
  8. key= 1; //key要重置
  9. deadFlag = true;
  10. //僵尸死亡动画
  11. new Thread( new Runnable() {
  12. @Override
  13. public void run() {
  14. while(deadFlag){
  15. try {
  16. Thread.sleep( 80);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. changeImage(); //这个里面会处理僵尸消失
  21. }
  22. }
  23. }).start();
  24. }
  25. @Override
  26. void eat() {
  27. //因为僵尸是向左移动的,判断僵尸的x 小于植物的 x+width 就表示要吃(同时还需要判断僵尸的屁股要大于植物的x,否则植物放到僵尸后面也被吃)
  28. List plants = panel.plants;
  29. Plant plant= null;
  30. for ( int i = 0; i < plants.size(); i++) {
  31. plant = (Plant)plants.get(i);
  32. if(x<plant.getX()+plant.getWidth() && index==plant.getIndex()
  33. && x+width >plant.getX()+plant.getWidth()/ 2){ //走过了植物的一半就不让吃了
  34. //吃
  35. eatPlant=plant;
  36. doEat();
  37. }
  38. }
  39. }
  40. void doEat(){
  41. musicEat= new MusicPlayer( "/music/eat.wav");
  42. musicEat.loop(- 1);
  43. //将僵尸对象添加到植物中,存储为一个list,当植物被吃完后,需要主动通知每一个僵尸对象,否则会发生植物吃完,仍然有僵尸执行吃的延时动作
  44. eatPlant.addZombie( this);
  45. action= 2;
  46. key= 1;
  47. moveFlag= false;
  48. eatFlag= true;
  49. //僵尸吃动画
  50. new Thread( new Runnable() {
  51. @Override
  52. public void run() {
  53. while(alive && eatFlag){
  54. changeImage();
  55. try {
  56. Thread.sleep( 80);
  57. } catch (InterruptedException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }
  62. }).start();
  63. }

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

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

豌豆攻击

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


  
  1. //击中
  2. private void hit() {
  3. //因为豌豆是向右飞行的,所以只需判断豌豆的 x_width 大于僵尸的x 就表示击中
  4. List zombies = panel.zombies;
  5. Zombie zombie= null;
  6. for ( int i = 0; i < zombies.size(); i++) {
  7. zombie = (Zombie)zombies. get(i);
  8. if(x+width>zombie.getX() && index==zombie.getIndex()){ //同一行,且子弹的x+width大于僵尸的X坐标,判断我击中
  9. //僵尸掉血
  10. hitZombie(zombie);
  11. }
  12. }
  13. }
  14. private void hitZombie(Zombie zombie) {
  15. musicHit= new MusicPlayer( "/music/hit.wav");
  16. musicHit.play();
  17. //僵尸血量减少
  18. int hp = zombie.getHp();
  19. hp--;
  20. zombie.setHp(hp);
  21. if(hp== 0){
  22. //执行僵尸死亡动画
  23. zombie.dead();
  24. panel.curCount+= 10;
  25. /*if(panel.curCount>=panel.winCount){
  26. panel.gameWin();
  27. }*/
  28. }
  29. //子弹爆炸
  30. boom();
  31. }
  32. //爆炸
  33. private void boom() {
  34. this.alive= false;
  35. this.image=(BufferedImage)imageMap. get( "7");
  36. width=image.getWidth();
  37. height=image.getHeight();
  38. new Thread( new Runnable() {
  39. @Override
  40. public void run() {
  41. try {
  42. Thread.sleep( 100);
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. }
  46. //子弹消失
  47. clear();
  48. }
  49. }).start();
  50. }
  51. //清除豌豆
  52. void clear(){
  53. this.alive= false;
  54. panel.wandous. remove( this);
  55. }

创建阳光植物

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

阳光植物类


  
  1. package main.plants;
  2. import java.awt.Graphics;
  3. import java.awt.image.BufferedImage;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import main.GamePanel;
  8. public class SunPlant extends Plant {
  9. private int x = 0;
  10. private int y = 0;
  11. private int width = 0;
  12. private int height = 0;
  13. private int index= 0;
  14. private int cost = 0;
  15. private BufferedImage image = null;
  16. private GamePanel panel= null;
  17. private HashMap imageMap= null;
  18. private int key= 1;
  19. private boolean alive= false;
  20. private int hp= 6;
  21. private List zombies = new ArrayList();
  22. private PlantsRect plantsRect= null;
  23. public SunPlant(int x,int y,int width,int height,GamePanel panel) {
  24. this.x=x;
  25. this.y=y;
  26. this.width=width;
  27. this.height=height;
  28. this.panel=panel;
  29. this.imageMap=panel.sunPlantImageMap;
  30. this.image=(BufferedImage)imageMap.get( "("+key+ ")");
  31. }
  32. @Override
  33. public void draw(Graphics g) {
  34. g.drawImage(image, x, y, width,height, null);
  35. }
  36. @Override
  37. void shoot() {
  38. //Sun
  39. new Thread( new Runnable() {
  40. @Override
  41. public void run() {
  42. while(alive){
  43. try {
  44. //每6秒发射一个阳光
  45. Thread.sleep( 6000);
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. if(alive){ //防止被吃了还能发射一次阳光
  50. createSun();
  51. }
  52. }
  53. }
  54. private void createSun() {
  55. Sun sun = new Sun(x+width/ 2- 22, y- 44, 45, 44, panel);
  56. panel.suns.add(sun);
  57. }
  58. }).start();
  59. }
  60. //摇晃动画
  61. @Override
  62. void waggle() {
  63. new Thread( new Runnable() {
  64. @Override
  65. public void run() {
  66. while(alive){
  67. changeImage();
  68. try {
  69. Thread.sleep( 110);
  70. } catch (InterruptedException e) {
  71. e.printStackTrace();
  72. }
  73. }
  74. }
  75. }).start();
  76. }
  77. //更换图片
  78. void changeImage(){
  79. key++;
  80. if(key> 18){
  81. key= 1;
  82. }
  83. this.image=(BufferedImage)imageMap.get( "("+key+ ")");
  84. width = this.image.getWidth();
  85. height = this.image.getHeight();
  86. }
  87. //种下植物
  88. @Override
  89. public void plant(PlantsRect rect) {
  90. this.x=rect.getX()+ 10;
  91. this.y=rect.getY()+ 12;
  92. this.index=rect.getIndex();
  93. this.alive= true;
  94. plantsRect = rect;
  95. //植物摇晃的动画
  96. waggle();
  97. shoot(); //定时发射阳光
  98. }
  99. @Override
  100. public void clear() {
  101. alive= false;
  102. //植物占的位置去除
  103. if(plantsRect!= null){
  104. plantsRect.setPlant( null);
  105. plantsRect= null;
  106. }
  107. //移除植物
  108. panel.plants.remove( this);
  109. }
  110. //添加僵尸对象,等植物吃完要通知每一个对象
  111. @Override
  112. public void addZombie(Zombie z) {
  113. zombies.add(z);
  114. }
  115. //通知所有僵尸对象
  116. @Override
  117. public void noteZombie() {
  118. Zombie zombie = null;
  119. //执行通知
  120. for ( int i = 0; i < zombies.size(); i++) {
  121. zombie = (Zombie)zombies.get(i);
  122. zombie.noteZombie();
  123. }
  124. //通知完清除这个对象
  125. zombies.clear();
  126. }
  127. public int getX() {
  128. return x;
  129. }
  130. public void setX(int x) {
  131. this.x = x;
  132. }
  133. public int getY() {
  134. return y;
  135. }
  136. public void setY(int y) {
  137. this.y = y;
  138. }
  139. public int getWidth() {
  140. return width;
  141. }
  142. public void setWidth(int width) {
  143. this.width = width;
  144. }
  145. public int getHeight() {
  146. return height;
  147. }
  148. public void setHeight(int height) {
  149. this.height = height;
  150. }
  151. public int getIndex() {
  152. return index;
  153. }
  154. public void setIndex(int index) {
  155. this.index = index;
  156. }
  157. public int getHp() {
  158. return hp;
  159. }
  160. public void setHp(int hp) {
  161. this.hp = hp;
  162. }
  163. public boolean isAlive() {
  164. return alive;
  165. }
  166. public void setAlive(boolean alive) {
  167. this.alive = alive;
  168. }
  169. public int getCost() {
  170. return cost;
  171. }
  172. public void setCost(int cost) {
  173. this.cost = cost;
  174. }
  175. }

阳光类


  
  1. package main.plants;
  2. import java.awt.Graphics;
  3. import java.awt.image.BufferedImage;
  4. import java.util.HashMap;
  5. import main.GamePanel;
  6. import main.common.MusicPlayer;
  7. public class Sun {
  8. private int x = 0;
  9. private int y = 0;
  10. private int width = 0;
  11. private int height = 0;
  12. private BufferedImage image = null;
  13. private GamePanel panel= null;
  14. private HashMap imageMap= null;
  15. private int key= 1;
  16. //是否存活
  17. private boolean alive= true;
  18. //向下移动的量
  19. private boolean moveDownFlag = false;
  20. private int moveDown = 0;
  21. //正在向目标移动标示
  22. private boolean moveTarget = false;
  23. //向目标移动的x、y速度
  24. private double mx= 0;
  25. private double my= 0;
  26. private int downMax = 100; //向下移动的距离
  27. private int count= 20; //收集一个得到的分数
  28. MusicPlayer musicPoints= null;
  29. MusicPlayer musicMoneyfalls= null;
  30. public Sun(int x,int y,int width,int height,GamePanel panel) {
  31. this.x=x;
  32. this.y=y;
  33. this.width=width;
  34. this.height=height;
  35. this.panel=panel;
  36. this.imageMap=panel.sunImageMap;
  37. this.image=(BufferedImage)imageMap.get( "("+key+ ")");
  38. //执行动画
  39. waggle();
  40. move(); //默认向下移动
  41. }
  42. public void draw(Graphics g) {
  43. g.drawImage(image, x, y, width,height, null);
  44. }
  45. //摇晃动画
  46. void waggle() {
  47. new Thread( new Runnable() {
  48. @Override
  49. public void run() {
  50. while(alive){
  51. changeImage();
  52. try {
  53. Thread.sleep( 100);
  54. } catch (InterruptedException e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. }
  59. }).start();
  60. }
  61. //更换图片
  62. void changeImage(){
  63. key++;
  64. if(key> 22){
  65. key= 1;
  66. }
  67. this.image=(BufferedImage)imageMap.get( "("+key+ ")");
  68. }
  69. //移动
  70. void move(){ //往下移动100
  71. moveDownFlag= true;
  72. new Thread( new Runnable() {
  73. int speed= 4;
  74. @Override
  75. public void run() {
  76. while(moveDownFlag){
  77. try {
  78. Thread.sleep( 100);
  79. } catch (InterruptedException e) {
  80. e.printStackTrace();
  81. }
  82. moveDown+=speed;
  83. y+=speed;
  84. //阳光静止10秒
  85. if(moveDown>downMax){
  86. moveDownFlag= false;
  87. stop();
  88. }
  89. }
  90. }
  91. }).start();
  92. }
  93. //停止8秒
  94. void stop(){ //
  95. new Thread( new Runnable() {
  96. @Override
  97. public void run() {
  98. try {
  99. Thread.sleep( 8000);
  100. } catch (InterruptedException e) {
  101. e.printStackTrace();
  102. }
  103. //阳光消失
  104. clear();
  105. }
  106. }).start();
  107. }
  108. //阳光清除
  109. void clear(){
  110. this.alive= false;
  111. panel.suns.remove( this);
  112. }
  113. //被点击
  114. public void click(){
  115. musicPoints= new MusicPlayer( "/music/points.wav");
  116. musicPoints.play();
  117. //向左上角移动
  118. int cx= 20,
  119. cy= 20; //收集点的X\Y坐标
  120. double angle = Math.atan2((y-cy), (x-cx)); //弧度
  121. //计算出X\Y每一帧移动的距离
  122. mx = Math.cos(angle)* 20;
  123. my = Math.sin(angle)* 20;
  124. moveTarget = true ;
  125. moveDown=downMax+ 1; //设定不再向下运动
  126. new Thread( new Runnable() {
  127. @Override
  128. public void run() {
  129. while(moveTarget){
  130. x-=mx;
  131. y-=my;
  132. try {
  133. Thread.sleep( 100);
  134. } catch (InterruptedException e) {
  135. e.printStackTrace();
  136. }
  137. //停止移动,到达目标位置
  138. if(y<= 20||x<= 20){
  139. musicMoneyfalls= new MusicPlayer( "/music/moneyfalls.wav");
  140. musicMoneyfalls.play();
  141. moveTarget= false;
  142. panel.sunCount+=count;
  143. panel.cardCanUse();
  144. clear();
  145. }
  146. }
  147. }
  148. }).start();
  149. }
  150. //检查坐标是否图形范围内
  151. public boolean isPoint(int x,int y){
  152. if(x>= this.x && y>= this.y && x<= this.x+ this.width && y <= this.y + this.height){
  153. return true;
  154. }
  155. return false;
  156. }
  157. public boolean isAlive() {
  158. return alive;
  159. }
  160. public void setAlive(boolean alive) {
  161. this.alive = alive;
  162. }
  163. public int getDownMax() {
  164. return downMax;
  165. }
  166. public void setDownMax(int downMax) {
  167. this.downMax = downMax;
  168. }
  169. }

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

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

给AD上课

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

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


  
  1. //创建一大波僵尸 5秒后
  2. new Thread( new Runnable() {
  3. @Override
  4. public void run() {
  5. try {
  6. Thread.sleep( 5000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. Random random = new Random();
  11. int count = random.nextInt( 30)+ 20; //随机数量的僵尸 20-50
  12. for( int i= 0;i<count;i++){
  13. createZombie(random);
  14. }
  15. //僵尸全部创建完成后,要开始计算胜利了
  16. winComputed= true;
  17. }
  18. }).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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场