java游戏超级玛丽以及游戏改进(视频请看b站尚学堂)

大一结束,小学期写了一个超级玛丽的小游戏,于是我在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的分享

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值