这个基于Java Swing的2D射击游戏项目,是我最近完成的一个综合性练习。作为一个有着多年Java开发经验的程序员,我一直想尝试用最基础的Java GUI技术来实现一个完整的游戏。这个项目不仅让我重温了Swing和AWT的使用技巧,更重要的是实践了游戏开发中的多线程同步、碰撞检测、实体管理等核心机制。
游戏采用经典的太空射击玩法,玩家控制一艘飞船躲避敌人攻击并射击敌人。项目完整实现了游戏主循环、实体系统、武器系统、道具系统、音效管理等模块。整个代码结构清晰,采用面向对象设计,核心类之间通过松耦合方式交互,便于后续扩展。
提示:虽然现在游戏开发更多使用Unity或Godot等引擎,但用原生Java实现游戏对理解底层机制非常有帮助。这也是为什么我选择从零开始构建这个项目。
游戏采用典型的三层架构,各层职责分明:
视图层(View):负责所有可视化内容
实体层(Entity):游戏中的所有动态对象
逻辑层(Logic):游戏核心规则
游戏采用继承+组合的面向对象设计:
java复制// 继承关系示例
GameObject (抽象类)
├─ Player
├─ Enemy (抽象类)
│ ├─ BasicEnemy
│ ├─ ArmoredEnemy
│ └─ FastEnemy
├─ Bullet
└─ PowerUp (抽象类)
├─ HealthPowerUp
├─ AmmoPowerUp
└─ ShieldPowerUp
// 组合关系示例
Player {
List<Weapon> weapons;
Weapon currentWeapon;
}
这种设计既保证了共性代码的复用,又允许特殊化行为通过子类实现。例如所有敌人都继承自Enemy基类,共享移动和受伤逻辑,但不同类型的敌人可以有独特的移动模式。
游戏采用"固定时间步长+多线程"的主循环设计:
java复制// 在GamePanel中实现Runnable接口
public void run() {
long lastTime = System.nanoTime();
double nsPerTick = 1000000000D / 60D; // 60FPS
while (running) {
long now = System.nanoTime();
double delta = (now - lastTime) / nsPerTick;
lastTime = now;
// 累积未处理的帧时间
unprocessed += delta;
while (unprocessed >= 1) {
update(); // 更新游戏状态
unprocessed--;
}
repaint(); // 重绘画面
Thread.yield();
}
}
这种设计保证了游戏逻辑以固定60FPS运行,而渲染则尽可能快地执行。即使机器性能不足导致帧率下降,游戏逻辑也不会因此变慢。
游戏中有三个主要线程需要协调:
共享资源如敌人列表、子弹列表需要使用同步控制:
java复制// 使用Collections.synchronizedList包装列表
List<Enemy> enemies = Collections.synchronizedList(new ArrayList<>());
// 或者在修改时同步
synchronized(enemies) {
enemies.add(newEnemy);
}
特别注意Swing组件只能在EDT线程中修改,因此游戏状态更新到UI需要通过SwingUtilities.invokeLater()。
游戏使用矩形碰撞检测,通过Rectangle2D的intersects()方法实现:
java复制public class CollisionDetector {
public static boolean checkCollision(GameObject obj1, GameObject obj2) {
return obj1.getBounds().intersects(obj2.getBounds());
}
// 更精确的像素级碰撞检测(性能开销大)
public static boolean checkPixelCollision(BufferedImage img1, Rectangle rect1,
BufferedImage img2, Rectangle rect2) {
// 实现略...
}
}
实际游戏中,我们采用分层检测策略:
所有游戏实体都继承自GameObject基类:
java复制public abstract class GameObject {
protected int x, y; // 位置
protected int width, height; // 尺寸
protected int dx, dy; // 速度
public abstract void update(); // 更新逻辑
public abstract void draw(Graphics2D g); // 绘制
public Rectangle getBounds() {
return new Rectangle(x, y, width, height);
}
// 其他公共方法和属性...
}
实体管理采用对象池模式,避免频繁创建销毁对象:
java复制public class EntityManager {
private List<GameObject> entities = new ArrayList<>();
private List<GameObject> toAdd = new ArrayList<>();
private List<GameObject> toRemove = new ArrayList<>();
public void update() {
// 先处理待删除实体
entities.removeAll(toRemove);
toRemove.clear();
// 再处理新增实体
entities.addAll(toAdd);
toAdd.clear();
// 更新所有实体
for(GameObject obj : entities) {
obj.update();
}
}
// 添加/移除实体的方法...
}
Player类封装了玩家所有属性和行为:
java复制public class Player extends GameObject {
private int health = 100;
private int maxHealth = 100;
private Weapon currentWeapon;
private List<Weapon> weapons = new ArrayList<>();
private boolean shielded = false;
public Player(int x, int y) {
super(x, y, 50, 50);
// 初始化武器库
weapons.add(new BasicWeapon());
weapons.add(new SpreadWeapon());
weapons.add(new LaserWeapon());
currentWeapon = weapons.get(0);
}
@Override
public void update() {
// 移动逻辑
x += dx;
y += dy;
// 边界检查
x = Math.max(0, Math.min(GamePanel.WIDTH - width, x));
y = Math.max(0, Math.min(GamePanel.HEIGHT - height, y));
// 更新武器冷却
currentWeapon.update();
}
public List<Bullet> shoot() {
return currentWeapon.fire(x + width/2, y);
}
// 其他方法:切换武器、受伤、拾取道具等...
}
玩家输入通过KeyListener实现:
java复制public class InputHandler implements KeyListener {
private Player player;
public InputHandler(Player player) {
this.player = player;
}
@Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT:
player.setDx(-5);
break;
case KeyEvent.VK_RIGHT:
player.setDx(5);
break;
// 其他按键处理...
}
}
@Override
public void keyReleased(KeyEvent e) {
// 松开按键时停止移动
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT:
case KeyEvent.VK_RIGHT:
player.setDx(0);
break;
// 其他按键释放处理...
}
}
}
武器系统采用策略模式,方便扩展新武器类型:
java复制public abstract class Weapon {
protected int damage;
protected int fireRate; // 射击间隔(帧)
protected int cooldown;
protected int ammo;
public abstract List<Bullet> fire(int x, int y);
public void update() {
if(cooldown > 0) cooldown--;
}
protected boolean canFire() {
return cooldown == 0 && ammo > 0;
}
}
// 具体武器实现
public class BasicWeapon extends Weapon {
public BasicWeapon() {
damage = 10;
fireRate = 10;
ammo = 100;
}
@Override
public List<Bullet> fire(int x, int y) {
List<Bullet> bullets = new ArrayList<>();
if(canFire()) {
bullets.add(new Bullet(x, y, 5, 15, 0, -10, damage, true));
cooldown = fireRate;
ammo--;
}
return bullets;
}
}
public class SpreadWeapon extends Weapon {
@Override
public List<Bullet> fire(int x, int y) {
List<Bullet> bullets = new ArrayList<>();
if(canFire()) {
// 发射三发散弹
bullets.add(new Bullet(x, y, 5, 15, -2, -10, damage, true));
bullets.add(new Bullet(x, y, 5, 15, 0, -10, damage, true));
bullets.add(new Bullet(x, y, 5, 15, 2, -10, damage, true));
cooldown = fireRate;
ammo -= 3;
}
return bullets;
}
}
敌人系统采用工厂模式生成不同类型的敌人:
java复制public abstract class Enemy extends GameObject {
protected int health;
protected int damage;
protected int scoreValue;
public Enemy(int x, int y, int width, int height) {
super(x, y, width, height);
}
public boolean takeDamage(int damage) {
health -= damage;
return health <= 0;
}
}
// 具体敌人类
public class BasicEnemy extends Enemy {
public BasicEnemy(int x, int y) {
super(x, y, 40, 40);
health = 30;
damage = 10;
scoreValue = 100;
dy = 2; // 向下移动速度
}
@Override
public void update() {
y += dy;
// 简单AI:随机左右移动
if(Math.random() < 0.02) {
dx = (int)(Math.random() * 5 - 2);
}
x += dx;
}
}
public class EnemyFactory {
public static Enemy createEnemy(String type, int x, int y) {
switch(type) {
case "basic":
return new BasicEnemy(x, y);
case "fast":
return new FastEnemy(x, y);
case "armored":
return new ArmoredEnemy(x, y);
default:
throw new IllegalArgumentException("Unknown enemy type");
}
}
}
敌人生成采用波次系统:
java复制public class WaveSystem {
private int currentWave = 0;
private int enemiesRemaining = 0;
public void startNextWave() {
currentWave++;
enemiesRemaining = 5 + currentWave * 2;
// 根据波次调整敌人类型比例
for(int i=0; i<enemiesRemaining; i++) {
String type;
double r = Math.random();
if(currentWave > 5 && r < 0.2) {
type = "armored";
} else if(currentWave > 3 && r < 0.4) {
type = "fast";
} else {
type = "basic";
}
int x = (int)(Math.random() * (GamePanel.WIDTH - 50));
Enemy enemy = EnemyFactory.createEnemy(type, x, -50);
GamePanel.getInstance().addEnemy(enemy);
}
}
}
游戏采用以下渲染优化技术:
双缓冲技术:避免画面闪烁
java复制public class GamePanel extends JPanel {
private BufferedImage buffer;
public GamePanel() {
buffer = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = buffer.createGraphics();
// 在缓冲图像上绘制
renderGame(g2d);
g2d.dispose();
// 一次性绘制缓冲图像
g.drawImage(buffer, 0, 0, null);
}
}
脏矩形渲染:只重绘发生变化的部分区域(本游戏因对象较多未采用)
对象池模式:重用子弹和敌人对象,减少GC压力
游戏中的资源管理要点:
图像资源:使用ImageIO加载后缓存
java复制public class ResourceManager {
private static Map<String, BufferedImage> images = new HashMap<>();
public static BufferedImage getImage(String path) {
if(!images.containsKey(path)) {
try {
images.put(path, ImageIO.read(new File(path)));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
return images.get(path);
}
}
音效资源:使用Clip对象预加载短音效
对象池:对频繁创建销毁的对象(如子弹)使用对象池
线程安全问题:
内存泄漏:
输入延迟:
这个基础框架可以进一步扩展:
游戏功能扩展:
技术升级:
内容扩展:
我在实现这个项目过程中最大的体会是,游戏开发中最重要的是良好的架构设计。前期花时间设计清晰的类关系和交互方式,后期扩展和维护会轻松很多。比如采用组件模式而不是深度继承,可以更灵活地组合游戏对象的行为。