Java 小游戏《超级马里奥》

文章目录

  • 一、效果展示
  • 二、代码编写
  • 1. 素材准备
  • 2. 创建窗口类
  • 3. 创建常量类
  • 4. 创建动作类
  • 5. 创建关卡类
  • 6. 创建障碍物类
  • 7. 创建马里奥类
  • 8. 编写程序入口
  • 一、效果展示

    二、代码编写

    1. 素材准备

    首先创建一个基本的 java 项目,并将本游戏需要用到的图片素材 image 导入。

    图片素材如下:
    https://pan.baidu.com/s/1db_IcPvPKWKbVPtodPWO5Q?pwd=03kv
    提取码:03kv

    2. 创建窗口类

    ① Java 内部已经给我们封装了窗口的各种方法,我们只需创建一个窗口类并重写父类的方法,即可使用;
    ② Alt + Enter 键 → implement methods 可一键补全所有的重写方法;
    ③ 实现多线程有两种方法,分别是继承 Thread 类和实现 Runnable 接口,这里我们用 Runnable 方法,因为 Java 不支持多继承。

    重写 paint 方法,实现场景、物体的绘制,使用多线程无限绘制窗口。

    完整代码如下:

    package com.zxe.beans;
    
    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, Runnable {
    
        //定义一个集合用于所有的关卡
        private List<LevelMap> levelMaps = new ArrayList<>();
        //定义一个变量,存放当前背景
        private LevelMap levelMap = new LevelMap();
        //定义变量,记录马里奥
        private Mario mario;
    
        //重写paint方法,实现场景、物体的绘制
        @Override
        public void paint(Graphics g) {
            //创建一张图片
            Image image = createImage(1045, 500);
            //设置图片
            Graphics graphics = image.getGraphics();
            graphics.drawImage(levelMap.getBg(), 0, 0, 1045, 500, this);
            //绘制障碍物
            for (Obstacle obstacle : levelMap.getObstacles()){
                graphics.drawImage(obstacle.getObstacleImage(), obstacle.getX(), obstacle.getY(), this);
            }
            //绘制马里奥
            graphics.drawImage(mario.getMarioImage(), mario.getX(), mario.getY(), this);
    
            //将图片描绘到当前窗口中
            g.drawImage(image, 0, 0, this);
        }
    
        @Override
        public void keyTyped(KeyEvent e) {
    
        }
    
        @Override
        public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == 37) {
                mario.runLeft();
            } else if (e.getKeyCode() == 39) {
                mario.runRight();
            } else if (e.getKeyCode() == 38) {
                mario.jump();
            }
        }
    
        public Mario getMario() {
            return mario;
        }
    
        public void setMario(Mario mario) {
            this.mario = mario;
        }
    
        @Override
        public void keyReleased(KeyEvent e) {
            if (e.getKeyCode() == 37) {
                mario.runLeftStop();
            } else if (e.getKeyCode() == 39) {
                mario.runRightStop();
            } else if (e.getKeyCode() == 38) {
                mario.jumpDown();
            }
        }
    
        @Override
        public void run() {
            //无限次绘制马里奥
            while (true) {
                repaint();
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //判断一下马里奥是否通关
                if (mario.getX() > 1040) {
                    levelMap = levelMaps.get(levelMap.getLevel());
                    mario.setLevelMap(levelMap);
                    mario.setX(50);
                    mario.setY(420);
                }
            }
        }
    
        public List<LevelMap> getLevelMaps() {
            return levelMaps;
        }
    
        public void setLevelMaps(List<LevelMap> levelMaps) {
            this.levelMaps = levelMaps;
        }
    
        public LevelMap getLevelMap() {
            return levelMap;
        }
    
        public void setLevelMap(LevelMap levelMap) {
            this.levelMap = levelMap;
        }
    }
    
    

    3. 创建常量类

    小游戏中的各种元素画面其实都是一张张的图片,而这些图片路径的定义都将放在常量类中完成。

    package com.zxe.beans;
    
    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 Constant {
        //给窗口定义一张图片
        public static BufferedImage bg;
        //右跳图片
        public static BufferedImage jumpR;
        //左跳图片
        public static BufferedImage jumpL;
        //右边站立
        public static BufferedImage standR;
        //左边站立
        public static BufferedImage standL;
        //定义一个集合,存放右跑动作
        public static List<BufferedImage> runR = new ArrayList<>();
        //定义一个集合,存放左跑动作
        public static List<BufferedImage> runL = new ArrayList<>();
        //为障碍物定义一个集合
        public static List<BufferedImage> onstacles = new ArrayList<>();
    
        //定义一个变量,记录文件路径前缀
        public static String prefix = "C:\\Users\\Lenovo\\Desktop\\demo\\file\\image\\";
    
        //初始化图片到系统中
        public static void initImage() {
            try {
                //加载图片
                bg = ImageIO.read(new File(prefix + "bg2.jpeg"));
                jumpR = ImageIO.read(new File(prefix + "mario_jump_r.png"));
                jumpL = ImageIO.read(new File(prefix + "mario_jump_l.png"));
                standR = ImageIO.read(new File(prefix + "mario_stand_r.png"));
                standL = ImageIO.read(new File(prefix + "mario_stand_l.png"));
                runR.add(ImageIO.read(new File(prefix + "mario_run_r1.png")));
                runR.add(ImageIO.read(new File(prefix + "mario_run_r2.png")));
                runL.add(ImageIO.read(new File(prefix + "mario_run_l1.png")));
                runL.add(ImageIO.read(new File(prefix + "mario_run_l2.png")));
                for (int i = 1; i <= 6; i++) {
                    onstacles.add(ImageIO.read(new File(prefix + "ob" + i + ".png")));
                }
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    
    }
    

    常量用 static 修饰,外部可直接通过类名调用常量,而无需创建对象。

    4. 创建动作类

    记录玛丽的动作状态,具体的常量名与代码分离,可以降低代码的耦合度,更规范化。

    5. 创建关卡类

    每个关卡的障碍物是不一样的,这里需要外部传入关卡号,在关卡类中把不同的障碍物拼成自定义的关卡。

    完整代码如下:

    package com.zxe.beans;
    
    import com.zxe.utils.Constant;
    
    import java.awt.image.BufferedImage;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 关卡地图类
     */
    public class LevelMap {
        //记录当前场景需要的图片
        private BufferedImage bg;
        //记录当前关卡
        private int level;
        //创建一个集合,用于存放障碍物
        private List<Obstacle> obstacles = new ArrayList<>();
    
        public LevelMap() {
        }
    
        public LevelMap(int level) {
            this.level = level;
            bg = Constant.bg;
            if (level == 1) {
                //绘制方块
                obstacles.add(new Obstacle(100, 370, 0, this));
                obstacles.add(new Obstacle(130, 370, 1, this));
                obstacles.add(new Obstacle(160, 370, 0, this));
                obstacles.add(new Obstacle(190, 370, 1, this));
                obstacles.add(new Obstacle(300, 260, 0, this));
                obstacles.add(new Obstacle(330, 260, 1, this));
                obstacles.add(new Obstacle(360, 260, 1, this));
                obstacles.add(new Obstacle(800, 300, 0, this));
                obstacles.add(new Obstacle(830, 300, 0, this));
                obstacles.add(new Obstacle(860, 300, 1, this));
                obstacles.add(new Obstacle(890, 300, 1, this));
                //绘制水管
                obstacles.add(new Obstacle(420, 420, 4, this));
                obstacles.add(new Obstacle(450, 420, 5, this));
                obstacles.add(new Obstacle(415, 390, 2, this));
                obstacles.add(new Obstacle(435, 390, 2, this));
                obstacles.add(new Obstacle(455, 390, 3, this));
                obstacles.add(new Obstacle(600, 420, 4, this));
                obstacles.add(new Obstacle(630, 420, 5, this));
                obstacles.add(new Obstacle(600, 390, 4, this));
                obstacles.add(new Obstacle(630, 390, 5, this));
                obstacles.add(new Obstacle(595, 360, 2, this));
                obstacles.add(new Obstacle(615, 360, 2, this));
                obstacles.add(new Obstacle(635, 360, 3, this));
    
            } else if (level == 2) {
                int i = 0;
                for (int y = 420; y >= 300; y -= 30) {
                    for (int x = 100; x <= 190 - 30 * i; x += 30) {
                        obstacles.add(new Obstacle(x + 30 * i, y, 0, this));
                    }
                    for (int x = 300; x <= 390 - 30 * i; x += 30) {
                        obstacles.add(new Obstacle(x, y, 0, this));
                    }
                    for (int x = 550; x <= 640 - 30 * i; x += 30) {
                        obstacles.add(new Obstacle(x + 30 * i, y, 0, this));
                    }
                    for (int x = 670; x <= 790 - 30 * i; x += 30) {
                        obstacles.add(new Obstacle(x, y, 0, this));
                    }
                    i++;
                }
            }
        }
    
        public BufferedImage getBg() {
            return bg;
        }
    
        public void setBg(BufferedImage bg) {
            this.bg = bg;
        }
    
        public int getLevel() {
            return level;
        }
    
        public void setLevel(int level) {
            this.level = level;
        }
    
        public List<Obstacle> getObstacles() {
            return obstacles;
        }
    
        public void setObstacles(List<Obstacle> obstacles) {
            this.obstacles = obstacles;
        }
    }
    
    

    6. 创建障碍物类

    障碍物的属性包括图片以及横纵坐标。

    完整代码如下:

    package com.zxe.beans;
    
    import com.zxe.utils.Constant;
    
    import java.awt.image.BufferedImage;
    
    /**
     * 障碍物类
     */
    public class Obstacle {
        //记录障碍物的坐标
        private int x;
        private int y;
        //定义一个变量,记录当前障碍物的图片信息
        private BufferedImage obstacleImage;
        //定义障碍物类型
        private int type;
        //定义变量存放当前的背景
        private LevelMap bg;
    
        public Obstacle(int x, int y, int type, LevelMap bg) {
            this.x = x;
            this.y = y;
            this.type = type;
            this.bg = bg;
            //根据障碍物的编号,从常量中的障碍物集合中获取对应的图片
            this.obstacleImage = Constant.onstacles.get(type);
        }
    
        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 BufferedImage getObstacleImage() {
            return obstacleImage;
        }
    
        public void setObstacleImage(BufferedImage obstacleImage) {
            this.obstacleImage = obstacleImage;
        }
    
        public int getType() {
            return type;
        }
    
        public void setType(int type) {
            this.type = type;
        }
    
        public LevelMap getBg() {
            return bg;
        }
    
        public void setBg(LevelMap bg) {
            this.bg = bg;
        }
    }
    
    

    7. 创建马里奥类

    马里奥的无限行走动作由多线程实现,定义一个状态量status,用于标记马里奥当前的运动状态,以便进行不同动作的来回切换。

    完整代码如下:

    package com.zxe.beans;
    
    import com.zxe.utils.Action;
    import com.zxe.utils.Constant;
    
    import java.awt.image.BufferedImage;
    
    /**
     * 马里奥类
     */
    public class Mario implements Runnable {
        //记录马里奥坐标信息
        private  int x;
        private int y;
        //记录马里奥状态
        private String status;
        //定义一个变量,记录马里奥当前动作所对应的图片信息
        private BufferedImage marioImage;
        //定义变量,记录当前的关卡地图,也可以获取障碍物的信息
        private LevelMap levelMap = new LevelMap();
    
        //创建线程执行马里奥的动作
        private Thread thread;
        //定义变量,记录马里奥的移动速度
        private int xSpeed;
        //定义变量,记录马里奥的跳跃速度
        private int ySpeed;
        //定义变量,记录马里奥的上升状态
        private int up;
    
        public Mario() {}
    
        public Mario(int x, int y) {
            this.x = x;
            this.y = y;
            //默认马里奥的动作是朝右站立
            status = Action.STAND_RIGHT;
            marioImage = Constant.standR;
            thread = new Thread(this);
            thread.start();
        }
    
        //马里奥向左移动的方法
        public void runLeft() {
            //判断当前是否为跳跃状态,如果不是,就改变状态
            if ( !status.contains("jump") ) {
                status = Action.RUN_LEFT;
            } else {
                status = Action.JUMP_LEFT;
            }
            xSpeed = -5;
        }
    
        //马里奥向右移动的方法
        public void runRight() {
            //判断当前是否为跳跃状态,如果不是,就改变状态
            if ( !status.contains("jump") ) {
                status = Action.RUN_RIGHT;
            } else {
                status = Action.JUMP_RIGHT;
            }
            xSpeed = 5;
        }
    
        public void jump() {
            if (status.contains("left")) {
                status = Action.JUMP_LEFT;
            } else {
                status = Action.JUMP_RIGHT;
            }
                ySpeed = -12;
        }
    
        public void jumpDown() {
            ySpeed = 12;
        }
    
        private void jumpStop() {
            if (status.contains("left")) {
                status = Action.STAND_LEFT;
            } else {
                status = Action.STAND_RIGHT;
            }
            ySpeed = 0;
        }
    
        //马里奥向左移动停止的方法
        public void runLeftStop() {
            if (!status.contains("jump")) {
                status = Action.STAND_LEFT;
            } else {
                status = Action.JUMP_LEFT;
            }
            xSpeed = 0;
        }
    
        //马里奥向右移动停止的方法
        public void runRightStop() {
            if (!status.contains("jump")) {
                status = Action.STAND_RIGHT;
            } else {
                status = Action.JUMP_RIGHT;
            }
            xSpeed = 0;
        }
    
    
        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 String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        public BufferedImage getMarioImage() {
            return marioImage;
        }
    
        public void setMarioImage(BufferedImage marioImage) {
            this.marioImage = marioImage;
        }
    
        public LevelMap getLevelMap() {
            return levelMap;
        }
    
        public void setLevelMap(LevelMap levelMap) {
            this.levelMap = levelMap;
        }
    
        public Thread getThread() {
            return thread;
        }
    
        public void setThread(Thread thread) {
            this.thread = thread;
        }
    
        public int getxSpeed() {
            return xSpeed;
        }
    
        public void setxSpeed(int xSpeed) {
            this.xSpeed = xSpeed;
        }
    
        public int getySpeed() {
            return ySpeed;
        }
    
        public void setySpeed(int ySpeed) {
            this.ySpeed = ySpeed;
        }
    
        public int getUp() {
            return up;
        }
    
        public void setUp(int up) {
            this.up = up;
        }
    
        @Override
        public void run() {
            int index = 0;
            //控制马里奥无限移动
            while (true) {
                //判断当前是否移动,xSpeed<0左移动,xSpeed>0右移动
                if (xSpeed < 0 || xSpeed > 0) {
                    x += xSpeed;
                    if (x < 0) {
                        x = 0;
                    }
                }
    
                if (ySpeed < 0 || ySpeed > 0) {
                    y += ySpeed;
                    if (y > 420) {
                        y = 420;
                        jumpStop();
                    }
                    if (y < 280) {
                        y = 280;
                    }
                }
    
                //判断移动状态,跑步状态图片切换
                if (status.contains("run")) {
                    index = index == 0 ? 1 : 0;
                }
                //根据马里奥的状态切换不同的图片
                if (Action.RUN_LEFT.equals(status)) {
                    marioImage = Constant.runL.get(index);
                }
                if (Action.RUN_RIGHT.equals(status)) {
                    marioImage = Constant.runR.get(index);
                }
                if (Action.STAND_LEFT.equals(status)) {
                    marioImage = Constant.standL;
                }
                if (Action.STAND_RIGHT.equals(status)) {
                    marioImage = Constant.standR;
                }
                if (Action.JUMP_LEFT.equals(status)) {
                    marioImage = Constant.jumpL;
                }
                if (Action.JUMP_RIGHT.equals(status)) {
                    marioImage = Constant.jumpR;
                }
    
                // 控制线程的速度
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
    }
    
    

    8. 编写程序入口

    创建游戏窗口,并对窗口的基本属性进行设置,创建三个关卡,并调用 repaint 方法绘制场景。

    package com.zxe;
    
    import com.zxe.beans.LevelMap;
    import com.zxe.beans.Mario;
    import com.zxe.utils.Constant;
    import com.zxe.beans.MyFrame;
    
    import javax.swing.*;
    
    public class Main {
        public static void main(String[] args) {
            //创建窗口对象
            MyFrame myFrame = new MyFrame();
            //设置窗口大小
            myFrame.setSize(1045,500);
            //设置窗口居中
            myFrame.setLocationRelativeTo(null);
            //设置窗口可见性
            myFrame.setVisible(true);
            //设置窗口关闭程序
            myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            //设置键盘监听事件
            myFrame.addKeyListener(myFrame);
            //设置窗口的大小不可改变
            myFrame.setResizable(false);
            //设置窗口标题
            myFrame.setTitle("超级玛丽");
    
            //加载图片
            Constant.initImage();
            //创建三个关卡地图
            for (int i = 1; i <= 3; i++) {
                myFrame.getLevelMaps().add(new LevelMap(i));
            }
            //设置当前关卡地图
            myFrame.setLevelMap(myFrame.getLevelMaps().get(0));
    
            //创建马里奥
            Mario mario = new Mario(50, 420);
            myFrame.setMario(mario);
            mario.setLevelMap(myFrame.getLevelMap());
    
            //绘制场景
            myFrame.repaint();
    
            Thread thread = new Thread(myFrame);
            thread.start();
        }
    }
    

    作者:栈老师不回家

    物联沃分享整理
    物联沃-IOTWORD物联网 » Java 小游戏《超级马里奥》

    发表回复