JDK17实现超级马里奥游戏+完整版最新源码

hello大家好,用java实现小游戏真的很锻炼编程技术,而且很有成就感。比起做增删改查的管理系统来说,简直是不同的两个阶层的程序员。

今天我就教大家用JDK17原生库来实现一个完整的 超级马里奥 ,初始编程的你,只要用心就能学会。会大大加深你对面向对象的理解!

源码为自己开发的源码, 商用必究!!!

从这个游戏中你可以学到:

  1. 场景是如何跟随玩家移动的(摄像机概念)。

  2. 游戏关卡是如何在数据库中配置的(以后新增关卡只需在数据库中插入数据)。

  3. 马里奥,红砖,蘑菇等游戏实体的管理。

  4. 2D游戏的分层架构: 画图+逻辑管理器+实体 。

  5. 游戏内的物理系统:碰撞检测,边界检测, 垂直重力加速度, 水平的惯性等。

视频演示

www.bilibili.com/video/BV1Sj...

图片演示

技术栈描述

项目框架

  • Java SE 17 - 主要编程语言
  • Swing - GUI框架(JFrame、JPanel、Timer等)
  • Java 2D API - 图形渲染(Graphics2D、BufferedImage)
  • Java Sound API - 音效处理(Clip、AudioSystem)
  • Maven - 项目构建管理
  • JDBC - 数据库连接

关键技术特性

  • 双缓冲渲染 - 消除画面闪烁
  • 60 FPS游戏循环 - 流畅的游戏体验
  • 资源缓存机制 - 图片和音效缓存
  • 多线程音频 - 并发音效播放

设计模式

1. 单例模式 (Singleton Pattern)

应用场景:

  • GameManager :游戏核心管理器
  • ImageLoader :图片资源管理器
  • SoundManager :声音管理器
  • DatabaseConfig :数据库配置类
  • LayerManager :层级渲染管理器

优势: 确保全局只有一个实例,便于资源管理和状态控制。

2. 工厂模式 (Factory Pattern)

应用场景:

  • PlantManagerExt 中的 plantPlant 方法根据 PlantType 枚举创建不同类型的植物对象
  • 根据植物类型(向日葵、豌豆射手、坚果墙等)动态创建相应的植物实例
    优势: 封装对象创建逻辑,便于扩展新的植物类型。

3. 策略模式 (Strategy Pattern)

应用场景:

  • GameObject 抽象类定义了 update() 和 render() 抽象方法
  • 不同的植物类( SunflowerPeashooter 等)实现不同的行为策略
    优势: 每种植物都有自己独特的行为逻辑,易于维护和扩展。

4. 观察者模式 (Observer Pattern)

应用场景:

  • 游戏事件处理系统,如鼠标点击事件通过 MouseListenerManagerExt 分发给各个游戏实体
  • 游戏状态变化时通知相关组件更新

5. 模板方法模式 (Template Method Pattern)

应用场景:

  • GameObject 基类定义了游戏对象的通用结构和行为模板
  • 子类重写特定方法实现自己的逻辑,如 update() 、 render() 等

6. 外观模式 (Facade Pattern)

应用场景:

  • DrawManagerExt 提供统一的绘制接口,封装了复杂的渲染逻辑
  • 各种 ManagerExt 类为复杂的游戏逻辑提供简化的接口

7. 组合模式 (Composite Pattern)

应用场景:

  • 游戏场景中的层级结构,通过 LayerManager 管理不同渲染层级的对象
  • 统一处理单个对象和对象集合的渲染

8. 命令模式 (Command Pattern)

应用场景:

  • 游戏中的各种操作(种植植物、收集阳光等)被封装成具体的方法调用
  • 便于实现撤销、重做等功能

9. 状态模式 (State Pattern)

应用场景:

  • 游戏状态管理(游戏中、暂停、结束、胜利)通过 Constants.java 中定义的状态常量进行切换
  • 不同状态下游戏有不同的行为表现

10. 享元模式 (Flyweight Pattern)

应用场景:

  • ImageLoader 使用缓存机制避免重复加载相同的图片资源
  • SoundManager 缓存音频资源

完整游戏源码,我已经整理清楚,移步:

gitcode.com/hadluo2/jav...

游戏实现的功能

1. 玩家控制系统

  • 马里奥角色控制 : Mario 类实现了完整的玩家控制
  • 移动机制 : 左右移动、跳跃、滑行、摩擦力模拟
  • 物理系统 : 重力、速度、碰撞检测等真实物理效果
  • 键盘输入 : 支持上下左右方向键控制移动,空格键射击

2. 角色形态系统

  • 小马里奥 : 初始形态,碰到敌人会死亡
  • 大马里奥 : 吃蘑菇后变大,有更强的生存能力
  • 射击的马里奥 : 吃花道具后获得射击能力
  • 形态转换 : 支持不同形态间的动态切换和动画效果

