hello大家好,用java实现小游戏真的很锻炼编程技术,而且很有成就感。比起做增删改查的管理系统来说,简直是不同的两个阶层的程序员。
今天我就教大家用JDK17原生库来实现一个完整的 超级马里奥 ,初始编程的你,只要用心就能学会。会大大加深你对面向对象的理解!
源码为自己开发的源码, 商用必究!!!
从这个游戏中你可以学到:
-
场景是如何跟随玩家移动的(摄像机概念)。
-
游戏关卡是如何在数据库中配置的(以后新增关卡只需在数据库中插入数据)。
-
马里奥,红砖,蘑菇等游戏实体的管理。
-
2D游戏的分层架构: 画图+逻辑管理器+实体 。
-
游戏内的物理系统:碰撞检测,边界检测, 垂直重力加速度, 水平的惯性等。
视频演示
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() 抽象方法- 不同的植物类(
Sunflower
、Peashooter
等)实现不同的行为策略
优势: 每种植物都有自己独特的行为逻辑,易于维护和扩展。
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
缓存音频资源
完整游戏源码,我已经整理清楚,移步:
游戏实现的功能
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目录下面。