实战篇:实时交互场景综合实现
概述
本篇把第 5 周的六个模块合成一个综合案例:一个"双人弹射对战"小游戏,走状态同步 + 权威服务器路线,综合运用碰撞检测(反弹)、倒计时(回合时限)、随机算法(弹射散布)与行为验证(反外挂)。通过完整代码展示实时交互场景的服务器侧工程结构。
一、案例设计与选型
1.1 玩法定义
双人弹射对战:
场地 1000×600(AABB 边界)
每人一个弹珠(圆碰撞体)
轮流发射弹珠撞击对方 → 命中扣血
回合时限 15s,超时自动发射
命中随机散布(种子随机)
服务端权威计算所有碰撞1.2 同步选型
为什么选状态同步:
2 人小场景 → 服务器算力完全够
碰撞/胜负权威 → 反作弊强
客户端简单(只表现)
技术组合:
状态同步 + 权威碰撞 + 回合倒计时 + 种子随机 + 速度校验1.3 代码结构
com.game.gamemodule.shoot
├── model
│ ├── Shooter.java // 玩家实体(弹珠)
│ ├── Aabb.java // 碰撞体
│ └── ShootGame.java // 对局(Tick 循环)
├── service
│ ├── PhysicsService.java // 碰撞与反弹
│ ├── TurnTimer.java // 回合倒计时
│ └── AntiCheatService.java // 行为验证
├── handler
│ └── ShootHandler.java // 玩家操作
└── constants
└── Ops.java // 消息 Opcode二、实体与碰撞
2.1 弹珠实体
java
package com.game.gamemodule.shoot.model;
public class Shooter {
private final long playerId;
private int x, y; // 位置
private int vx, vy; // 速度
private int hp = 3; // 血量
public Shooter(long playerId, int x, int y) {
this.playerId = playerId;
this.x = x;
this.y = y;
}
public void move(int dt) {
x += vx * dt;
y += vy * dt;
}
// getter / setter 略
}2.2 AABB 与圆
java
package com.game.gamemodule.shoot.model;
public class Aabb {
public final int minX, minY, maxX, maxY;
public Aabb(int minX, int minY, int maxX, int maxY) {
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
// 圆(弹珠)与矩形边界相交检测
public boolean intersectsCircle(int cx, int cy, int r) {
int px = Math.max(minX, Math.min(cx, maxX));
int py = Math.max(minY, Math.min(cy, maxY));
int dx = cx - px;
int dy = cy - py;
return dx * dx + dy * dy <= r * r;
}
}三、物理与碰撞服务
3.1 反弹逻辑
java
package com.game.gamemodule.shoot.service;
import com.game.gamemodule.shoot.model.Aabb;
import com.game.gamemodule.shoot.model.Shooter;
public class PhysicsService {
private static final Aabb FIELD = new Aabb(0, 0, 1000, 600);
private static final int RADIUS = 20;
// 更新弹珠:移动 + 边界反弹
public void update(Shooter s, int dt) {
s.move(dt);
// X 轴边界反弹
if (s.getX() - RADIUS < FIELD.minX) {
s.setX(FIELD.minX + RADIUS);
s.setVx(Math.abs(s.getVx())); // 反向
} else if (s.getX() + RADIUS > FIELD.maxX) {
s.setX(FIELD.maxX - RADIUS);
s.setVx(-Math.abs(s.getVx()));
}
// Y 轴同理
if (s.getY() - RADIUS < FIELD.minY) {
s.setY(FIELD.minY + RADIUS);
s.setVy(Math.abs(s.getVy()));
} else if (s.getY() + RADIUS > FIELD.maxY) {
s.setY(FIELD.maxY - RADIUS);
s.setVy(-Math.abs(s.getVy()));
}
}
// 双弹珠相撞:互相弹开(简化:交换速度投影)
public void collide(Shooter a, Shooter b) {
int dx = a.getX() - b.getX();
int dy = a.getY() - b.getY();
int dist2 = dx * dx + dy * dy;
if (dist2 < 4 * RADIUS * RADIUS) {
int tmp = a.getVx(); a.setVx(b.getVx()); b.setVx(tmp);
tmp = a.getVy(); a.setVy(b.getVy()); b.setVy(tmp);
}
}
// 命中检测:A 弹珠是否击中 B(圆 vs 圆)
public boolean hit(Shooter a, Shooter b) {
int dx = a.getX() - b.getX();
int dy = a.getY() - b.getY();
return dx * dx + dy * dy < 4 * RADIUS * RADIUS;
}
}要点:
边界反弹:越过边界 → 位置修正 + 速度反向
弹珠互撞:距离判断 + 速度交换(简化弹性碰撞)
命中:圆与圆距离判定
整数运算保证确定性四、回合倒计时
4.1 倒计时实现
java
package com.game.gamemodule.shoot.service;
import com.game.gamemodule.shoot.model.ShootGame;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class TurnTimer {
private final HashedWheelTimer timer = new HashedWheelTimer();
private final ConcurrentHashMap<Long, Timeout> timeouts = new ConcurrentHashMap<>();
// 开启回合倒计时:超时自动发射
public void start(ShootGame game, long playerId, long millis) {
cancel(playerId);
Timeout timeout = timer.newTimeout(t -> {
timeouts.remove(playerId);
// 触发时校验:仍是该玩家回合
if (game.isCurrentPlayer(playerId)) {
game.autoShoot(); // 超时自动发射
}
}, millis, TimeUnit.MILLISECONDS);
timeouts.put(playerId, timeout);
}
public void cancel(long playerId) {
Timeout timeout = timeouts.remove(playerId);
if (timeout != null) {
timeout.cancel();
}
}
public void clearAll() {
timeouts.values().forEach(Timeout::cancel);
timeouts.clear();
}
}要点:
绝对时间判定在 ShootGame 内(isCurrentPlayer 校验)
玩家操作 → cancel 旧定时器
房间解散 → clearAll
防幽灵回调(校验仍是当前回合)五、随机算法(弹射散布)
5.1 种子随机
java
package com.game.gamemodule.shoot.service;
import java.util.Random;
public class SeededRandom {
private final Random random;
// 服务器生成种子(回放/审计用)
public SeededRandom(long seed) {
this.random = new Random(seed);
}
// 发射角度微调(散布):-spread ~ +spread
public int scatter(int baseAngle, int spread) {
return baseAngle + random.nextInt(spread * 2) - spread;
}
}说明:
种子由服务器生成并记录(可追溯)
散射角度影响发射方向 → 增加策略深度
弹射命中后的伤害判定用 PRD(保底/均匀)
反作弊:命中由服务器碰撞判定(客户端上报无效)六、行为验证(反外挂)
6.1 速度与命中校验
java
package com.game.gamemodule.shoot.service;
public class AntiCheatService {
private static final int MAX_SPEED = 300; // 每 Tick 最大速度
// 发射校验:速度在合法上限内
public boolean validShoot(int vx, int vy) {
return vx * vx + vy * vy <= MAX_SPEED * MAX_SPEED;
}
// 命中校验:服务器权威判定,客户端上报无效
public boolean isServerAuthoritative() {
return true; // 碰撞/扣血全部在服务器计算
}
}反外挂设计:
发射速度上限(防"无限加速"外挂)
命中与扣血全服务端(客户端改不了)
操作频率限制(频控,见第 4 周)
行为日志(审计)七、对局 Tick 与主流程
7.1 Tick 循环
java
package com.game.gamemodule.shoot.model;
import com.game.gamemodule.shoot.service.PhysicsService;
import java.util.List;
public class ShootGame {
private final long playerA;
private final long playerB;
private long currentTurn; // 当前回合玩家
private final List<Shooter> shooters;
private final PhysicsService physics = new PhysicsService();
private static final long TICK_MS = 50; // 20Hz
// 每个 Tick:更新物理 + 判定
public void tick() {
for (Shooter s : shooters) {
physics.update(s, 1); // 移动 + 边界反弹
}
physics.collide(shooters.get(0), shooters.get(1));
checkHit();
// 广播状态给全员(此处由外部调用方执行)
}
private void checkHit() {
Shooter a = shooters.get(0);
Shooter b = shooters.get(1);
// 只在发射后检测(简化:始终检测)
if (physics.hit(a, b)) {
b.setHp(b.getHp() - 1);
// 扣血事件广播 + 胜负判定
}
}
public boolean isCurrentPlayer(long playerId) {
return currentTurn == playerId;
}
}7.2 完整流程
1. 匹配成功 → 创建 ShootGame(种子随机生成)
2. 开局广播:场地、双方初始位置、种子、先手
3. 回合循环:
当前玩家发射(带角度/力度)
→ 服务器校验速度 → 计算散射 → 广播弹道
→ Tick 循环推进物理(反弹/相撞)
→ 命中判定 → 扣血广播 → 换回合(15s 倒计时)
4. 血量归零 → 判定胜负 → 结算(复用第 4 周结算服务)
5. 房间回 Waiting / 解散7.3 验证清单
1. 弹珠碰到边界反弹(位置修正正确)
2. 双弹相撞弹开(速度交换)
3. 命中扣血:服务器判定,客户端上报无效
4. 发射超速被拒(反外挂)
5. 回合超时自动发射(倒计时触发)
6. 玩家操作后旧定时器取消(不误触发)
7. 种子随机:同种子发射序列可复现
8. 断线重连:下发全量状态快照恢复八、小结
本篇用"双人弹射对战"综合呈现实时交互场景的服务器工程:选型上按"小规模 + 强反作弊"选定状态同步权威模式;PhysicsService 以整数运算实现移动、边界反弹、弹珠相撞与命中判定;TurnTimer 用 HashedWheelTimer 驱动回合倒计时,操作即取消、触发再校验防幽灵回调;SeededRandom 让弹射散射可复现可审计;AntiCheatService 用速度上限与服务端权威命中堵住常见外挂。整个案例证明:实时交互并不神秘,选对同步模型、算好碰撞、管好时间与随机,再用服务端权威守住边界,一套稳定可靠的实时玩法就立住了。