3. 道具系统

  • 蘑菇道具 : Mushroom - 使马里奥变大或获得额外生命
  • 花道具 : Flower - 赋予马里奥射击能力
  • 道具砖块 : PowerUpBrick - 问号砖块,撞击后产生道具
  • 金币系统 : 砖块中隐藏金币,收集后增加分数

4. 敌人系统

  • 敌人类 : Enemy 实现了智能敌人行为
  • 自动移动 : 敌人会自动左右移动巡逻
  • 碰撞反应 : 遇到障碍物会自动转向
  • 攻击机制 : 与马里奥碰撞时造成伤害
  • 被击败 : 可被马里奥踩踏或子弹击中消灭

5. 射击系统

  • 子弹发射 : Bullet 实现火球射击功能
  • 弹道物理 : 子弹具有重力、弹跳效果
  • 射击冷却 : 防止连续射击的冷却机制
  • 敌人击中 : 子弹可以击败敌人

6. 碰撞检测系统

  • 精确碰撞 : DamageCheckManagerExt 实现全面的碰撞检测
  • 多层碰撞 : 马里奥与地形、敌人、道具的碰撞
  • 方向判断 : 能够判断碰撞的方向(上下左右)
  • 物理反应 : 根据碰撞方向产生相应的物理反应

7. 音效系统

  • 背景音乐 : SoundManager 管理所有音频
  • 音效播放 : 跳跃、收集道具、击败敌人等动作音效
  • 音量控制 : 支持背景音乐和音效的独立开关
  • 音频缓存 : 优化音频加载和播放性能

8. 关卡系统

关卡的配置都是在数据库里面,主要分为以下表:

levels表: 配置了主关卡信息,包括关卡 名称,世界长度,马里奥生命条数,背景乐。

bricks表: 砖块表, 包括了: x,y坐标,连续有多少块砖。第多少块砖有硬币等。

enemys表: 移动的敌人表,包括了: x,y左边, 移动速度等。

grounds表: 地面表, 类似于砖块, 没有隐藏道具,无法死亡。

pips表: 静态的下水管道。

power_bricks表: 道具砖块表, 里面可以顶出 蘑菇,花道具。

目前游戏只有2关,后续可以直接在表中插入数据配置关卡场景。无需改动任何代码。

游戏实现原理

本小结将讲解游戏中各大类的具体功能,每个类都是实现游戏不可或缺的部分,他们紧密相连来实现一个完整的游戏系统。

数据库加载关卡

DatabaseManager 类是连接数据库的核心,里面加载了db.properties得到数据库信息,然后连接数据库。然后通过LevelManager类去加载数据库指定关卡的数据。

kotlin 复制代码
/**
 * 开始指定关卡
 */
public boolean startLevel(int levelNumber) {
    // 加载关卡
    this.levels = databaseManager.getLevelByNumber(Levels.class, levelNumber);
    System.out.println("加载关卡:" + levels);
    if (this.levels == null) {
        return false;
    }
    // 加载地面砖块
    this.grounds = databaseManager.getLevelByLevelId(Grounds.class, levelNumber);
    // 加载 金币道具砖块
    this.bricks = databaseManager.getLevelByLevelId(Bricks.class, levelNumber);
    // 特色道具砖块
    this.powerBricks = databaseManager.getLevelByLevelId(PowerBricks.class, levelNumber);
    this.enemys = databaseManager.getLevelByLevelId(Enemys.class, levelNumber);
    this.pips = databaseManager.getLevelByLevelId(Pips.class, levelNumber);


    this.currentLevelNumber = levelNumber;
    return true;
}

游戏循环的启动

点击开始游戏时会初始化 GameFrame 类,这个就是游戏的主界面,里面有一个 GamePanel ,就是游戏内容画图的核心。在GamePanel 的里面有一个循环定时器,就是游戏的主循环位置:

scss 复制代码
/**
 * 开始游戏循环
 */
public void startGameLoop() {
    if (gameTimer == null || !running) {
        gameManager.startGame(1);
        running = true;

        // 创建Timer,每隔FRAME_DELAY毫秒执行一次
        gameTimer = new Timer(FRAME_DELAY, e -> {
            if (running) {
                // 更新游戏逻辑
                try {
                    gameManager.update();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                // 重绘画面
                repaint();
            }
        });
        gameTimer.start();
    }
}

游戏循环的逻辑很清晰, 就是先更新一些逻辑数据,比如玩家的坐标值,敌人,子弹的状态,是否死亡等等。然后调用 repaint(); 方法去画图,就会执行当前类的画图逻辑:

typescript 复制代码
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    // 清空后缓冲
    backGraphics.setColor(Color.BLACK);
    backGraphics.fillRect(0, 0, Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT);

    // 根据游戏状态绘制不同内容
    switch (gameManager.getGameState()) {
        case Constants.GAME_STATE_PLAYING:
            drawGame(backGraphics);
            break;
        case Constants.GAME_STATE_PAUSED:
            drawGame(backGraphics);
            drawPauseOverlay(backGraphics);
            break;
        case Constants.GAME_STATE_GAME_OVER:
            drawGameOver(backGraphics);
            break;
        case Constants.GAME_STATE_VICTORY:
            drawVictory(backGraphics);
            break;
    }

    // 将后缓冲绘制到屏幕
    g.drawImage(backBuffer, 0, 0, null);
}

