第一次接触Repast Simphony时,我也被这个强大的多Agent仿真平台震撼到了。它就像是一个虚拟世界的实验室,让我们能够用代码构建各种复杂系统的动态模型。今天要分享的僵尸感染传播模型,就是我在学习Repast时完成的第一个完整项目。
Repast Simphony最吸引我的地方在于它提供了完整的仿真开发生命周期支持。从模型设计、代码实现到可视化展示和数据分析,全部都能在一个集成环境中完成。对于初学者来说,最需要适应的就是它的项目结构。在Eclipse中切换到Repast Simphony视图后,你会发现标准Java项目被扩展成了包含context.xml、scenario.rs等配置文件的特殊结构。
这里有个小技巧:创建新项目时,建议直接使用Repast提供的模板。我第一次手动配置项目花了整整半天时间排查各种路径问题,而用模板只需要几分钟就能搭建好基础环境。项目中的关键目录包括:
在僵尸模型中,我们主要用到三种空间投影:
实际开发中发现,ContinuousSpace和Grid的配合使用特别重要。僵尸和人类的移动在视觉上需要连续平滑,但感染判断又需要精确到具体网格位置。这就涉及到坐标转换的问题:
java复制// 连续坐标转网格坐标
NdPoint continuousPt = space.getLocation(obj);
GridPoint gridPt = new GridPoint((int)continuousPt.getX(), (int)continuousPt.getY());
// 网格坐标转连续坐标
NdPoint newContinuousPt = new NdPoint(gridPt.getX(), gridPt.getY());
WrapAroundBorders是我最推荐的边界处理方式,它让空间变成环形拓扑结构。这意味着当Agent移动到边界时会从另一侧重新出现,这种设计既符合游戏场景需求,又能避免复杂的边界条件判断。在初始化空间时这样配置:
java复制ContinuousSpace<Object> space = spaceFactory.createContinuousSpace(
"space", context,
new RandomCartesianAdder<Object>(),
new WrapAroundBorders(), 50, 50);
僵尸的核心行为包括追踪人类和感染传播。在step()方法中,我实现了基于网格邻域查询的追踪逻辑:
java复制@ScheduledMethod(start = 1, interval = 1)
public void step() {
GridPoint currentPos = grid.getLocation(this);
GridCellNgh<Human> ngh = new GridCellNgh<>(grid, currentPos, Human.class, 1, 1);
List<GridCell<Human>> neighbors = ngh.getNeighborhood(true);
// 找出人类最多的网格点
GridPoint target = findMostHumans(neighbors);
moveTowards(target);
infect();
}
这里有个性能优化点:SimUtilities.shuffle()可以随机打乱邻居列表顺序,避免Agent总是朝固定方向移动产生规律性模式。
人类的逃跑行为通过@Watch注解实现事件驱动,这是Repast中非常实用的特性:
java复制@Watch(watcheeClassName = "jzombies.Zombie",
watcheeFieldNames = "moved",
query = "within_moore 1",
whenToTrigger = WatcherTriggerSchedule.IMMEDIATE)
public void run() {
// 寻找僵尸最少的移动方向
GridPoint safest = findSafestDirection();
if(energy > 0) {
moveTowards(safest);
energy--;
} else {
rest(); // 能量恢复
}
}
能量系统的设计让模型更有趣 - 人类不能无限逃跑,必须合理安排移动和休息。这个机制后来被我扩展成了资源收集系统。
JZombiesBuilder类是整个模型的组装车间。我建议按这个顺序构建环境:
java复制@Override
public Context build(Context<Object> context) {
context.setId("jzombies");
// 1. 创建空间
createSpaces(context);
// 2. 构建网络
buildNetwork(context);
// 3. 添加Agent
populateAgents(context);
return context;
}
通过Parameters实现运行时参数配置是个好习惯:
xml复制<!-- 在scenario.rs文件中定义 -->
<parameter name="zombie_count" type="int" value="5" />
<parameter name="human_count" type="int" value="20" />
代码中这样读取参数:
java复制Parameters params = RunEnvironment.getInstance().getParameters();
int zombies = params.getInteger("zombie_count");
int humans = params.getInteger("human_count");
在Displays配置时,我总结了几点经验:
Repast的数据收集系统非常强大。我通常会设置这些数据集:
java复制// 示例:设置时间序列图表
TimeSeriesChart chart = new TimeSeriesChart("Population Dynamics");
chart.addSeries("Zombies", zombieCountDataSource);
chart.addSeries("Humans", humanCountDataSource);
完成基础版本后,我尝试了几种有趣的扩展:
性能优化方面,这些措施很有效:
记得第一次运行时,我的模型在100个Agent时就卡顿了。通过优化邻域查询和减少实时渲染元素,最终版本可以流畅运行1000+Agent的仿真。