对面向对象阶段性了解
对面向对象学习一阶段后大家可能有很大的疑惑,什么是面向对象,该怎样用面向对象。由于我自己也是刚接触java没多久,对这些问题的了解可能也不是那么多,下面就用一个实例表达一下自己对面向对象三大特征的简单了解。
一个意外写的简单小例子:蜜蜂吃花蜜
规则如下:控制蜜蜂去吃花蜜,花蜜会在面板中随机移动,蜜蜂吃掉一个花蜜后会产生新的花蜜,分数加5分,当加的分数为10的倍数时,会在面板的左侧随机生成一个蝴蝶,蜜蜂如果装上蝴蝶的话分数会减少5分,当分数达到50分时游戏结束。
图中共有三个可以移动的对象:蜜蜂,蜂蜜,蝴蝶
既然找到了这些对象,那我们对这些对象简单的分析可以知道这些对象的共有特征,图片,图片的宽高,图片在面板上显示的坐标,还有就是三者都需要移动的方法。那么我们首先就可以把这些对象的共有特征封装到同一个父类中定义。
oop1:定义一个父类:
public abstract class FlyingObject {
protected BufferedImage image;
protected int width;
protected int height;
protected int x;
protected int y;
public abstract void step();
}
我们可以看到在父类中的属性和方法全都是刚才分析到的。
在这里可以注意到一个关键字: abstract
abstract 的意思是抽象的,能够用在类和方法上,作为抽象类或者抽象方法。
抽象类有以下特征:
抽象类是天生的父类,类中的方法只能由子类调用。
抽象类限制实例化,就是抽象类不能被实例化。
抽象类虽然不能被实例化,但抽象类中有构造方法。
抽象方法有以下特征:
没有方法体。
必须在子类中被重写,除非子类是抽象类。
有的可能会有疑问,父类中的类本来就是子类的共有的方法,为什么还要用抽象方法呢。
我感觉,抽象方法在很大的帮助我们对代码的书写,它可以给我们一个提示,当然我们可以选择不进行重写,这只是我的个人见解
为了方便理解接口,我在程序中定义了一个接口用作分数的加减。
oop2:定义一个分数接口:
public interface Score {
public abstract int getScore();
}
那么在这里我们可以看到另一个关键字:interface
Interface是接口的意思。
接口有以下几个特征:
接口里的属性是默认public static final 修饰的常量
接口里的方法都是默认public abstract修饰的抽象方法,全都没有方法体。
接口没有构造方法,不能被实例化
接口里的方法是一定要被重写实现的
接口里的变量都要进行初始化
大家在这可以看出其实接口就是比抽象类更加抽象的抽象类。
有了父类和接口后我们要定义三个对象的类:
oop3:定义蜜蜂,蝴蝶,和花蜜的类
Bee.class
public class Bee extends FlyingObject{
private int speed;//移动速度
public boolean left,right,up,down;//向各个方向移动时的开始条件
//构造方法
public Bee(){
//初始化蜜蜂的属性
image = Game.bee;
width = image.getWidth();
height = image.getHeight();
this.x = 150;
this.y = 150;
speed=2;
}
@Override
public void step() {
// TODO 自动生成的方法存根
}
}
Water.class
public class Water extends FlyingObject implements Score{
private int xspeed;//定义水的x坐标移动速度
private int yspeed; ;//定义水的y坐标移动速度
public Water(){
image = Game.water;
width = image.getWidth();
height = image.getHeight();
this.x = (int)(Math.random()*300+50);
this.y = (int)(Math.random()*300+50);
xspeed = 1;
yspeed = 2;
}
@Override
public void step() {
// TODO 自动生成的方法存根
}
@Override
public int getScore() {
// TODO 自动生成的方法存根
return 5;
}
}
Butterfly.class
public class Butterfly extends FlyingObject implements Score{
private BufferedImage[] images;//定义一个图片数组实现蝴蝶动态
private int index;//帮助实现动态
public Butterfly(){
image = Game.but[0];
width = image.getWidth();
height = image.getHeight();
this.x = -width;
this.y = (int)(Math.random()*(Game.HEIGHT-90));
images = Game.but;
index = 0;
}
@Override
public void step() {
// TODO 自动生成的方法存根
}
@Override
public int getScore() {
// TODO 自动生成的方法存根
return 5;
}
}
可以看到我们子类在继承过父类后拥有了父类的属性和方法,而且用implements连接到Score接口后,子类对getScore方法的重写。
这里我们可以了解到面向对象的三大概念中的其中之一:多态
其实我们可以这样理解多态:每个事物都有其不同的形态,每个方法都会有不同的对象来适用
然后我们在定义一个Game类作为程序的入口和执行者。
oop4:
在定义Game类之前呢,我们需要先定义一个工具类帮助我们做图片的传输工作,这个类在这里不细说,大家可以看一下,有需要的可以百度查一下。
GameUtil.class
public class GameUtil {
public static BufferedImage getImg(String path){
BufferedImage image = null;
try {
URL u = GameUtil.class.getClassLoader().getResource(path);
image = ImageIO.read(u);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return image;
}
}
Game.class
public class Game extends JPanel{
public static final int WIDTH = 400;
public static final int HEIGHT = 400;
public static BufferedImage bg = GameUtil.getImg("images/bg.png");
public static BufferedImage bee = GameUtil.getImg("images/bee.png");
public static BufferedImage water = GameUtil.getImg("images/water.png");
public static BufferedImage[] but {
GameUtil.getImg(“images/butterfly2.png”) ,GameUtil.getImg(“images/butterfly3.png”)};
public static Bee flyBee = new Bee();
public static Water flyWater = new Water();
public static Butterfly butter = new Butterfly();
private int time = 0;
private int score = 0;
public static void main(String[] args) {
JFrame f = new JFrame();
Game g = new Game();
f.add(g);
f.setSize(WIDTH,HEIGHT);
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setAlwaysOnTop(true);
f.setVisible(true);//可以自动调用paint方法
}
@Override
public void paint(Graphics g) {
// TODO 自动生成的方法存根
// Graphics是制图的意思 在这里g相当于我们画图时的画笔
g.drawImage(bg, 0, 0, null);
paintBee(g);
paintWater(g);
paintString(g);
paintButterfly(g);
}
private void paintBee(Graphics g) {
// TODO 自动生成的方法存根
g.drawImage(flyBee.image, flyBee.x, flyBee.y, null);
}
private void paintWater(Graphics g){
g.drawImage(flyWater.image, flyWater.x, flyWater.y, null);
}
private void paintButterfly(Graphics g) {
// TODO 自动生成的方法存根
if(score%10==0&&score!=0){
butter.fly(g);
}else{
butter = new Butterfly();
}
}
private void paintString(Graphics g) {
// TODO 自动生成的方法存根
Font f = new Font(Font.SANS_SERIF,Font.BOLD,15);
g.setFont(f);
int x = 15;
int y = 20;
g.drawString("TIMER:"+time+" s", x, y);
y+=20;
g.drawString("SCORE:"+score, x, y);
}
}
到这里我们就可以看到背景还有蜜蜂,花蜜还有一些参数画到了面板上,但是仅仅是显示还不能移动,下面是显示效果:
我们可以看到在Game类里可能出现了很多的新名词,别急一点一点看:
g.drawImage(Imgae,x,y,null);
这句话的意思就是让我们用画笔在坐标为(x,y)的地方画出我们想要画的图片,最后的null先不说,写成null即可。
注意:java中的坐标是从左上角开始的,和我们平常用的坐标不同的是在数学环境中的第四象限可以看成是java坐标系的第一象限,如下图:
以及我们平常所画的对象的坐标都是从该对象的左上角的坐标开始画的,所以我们在使用时要注意
JFrame f = new JFrame(“titleName”);
Game g = new Game();
f.add(g);
那儿对这几句话呢,我们可以看做是首先我们要实例化一个JFrame类,这个JFrame是java本身的一个类,可以为我们创建窗口,然后我们要实例化自己的Game类,之后我们要把Game类的方法和属性添加进窗口中实现。
f.setLocationRelativeTo(null);
设置窗口居中
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
设置窗口关闭
f.setAlwaysOnTop(true);
设置窗口始终在屏幕显示的最上层
f.setVisible(true);(这句话最重要)
这句话的意思是让定义的窗口可以在屏幕上显示,然后自动调用paint方法。
Font f = new Font(Font.SANS_SERIF,Font.BOLD,15);
这句话的是用来设置显示的字的具体表现的,有很多的用法,具体可以百度
g.setFont(f);
设置完之后需要把画笔设置成为我们所需要的类型
其实关于面向对象的关系在上面的程序中我们也能够感觉到面向对象的三大特征之间的关系了,但是我们既然是写的一个小例子就要有一定的过程,要承上启下,所以下面是游戏的执行过程,这过程不会太过细说,但我会把一些具体的地方写出来
那么下面就是程序的全部程序了
改写oop3:
Bee.class
public class Bee extends FlyingObject{
private int speed;//移动速度
public boolean left,right,up,down;//向各个方向移动时的开始条件
//构造方法
public Bee(){
//初始化蜜蜂的属性
image = Game.bee;
width = image.getWidth();
height = image.getHeight();
this.x = 150;
this.y = 150;
speed=2;
}
//蜜蜂的移动方法
@Override
public void step() {
// TODO 自动生成的方法存根
if(left){
x-=speed;
if(x<0){
x=Game.WIDTH;
}
}
if(right){
x+=speed;
if(x>Game.WIDTH){
x=0;
}
}
if(up){
y-=speed;
if(y<0){
y=Game.HEIGHT;
}
}
if(down){
y+=speed;
if(y>Game.HEIGHT){
y=0;
}
}
}
//判断蜜蜂是否和蝴蝶碰撞
public boolean hitBy(Butterfly f){
int x1 = f.x;
int y1 = f.y;
int x2 = x1+f.width;
int y2 = y1+f.height-20;
int x3 = x+width;
int y3 = y+height;
boolean b = ((x1>x&&x1<x3)||(x>x1&&x<x2)) && ((y>y1&&y<y2)||(y3>y1&&y1>y));
return b;
}
}
Water.class
public class Water extends FlyingObject implements Score{
private int xspeed;
private int yspeed;
public Water(){
image = Game.water;
width = image.getWidth();
height = image.getHeight();
this.x = (int)(Math.random()*300+50);
this.y = (int)(Math.random()*300+50);
xspeed = 1;
yspeed = 2;
}
@Override
public void step() {
// TODO 自动生成的方法存根
y+=yspeed;
x+=xspeed;
//x
if (x < 0) {
xspeed = 3;
}
if (x > Game.WIDTH - width) {
xspeed = -2;
}
//y
if (y < 0) {
yspeed = (int)(Math.random()*2+2);
}
if (y > Game.HEIGHT - height) {
yspeed = -(int)(Math.random()*2+1);
}
}
public boolean hitBy(Bee bee){
int x1 = x+width;
int y1 = y+height;
int x2 = bee.x;
int y2 = bee.y+height/2+15;
boolean b = (x2>x&&x2<x1)&&(y2>y&&y2<y1);
return b;
}
@Override
public int getScore(){
return 5;
}
}
Butterfly.class
public class Butterfly extends FlyingObject implements Score{
private BufferedImage[] images;
private int index;
public Butterfly(){
image = Game.but[0];
width = image.getWidth();
height = image.getHeight();
this.x = -width;
this.y = (int)(Math.random()*(Game.HEIGHT-90));
images = Game.but;
index = 0;
}
@Override
public void step() {
// TODO 自动生成的方法存根
}
public void fly(Graphics g){
if(x>Game.WIDTH){
this.x = -width;
this.y = (int)(Math.random()*(Game.HEIGHT-90));
Game.butter = new Butterfly();
}else{
image = images[index];
index++;
g.drawImage(image, x++, y, null);
if(index==2){
index=0;
}
}
}
@Override
public int getScore() {
// TODO 自动生成的方法存根
return 5;
}
}
改写oop4:
Game.class
public class Game extends JPanel{
public static final int WIDTH = 400;
public static final int HEIGHT = 400;
public static final int START = 0;
public static final int RUNNING = 1;
public static final int GAME_OVER = 2;
public static BufferedImage bg = GameUtil.getImg("images/bg.png");
public static BufferedImage bee = GameUtil.getImg("images/bee.png");
public static BufferedImage water = GameUtil.getImg("images/water.png");
public static BufferedImage start = GameUtil.getImg("images/start.png");
public static BufferedImage gameover = GameUtil.getImg("images/gameover.png");
public static BufferedImage[] but = {GameUtil.getImg("images/butterfly2.png")
,GameUtil.getImg("images/butterfly3.png")};
public static Bee flyBee = new Bee();
public static Water flyWater = new Water();
public static Butterfly butter = new Butterfly();
private int time = 0;
private int score = 0;
private int state = START;
public static void main(String[] args) {
JFrame f = new JFrame();
Game g = new Game();
f.add(g);
f.setSize(WIDTH,HEIGHT);
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setAlwaysOnTop(true);
f.setVisible(true);
g.action();
f.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// TODO 自动生成的方法存根
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
flyBee.left = true;
break;
case KeyEvent.VK_RIGHT:
flyBee.right = true;
break;
case KeyEvent.VK_UP:
flyBee.up = true;
break;
case KeyEvent.VK_DOWN:
flyBee.down = true;
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
// TODO 自动生成的方法存根
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
flyBee.left = false;
break;
case KeyEvent.VK_RIGHT:
flyBee.right = false;
break;
case KeyEvent.VK_UP:
flyBee.up = false;
break;
case KeyEvent.VK_DOWN:
flyBee.down = false;
break;
}
}
});
}
@Override
public void paint(Graphics g) {
// TODO 自动生成的方法存根
g.drawImage(bg, 0, 0, null);
paintBee(g);
paintWater(g);
paintString(g);
paintState(g);
paintButterfly(g);
}
private void paintBee(Graphics g) {
// TODO 自动生成的方法存根
g.drawImage(flyBee.image, flyBee.x, flyBee.y, null);
}
private void paintWater(Graphics g){
g.drawImage(flyWater.image, flyWater.x, flyWater.y, null);
}
private void paintState(Graphics g) {
// TODO 自动生成的方法存根
switch (state) {
case START:
g.drawImage(start, 0, 0, null);
break;
case GAME_OVER:
g.drawImage(gameover, 0, 0, null);
break;
}
}
private void paintButterfly(Graphics g) {
// TODO 自动生成的方法存根
if(state==RUNNING){
if(score%10==0&&score!=0){
butter.fly(g);
}else{
butter = new Butterfly();
}
}
}
private void paintString(Graphics g) {
// TODO 自动生成的方法存根
Font f = new Font(Font.SANS_SERIF,Font.BOLD,15);
g.setFont(f);
int x = 15;
int y = 20;
g.drawString("TIMER:"+time+" s", x, y);
y+=20;
g.drawString("SCORE:"+score, x, y);
}
public void action(){
MouseAdapter mouse = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// TODO 自动生成的方法存根
if(state==START){
state = RUNNING;
}
if(state==GAME_OVER){
flyBee = new Bee();
flyWater = new Water();
score = 0;
time = 0;
state = START;
}
}
};
this.addMouseListener(mouse);
Timer time = new Timer();
time.schedule(new TimerTask() {
@Override
public void run() {
// TODO 自动生成的方法存根
if(state==RUNNING){
stepAction();
eatAction();
hitAction();
gameOver();
}
repaint();
}
}, 10,10);
}
int timeNum = 0;
protected void stepAction() {
// TODO 自动生成的方法存根
timeNum++;
if(timeNum%100==0){
time++;
}
flyBee.step();
flyWater.step();
}
protected void eatAction() {
// TODO 自动生成的方法存根
if(flyWater.hitBy(flyBee)){
score+=flyWater.getScore();
flyWater = new Water();
}
}
protected void hitAction() {
// TODO 自动生成的方法存根
if(flyBee.hitBy(butter)){
score -=butter.getScore();
}
}
protected void gameOver(){
if(score>=50){
state = GAME_OVER;
}
}
}
这个就是全部的过程了,那么在我看来,在这里其实需要注意的就是几个方法:
我们从Game类说起:
f.addKeyListener(new KeyAdapter());那么在这里这就是一个匿名内部类,我们为窗口添加按键事件监听器,
public void keyPressed(KeyEvent e)这个方法主要监听的是按键按下的事件。
public void keyReleased(KeyEvent e)这个方法主要是监听键盘松开的事件。
那么我们就可以获得键盘事件来让蜜蜂执行移动的过程,我们可以看到按钮是否被按下对蜜蜂坐标的影响,当我们按下左键时,bee.left = true;而我们在Bee类中的step方法中判断left的值,当为真时就让x坐标以speed的速度移动,当我们松开时bee.left = false;蜜蜂的坐标停止变化,那么其他的方向也和这个一样。
在这里其实牵扯到一个问题,我们是不可以直接移动图片的,因为图片是画出来的,想要移动图片我们需要去重画图片的位置,把图片分成一帧一帧的效果才能实现移动的动态。(这段说的有点绕)那么我们就需要一个定时器去记时这个过程就是下面这句话的实现:
Timer time = new Timer();
time.schedule(new TimerTask() {
@Override
public void run() {
repaint();
}
},10,10);
在这里的就是每秒一百次的频率去重复执行run();方法内的内容,在run方法内系统自动调用repaint()方法来重复执行paint()方法。那么这样就可以实现图片的重画了,既然图片重画,我们就需要在定时器里添加运动的方法,stepAction();然后去调用这些方法,实现图片坐标的改变,然后重画图片。
然后我们说对象之间的动作。
对象之间的动作就是图片之间的碰撞。
这里我们需要所以下碰撞:如下图
这些就是碰撞的情况,我们可以根据自己的需要来进行判断
蜜蜂碰到花蜜,会吃掉花蜜,分数增加,产生新的花蜜对象。
蜜蜂碰到蝴蝶,分数减少,蝴蝶消失。
这都可以在程序中具体分析。
最后要说的两点就是蝴蝶的产生和状态的改变。
先说蝴蝶的产生。蝴蝶在分数是10的倍数时生成,而且我们需要判断蝴蝶越界时的动作,蝴蝶需要随机在面板左侧产生,当蝴蝶移动到面板最右侧时需要对新的蝴蝶对象进行赋值,我们需要一个新的蝴蝶对象和新的坐标,注意到这两点在去看程序应该好理解一些。
最后就是状态的改变,游戏要有开始,进行时和结束的状态,我们要对这些状态进行改变,最开始的状态我们定义为START,我们想要改变状态就需要点击鼠标进入程序,这里我加了一个鼠标监听器,在action方法中定义了一个内部类去判断状态的改变:如下、
MouseAdapter mouse = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// TODO 自动生成的方法存根
}
};
this.addMouseListener(mouse);
this所指代的对象就是我们的Game,我们在对象中添加这个监听效果,点击时就会开始执行方法体。
这个小例子到这里就结束了,由于我也是刚接触java没多久,大概一个月吧,所以有错误的地方或者不足的地方欢迎各位大神指正,而且可能第一次做例子写的不是太好,废话多,重点少,所以请谅解。