引言:
作为辅助,带领我家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