争取今天晚上能搞一半啊,啊啊啊啊,感觉事多的忙不过来
设计思路:
1)创建并完成常量类
------->一张图片的情况
先完成对图片的封装------>把图片加载一遍
(老实说,我也不太知道为什么,反正我觉得图片只是图片,但是从来没有变成你程序中的一个变量或者常量什么的,所以要先把图片变成常量,之后方便使用)
而我们怎么能把图片变成一个数据呢?----->ImageIO流,参考下面的IO流
所以我们定义与图片数量相同的数据个数,并把他们用ImageIO加载到里边
-------->多张图片的情况
用一个集合把有一定关系的多张图片放到一起
public static List<BufferedImage> run_R = new ArrayList<>();
2)创建背景类
背景有几个要点:
首先第一肯定是现在的背景是什么,所以以后需要用一张图片记录当前的关卡背景
//当前场景要显示的图片
private BufferedImage bgImage =null;
然后还要时刻关注自己现在玩到第几关了,如下:
//记录当前是第几个场景 玩到了第几关
private int sort;
由于如果到了最后一个场景,我们需要换一个背景图片,所以我们需要用一个变量判断现在是不是最后一个场景
//判断是否是最后一个场景
private boolean flag;
3)绘制背景(难点)
常量---->缓冲区----->屏幕上
先把常量图片加载到缓冲区之后,之后再一次性画到屏幕上,这样有效避免当图片直接画到屏幕上时易出现斑驳的情况
(因为第一次加载肯定还是要一会的,如果画面卡卡的,这样容易给用户带来不好的游戏体验)
//用于存储 所有的背景
private List<BackGround> allBg =new ArrayList<>();
//用于存储 当前的背景
private BackGround nowBg =new BackGround();
//用于双缓存
//定义一个变量用于双缓存 TODO????????
private Image offScreenImage =null;
@Override
public void paint(Graphics g) {
if(offScreenImage ==null){
offScreenImage =createImage(800,600);
}
Graphics graphics = offScreenImage.getGraphics();
graphics.fillRect(0,0,800,600);
//fillRect 用于在指定的矩形内绘制一个填充的矩形
//绘制背景 将图像绘制到了缓冲区上
graphics.drawImage(nowBg.getBgImage(), 0,0,this);
//将缓冲区的图片绘制到窗口中
g.drawImage(offScreenImage,0,0,this);
}
支撑知识点:
1.访问限制修饰符
1)修饰成员变量和成员方法的
修饰符 | 任何地方 | 子类(可以不在一个包下面) | 同一个包下 | 同一个类下 |
---|---|---|---|---|
public | yes | yes | yes | yes |
protected | yes | yes | yes | |
default(缺省) | yes | yes | ||
private | yes |
记忆:
protected保护自己的子女,底线是保护自己的子女(子类) ,保护自己的家(包),保护自己(同一个类)
private私人化,只有自己能拥有
default介于两者之间
2)修饰外部类的
记住只有这两个!!面试题可能会有!
1.public
2.缺省
可以参考这篇文章java的四个访问修饰符_Java中的四种访问修饰符-CSDN博客
2.static关键字
1)Static的定义:
static关键字表示这是类拥有的变量,也称静态变量
我们一般都说成员变量,那都是指没有被static修饰的变量,所以这是必须得创建出对象出来之后才能使用,你的对象,我的对象都是不能互相看见各自的变量的。
但是类变量就相当于大家都有,而且就算你不创建对象,直接调用类也能使用,所以这种类一般被称作为工具类啊,这样更方便我们使用,如果是成员类的话,各自的变量被别人随意修改了这可不行,当然具有static修饰的变量的类也不一定全是工具类啊,这个以后也可以讲。
所以,你看,这也更加反映了我们java面向对象编程,因为我们只能把代码写在类里边,要是写在外边是会报错的,进而衍生出来了工具类啊,成员类等等。
2)Static修饰的变量和方法的特点:
静态变量不属于对象,而是类
并且静态变量是随着类的加载而加载的
3.BufferedImage
BufferedImage
BufferedImage是其Image抽象类的实现类,是一个带缓冲区图像类,主要作用是将一幅图片加载到内存中
(BufferedImage生成的图片在内存里有一个图像缓冲区,利用这个缓冲区我们可以很方便地操作这个图片),提供获得绘图对象、图像缩放、选择图像平滑度等功能,通常用来做图片大小变换、图片变灰、设置透明不透明等。
至于为什么要用缓存图像类,可以看下边的为什么要先缓存图片
5.相对路径与绝对路径
相对路径和绝对路径就相当于
在一栋楼里边,小A问你小B在哪?你告诉他小A在XX层,这个就是绝对路径,但是如果你告诉他他在我们下边多少层,或者在我们上边多少层,这个就叫相对路径
相对路径中我们一般用:
../”来表示上一级目录,“../../”表示上上级的目录
--->想更加详细了解的可以看这一篇:绝对路径和相对路径详解_相对路径和绝对路径-CSDN博客
6.List和ArrayList
图示:
说明:
1、List是一个接口,而ArrayList是List接口的一个实现类。
2、ArrayList类继承并实现了List接口。
3、因此,List接口不能被构造,也就是我们说的不能创建实例对象,接口代表一种能力,定义了一种规则规范,具体怎么实现,还得使用接口下的实现类
我们可以像下面那样为List接口创建一个指向自己的对象引用,而ArrayList实现类的实例对象就在这充当了这个指向List接口的对象引用。
在java中List list=new List(); 会导致编译出错,这是因为List是一个接口,接口不能被实例化。
而ArrayList list=new ArrayList();这种实例化方式就是正确的。这是因为ArrayList是一个类,继承并实现了List接口。
如何实例化List接口?
虽然List不能直接被实例化,但是他可以通过继承自本接口的实现类的对象实例化List对象,如List list=new ArrayList();这种实例化的方式:创建了一个ArrayList实现类的对象后把它上溯到了List接口。此时它就是一个List对象了,它有些ArrayList类具有的,但是List接口没有的属性和方法,它就不能再用了。 而 ArrayList list=newArrayList();创建一对象则保留了ArrayList的所有属性和方法。
两种实例化方式的区别?
为什么不直接使用 ArrayList list=newArrayList();的方式来实例化ArrayList对象,并调用其中的方法,而有时候大部分会通过List list=new ArrayList();来实例化对象的原因?
1) 两种实例化的方式有所区别,第一种方式创建的对象继承了ArrayList的所有属性和方法。 而第二种方式创建的对象只能调用List接口中的方法,不能调用ArrayList中的方法。
2) 使用第二种方式创建的对象更为方便。List接口有多个实现类,现在用的是ArrayList,如果要将其更换成其它的实现类,如 LinkedList或者Vector等等,这时只需要改变这一行就行了: List list = new LinkedList(); 其它使用了list地方的代码根本不需要改动。 在编写代码时,较为方便。
使用ArrayList的函数:
7.io流
1)使用到io流的地方的代码:
将图片加载到静态值中
public static void init(){
try {
加载背景图片 imageio流
//调用read方法,拼接路径
bg= ImageIO.read(new File(path+"bg.png"));
bg2= ImageIO.read(new File(path+"bg2.png"));
//加载马里奥向左站立
stand_L= ImageIO.read(new File(path+"s_mario_stand_L.png"));
//加载马里奥向右站立
stand_R= ImageIO.read(new File(path+"s_mario_stand_R.png"));
//加载城堡
tower= ImageIO.read(new File(path+"tower.png"));
//加载旗杆
gan= ImageIO.read(new File(path+"gan.png"));
//加载马里奥向左跳跃
jump_L= ImageIO.read(new File(path+"s_mario_jump1_L.png"));
//加载马里奥向右跳跃
jump_R= ImageIO.read(new File(path+"s_mario_jump1_R.png"));
} catch (IOException e) {
e.printStackTrace();
}
2)简单了解可参见这个视频:io流学习
3)总结
1.概念
java将文件和目录抽象为File类,所以File类既可以表示文件,也可以表示目录
当你传一个文件路径时,它就代表一个文件;
当你传一个目录路径时,它就代表一个目录
2.ImageIO 实现图片加载技术
ImageIO.read()是核心方法,帮助我们读取图片信息,并返回Image对象
参数是 File对象
返回值是BufferedImage对象
public static BufferedImage read(File input)
8.异常处理
详细学习可参考这一篇文章:一次搞懂Java异常处理(超详细!!!)!-CSDN博客
1)使用到异常处理的地方:
//加载马里奥向右跑
for (int i = 1; i <=2; i++) {
try {
run_R.add(ImageIO.read(new File(path+"s_mario_run"+i+"_R.png")));
} catch (IOException e) {
e.printStackTrace();
}
}
2)异常处理简单来说
idea程序觉得你这个代码有些潜在的风险可能会导致这个程序出bug
所以它想提示你这样写是会存在问题的,所以不处理这个异常的话,等真正出现错误了,用户那边的反馈可能是直接死机,但是我们为了让死机的情况减少发生,我们就会让程序自己捕获这个异常,这样用户得到的反馈就只是错了,而不会发生崩掉的情况
所以学习异常不是让我们以后不出异常,而是程序出了异常之后,该如何处理。体现的是程序的安全,健壮性。
3)常见异常:(有时候面试会问!!)
9.JavaBean
javaBean体现的是数据的封装性强,完整性强,规范性强
1)javaBean的特点:
- 这个Java类必须具有一个无参的构造函数
- 属性必须私有化。
- 私有化的属性必须通过public类型的方法暴露给其它程序,并且方法的命名也必须遵守一定的命名规范。
2)使用到JavaBean的地方:
public class BackGround {
//当前场景要显示的图片
private BufferedImage bgImage =null;
//记录当前是第几个场景 玩到了第几关
private int sort;
//判断是否是最后一个场景
private boolean flag;
//空参构造
public BackGround() {
}
public BufferedImage getBgImage() {
return bgImage;
}
public int getSort() {
return sort;
}
}
分析:bgImage,sort,flag属性都是私有化的,不允许别人轻易访问
getBgImage方法可以有效地将属性暴露给其他程序
BackGround()无参构造
10.泛型
具体学习可参考:Java 中的泛型(两万字超全详解)_java 泛型-CSDN博客
1)泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
2)我的理解:
泛型就是由于程序的扩展性和复用性要足够强大,所以要使用泛型能够接受所有不同类型的数据,由于不知道这个类,这个接口,以后处理哪一类数据的问题,所以需要泛型这个变色龙,帮我们接受日后所有类型的数据<-------写方法和函数时用到泛型
帮我们更快的了解这个集合或者什么函数接受的是哪一类数据类型<-----使用泛型时
3)使用泛型的例子:
如果没有使用泛型时,我们向全部是String类型的集合里边加入了integer类型的变量,如下图,运行时就会报一个ClassCastException异常
@ Test
public void test() {
ArrayList list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add(111);
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
}
那如何可以避免上述异常的出现?即我们希望当我们向集合中添加了不符合类型要求的对象时,编译器能直接给我们报错,而不是在程序运行后才产生异常。这个时候便可以使用泛型
了。
(所以java的很多强大之处就在于可以在写程序时就可以帮我们去规避在真正现实中一些其他用户不规范使用而导致的程序崩溃的情况)
@ Test
public void test() {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add(111);// 在编译阶段,编译器会报错
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
}
- < String > 是一个泛型,其限制了 ArrayList 集合中存放对象的数据类型只能是 String,当添加一个非 String 对象时,编译器会直接报错。这样,我们便解决了上面产生的 ClassCastException 异常的问题(这样体现了泛型的类型安全检测机制)。
11.三目运算符
1)格式 ___?___:___
2)含义
问号前面:写一个表达式,那个表达式成立吗?
冒号前面:如果表达式成立,那么我就执行冒号前面的式子
冒号后面:如果不成立,就执行后面的式子
3)使用到三目运算符的地方:
//创建全部的场景
for(int i=1;i<=3;i++){
allBg.add(new BackGround(i, i==3? true:false));
}
12.for循环
学习可参考:深入理解循环控制语句 - for_for循环-CSDN博客
1)主要内容
for循环在Java中用于控制代码块重复执行固定次数。它由4个部分组成:
①初始化语句,循环开始前执行一次,通常用于设置循环计数器的初始值
②条件判断语句,循环开始后,每次循环前执行,如果结果为true,则执行循环体;否则,循环结束。
③条件控制语句,循环开始后,每次循环后执行,通常用于改变循环计数器的值
④循环体语句,循环开始后,每次循环执行
for (初始化语句; 条件判断; 条件控制语句) {
// 循环体,需要重复执行的代码
}
13.Graphics的一些方法
1.如何创建对象:
要使用Graphics类,我们必须先需要获取一个Graphics对象。
我们可以通过调用组件的getGraphics()方法来获取该对象,例如JPanel、JFrame、BufferedImage。然后,我们可以使用Graphics对象的各种方法来绘制您想要的图形
2.Graphics的一些方法
1)drawImage 表示在指定位置绘制指定的图像
2)fillRect
具体学习可以看这一篇博客:Java图像编程之:Graphics_java graphics-CSDN博客
14.JFrame重写的paint()方法
1. 为什么要重写paint方法?
窗体有一个系统自带的刷新方法,当我们使用JFrame创建出窗体时,遇到窗体的尺寸改变或者窗体的部分像素被移动到屏幕之外,都会导致窗体的刷新。但这时如果窗体中有此前绘制出的图像,则图像会随窗体的刷新而消失,这时候我们就需要将绘制图像的方法重写入JFrame的paint方法中,让图像随窗体的刷新而同步再次被绘制出来。
2.paint方法要在JFrame类继承或者JPanel两个属性下才能实现,并且是系统自动调用的。
细节注意:
1.双缓存的作用:
在这些最基本的东西设置完以后,还需要一个方法来解决游戏中经常会出现的闪屏问题。
这个方法 就是双缓存方法,现在类中定义一个 BufferedImage 的图片,然后从该图片中获取到图片的 Graphics graphics,然后利用画笔 graphics 将所要绘制的东西绘制到这个空的图片中,然后在利用窗体类中的 paint 方法中的画笔 graphics 将这个已经绘制好的图片绘制到窗体类中,
这样利用空白图片作为程序运 行中的中转,就可以很好的解决游戏运行过程中出现的闪屏问题。
当图像信息量较大,采用以上直接显示的方法,可能前面一部分显示后,显示后面一部分时,由于后面一部分还未从文件读出,使显示呈斑驳现象。为了提高显示效果,许多应用程序都采用图像缓冲技术,即先把图像完整装入内存,在缓冲区中绘制图像或图形,然后将缓冲区中绘制好的图像或图形一次性输出在屏幕上。
2.先缓存图片的原因:
当游戏运行时可以直接调用这些集合中的图片进行遍历,在调用的时候更加方便,而且可以使马里奥或者敌人在移动的时候产生动态效果。
首先在类中应当几个定义 BufferedImage 类型,属性名字为 jump_L,jump_R,stand_L,stand_R, 列表 run_L 和 run_R,这些属性的作用在于存放所有的马里奥图片,里面包括了马里奥的移动图片, 站立图片以及马里奥跳跃的图片。这样在程序运行的时候就可以从该类中的这个属性里面将所需要的马 里奥图片直接调用出来,并且还可以在马里奥移动时不断遍历里面的图片,这样就可以使马里奥产生移 动的动态效果。
接下来要在该类中定义开始图片,结束图片以及背景图片,默认的初始值都为 null。
注意这些所有的属性都是静态的,包括下面要提到的所有的属性,这样做的目的是为了在程序运行时先加载这些图片。然后应当定义存放食人花的 List 集合 flower,这个集合将食人花的不同形态,张嘴、闭嘴 图片存放进去,这样在运行的时候进行遍历就可以打到动态效果。同理存放蘑菇怪的集合 mogu,以及 存放所有障碍物的集合 obstacle。
代码展示:
1)目前的MyFrame类的代码:
package com.View;
import com.Constants.StaticValue;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
public class MyFrame extends JFrame implements KeyListener {
//为什么要存储场景 TODO惹??
//BackGround类的list,里边全部是background类,不是buffered图片,要区别开来
//用于存储 所有的背景
private List<BackGround> allBg =new ArrayList<>();
//用于存储 当前的背景
private BackGround nowBg =new BackGround();
//用于双缓存
//定义一个变量用于双缓存 TODO????????
private Image offScreenImage =null;
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
//无参构造
public MyFrame() {
//设置窗口的大小为800*600
this.setSize(800,600);
//设置窗口的居中显示
this.setLocationRelativeTo(null);
//设置窗口的可见性
this.setVisible(true);
//设置点击窗口上的关闭键,结束程序
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//设置窗口大小不可变
this.setResizable(false);
//向窗口对象添加键盘监听器
this.addKeyListener(this);
//设置窗口名称
this.setTitle("超级玛丽");
//初始化图片
StaticValue.init();
//创建全部的场景
for(int i=1;i<=3;i++){
allBg.add(new BackGround(i, i==3? true:false));
}
//将第一个场景设置为当前的场景
nowBg =allBg.get(0);
//绘制图像 进行图像的绘制
repaint();
}
@Override
public void paint(Graphics g) {
if(offScreenImage ==null){
offScreenImage =createImage(800,600);
}
Graphics graphics = offScreenImage.getGraphics();
graphics.fillRect(0,0,800,600);
//fillRect 用于在指定的矩形内绘制一个填充的矩形
//绘制背景 将图像绘制到了缓冲区上
graphics.drawImage(nowBg.getBgImage(), 0,0,this);
//将缓冲区的图片绘制到窗口中
g.drawImage(offScreenImage,0,0,this);
}
public static void main(String[] args) {
new MyFrame();
}
}
2)BackGround类代码:
package com.View;
import com.Constants.StaticValue;
import java.awt.image.BufferedImage;
//加载图片常量之后方便日后的使用
public class BackGround {
//当前场景要显示的图片
private BufferedImage bgImage =null;
//记录当前是第几个场景 玩到了第几关
private int sort;
//判断是否是最后一个场景
private boolean flag;
//空参构造
public BackGround() {
}
public BufferedImage getBgImage() {
return bgImage;
}
public int getSort() {
return sort;
}
public boolean isFlag() {
return flag;
}
public BackGround(int sort, boolean flag) {
this.sort = sort;
this.flag = flag;
if(flag) { //表示此时为最后一个场景
bgImage = StaticValue.bg2;
}else{
bgImage = StaticValue.bg;
}
}
}
3)StaticValue类:
这里代码都是类似的,所以可以直接复制粘贴自己已经写过的,但是注意在复制粘贴的时候,路径要写对,以及图片究竟是加载几张,在for循环里边i到底从几开始,从几结束要注意一下,要改过来
package com.Constants;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class StaticValue {
//单张图片
//背景1 和 背景2
public static BufferedImage bg=null;
public static BufferedImage bg2=null;
//马里奥向左跳跃
public static BufferedImage jump_L=null;
//马里奥向右跳跃
public static BufferedImage jump_R=null;
//马里奥向左站立
public static BufferedImage stand_L=null;
//马里奥向右站立
public static BufferedImage stand_R=null;
//城堡
public static BufferedImage tower=null;
//旗杆
public static BufferedImage gan=null;
//一组图片
//障碍物 地面,砖块,水管等
public static List<BufferedImage> obstacle = new ArrayList<>();
//马里奥向右跑 两张图片
public static List<BufferedImage> run_R = new ArrayList<>();
//马里奥向左跑
public static List<BufferedImage> run_L = new ArrayList<>();
//蘑菇敌人 三张图片 两张走路图像,一张死亡图像
public static List<BufferedImage> mogu = new ArrayList<>();
//食人花敌人 两张图象 一张张嘴,一张闭嘴
public static List<BufferedImage> flower = new ArrayList<>();
//路径的前缀 ,方便后续调用 使用绝对路径获取照片的路径
public static String path =System.getProperty("user.dir")+"/src/com/img/";
//初始化方法
public static void init(){
try {
加载背景图片 imageio流
//调用read方法,拼接路径
bg= ImageIO.read(new File(path+"bg.png"));
bg2= ImageIO.read(new File(path+"bg2.png"));
//加载马里奥向左站立
stand_L= ImageIO.read(new File(path+"s_mario_stand_L.png"));
//加载马里奥向右站立
stand_R= ImageIO.read(new File(path+"s_mario_stand_R.png"));
//加载城堡
tower= ImageIO.read(new File(path+"tower.png"));
//加载旗杆
gan= ImageIO.read(new File(path+"gan.png"));
//加载马里奥向左跳跃
jump_L= ImageIO.read(new File(path+"s_mario_jump1_L.png"));
//加载马里奥向右跳跃
jump_R= ImageIO.read(new File(path+"s_mario_jump1_R.png"));
} catch (IOException e) {
e.printStackTrace();
}
//加载马里奥向左跑 因为有2张图片,所以要用for循环来加载
for (int i = 1; i <=2; i++) {
try {
run_L.add(ImageIO.read(new File(path+"s_mario_run"+i+"_L.png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载马里奥向右跑
for (int i = 1; i <=2; i++) {
try {
run_R.add(ImageIO.read(new File(path+"s_mario_run"+i+"_R.png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载障碍物的图片
try {
obstacle.add(ImageIO.read(new File(path+"brick.png"))); //可破坏砖块
obstacle.add(ImageIO.read(new File(path+"soil_up.png"))); //上地面
obstacle.add(ImageIO.read(new File(path+"soil_base.png"))); //下地面
} catch (IOException e) {
e.printStackTrace();
}
// 加载水管
for (int i = 1; i <=4; i++) {
try {
obstacle.add(ImageIO.read(new File(path+"pipe"+i+".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载不可被破坏的砖块和旗子
try {
obstacle.add(ImageIO.read(new File(path+"brick2.png"))); //不可破坏砖块
obstacle.add(ImageIO.read(new File(path+"flag.png")));
} catch (IOException e) {
throw new RuntimeException(e);
}
//加载蘑菇敌人
for (int i = 1; i <=3; i++) {
try {
mogu.add(ImageIO.read(new File(path+"fungus"+i+".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载食人花图片 注意由于图片名称是从1开始,所以i也是从1开始
for (int i = 1; i <=2; i++) {
try {
flower.add(ImageIO.read(new File(path+"flower1."+i+".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}