尽管在清单 1 中使用了具体的颜色值(即红色是 200,绿色是 0,蓝色是 0),但是我们可以容易地改进代码,让它支持颜色范围。使用颜色范围降低了在绘图程序中使用的颜色选择的精度,因而更容易创建路径图像。
使用更多的颜色使我们可以定义更多状态,还可以描述更复杂的行为。例如,可以使用 RGB 方案中的不同颜色段创建重叠的控制路径。如果上述每一种状态都是由一种颜色的不同深浅而不是由不同颜色编码的,那么三个独立的控制路径可以彼此重叠。当然,使用一种颜色的不同深浅使得区分不同行为的细微颜色差别变得困难了。大多数图像编辑程序可显示出所选像素的准确颜色值,这使这个问题没那么严重了。
还可以定义三个以上的控制路径。如果通过一个位掩码(bitmask)访问每一个颜色值,那么将只受图像格式的位数限制(通常是 24,如果使用 alpha 值则为 32)。使用精确的位的路径比使用颜色段的路径更复杂,但是这是可以做到的。您需要有一个可以合并各个图像控制路径的程序或者使用一个图像并在上面添加绘制。如果不需要支持重叠的路径(即在一个位置上有多种状态),那么在一个位置上可以有 2^24(或者 2^32)种状态。还可混合这两种方法。例如,通过位掩码使用红色段,而用绿色段和蓝色段表示其他状态。
图 4 显示了我们的逃生模拟所使用的完整控制路径。注意多种颜色的使用,以及如何用颜色表示不同位置上的不同行为。
图 4. 完整的控制路径
图 5 局部放大了控制路径以看得更清楚。
图 5. 控制路径局部细节
逃生!
在定义了状态映射后,就可以开始在 2D 空间中移动对象了。这个示例逃生应用程序让可移动对象成为 Entity 类的实例。定义了两个主要子类: Person 和 Alarm 。 Person 可以移动,而 Alarm 是静止的。清单 2 定义了 Entity 接口。
清单 2. Entity 接口
2 void addToPanel(JPanel panel, boolean shared);
3
4 void updateTick();
5 }
6
addToPanel() 方法创建一个或者多个 Swing 组件来表示对象并将它们添加到所提供的面板中,这些组件一般是带有图标集的 JLabel 。面板通常是 2D 空间的实现。它的背景显示动画背景。
updateTick() 方法使对象在动画的每一周期活动。 Alarm 对象改变它们的颜色以创建闪烁的效果。 Person 对象则移动。
Alarm 对象是简单的闪烁对象,其实现如清单 3 所示。
清单 3. Alarm.updateTick: 闪烁
2 if (++tick % CYCLE == 0) {
3 opaque = !opaque;
4 }
5 }
6
Person 对象比 Alarm 复杂。它们沿着定义的控制路径移动,如清单 4 所示。
清单 4. Person.updateTick: 沿着路径移动
2 public synchronized void updateTick() {
3 tick++;
4 Integer tock = stops.get(new Integer(tick));
5 if (tock != null) { // adjust startTime if requested
6 startTick = tick + tock.intValue();
7 }
8 if (tick < startTick) return; // not my time yet
9 if (isAtExit()) return;
10 // Process individual movement
11 Point2D location = getPosition();
12 int x = (int)location.getX();
13 int y = (int)location.getY();
14 switch (manager.stateAt(x, y)) {
15 case BuildingManager.STATE_EXIT:
16 atExit = true;
17 break;
18 case BuildingManager.STATE_START:
19 case BuildingManager.STATE_INTERSECTION:
20 // process any hints
21 if (manager.stateAt(x - 1, y) ==
22 BuildingManager.STATE_HINT)
23 setDirection(Person.DIR_WEST);
24 else if (manager.stateAt(x + 1, y) ==
25 BuildingManager.STATE_HINT)
26 setDirection(Person.DIR_EAST);
27 else if (manager.stateAt(x, y + 1) ==
28 BuildingManager.STATE_HINT)
29 setDirection(Person.DIR_SOUTH);
30 else if (manager.stateAt(x, y - 1) ==
31 BuildingManager.STATE_HINT)
32 setDirection(Person.DIR_NORTH);
33 // no hints, select a direction
34 if (getDirection() == DIR_NONE) {
35 if (manager.stateAt(x - 1, y) !=
36 BuildingManager.STATE_NONE)
37 setDirection(Person.DIR_WEST);
38 else if (manager.stateAt(x + 1, y) !=
39 BuildingManager.STATE_NONE)
40 setDirection(Person.DIR_EAST);
41 else if (manager.stateAt(x, y + 1) !=
42 BuildingManager.STATE_NONE)
43 setDirection(Person.DIR_SOUTH);
44 else if (manager.stateAt(x, y - 1) !=
45 BuildingManager.STATE_NONE)
46 setDirection(Person.DIR_NORTH);
47 }
48 case BuildingManager.STATE_HALLWAY:
49 case BuildingManager.STATE_HINT:
50 // effect motion in selected direction
51 int tempX = x;
52 int tempY = y;
53 switch (getDirection()) {
54 case DIR_EAST: x += 1; break;
55 case DIR_WEST: x -= 1; break;
56 case DIR_NORTH: y -= 1; break;
57 case DIR_SOUTH: y += 1; break;
58 }
59 int check = manager.stateAt(x, y);
60 if (check == manager.STATE_UNKNOWN ||
61 check == manager.STATE_NONE) {
62 // went off the path, backup
63 x = tempX;
64 y = tempY;
65 if (getDirection() == DIR_EAST ||
66 getDirection() == DIR_WEST) {
67 if (manager.stateAt(x, y + 1) !=
68 BuildingManager.STATE_NONE &&
69 manager.stateAt(x, y + 1) !=
70 BuildingManager.STATE_UNKNOWN) {
71 setDirection(Person.DIR_SOUTH);
72 y += 1;
73 }
74 else {
75 // Only direction not checked is north
76 setDirection(Person.DIR_NORTH);
77 y -= 1;
78 }
79 }
80 else {
81 if (manager.stateAt(x + 1, y) !=
82 BuildingManager.STATE_NONE &&
83 manager.stateAt(x + 1, y) !=
84 BuildingManager.STATE_UNKNOWN) {
85 setDirection(Person.DIR_EAST);
86 x += 1;
87 }
88 else {
89 // Only direction not checked is south
90 setDirection(Person.DIR_WEST);
91 x -= 1;
92 }
93 }
94 }
95 setNextPoint(new Point(x, y));
96 }
97 }
98
这个相当复杂的方法主要是检查当前位置的映射。然后选择要去的非常好的位置。它试图尽可能地沿同一个方向走。注意 hints是标志,提供了优先选择的方向。它们通常用于开始位置和交叉路口。
Person 可以过早地(也就是在达到路径终点之前)停止,也可以设定为经过固定的时间后才开始移动。 Person 对象还可以留下逐渐消失的图像痕迹(或者历史)以描绘出它们的运动,如图 6 所示。
图 6. 一个带有历史痕迹的 person 移动