/**
 * 绘制游戏画面
 */
private void drawGame(Graphics2D g2d) {
    // 绘制游戏实体
    DrawManagerExt.onDraw(g2d);
}

画图的逻辑就是获取到所有的游戏实体,然后调用实体自身的 render方法进行画图(传递了Graphics 用来画图的对象 )。

植游戏统一的碰撞检测

为了统一碰撞检测,我们将所有游戏实体的碰撞放到了 DamageCheckManagerExt 里面,这样作不仅仅提高了效率,还便于统一管理:

scss 复制代码
/**
 * 检查所有碰撞
 */
public static void check() {
    GameManager gameManager = GameManager.getInstance();
    Mario mario = gameManager.getMario();
    if (mario == null || !mario.isAlive()) {
        return;
    }

    // 重置地面状态,将在碰撞检测中重新设置
    mario.setOnGround(false);

    // 检查马里奥与地面的碰撞
    checkMarioGroundCollision(mario, gameManager.getGrounds());

    // 检查马里奥与砖块的碰撞
    checkMarioBrickCollision(mario, gameManager.getBricks());
    
    // 检查马里奥与道具砖块的碰撞
    checkMarioPowerUpBrickCollision(mario, gameManager.getPowerUpBricks());
    
    // 检查马里奥与道具的碰撞
    checkMarioPowerUpCollision(mario, gameManager.getPowerUps());
    
    // 检查道具与地形的碰撞
    checkPowerUpTerrainCollision(gameManager.getPowerUps(), gameManager.getGrounds(), gameManager.getBricks(), gameManager.getPowerUpBricks());
    
    // 检查子弹与地形的碰撞
    checkBulletTerrainCollision(gameManager.getBullets(), gameManager.getGrounds(), gameManager.getBricks(), gameManager.getPowerUpBricks());
    
    // 检查敌人与地形的碰撞
    checkEnemyTerrainCollision(gameManager.getEnemies(), gameManager.getGrounds(), gameManager.getBricks(), gameManager.getPowerUpBricks());
    
    // 检查敌人与马里奥的碰撞
    checkEnemyMarioCollision(gameManager.getEnemies(), mario);
    
    // 检查子弹与敌人的碰撞
    checkBulletEnemyCollision(gameManager.getBullets(), gameManager.getEnemies());
    
    // 检查马里奥与管道的碰撞
    checkMarioPipeCollision(mario, gameManager.getPipes());
    
    // 检查敌人与管道的碰撞
    checkEnemyPipeCollision(gameManager.getEnemies(), gameManager.getPipes());
    
    // 检查子弹与管道的碰撞
    checkBulletPipeCollision(gameManager.getBullets(), gameManager.getPipes());
    
    // 检查马里奥与终点屋的碰撞
    checkMarioFinishHouseCollision(mario, gameManager.getFinishHouses());

    // 检测 马里奥是否掉出 窗外
    checkY(mario);
}

游戏还涉及到很多有趣的设计,比如: 马里奥的子弹的轨迹和障碍物的回弹,马里奥本人的水平移动惯性和垂直掉落时的加速度等待,我就不一一讲解了,大家可以跟着源码来打开新世界的大门。。。

游戏启动

将源码导入到idea中,这个项目就是一个普通的maven管理的项目, 导入前,请设置好maven的仓库配置。

设置好JDK的环境为17

用navicate工具连接数据库,新建数据库,然后执行sql创建表:

数据库的版本用8就可以了。

修改数据库配置db.properties:

等待编译好,启动Main就可以了。游戏图片,声音素材资源在resource目录下面。

相关推荐
小王子1024几秒前
Django+DRF 实战:自定义异常处理流程
后端·django·web开发
考虑考虑3 分钟前
go中的切片
后端·go
天南星1 小时前
java-WebSocket在Java生态中的发展历程
java·后端·websocket
工藤学编程1 小时前
分库分表之实战-sharding-JDBC绑定表配置实战
数据库·分布式·后端·sql·mysql
fmvrj342022 小时前
云原生:数字化转型的核心引擎
后端
码出极致2 小时前
Redisson分布式缓存与数据一致性保障
后端
用户790349033712 小时前
springboot集成redisson实现redis分布式锁
后端
陈随易2 小时前
程序员的新玩具,MoonBit(月兔)编程语言科普
前端·后端·程序员
码出极致2 小时前
Redisson秒杀系统中的分布式锁应用
后端
xiaok2 小时前
@Param注解的作用
java·后端