hello大家好,用java实现小游戏真的很锻炼编程技术,而且很有成就感。比起做增删改查的管理系统来说,简直是不同的两个阶层的程序员。
今天我就教大家用JDK17原生库来实现一个简单的魂斗罗游戏,初始编程的你,只要用心就能学会。会大大加深你对面向对象的理解!
源码为自己开发的源码, 商用必究!!!
从这个游戏中你可以学到:
- 场景是如何跟随玩家移动的(世界,摄像机原理)。
2. 游戏关卡是如何在数据库中配置的(以后新增关卡只需在数据库中插入数据)。
-
魂斗罗的 散花子弹 等道具是如何实现的。
-
2D游戏的分层架构: 画图+逻辑管理器+实体 。
-
游戏内的物理系统:重力系统,碰撞检测,边界检测。
视频演示
图片演示
技术栈描述
项目框架
- Java SE 17 - 主要编程语言
- Swing - GUI框架(JFrame、JPanel、Timer等)
- Java 2D API - 图形渲染(Graphics2D、BufferedImage)
- Java Sound API - 音效处理(Clip、AudioSystem)
- Maven - 项目构建管理
- JDBC - 数据库连接
关键技术特性
- 双缓冲渲染 - 消除画面闪烁
- 60 FPS游戏循环 - 流畅的游戏体验
- 资源缓存机制 - 图片和音效缓存
- 多线程音频 - 并发音效播放
设计模式
1. 单例模式
使用场景: 管理全局唯一的资源和状态
实现类:
GameManager - 游戏逻辑管理器
ImageLoader - 图片资源加载器
SoundManager - 音效管理器
LevelManager - 关卡管理器
DatabaseManager - 数据库管理器
2. 模板方法模式
使用场景: 定义游戏对象的通用行为框架
实现类: GameObject 抽象基类
核心结构:
public abstract class GameObject {
// 通用属性和具体方法
protected int x, y, width, height;
// 抽象方法,由子类实现具体行为
public abstract void update();
public abstract void render(Graphics g);
// 通用的碰撞检测方法
public boolean intersects(GameObject other) {
return getBounds().intersects(other.getBounds());
}
}
子类实现: Player、Enemy、Bullet等都继承GameObject并实现具体的update()和render()方法。
3. 策略模式
使用场景: 不同类型敌人的行为策略
实现方式:
Enemy 抽象基类定义通用接口
MovingEnemy - 移动策略:巡逻移动,碰撞玩家造成伤害。
ShootingEnemy - 射击策略:坦克类型敌人。固定位置,定时向玩家射击。
4. 观察者模式
使用场景: 游戏状态管理和事件通知
实现方式:
GameManager 作为主题,管理游戏状态变化
游戏状态变化时通知相关组件更新
分数变化、关卡完成、游戏结束等事件的处理
体现在:
// 游戏状态检查和通知
private void checkGameState() {
// 检查胜利条件
if (enemies.stream().noneMatch(Enemy::isActive)) {
gameState = Constants.STATE_LEVEL_COMPLETE;
// 通知相关组件状态变化
}
}
5. 工厂方法模式
使用场景: 动态创建游戏对象
实现位置: LevelManager 中的对象创建方法
6. 资源管理模式
使用场景: 图片和音效资源的统一管理
实现特点:
ImageLoader 使用Map缓存已加载的图片
SoundManager 管理音效播放和音量控制
避免重复加载,提高性能
7. 状态模式
游戏状态管理:游戏状态常量
完整游戏源码,我已经整理清楚,移步:
游戏实现的功能
玩家控制系统
- 上下左右键位控制
- z键射击
- 空格跳跃
生命与血量系统
- 多生命机制 :玩家拥有200条生命,每次被击中一次就减少一条。在Constants常量类里面可以配置生命条数。
武器系统
- 基础射击 :200毫秒射击间隔
- 武器升级 :通过道具提升武器等级
- 子弹类型 :普通子弹和散花子弹。 散花子弹 为5个方向同时射击的子弹
敌人系统
移动的敌人: 具备巡逻AI,碰到边界自动转向,碰到玩家双方都死亡。
射击的坦克:固定位置,只能瞄准玩家射击, 炮头有自动跟踪玩家功能。
道具类型
散花弹道具
生命加1道具
关卡系统
关卡的配置都是在数据库里面,主要分为以下表:
levels表: 配置了主关卡信息,包括关卡名称,背景乐,世界长度,终点门的位置等主要信息。
enemies表: 配置了两种敌人的信息,包括关卡id, 敌人的位置,敌人的类型(移动或射击),移动速度,射击速度。
obstacles表: 配置了红砖障碍物信息,包括关卡id, 红砖位置,长度等信息。
power_ups表: 配置了道具信息,包括关卡id, 道具位置,道具类型等信息。
trees表: 配置了背景里的树木信息,包括关卡id,树木的位置,大小等信息。
mountains表: 配置了背景里的山的信息,包括关卡id,山的位置,大小等信息。
目前游戏只有2关,后续可以直接在表中插入数据配置关卡场景。无需改动任何代码。
游戏实现原理
本小结将讲解游戏中各大类的具体功能,每个类都是实现游戏不可或缺的部分,他们紧密相连来实现一个完整的游戏系统。
数据库加载关卡
DatabaseManager 类是连接数据库的核心,里面加载了db.properties得到数据库信息,然后连接数据库。EnemyDAO,LevelDAO 就是 基础的表的增删改查。然后通过 LevelManager的loadLevel方法
加载到了数据库指定关卡的数据。
/**
* 加载指定关卡
* @param levelId 关卡ID
* @return 是否加载成功
*/
public boolean loadLevel(int levelId) {
reset();
try {
currentLevel = LevelLoader.loadLevel(levelId);
currentLevelId = levelId;
levelStartTime = System.currentTimeMillis();
remainingTime = currentLevel.getTimeLimit();
levelCompleted = false;
// 创建敌人对象
createEnemies();
// 创建道具对象
createPowerUps();
// 创建障碍物对象
createObstacles();
System.out.println("关卡加载成功: " + currentLevel.getName());
return true;
} catch (IOException e) {
System.err.println("关卡加载失败: " + e.getMessage());
return false;
}
}
游戏循环的启动
点击开始游戏时会初始化 GameFrame 类,这个就是游戏的主界面,里面有一个 GamePanel ,就是游戏内容画图的核心。在GamePanel 的里面有一个循环定时器,就是游戏的主循环位置:
@Override
public void actionPerformed(ActionEvent e) {
// 游戏主循环
if (gameManager.getGameState() == Constants.STATE_PLAYING) {
gameManager.update();
levelManager.update(); // 更新关卡状态
updatePowerUps(); // 更新道具状态
checkEndGateCollision(); // 检查终点门碰撞
updateCamera(); // 更新摄像机位置
}
repaint();
}
游戏循环的逻辑很清晰, 就是先更新一些逻辑数据,比如玩家的坐标值,敌人,子弹的状态,是否死亡等等。然后调用 repaint(); 方法去画图,就会执行当前类的画图逻辑:
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (gameManager.getGameState() == Constants.STATE_PLAYING) {
// 应用摄像机变换
g2d.translate(-cameraX, 0);
drawBackground(g2d);
drawGameObjects(g2d);
// 恢复变换绘制UI
g2d.translate(cameraX, 0);
drawUI(g2d);
} else if (gameManager.getGameState() == Constants.STATE_PAUSED) {
g2d.translate(-cameraX, 0);
drawBackground(g2d);
drawGameObjects(g2d);
g2d.translate(cameraX, 0);
drawUI(g2d);
drawPauseScreen(g2d);
} else if (gameManager.getGameState() == Constants.STATE_GAME_OVER) {
g2d.translate(-cameraX, 0);
drawBackground(g2d);
drawGameObjects(g2d);
g2d.translate(cameraX, 0);
drawGameOverScreen(g2d);
} else if (gameManager.getGameState() == Constants.STATE_VICTORY) {
g2d.translate(-cameraX, 0);
drawBackground(g2d);
drawGameObjects(g2d);
g2d.translate(cameraX, 0);
drawVictoryScreen(g2d);
}
}
画图的逻辑就是获取到所有的游戏实体,然后调用实体自身的 render方法进行画图(传递了Graphics 用来画图的对象 )。
玩家散花弹的绘制
玩家的子弹都是统一在Player玩家类里面实现。用 weaponUpgraded 变量代表是否接到了散花弹道具,接到了就是true。
private void shoot() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastShootTime >= SHOOT_COOLDOWN) {
int bulletX = 0, bulletY = 0;
if(direction == Direction.LEFT) {
bulletX = x ;
bulletY = y + height / 2 - Constants.BULLET_HEIGHT ;
}else if(direction == Direction.RIGHT) {
bulletX = x + width;
bulletY = y + height / 2 - Constants.BULLET_HEIGHT ;
}
if (weaponUpgraded) {
// 散花子弹:发射5发子弹,角度分散
int[] angles = {-30, -15, 0, 15, 30}; // 角度数组
for (int angle : angles) {
double radians = Math.toRadians(angle);
int velX = (int)(direction.getValue() * Constants.BULLET_SPEED * Math.cos(radians));
int velY = (int)(Constants.BULLET_SPEED * Math.sin(radians));
bullets.add(new Bullet(bulletX, bulletY, velX, velY, true));
}
System.out.println("玩家发射散花子弹");
SoundManager.getInstance().playSound("up_w.wav");
} else {
// 普通子弹:单发直线子弹
bullets.add(new Bullet(bulletX, bulletY, direction.getValue() * Constants.BULLET_SPEED, 0, true));
System.out.println("玩家发射子弹");
SoundManager.getInstance().playSound("fire.wav");
}
lastShootTime = currentTime;
}
}
实现原理解析:
- 角度分散设计
- 预定义角度数组: {-30, -15, 0, 15, 30}
- 覆盖60度扇形范围(-30°到+30°)
- 中心子弹(0°)保持直线射击
- 上下各两发子弹形成扇形分布
- 三角函数计算
- 角度转弧度 : Math.toRadians(angle) 将角度转换为弧度制
- 水平速度 : velX = direction.getValue() * BULLET_SPEED * Math.cos(radians)
- Math.cos(radians) 计算水平分量
- direction.getValue() 考虑玩家朝向(左-1,右+1)
- 垂直速度 : velY = BULLET_SPEED * Math.sin(radians)
- Math.sin(radians) 计算垂直分量
- 正值向下,负值向上
- 物理原理
- 利用 向量分解 原理,将子弹速度分解为X、Y两个分量
- 每发子弹都有独立的运动轨迹
- 形成类似散弹枪的效果
重力系统的实现
Player玩家类里面有一个 是否处于地面的变量,当不处于地面时,才会有重力系统的应用,重力状态判断代码:
if (!onGround) {
velocityY += 1; // 重力加速度
y += velocityY; // y是玩家y轴的位置
}
- 只有当玩家不在地面上( !onGround )时才应用重力
- 重力加速度设为固定值 1 ,每帧增加垂直速度
- 通过累积速度实现加速下落效果
游戏还涉及到很多有趣的设计,比如: 摄像机玩家的跟随, 碰撞检测,玩家跳跃机制,坦克炮口的跟踪等等,我就不一一讲解了,大家可以跟着源码来打开新世界的大门。。。
游戏启动
将源码导入到idea中,这个项目就是一个普通的maven管理的项目, 导入前,请设置好maven的仓库配置。
设置好JDK的环境为17
用navicate工具连接数据库,新建数据库,然后执行sql创建表:
数据库的版本用8就可以了。
修改数据库配置db.properties:
等待编译好,启动Main就可以了。游戏图片,声音素材资源在resource目录下面。