【附源码】JDK17实现仿魂斗罗小游戏

0 阅读9分钟

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

今天我就教大家用JDK17原生库来实现一个简单的魂斗罗游戏,初始编程的你,只要用心就能学会。会大大加深你对面向对象的理解!

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

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

  1. 场景是如何跟随玩家移动的(世界,摄像机原理)。

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

  1. 魂斗罗的 散花子弹 等道具是如何实现的。

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

  3. 游戏内的物理系统:重力系统,碰撞检测,边界检测。

 

 

视频演示

www.bilibili.com/video/BV1L1…

 

图片演示

 

 

 

 

 

 

 

技术栈描述

项目框架

  • 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. 状态模式

游戏状态管理:游戏状态常量

 

 

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

 gitcode.com/hadluo2/jav…

 

 

游戏实现的功能

玩家控制系统

  • 上下左右键位控制 
  • 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;
        }
    }

复制代码

实现原理解析:

  1. 角度分散设计
  • 预定义角度数组: {-30, -15, 0, 15, 30}
  • 覆盖60度扇形范围(-30°到+30°)
  • 中心子弹(0°)保持直线射击
  • 上下各两发子弹形成扇形分布
  1. 三角函数计算
  • 角度转弧度 : 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) 计算垂直分量
  • 正值向下,负值向上
  1. 物理原理
  • 利用 向量分解 原理,将子弹速度分解为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目录下面。