大一结束,小学期写了一个超级玛丽的小游戏,于是我在b站上看了尚学堂的超级玛丽的视频,老师讲得很好,我也学到了很多,并在此基础上添加了一些我自己的改进,并将其放在了我的博客,同时我也想把我学到的东西记录下来,如果侵权,望告知
总述
该项目分为7个类:
1.MyFrame(窗口类)
2.Mario(马里奥类)
3.Enemy(敌人类)
4. Obstacle(障碍物类)
5.BackGround(背景类)
6.StaticValue(静态常量类)
7.Music(音乐类)
在我看来首先明确这几个类的作用是很有必要的,因为这会让我们在学习中有更明确的思路,以及在后序的创新中,更是不可缺少。
作用
MyFrame类:
1.进行图像的显示,将我们存放在StaticValue类中的图片绘制在窗口里
2.对键盘进行监控,实现玩家的操控
3.整个程序的运行
StaticValue类:
1.将图片进行存放
BackGround类:
1.关卡的设置
2.图片的设置
Obstacle类
1.实现障碍物的创建
2.当玛丽奥到达旗子处后,马里奥的运行
Enemy类
1.敌人的创建
2.敌人的运行模式
Mario类
1.马里奥的创建
2.马里奥的运行逻辑
3.马里奥的死亡
4.实现障碍物的阻挡
Music类
1.实现音乐
具体讲解
由于对于我来说直接贴代码对我起不到什么作用,于是我只将我觉得值得学习的代码贴出,具体的代码可以私信我,或者文章阅读量达到100(dog)
一.MyFrame类
我学习到的第一个知识点就是如何对窗口进行创建——继承JFrame类
在继承JFrame类后,对窗口的构造需要在构造器中执行
在构造器中除了实现最基础的窗口功能,如窗口的名称,大小,位置,可见性外,还有许多非常重要的功能,比如:
马里奥的初始化
mario = new Mario(10,355);
对所有关卡的创建
// 创建全部场景
for (int i = 1; i <= 4; i++) {
allBg.add(new BackGround(i, i));
}
将背景内容传给Mario类
mario.setBankGround(nowBg);
设置开始场景
// 将第一个场景设置为当前场景
nowBg = allBg.get(0);
该类的paint方法也十分重要,同时也是我的第一个创新点,那就是菜单栏的创建:
(由于学校要求写出游戏的菜单栏,但视频里面又没有这个玩意,没办法只有自己写了,纯纯的被逼出来的)
public void paint(Graphics g) {
if (offScreenImage == null) {
offScreenImage = createImage(800, 600);
}
Graphics graphics = offScreenImage.getGraphics();
if (state == 0) {
graphics.drawImage(StaticValue.start, 0, 0, this);
graphics.setColor(Color.yellow);
graphics.setFont(new Font("仿宋", Font.BOLD, 40));
graphics.drawString("点击1 开始游戏", 270, 400);
graphics.setColor(Color.yellow);
graphics.setFont(new Font("仿宋", Font.BOLD, 40));
graphics.drawString("点击2 结束游戏", 270, 300);
graphics.setColor(Color.yellow);
graphics.setFont(new Font("仿宋", Font.BOLD, 40));
graphics.drawString("点击3 游戏说明", 270, 200);
}
if (state == 1) {
graphics.fillRect(0, 0, 800, 600);
// 绘制背景
graphics.drawImage(nowBg.getBgImage(), 0, 0, this);
// 绘制敌人
for (Enemy e : nowBg.getEnemyList()) {
graphics.drawImage(e.getShow(), e.getX(), e.getY(), this);
}
// 绘制障碍物
for (Obstacle ob : nowBg.getObstacleList()) {
graphics.drawImage(ob.getShow(), ob.getX(), ob.getY(), this);
}
// 绘制城堡
graphics.drawImage(nowBg.getTower(), 660, 350, this);
// 绘制旗杆
graphics.drawImage(nowBg.getGan(), 500, 220, this);
// 绘制马里奥
graphics.drawImage(mario.getShow(), mario.getX(), mario.getY(), this);
// 添加分数
Color c = graphics.getColor();
graphics.setColor(Color.BLACK);
graphics.setFont(new Font("黑体", Font.BOLD, 25));
graphics.drawString("当前的分数为: " + mario.getScore(), 300, 100);
graphics.setColor(c);
}
if (state == 2) {
System.exit(0);
}
if (state == 3) {
graphics.drawImage(StaticValue.introduce, 0, 0, this);
graphics.setColor(Color.black);
graphics.setFont(new Font("仿宋", Font.BOLD, 20));
graphics.drawString("点击左键是向左移动,点击右键向右移动,点击上键向上跳", 30, 100);
graphics.drawString("点击B,开始游戏", 30, 300);
}
// 将图像绘制到窗口中
g.drawImage(offScreenImage, 0, 0, this);
}
以上的是我创新后的代码,我知道这又臭又长,所以我想简单的介绍一下:
其实很简单,就是设置一个变量,这个变量的值是由键盘来决定的,这里使用的是KeyListener接口,通过读取键盘上的值,来赋予变量不同的值,再由变量的值来决定paint函数该绘制哪个画面
以下是我键盘读取的代码:
@Override
public void keyTyped(KeyEvent e) {
}
// 当键盘按下按键时使用
@Override
public void keyPressed(KeyEvent e) {
// 向右移动
if (e.getKeyCode() == 39) {
mario.rightMove();
}
// 向左移动
if (e.getKeyCode() == 37) {
mario.leftMove();
}
// 跳跃
if (e.getKeyCode() == 38) {
mario.jump();
}
}
// 当键盘松开时按键时使用
@Override
public void keyReleased(KeyEvent e) {
// 向左停止
if (e.getKeyCode() == 37) {
mario.leftStop();
}
// 向右停止
if (e.getKeyCode() == 39) {
mario.rightStop();
}
if (e.getKeyCode() == KeyEvent.VK_1) {
state = 1;
}
if (e.getKeyCode() == KeyEvent.VK_2) {
state = 2;
}
if (e.getKeyCode() == KeyEvent.VK_3) {
state = 3;
}
if (e.getKeyCode() == KeyEvent.VK_B) {
state = 1;
}
}
接着,我想介绍的内容是当state=1,也就是游戏开始时,paint函数里的内容:
这里面就是每进入一个关卡时,将关卡里面的所有内容进行重新绘制,在绘制图片的方面,我使用的是双缓存,但由于我学到的也只是皮毛,所以不懂的可以自己搜一下,简单来说就是同时绘制两张图,让图片交替更加自然
最后,我想说明的是该类的一个线程,同时我也觉得在整个项目里,我学到的最多的就是线程,让我深深的领会了线程的妙处。
在整个项目中,线程的作用一句话总结就是持续性的判断,比如在MyFrame类中,线程就会持续性判断:是否需要关卡转换,马里奥是否死亡,游戏是否结束
二.StaticValue类
这个类很简单,就是将图片存放在变量里面方便调用,说实话没有什么难度就不细说了
三.Obstacle类
1.Obstacle类的构造器
public Obstacle(int x, int y, int type, BackGround bg){
this.x = x;
this.y = y;
this.type=type;
this.bg = bg;
show=StaticValue.obstacle.get(type);
// 如果是旗子的话,启动线程
if(type==8){
thread.start();
}
}
就是将障碍物的坐标,类型,图片定下来
2.Obstacle类的线程
用于判断马里奥是否到达旗子处
四. Enemy类
1.Enemy类的构造器
这里的原代码只有两个怪物,一个是蘑菇怪(后面我改成了螃蟹怪了),一个是食人花,但在后面我自己创造出了三个新怪物,其中的火焰怪和鸟怪是参考蘑菇怪,而激光怪是我个人写出来的。
public Enemy(int x, int y, boolean face_to, int type, BackGround bg) {
this.x = x;
this.y = y;
this.face_to = face_to;
this.type = type;
this.bg = bg;
show = StaticValue.mogu.get(0);
thread.start();
}
// 食人花敌人的构造函数
public Enemy(int x, int y, boolean face_to, int type, int max_up, int max_down, BackGround bg) {
this.x = x;
this.y = y;
this.face_to = face_to;
this.type = type;
this.max_down = max_down;
this.max_up = max_up;
this.bg = bg;
show = StaticValue.flower.get(0);
thread.start();
}
//光束的构造函数
public Enemy(int x, int y, boolean face_to, int type, BackGround bg,int time_to,boolean vanish) {
this.x = x;
this.y = y;
this.face_to = face_to;
this.type = type;
this.bg = bg;
this.time_to = time_to;
this.vanish = vanish;
show = StaticValue.light.get(0);
thread.start();
}
(蘑菇怪,火焰怪,鸟怪的构造器相同)
2.Enemy类的线程
2.1怪物的运动模式,因为怪物的运动是持续的所以线程也应该是一个死循环;
蘑菇:
if (type == 1) {
if (face_to) {
this.x -= 2;
} else {
this.x += 2;
}
image_type = image_type == 1 ? 0 : 1;
show = StaticValue.mogu.get(image_type);
}
食人花:
// 判断是否为食人花敌人
if (type == 2) {
if (face_to) {
this.y -= 2;
} else {
this.y += 2;
}
image_type = image_type == 1 ? 0 : 1;
// 判断食人花是否到达极限位置
if (face_to && (this.y == max_up)) {
face_to = false;
}
if ((!face_to) && (this.y == max_down)) {
face_to = true;
}
show = StaticValue.flower.get(image_type);
}
激光:
if(type == 5){
if(vanish){
this.time_to -= 20;
}else{
this.time_to += 20;
}
if(vanish && (this.time_to == 0)){
vanish = false;
image_type = 0;
}
if(!vanish && (this.time_to == 800)){
vanish = true;
image_type = 1;
}
show = StaticValue.light.get(image_type);
}
2.2怪物遇到障碍物时的反应
for (int i = 0; i < bg.getObstacleList().size(); i++) {
Obstacle ob1 = bg.getObstacleList().get(i);
if (type == 3) {
//判断是否可以向右走
if (ob1.getX() == this.x + 40 && (ob1.getY() + 20 > this.y && ob1.getY() - 4 < this.y)) {
canRight = false;
}
// 判断是否可以向左走
if (ob1.getX() == this.x - 20 && (ob1.getY() + 20 > this.y && ob1.getY() - 4 < this.y)) {
canLeft = false;
}
} else if (type == 4) {
//判断是否可以向右走
if (ob1.getX() == this.x + 30 && (ob1.getY() + 20 > this.y && ob1.getY() - 4 < this.y)) {
canRight = false;
}
// 判断是否可以向左走
if (ob1.getX() == this.x - 10 && (ob1.getY() + 20 > this.y && ob1.getY() - 4 < this.y)) {
canLeft = false;
}
} else {
//判断是否可以向右走
if (ob1.getX() == this.x + 36 && (ob1.getY() + 65 > this.y && ob1.getY() - 35 < this.y)) {
canRight = false;
}
// 判断是否可以向左走
if (ob1.getX() == this.x - 36 && (ob1.getY() + 65 > this.y && ob1.getY() - 35 < this.y)) {
canLeft = false;
}
}
}
个人认为,这一部分是最麻烦的,需要不停的调试
五.Mario类
在这个类中最重要的就是判断马里奥的运动状态,我也从中学到了一点,那就是用字符串来表达马里奥的状态
// 马里奥向左移动
public void leftMove() {
// 改变速度
xSpeed = -5;
// 判断马里奥是否碰到旗子
if (bankGround.isReach()) {
xSpeed = 0;
}
// 判断马里奥是否在空中
if (status.indexOf("jump") != -1) {
status = "jump--left";
} else {
status = "move--left";
}
}
// 马里奥向右移动
public void rightMove() {
xSpeed = 5;
// 判断马里奥是否碰到旗子
if (bankGround.isReach()) {
xSpeed = 0;
}
if (status.indexOf("jump") != -1) {
status = "jump--right";
} else {
status = "move--right";
}
}
个人认为,如果要对马里奥进行创新就可以从这里入手,如果有时间可以试一下
接着是需要在线程中持续判断的部分:
马里奥是否处于障碍物上
是否可以往右走
是否达到旗杆位置
是否顶到砖块
是否碰到敌人死亡,或杀死敌人
以及马里奥运动时的图片
六.BackGround类
background是整个游戏的关卡设计,原本的马里奥总共有三关,我再最后一关前面多写了一关
第一关:
// 判断是否是第一关
if(sort==1) {
// 绘制第一关的地面, 上地面type=1,下地面type=2
for (int i = 0; i < 27; i++) {
obstacleList.add(new Obstacle(i * 30, 420, 1, this));
}
for (int j = 0; j <= 120; j += 30) {
for (int i = 0; i < 27; i++) {
obstacleList.add(new Obstacle(i * 30, 570 - j, 2, this));
}
}
// 绘制砖块A
for (int i = 120; i <= 150; i += 30) {
obstacleList.add(new Obstacle(i, 300, 7, this));
}
// 绘制砖块B-F
for (int i = 300; i <= 570; i += 30) {
if (i == 360 || i == 390 || i == 480 || i == 510 || i == 540) {
obstacleList.add(new Obstacle(i, 300, 7, this));
} else {
obstacleList.add(new Obstacle(i, 300, 0, this));
}
}
// 绘制砖块G
for (int i = 420; i <= 450; i += 30) {
obstacleList.add(new Obstacle(i, 240, 7, this));
}
// 绘制水管
for (int i = 360; i <= 600; i += 25) {
if (i == 360) {
obstacleList.add(new Obstacle(620, i, 3, this));
obstacleList.add(new Obstacle(645, i, 4, this));
} else {
obstacleList.add(new Obstacle(620, i, 5, this));
obstacleList.add(new Obstacle(645, i, 6, this));
}
}
// 绘制第一关的蘑菇敌人
enemyList.add(new Enemy(580,385,true,1,this));
// 绘制第一关的食人花敌人
enemyList.add(new Enemy(635,420,true,2,328,428,this));
//绘制第一关蘑菇敌人
enemyList.add(new Enemy(670,385,true,1,this));
enemyList.add(new Enemy(635,20,true,5,this,800,true));
}
第二关:
// 判断是否为第二关
if(sort==2){
// 绘制第二关的地面,上地面type=1,下地面type=2
for(int i=0;i<27;i++){
obstacleList.add(new Obstacle(i*30,420,1,this));
}
for(int j=0;j<=120;j+=30){
for(int i=0;i<27;i++){
obstacleList.add(new Obstacle(i*30,570-j,2,this));
}
}
// 绘制第一个水管
for (int i = 360; i <= 600; i += 25) {
if (i == 360) {
obstacleList.add(new Obstacle(60, i, 3, this));
obstacleList.add(new Obstacle(85, i, 4, this));
} else {
obstacleList.add(new Obstacle(60, i, 5, this));
obstacleList.add(new Obstacle(85, i, 6, this));
}
}
// 绘制第二个水管
for (int i = 330; i <= 600; i += 25) {
if (i == 330) {
obstacleList.add(new Obstacle(620, i, 3, this));
obstacleList.add(new Obstacle(645, i, 4, this));
} else {
obstacleList.add(new Obstacle(620, i, 5, this));
obstacleList.add(new Obstacle(645, i, 6, this));
}
}
// 绘制砖块C
obstacleList.add(new Obstacle(300,330,0,this));
// 绘制砖块B、E、G
for (int i =270;i<=330;i+=30){
if(i==270||i==330){
obstacleList.add(new Obstacle(i,360,0,this));
}else {
obstacleList.add(new Obstacle(i,360,7,this));
}
}
// 绘制砖块A、D、F、H、I
for (int i =240;i<=360;i+=30){
if (i ==240||i==360){
obstacleList.add(new Obstacle(i,390,0,this));
}else {
obstacleList.add(new Obstacle(i,390,7,this));
}
}
// 绘制妨碍1砖块
obstacleList.add(new Obstacle(240,300,0,this));
// 绘制空1-4砖块
for (int i =360;i<=540;i+=60){
obstacleList.add(new Obstacle(i,270,7,this));
}
//绘制第二关阻挡方块
obstacleList.add(new Obstacle(300,240,7,this));
obstacleList.add(new Obstacle(600,240,7,this));
// 绘制第二关的第一个食人花敌人
enemyList.add(new Enemy(75,420,true,2,328,418,this));
// 绘制第二关的第二更食人花敌人
enemyList.add(new Enemy(635,420,true,2,298,388,this));
// 绘制第二关的第一个蘑菇敌人
enemyList.add(new Enemy(200,385,true,1,this));
// 绘制第二更的第二个蘑菇敌人
enemyList.add(new Enemy(500,385,true,1,this));
//绘制第二关的第一个鸟敌人
enemyList.add(new Enemy(500,240,true,3,this));
}
第三关:
//是否为第三关
if(sort == 3){
// 绘制第三关的地面,上地面type=1,下地面type=2
for(int i=0;i<3;i++){
obstacleList.add(new Obstacle(i*30,420,1,this));
}
for(int i=24;i<27;i++){
obstacleList.add(new Obstacle(i*30,420,1,this));
}
for(int j=0;j<=120;j+=30){
for(int i=0;i<3;i++){
obstacleList.add(new Obstacle(i*30,570-j,2,this));
}
for(int i=24;i<27;i++){
obstacleList.add(new Obstacle(i*30,570-j,2,this));
}
}
//左边第一个方块
obstacleList.add(new Obstacle(120,380,7,this));
obstacleList.add(new Obstacle(150,380,7,this));
//左上第一排四个
for(int i = 210;i <= 300;i += 30){
obstacleList.add(new Obstacle(i,350,7,this));
}
//中间三个
obstacleList.add(new Obstacle(360,350,7,this));
obstacleList.add(new Obstacle(420,440,7,this));
//右上二
obstacleList.add(new Obstacle(480,320,7,this));
//右上8
for(int i = 480;i < 630;i+=30){
obstacleList.add(new Obstacle(i,350,7,this));
}
//右下四
for(int i = 480; i < 570; i += 30){
obstacleList.add(new Obstacle(i,440,7,this));
}
//右二
obstacleList.add(new Obstacle(600,500,7,this));
obstacleList.add(new Obstacle(660,440,7,this));
//阻挡
obstacleList.add(new Obstacle(580,380,7,this));
obstacleList.add(new Obstacle(240,380,7,this));
//huo
enemyList.add(new Enemy(550,380,true,4,this));
//激光
enemyList.add(new Enemy(180,20,true,5,this,0,false));
enemyList.add(new Enemy(330,20,true,5,this,800,true));
enemyList.add(new Enemy(450,20,true,5,this,0,false));
}
第四关:
// 是否为第四关
if(sort==4){
// 绘制第四关的地面,上地面type=1,下地面type=2
for(int i=0;i<27;i++){
obstacleList.add(new Obstacle(i*30,420,1,this));
}
for(int j=0;j<=120;j+=30){
for(int i=0;i<27;i++){
obstacleList.add(new Obstacle(i*30,570-j,2,this));
}
}
// 绘制第四个背景的A-O砖块
int temp = 290;
for (int i =390;i>=270;i-=30){
for (int j =temp;j<=410;j+=30){
obstacleList.add(new Obstacle(j,i,7,this));
}
temp+=30;
}
// 绘制第三个背景的P-R砖块
temp=60;
for (int i =390;i>=360;i-=30){
for (int j=temp;j<=90;j+=30){
obstacleList.add(new Obstacle(j,i,7,this));
}
temp+=30;
}
// 绘制旗杆
gan = StaticValue.gan;
// 绘制城堡
tower=StaticValue.tower;
// 添加旗子到旗杆上
obstacleList.add(new Obstacle(515,220,8,this));
// 绘制第三关的蘑菇敌人
enemyList.add(new Enemy(150,385,true,1,this));
//第四关的激光
enemyList.add(new Enemy(150,20,true,5,this,0,false));
enemyList.add(new Enemy(240,20,true,5,this,0,false));
}
间隔一年的链接,说实话我都没想到会有这么多的浏览量,现在就把链接放出来,拿资源的顺便点个赞吧:
链接:https://pan.baidu.com/s/1UwxyIXBDIskh16gX1ERdsg?pwd=p6vy
提取码:p6vy
--来自百度网盘超级会员V5的分享