类 BuildingViewer 创建让对象在其中移动的容器。 paintChldren() 方法首先绘制背景图像,然后是报警消息,最后是表示不同实体的子组件。
清单 8. BuildingViewer.paintChildren
2 private static final Color evacColor = new Color(255, 0, 0, 128);
3 :
4 /** draw the background, massage and entities */
5 public void paintChildren(Graphics g) {
6 paintCount++;
7 if (background != null) {
8 Graphics g2 = g.create();
9 try {
10 // draw the background
11 g2.drawImage(background.getImage(),
12 (int)getLocation().getX(),
13 (int)getLocation().getY(),
14 (int)getLocation().getX() + getWidth(),
15 (int)getLocation().getY() + getHeight(),
16 0, 0,
17 background.getIconWidth(),
18 background.getIconHeight(),
19 Color.black, null);
20 // draw the alert message (if any)
21 if (alertMessage != null) {
22 if (paintCount % alertPeriod >= (alertPeriod / 2)) {
23 Font f = g2.getFont();
24 Font f2 = f.deriveFont((float)alertSize);
25 FontMetrics fm = Toolkit.getDefaultToolkit().
26 getFontMetrics(f2);
27 int fHeight = fm.getHeight(),
28 fAscent = fm.getAscent();
29 int sWidth = fm.stringWidth(alertMessage);
30 g2.setFont(f2);
31 Graphics2D g2d = (Graphics2D)g2;
32 g2d.setStroke(new BasicStroke(10));
33 g2.setColor(evacColor);
34 g2.drawString(alertMessage,
35 (getWidth() - sWidth) / 2,
36 (getHeight() - fHeight) / 2 +
37 Ascent);
38 }
39 }
40 } finally {
41 g2.dispose();
42 }
43 }
44 super.paintChildren(g);
45 }
46
要创建有效的动画,需要移动的对象。清单 9 显示的代码根据所提供的输入创建一系列同样类型(即残疾的、非残疾的、消防员等)的 Person 实体。
清单 9. 创建相同类型的实体
2 protected static void initLoop(BuildingManager manager, ImageIcon icon,
3 int[] locs, String[] names,
4 int[] starts, int[] appear, int[][][] stops)
5 {
6 LinkedList startPts = (LinkedList)manager.
7 getAvailableStartingPoints();
8 // for all specified locations - create a Person
9 for (int i = 0; i < locs.length; i++) {
10 JLabel label = new JLabel(names[i], icon, JLabel.CENTER);
11 label.setFont(new Font(label.getFont().getName(),
12 label.getFont().getStyle(), 20));
13 Person person = new Person(manager, label,
14 (Point2D)startPts.get(
15 Math.min(startPts.size() - 1, locs[i])),
16 starts[i]);
17 person.setAppearTick(appear[i]);
18 // defines stop locations for each Person
19 for (int j = 0; j < stops[i].length; j++) {
20 person.addStop(stops[i][j][0], stops[i][j][1]);
21 }
22 manager.addEntity(person);
23 }
24 }
25
清单 10 用清单 9 中的 initLoop 代码定义了一组 Person 实体。这段代码使用几个平行的数组(根据 locs 数组的长度)提供有关要创建的对象的信息。 locs 数组为用控制路径提供的一组定义好的开始位置提供了索引。 starts 值指定在什么时间让 Person 开始移动。 appear 值定义了什么时候应当让 Person 变为可见(通常是在开始移动之前)。 stops 值指定每个 Person 可以有的停止点(可能有多个)。
尽管下面显示的代码是以手工键入值,但是也可以通过增加表示位置实体状态的新颜色来从控制路径获得大多数这些输入值。这种增强可以简化这些值的输入,并减少控制路径改变时出错的可能性。
清单 10. 创建所有 Person 实体
2 static public void createPeople(BuildingManager manager,
3 ImageIcon employIcon,
4 ImageIcon fireIcon,
5 ImageIcon disabledIcon)
6 {
7 // Main character - ALEX
8 int locs[] = new int[] {42};
9 String names[] = new String[] {"Alex"};
10 int starts[] = new int[] { 300 };
11 int appear[] = new int[] { 0 };
12 int stops[][][] = new int[][][] {{}};
13 initLoop(manager, employIcon, locs, names, starts, appear, stops);
14 // Some disabled people
15 locs = new int[] { 39, 45 };
16 names = new String[] { "Karen", "Mike" };
17 starts = new int[] { 0, 0};
18 appear = new int[] { 0, 0 };
19 stops = new int[][][] {{{1, 164}, {560, 20}},
20 {{1, 141}, {460, 30}}};
21 initLoop(manager, disabledIcon, locs, names, starts, appear, stops);
22 // Some Assisters
23 locs = new int[] {44, 49, 37, 46};
24 names = new String[] { "Tom", "Joe", "Cathy", "Larry" };
25 starts = new int[] { 0, 0, 0, 0};
26 appear = new int[] { 0, 0, 0, 0 };
27 stops = new int [][][] {{{120, 52}, {560, 20}},
28 {{155, 24}, {560, 20}},
29 {{122, 27}, {460, 30}},
30 {{100, 59}, {460, 30}}};
31 initLoop(manager, employIcon, locs, names, starts, appear, stops);
32 // A firemen
33 locs = new int[] { 25 };
34 names = new String[] { "FD", "FD 2", "FD 3" };
35 starts = new int[] { 400, 400, 400};
36 appear = new int[] { 400, 400, 400 };
37 stops = new int [][][] {{},{}, {}};
38 initLoop(manager, fireIcon, locs, names, starts, appear, stops);
39 :
40 : **** many additional definition sets omitted ****
41 :
42 }
43
最后,清单 11 显示了如何创建一个 Alarm 实体。显然,我们可以容易地根据需要增加更多的报警。
清单 11. 创建报警
2 static public void createAlarms(BuildingManager manager) {
3 final int alarms[] = { 12 };
4 LinkedList startPts = (LinkedList)manager.
5 getAvailableStartingPoints();
6 for (int i = 0; i < alarms.length; i++) {
7 manager.addEntity(
8 new Alarm(manager, (Point2D)startPts.get(alarms[i])));
9 }
10 }
11
结束语
在本文中,我们展示了如何用无损图像、Swing 技术和自定义的动画引擎来生成 2D 动画中的运动路径。这种方法使我们可以用控制路径以一种快速和可预言的方式可视化地创建动画。这种技术的优点如下:
易于使用
大多数编辑程序都有几种生成直线、圆弧和其他形状的方法。这些选择使我们可以手工迅速生成一些路径,同时减少错误。这对于某些行为是非常有用的。
引用图像
当动画相对于背景图像运动时(如在 图 1中),我们可能希望让对象在图像中的某些区域内运动,如让对象保持在走廊中。许多图像编辑程序可以让我们使用半透明的图层在背景图上面生成控制图像。这样我们就可以容易地创建与背景图相匹配的控制路径,因为在生成控制图像时可以同时看到叠加在一起的两幅图像。
增加绘制
通过混合颜色,我们可以在一个位置上编码多种行为。例如,使用 RGB 颜色时,可以用红色 (0xFF0000) 表示一个对象所要走的路径,绿色 (0x00FF00) 编码另一个对象要走的路径。使用增加绘制时,在路径交叉的点将会是黄色 (0xFFFF00)。
使用这种增加绘制模型时,当使用比如说一个 32 位颜色模型时,可以从一个给定位置以位掩码的形式提取出 32 种不同的行为。尽管我们只描述了一种简单的行为,但是可以编码的行为的数量只受在该图像格式中每种颜色所具有的位数的限制。
我们还给出并描述了沿着一组路径移动对象的一个简单动画引擎。在例子中,每一个对象称为 Entity 并实现为 JLabel ,它们被驱动周期性地更新其位置和/或者外观。使用一个长期运行的计时器线程驱动这个过程。用一个 JPanel 作为所有对象的容器,并作为绘制背景的方法。