引言
恐龙跳跳(类似Chrome离线小恐龙游戏)是平台跳跃类游戏的极简代表。其核心算法涉及:物理模拟(重力、跳跃、碰撞)、无限地形生成、障碍物管理。本文用Java从零实现一个完整的平台跳跃物理引擎与程序化地形生成系统。
核心算法设计
1. 物理引擎(Verlet积分)
相比欧拉法,Verlet积分在能量守恒方面更稳定,适合平台跳跃游戏。
import java.util.*;
/**
* 二维向量
*/
class Vec2 {
double x, y;
public Vec2(double x, double y) { this.x = x; this.y = y; }
public Vec2 add(Vec2 v) { return new Vec2(x + v.x, y + v.y); }
public Vec2 sub(Vec2 v) { return new Vec2(x - v.x, y - v.y); }
public Vec2 mul(double s) { return new Vec2(x * s, y * s); }
}
/**
* 物理对象:恐龙或障碍物
*/
class PhysicsBody {
Vec2 position; // 当前位置
Vec2 prevPosition; // 上一帧位置(Verlet积分需要)
Vec2 acceleration; // 加速度
double width, height; // 包围盒尺寸
boolean isStatic; // 是否静止(如地面、平台)
public PhysicsBody(Vec2 pos, double w, double h, boolean isStatic) {
this.position = pos;
this.prevPosition = pos;
this.acceleration = new Vec2(0, 0);
this.width = w;
this.height = h;
this.isStatic = isStatic;
}
/**
* Verlet积分更新位置
* 公式:x(t+Δt) = 2x(t) - x(t-Δt) + a(t)Δt²
*/
public void updateVerlet(double dt) {
if (isStatic) return;
Vec2 velocity = position.sub(prevPosition);
// 加入阻尼,模拟空气阻力
velocity = velocity.mul(0.99);
Vec2 nextPos = position.add(velocity).add(acceleration.mul(dt * dt));
prevPosition = position;
position = nextPos;
acceleration = new Vec2(0, 0); // 重置加速度
}
/**
* AABB轴对齐包围盒碰撞检测
*/
public boolean intersects(PhysicsBody other) {
return position.x < other.position.x + other.width &&
position.x + width > other.position.x &&
position.y < other.position.y + other.height &&
position.y + height > other.position.y;
}
/**
* 获取包围盒四条边
*/
public double left() { return position.x; }
public double right() { return position.x + width; }
public double top() { return position.y + height; }
public double bottom() { return position.y; }
}
/**
* 平台跳跃物理世界
*/
class PlatformerWorld {
double gravity = -0.5; // 重力加速度
double groundY = 0; // 地面高度
List<PhysicsBody> bodies; // 所有物理对象
PhysicsBody player; // 玩家(恐龙)
double gameSpeed = 3.0; // 游戏滚动速度
public PlatformerWorld() {
this.bodies = new ArrayList<>();
// 创建玩家:宽20高20,位于(50, 100)
this.player = new PhysicsBody(new Vec2(50, 100), 20, 20, false);
this.bodies.add(player);
}
/**
* 添加平台或障碍物
*/
public void addPlatform(double x, double y, double w, double h) {
PhysicsBody platform = new PhysicsBody(new Vec2(x, y), w, h, true);
bodies.add(platform);
}
/**
* 玩家跳跃:施加向上的瞬时速度
*/
public void jump() {
// 只有在地面或平台上才能跳跃
if (isOnGroundOrPlatform()) {
// Verlet中通过修改prevPosition来模拟瞬时速度
player.prevPosition = new Vec2(player.position.x, player.position.y + 12);
}
}
/**
* 检测玩家是否站在地面或平台上
*/
private boolean isOnGroundOrPlatform() {
// 地面检测
if (Math.abs(player.bottom() - groundY) < 1.0) return true;
// 平台检测:下方是否有平台
for (PhysicsBody b : bodies) {
if (b != player && b.isStatic) {
if (Math.abs(player.bottom() - b.top()) < 2.0 &&
player.right() > b.left() && player.left() < b.right()) {
return true;
}
}
}
return false;
}
/**
* 物理帧更新
*/
public void update(double dt) {
// 1. 施加重力
if (!isOnGroundOrPlatform()) {
player.acceleration = new Vec2(0, gravity);
}
// 2. Verlet积分更新所有物体
for (PhysicsBody b : bodies) {
b.updateVerlet(dt);
}
// 3. 碰撞检测与响应
resolveCollisions();
// 4. 地面约束
if (player.bottom() < groundY) {
player.position = new Vec2(player.position.x, groundY + player.height);
player.prevPosition = new Vec2(player.prevPosition.x, player.position.y);
}
// 5. 世界滚动:所有静态物体向左移动,模拟恐龙向右跑
for (PhysicsBody b : bodies) {
if (b.isStatic) {
b.position = new Vec2(b.position.x - gameSpeed, b.position.y);
}
}
// 6. 移除超出左边界的地形
bodies.removeIf(b -> b.isStatic && b.right() < -100);
}
/**
* 碰撞响应:将玩家推出碰撞体
*/
private void resolveCollisions() {
for (PhysicsBody b : bodies) {
if (b == player || !b.isStatic) continue;
if (player.intersects(b)) {
// 判断碰撞方向
double overlapTop = player.top() - b.bottom(); // 从下方撞入
double overlapBottom = b.top() - player.bottom(); // 落到平台上
double overlapLeft = player.right() - b.left(); // 从左侧撞入
double overlapRight = b.right() - player.left(); // 从右侧撞入
// 找到最小穿透深度,沿该方向推出
double minOverlap = Math.min(
Math.min(overlapTop, overlapBottom),
Math.min(overlapLeft, overlapRight)
);
if (minOverlap == overlapBottom) {
// 站在平台上
player.position = new Vec2(player.position.x, b.top() + player.height);
player.prevPosition = new Vec2(player.prevPosition.x, player.position.y);
} else if (minOverlap == overlapTop) {
player.position = new Vec2(player.position.x, b.bottom());
player.prevPosition = new Vec2(player.prevPosition.x, player.position.y);
} else if (minOverlap == overlapLeft) {
// 撞到平台左侧,游戏结束或反弹
player.position = new Vec2(b.left() - player.width, player.position.y);
} else {
player.position = new Vec2(b.right(), player.position.y);
}
}
}
}
}
2. 程序化地形生成
使用一维Perlin噪声生成连续起伏的平台高度,保证地形可玩性(不会出现无法跳过的间隙)。
/**
* 一维Perlin噪声生成器
* 用于生成平滑随机地形
*/
class PerlinNoise1D {
private final int[] permutation;
private final int size = 256;
public PerlinNoise1D(long seed) {
Random rand = new Random(seed);
permutation = new int[size * 2];
for (int i = 0; i < size; i++) permutation[i] = i;
// Fisher-Yates shuffle
for (int i = size - 1; i > 0; i--) {
int j = rand.nextInt(i + 1);
int tmp = permutation[i];
permutation[i] = permutation[j];
permutation[j] = tmp;
}
System.arraycopy(permutation, 0, permutation, size, size);
}
private double fade(double t) {
return t * t * t * (t * (t * 6 - 15) + 10); // 6t⁵ - 15t⁴ + 10t³
}
private double lerp(double a, double b, double t) {
return a + t * (b - a);
}
private double grad(int hash, double x) {
return (hash & 1) == 0 ? x : -x;
}
public double noise(double x) {
int X = (int) Math.floor(x) & (size - 1);
double xf = x - Math.floor(x);
double u = fade(xf);
return lerp(grad(permutation[X], xf), grad(permutation[X + 1], xf - 1), u);
}
}
/**
* 无限地形生成器
*/
class InfiniteTerrainGenerator {
PerlinNoise1D heightNoise;
PerlinNoise1D gapNoise;
double chunkWidth = 200; // 每块地形宽度
double lastGeneratedX = 0;
Random random;
public InfiniteTerrainGenerator(long seed) {
this.heightNoise = new PerlinNoise1D(seed);
this.gapNoise = new PerlinNoise1D(seed + 1);
this.random = new Random(seed);
}
/**
* 根据游戏世界状态生成新地形块
* @param world 物理世界
* @param screenRight 屏幕右边界
*/
public void generateAhead(PlatformerWorld world, double screenRight) {
while (lastGeneratedX < screenRight + 500) {
generateChunk(world, lastGeneratedX);
lastGeneratedX += chunkWidth;
}
}
private void generateChunk(PlatformerWorld world, double startX) {
double baseHeight = 50; // 基础地面高度
double noiseScale = 0.01;
// 使用Perlin噪声生成地形高度
double n = heightNoise.noise(startX * noiseScale);
double platformHeight = baseHeight + n * 40; // 高度范围:10-90
// 随机决定是否生成平台(连续平台不生成间隙)
double gapValue = gapNoise.noise(startX * noiseScale * 2);
if (gapValue > 0.3) {
// 生成平台
double platformWidth = 80 + random.nextDouble() * 80;
world.addPlatform(startX, platformHeight, platformWidth, 10);
} else {
// 生成地面(避免过大间隙,保证可跳性)
world.addPlatform(startX, baseHeight - 20, chunkWidth, 20);
}
// 随机生成低矮障碍物(仙人掌类)
if (random.nextDouble() < 0.3) {
double obsX = startX + 20 + random.nextDouble() * (chunkWidth - 40);
double obsHeight = 15 + random.nextDouble() * 15;
world.addPlatform(obsX, baseHeight - 20 + obsHeight, 10, obsHeight);
}
}
}
3. 跳跃AI决策
让AI自动判断何时跳跃。
/**
* 简单的跳跃AI:基于前方障碍物检测决定跳跃时机
*/
class JumpAI {
double detectionRange = 80; // 前方检测距离
double minJumpDistance = 30; // 最小跳跃触发距离
/**
* 决策是否跳跃
* @param player 玩家对象
* @param bodies 世界中所有物体
* @return true表示应该跳跃
*/
public boolean shouldJump(PhysicsBody player, List<PhysicsBody> bodies) {
double playerBottom = player.bottom();
double playerRight = player.right();
for (PhysicsBody b : bodies) {
if (b.isStatic && b != player) {
// 检测前方是否有高于地面的障碍物
if (b.left() > playerRight && b.left() < playerRight + detectionRange) {
if (b.top() > playerBottom + 5) { // 障碍物高度超过脚底
// 判断距离是否进入跳跃区间
double dist = b.left() - playerRight;
if (dist <= minJumpDistance + 10 && dist >= minJumpDistance - 5) {
return true;
}
}
}
}
}
return false;
}
}
演示程序
public class DinoJumpDemo {
public static void main(String[] args) {
PlatformerWorld world = new PlatformerWorld();
InfiniteTerrainGenerator terrain = new InfiniteTerrainGenerator(12345);
JumpAI ai = new JumpAI();
// 初始化地形
terrain.generateAhead(world, 800);
System.out.println("=== 恐龙跳跳模拟 ===");
double dt = 0.5; // 时间步长
for (int frame = 0; frame < 50; frame++) {
// AI决策
if (ai.shouldJump(world.player, world.bodies)) {
world.jump();
System.out.printf("[帧%d] 跳跃! 高度:%.1f\n", frame, world.player.position.y);
}
// 更新物理
world.update(dt);
terrain.generateAhead(world, world.player.position.x + 800);
// 输出状态
System.out.printf("帧%d: 恐龙位置(%.1f, %.1f), 地面物体数:%d\n",
frame, world.player.position.x, world.player.position.y, world.bodies.size());
// 检测是否撞到障碍物(简化:高度过低表示掉落)
if (world.player.position.y < -100) {
System.out.println("恐龙掉落,游戏结束!");
break;
}
}
}
}
复杂度分析
- Verlet积分:每帧O(n),n为物体数量
- 碰撞检测:O(n²),使用空间划分可优化至O(n)
- Perlin噪声:O(1)每采样点
- 地形生成:O(m)每帧,m为新增平台数
总结
本文从物理模拟、碰撞检测、程序化生成三个维度构建平台跳跃游戏核心。Verlet积分提供稳定的物理模拟;AABB碰撞检测高效处理平台交互;Perlin噪声保证地形既随机又可玩。读者可在此基础上扩展双倍跳跃、斜坡滑动、敌人AI等高级特性。