设置呈现(RenderingHints)提示
8
.2 设置呈现(Render)提示
默然:对Render这个词,Java 2 API帮助的官方翻译,被译为呈现,而这本书译成了绘制,为了统一,我把它都改成了呈现,并作了单词注释。
在本节中,我们将学习如何控制所绘制的物体的呈现(render)质量。Java 2-D给我们提供了RenderingHints类,它包含设置和控制呈现质量所需的所有可能的值。
为了用特定的绘制基准注册控制值,RenderingHints类实现了Map接口。Map接口允许把一个Object值和一个关联键值进行映射。多数情况下键值可以映射到一个Object值。因而,可以确保所有的键映射是惟一的。
表8.1所示为RenderingHints类中所有的键值对。
键
|
值
|
KEY_ALPHA_INTERPOLATION
Alpha 插值提示键
|
ALPHA_INTERPOLATION_DEFAULT
ALPHA_INTERPOLATION_QUALITY
ALPHA_INTERPOLATION_SPEED
|
KEY_ANTIALIASING
抗锯齿提示键
|
VALUE_ANTIALIAS_DEFAULT
VALUE_ANTIALIAS_OFF
VALUE_ANTIALIAS_ON
|
KEY_COLOR_RENDERING
颜色呈现提示键
|
VALUE_COLOR_RENDER_DEFAULT
VALUE_COLOR_RENDER_QUALITY
VALUE_COLOR_RENDER_SPEED
|
KEY_DITHERING
抖动提示键
|
VALUE_DITHER_DEFAULT
VALUE_DITHER_DISABLE
VALUE_DITHER_ENABLE
|
KEY_FRACTIONALMETRICS
字体规格提示键
|
VALUE_FRACTIONALMETRICS_DEFAULT
VALUE_FRACTIONALMETRICS_OFF
VALUE_FRACTIONALMETRICS_ON
|
KEY_INTERPOLATION
插值提示键
|
VALUE_INTERPOLATION_BICUBIC
VALUE_INTERPOLATION_BILINEAR
VALUE_INTERPOLATION_NEAREST_NEIGHBOR
|
KEY_RENDERING
呈现提示键
|
VALUE_RENDER_DEFAULT
VALUE_RENDER_QUALITY
VALUE_RENDER_SPEED
|
KEY_STROKE_CONTROL
笔划规范化控制提示键
|
VALUE_STROKE_DEFAULT
VALUE_STROKE_NORMALIZE
VALUE_STROKE_PURE
|
KEY_TEXT_ANTIALIASING
文本抗锯齿提示键
|
VALUE_TEXT_ANTIALIAS_DEFAULT
VALUE_TEXT_ANTIALIAS_OFF
VALUE_TEXT_ANTIALIAS_ON
|
RenderingHints 类定义了多种着色微调,它们存储在一个映射集的 Graphics2D 对象里。setRenderingHint() 方法的参数是一个键以及对应的键值。在我们的代码中,第一个参数是代表 alpha 合成微调的键,第二个参数是该微调的值。该微调的其它可能的值有 VALUE_ALPHA_INTERPOLATION_DEFAULT,代表平台缺省值;以及 VALUE_ALPHA_INTERPOLATION_SPEED,代表追求速度而不是质量。
您还可以为下面的键提供微调:
键 描述
KEY_ANTIALIASING 决定是否使用抗锯齿。当着色有倾斜角度的线时,通常会得到一组阶梯式的像素排列,使这条线看上去不平滑,经常被称为 锯齿状图形。抗锯齿是一种技术,它设置有倾斜角度的线的像素亮度,以使线看起来更平滑。因此,这个微调是用来决定在着色有倾斜角度的线时是否在减少锯齿状图形上花费时间。可能的值有 VALUE_ANTIALIAS_ON, _OFF 或 _DEFAULT。
KEY_COLOR_RENDERING 控制颜色着色的方式。可能的值有 VALUE_COLOR_RENDER_SPEED, _QUALITY 或 _DEFAULT。
KEY_DITHERING 控制如何处理抖动。抖动是用一组有限的颜色合成出一个更大范围的颜色的过程,方法是给相邻像素着色以产生不在该组颜色中的新的颜色幻觉。可能的值有 VALUE_DITHER_ENABLE, _DISABLE 或 _DEFAULT。
KEY_FRACTIONALMETRICS 控制显示文本的质量。可能的值有 VALUE_FRACTIONALMETRICS_ON, _OFF 或 _DEFAULT。
KEY_INTERPOLATION 确定怎样做内插。
在对一个源图像做变形时,变形后的像素很少能够恰好对应目标像素位置。在这种情况下,每个变形后的像素的颜色值不得不由四周的像素决定。
内插就是实现上述过程。有许多可用的技术。可能的值,按处理时间从最多到最少,是 VALUE_INTERPOLATION_BICUBIC, _BILINEAR 或 _NEAREST_NEIGHBOR。
KEY_RENDERING 确定着色技术,在速度和质量之间进行权衡。可能的值有 VALUE_RENDERING_SPEED, _QUALITY 或 _DEFAULT。
KEY_TEXT_ANTIALIASING 确定对文本着色时是否抗锯齿。可能的值有 VALUE_TEXT_ANTIALIASING_ON, _OFF 或 _DEFAULT。
您还可以为下面的键提供微调:
键 描述
KEY_ANTIALIASING 决定是否使用抗锯齿。当着色有倾斜角度的线时,通常会得到一组阶梯式的像素排列,使这条线看上去不平滑,经常被称为 锯齿状图形。抗锯齿是一种技术,它设置有倾斜角度的线的像素亮度,以使线看起来更平滑。因此,这个微调是用来决定在着色有倾斜角度的线时是否在减少锯齿状图形上花费时间。可能的值有 VALUE_ANTIALIAS_ON, _OFF 或 _DEFAULT。
KEY_COLOR_RENDERING 控制颜色着色的方式。可能的值有 VALUE_COLOR_RENDER_SPEED, _QUALITY 或 _DEFAULT。
KEY_DITHERING 控制如何处理抖动。抖动是用一组有限的颜色合成出一个更大范围的颜色的过程,方法是给相邻像素着色以产生不在该组颜色中的新的颜色幻觉。可能的值有 VALUE_DITHER_ENABLE, _DISABLE 或 _DEFAULT。
KEY_FRACTIONALMETRICS 控制显示文本的质量。可能的值有 VALUE_FRACTIONALMETRICS_ON, _OFF 或 _DEFAULT。
KEY_INTERPOLATION 确定怎样做内插。
在对一个源图像做变形时,变形后的像素很少能够恰好对应目标像素位置。在这种情况下,每个变形后的像素的颜色值不得不由四周的像素决定。
内插就是实现上述过程。有许多可用的技术。可能的值,按处理时间从最多到最少,是 VALUE_INTERPOLATION_BICUBIC, _BILINEAR 或 _NEAREST_NEIGHBOR。
KEY_RENDERING 确定着色技术,在速度和质量之间进行权衡。可能的值有 VALUE_RENDERING_SPEED, _QUALITY 或 _DEFAULT。
KEY_TEXT_ANTIALIASING 确定对文本着色时是否抗锯齿。可能的值有 VALUE_TEXT_ANTIALIASING_ON, _OFF 或 _DEFAULT。
现在,我们对应用程序可用的呈现(Render)提示已经很熟了。下面来看看在applet中如何设置这些属性。有两种方法来设置呈现提示(RenderingHint),如果只想设置单一一个呈现提示,那么可以调用Graphics2D类的setRenderingHint方法并提供一个键和一个值。下面的代码段启用文字消除锯齿现象特性,并在当前Graphics2D变换中绘制一个文本串。
//假设g2d指向一个有效的Graphics2D容器
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIANIASING,
RenderingHints.VALUE_TEXT_ANTIANIAS_ON);
g2d.drawString(“启用文本消除锯齿”,0,0);
如果想设置几个呈现提示(RenderingHints),可以多次调用setRenderHint,或者创建值的完整映射,并使用Graphics2D的setRenderingHints方法一次把它们都设置好。
RenderingHints rh=new RenderingHints(RenderingHints. KEY_ANTIALIASING,
RenderingHints. VALUE_ANTIALIAS_ON);
rh.put(RenderingHints.KEY_STROKE_CONTROL
, RenderingHints.VALUE_STROKE_PURE);
rh.put(RenderingHints.KEY_ALPHA_INTERPOLATION
, RenderingHints.ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHints(rh);
//在此绘制一些好看的东西。。。
正如上面的代码段所看到的,可以用RenderingHints的构造函数定义一个呈现提示。使用put方法,传入一个键值对,来对映射添加一系列呈现提示。对于任何以DEFAULT结尾的提示值,会对该提示键使用平台的默认绘制操作。
注意:不能保证任何特定的系统都能应用我们设置的呈现提示。如果和提示相关的特定绘制算法在系统中不可用,则会采用本地默认的算法和设置。如果applet在有些系统中看起来很好,而在其他的系统中不好也不要紧(
默然:个人觉得不是不要紧,而是没有办法解决呀。),这只是平台独立给我们的一个负面效应而已。
呈现提示的使用是求得速度和质量平衡的一个很好的例子。如果考虑质量给定苛刻的呈现提示,那么在绘制速度上会有显著的牺牲。反之亦然,低要求的呈现提示会很快产生图像,但呈现质量会下降。因此,对于运行较快的计算机设置高质量要求的呈现提示,而对运行较慢的计算机设置低要求的呈现提示总是好主意。
找出一个给定系统的方法是判断特定的绘制硬件(比如显卡)在系统中是否可用,假设有一个假想的isAccelerated方法告诉系统是否可以使用一种类型的图像加速。下面的代码允许根据isAccelerated方法的结果来设置提示:
//假设renderQuality是RenderingHints的私有类成员
if(isAccelerated()){
renderQuality=new RenderingHints(RenderingHints. KEY_RENDERING,
RenderingHints. VALUE_RENDER_QUALITY);
}else{
renderQuality=new RenderingHints(RenderingHints. KEY_RENDERING,
RenderingHints. VALUE_RENDER_SPEED);
}
只需调用一次上面的代码,它可以放在init方法或者其他一个相关的方法中。如果现在要设置呈现提示,所需要做的只是在paint方法中调用setRenderingHints方法:
g2d.setRenderingHints(renderQuality);
//做一些绘制。。。
在第9章中,我们将在图形加速的世界里做一些更深的钻研,并讨论如何检测其性能。
8
.3 图像处理
目前为止,我们关于Java 2-D图像的讨论只是局限于静态的图像数据。静态是指图像数据可以被创建,但是其数据不能被直接访问或者修改。虽然Image类允许创建图像的缩放实例,但是没有直接的方法来处理图像数据本身。
在接下来的小节里,我们将探索一下那些允许直接修改图像内容的类。下面从BufferedImage类开始。
8.3
.1
BufferedImage类
BufferedImage类对于快速生成自定义的图像可能是一个很方便的类,它扩展了Image类,提供了直接写相关的2-D光栅数据的方法。
BufferedImage类的魔力在于它自身的Graphics2D容器。BufferedImage类的每个实例可以使用createGraphics方法创建它自己的Graphics2D对象,然后可以像使用由Applet类中得到的Graphics2D容器那样使用从缓冲图像中得到的Graphics2D容器。
我们可以使用以3个整数值(宽,高和图像的类型)为参数的构造函数来创建一个BufferedImage对象。其中图像类型参数指的是在图像中储存数据的方式。比如BufferedImage.TYPE_USHORT_555_RGB表明像素数据存储为RGB,红色分量占5位,绿色分量占5位,蓝色分量占5位,没有Alpha通道。常用的图像格式有TYPE_BYTE_GRAY,TYPE_INT_RGB和TYPE_INT_ARGB(默然:常量中的USHORT表示图像不带Alpha通道,BYTE表示图像以单字节方式打包,INT表示图像以整数方式打包)在Java 2文档中可以找到这些值的完整列表。
使用一个BufferedImage类需要先复制数据(比如一个Shape)到一个BufferedImage类里,然后再把BufferedImage绘制到applet窗体上。
下面的BufferedImageTest applet把几何形状复制到BufferedImage对象上,然后使用Graphics2D容器在applet窗体上随机位置绘制BufferedImage对象。
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
public class BufferedImageTest extends Applet{
//一个BufferedImage,还有它的宽和高
private BufferedImage image;
private final int BI_WIDTH=100;
private final int BI_HEIGHT=100;
//用来生成随机颜色和屏幕位置
private java.util.Random random;
public void init(){
random=new java.util.Random();
//创建一个(BI_WIDTH*BI_HEIGHT)的缓冲图像
image=new BufferedImage(BI_WIDTH,BI_HEIGHT,BufferedImage.TYPE_INT_BGR);
//为BufferedImage创建一个Graphics2D容器。记住,它和Applet的
//Graphics2D没有什么关系
Graphics2D bi_g2d=image.createGraphics();
//我们将在BufferedImage上绘制一些条纹
//条纹的宽和高分别是图像宽和高的十分之一
final int stripWidth=BI_WIDTH/10;
final int stripHeight=BI_HEIGHT/10;
//用随机颜色绘制垂直的条纹
bi_g2d.setPaint(new Color(random.nextInt()));
int x=stripWidth/2;
while(x<BI_WIDTH){
bi_g2d.fill(new Rectangle(x,0,stripWidth,BI_HEIGHT));
x+=2*stripWidth;
}
//给条纹设置一个透明通道
bi_g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.7f));
//使用随机颜色绘制水平的条纹
bi_g2d.setPaint(new Color(random.nextInt()));
int y=stripHeight/2;
while(y<BI_HEIGHT){
bi_g2d.fill(new Rectangle(0,y,BI_WIDTH,stripHeight));
y+=2*stripHeight;
}
//在图像的周围呈现一个深色不透明的外框
bi_g2d.setStroke(new BasicStroke(2.0f));
bi_g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
bi_g2d.setPaint(Color.black);
bi_g2d.draw(new Rectangle(0,0,BI_WIDTH,BI_HEIGHT));
}
public void paint(Graphics g){
//将g转换,以获得一个可用的Graphics2D对象
Graphics2D g2d=(Graphics2D)g;
//在随机的位置上画一串图像
for(int i=0;i<20;i++){
g2d.drawImage(image,AffineTransform.getTranslateInstance(random.nextInt()%getSize().getWidth(),random.nextInt()%getSize().getHeight()),this);
}
}
}
只用几行代码,便可以轻易地创建即使最强大的画图程序都可能做不出来的绘制方式和纹理。在画图程序中创建非常精确的纹理是很困难的,但是使用Java 2-D创建出非常精确的纹理并不困难。说到纹理,为什么不把BufferedImage用作TexturePaint对象来使用呢?事实上,我们已经在第7章的TextureTest applet中这样做了。下面来看另外一个例子。
可以使用如下的方法来创建一个BufferedImage:
private BufferedImage image;
public void init(){
image=new BufferedImage(50,50,BufferedImage.TYPE_INT_RGB);
Graphics2D g2d=image.createGraphics();
g2d.setPaint(Color.red);
g2d.fill(new Rectangle(0,0,25,25));
g2d.setPaint(Color.green);
g2d.fill(new Rectangle(25,0,25,25));
g2d.setPaint(Color.blue);
g2d.fill(new Rectangle(0,25,25,25));
g2d.setPaint(Color.gray);
g2d.fill(new Rectangle(25,25,25,25));
}
然后,可以设置BufferedImage为paint并绘制一个形状:
public void paint(Graphics g){
//把传进来的Craphics容器强制转换为一个有用的Graphics2D
Graphics2D g2d=(Graphics2D)g;
//创建纹理并把它加到Graphics容器中
g2d.setPaint(new TexturePaint(image,new Rectangle(0,0,10,10)));
g2d.translate(50,25);
g2d.fill(new Ellipse2D.Double(0,0,200,100));
}
注意,在上面的代码中,我们可以根据实际的图像尺寸任意设置绘制的重复频率。这里,BufferedImage对象包含50行50列像素数据,然而,重复矩形只是这个尺寸的一半。Graphics2D容器会自动调整TexturePaint来适应重复区域。
在第9章中,我们会在讨论离屏绘制缓冲时回顾BufferedImage类。下面将进一步学习使用BufferedImage对象来储存复杂的Shape操作以获得更快的绘制速度。
8.3.2
使用图像增强处理
在java.awt.image包中提供了BufferedImageOp接口,它用来描述针对图像的一个单一的输入/输出操作。因此,可以在源图像上应用一个图像操作,把结果存储到一个输出图像,也就是目标图像中。
目前有5个实现了BufferedImageOp接口的类:AffineTransformOp,ColorConvertOp,ConvolveOp,LookupOp和RescaleOp。这些类实现了对图像进行几何变换,模糊,锐化和颜色调整所需要的操作。
增强图像的一般步骤如下:
1)创建原始的Image数据。
2)创建一个新的BufferedImage对象,它包含输出所具备的性质(包括图像的尺寸和图像类型)。
3)创建BufferedImageOp对象,它包含可能所需的任何相关数据。
4)使用BufferedImageOp对象过滤原来的Image,然后把结果储存到目标BufferedImage对象(它有时可以同时是源图像)。
下面的BlurTest applet,遵循上面的步骤创建了一个模糊处理过的图像,它允许用户在文本框中输入一个文件名。如果该文件存在,这个图像以及图像的一个模糊处理过的版本就会被绘制到applet窗体上。代码如下:
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
public class BlurTest extends Applet implements ActionListener{
//原始和模糊后的BufferedImage
private BufferedImage sourceImage;
private BufferedImage destImage;
//用来接受文件名的文本域
private TextField input;
public void init(){
//创建一个布局并添加文本域和Ok按钮
setLayout(new BorderLayout());
Panel p=new Panel();
input=new TextField("",20);
p.add(input);
Button ok=new Button("OK");
ok.addActionListener(this);
p.add(ok);
add(p,BorderLayout.SOUTH);
}
public void paint(Graphics g){
Graphics2D g2d=(Graphics2D)g;
//如果文本框中只有空格,绘制一个友好的输入文件名的提示
if("".equals(input.getText().trim())){
g2d.drawString("输入一个用于模糊的文件名",10,50);
return;
}
//加载输入的图像
MediaTracker mt=new MediaTracker(this);
Image img=getImage(getCodeBase(),input.getText());
mt.addImage(img,0);
try{
//在进行下一步前等待直到图像完全加载
mt.waitForID(0);
}catch(InterruptedException e){
//Nothing
}
//我们的模糊操作需要一个BufferedImage对象,
//所以把输入图像绘制到一个新的BufferedImage对象
sourceImage=new BufferedImage(img.getWidth(this),
img.getHeight(this),
BufferedImage.TYPE_INT_RGB);
sourceImage.createGraphics().drawImage(img,null,this);
//创建目的图像
destImage=new BufferedImage(sourceImage.getWidth(this),
sourceImage.getHeight(this),
BufferedImage.TYPE_INT_RGB);
//模糊输入图像
blur(sourceImage,destImage,0.1f,3,3);
//绘制源图像和目标图像
AffineTransform at=new AffineTransform();
int width=sourceImage.getWidth(this);
g2d.drawImage(sourceImage,at,this);
at.translate(width+20,0);
g2d.drawImage(destImage,at,this);
}
public void blur(
BufferedImage sourceImage, //输入图像数据
BufferedImage destImage, //输出图像数据
float weight, //Kernel数据的权重
int width, //Kernel的宽
int height //Kernel的高
){
//Kernel数据数组
float[] elements=new float[width*height];
//用权重填充数组
java.util.Arrays.fill(elements,weight);
//使用元素数组和宽,高来创建一个Kernel对象
Kernel k=new Kernel(width,height,elements);
ConvolveOp blurOp=new ConvolveOp(k);
//模糊图像
blurOp.filter(sourceImage,destImage);
}
public void actionPerformed(ActionEvent e){
//OK按钮被按下,更新
repaint();
}
}
使用那些实现了BufferedImageOp接口的类可以做的事情很多,除了模糊图像外,还可以创建灰度图像,提供图像“逆转”,颜色的增强或者删除等。所以,下一次如果设计的游戏包含同样的图像只是颜色调色板不同,可以考虑实现一个简单的调色板交换机制,面不要依赖于喜欢的图型程序。具体实现的细节留给读者。
最后提一下,注意由于实现BufferedImageOp接口允许单一的图像操作,它们不能用于有多个图像源或者想在原始图像数据应用多个操作的情况。因此,在这样的情况中,必须一次执行多个步骤并在必要时提供临时的图像。
8.4
总结
很多图形包和Java 2D相比在基本二维服务上要逊色一些,从第7章和第8章都可以看出,Java 2-D在图像,线条和文本绘制方面有极大的功能。形状和基本技能的处理应该便于使用,并且应该在一定程序上减少对图像文件的需要,这样可以明显减少下载的次数。构造区域几何学,内置的冲突检测方法和剪裁区域都是使用Java 2-D所提供的几何能力的好方法。设置绘制提示是控制细节层次和绘制质量的好方法,实现了对游戏性能的全面控制。最后,图像处理提供了非常强大的方式来增强和修改图像而无须创建多余的消耗内存的图像文件。
如果读者还不确信,试着用一种其他的图形包来实现本章所讨论的主题,相信Java 2-D是完成这些游戏的最佳选择。
8.5
练习
8.1写一个名为createBounds的方法,以一个任意Point对象组成的数组作为输入,返回一个最紧的圈住这些点的Rectangle2D对象。如下图所示为这个createBounds方法应该怎样提供这样一个紧紧圈住点的Rectangle2D对象。
提示:在寻找矩形的极限点时,把每一个Point对象分成x-和y-值,这样可以按照整个点的队列而不是数组中特定的一些点来计算最小和最大值。
8.2写一个通用的工具方法adjustBounds,它根据一个给定的double值来缩小(或者扩大)一个Rectangle2D对象。这个方法在完成修正之后应该返回同一个中心的Rectangle2D对象。在写代码之前,最好在纸上拟定问题的方案。测试这个方法,确信它保持了矩形的中心不变而且正确的扩大或者缩小给定的Rectangle2D对象。
8.3对BoundedImage类添加一个方法——moveBy。原型如下:
public void moveBy(double dx,double dy){
//方法主体
}
8.4写一段根据同一个定位点集中图像和形状的代码。记住,可以直接访问形状的中心,但是图像却是以其左上角作为参照的。
8.5写一段创建图像的“逆图像”的代码。对于像.gif图像这样以颜色作为索引的图像,这可能意味着使用颜色查询表中“相反”方向的点来替换每一个像素。下面的代码可以帮助我们开头。
byte[] negatives=new byte[256];
for(int i=0;i<200;i++){
negatives[i]=(byte)(256-i);
}
ByteLookupTable blut=new ByteLookupTable(0,negatives);
LookupOp lop=new LookupOp(blut,null);
lop.fillter(sourceImage,destImage);
ByteLookupTable类(在java.awt.image包中)提供了一种快速方便地重新对颜色数据索引采样的方法。
ByteLookupTable的构造函数还有一个变休,它以1B数据的二维数组(数组的数组)作为参数之一,这个方法对于实现本章前面提到的调色板交换机制是很好的。自己试试。如果仍然感到迷惑,等到第14章时再看看一个单一的图像如何可以用来产生一系列的其他图像。
8.6现在,你应该如如何应用映射来实现Java的HashTable类有了很好的掌握。如果略过了第4章中处理哈希表的练习,可以回头现款 试。在下一章我们讨论自定义字体绘制方案时,可能需要知道如何使用它们。
现在的位置和目标
第2篇介绍了成功创建有吸引力的用户界面所需的基础。Java 2-D和抽象Window工具包(AWT)包含了很多可以帮助我们尽可能简单地创建游戏程序图形终端的好东西。现在我们已经理解了AWT的每一个组件,可以把它们组合起来创建一些很好的效果和用户界面。
不管读者是否相信,我们已经覆盖了很多目前Java游戏编程现状的背景。如果你用C或者C++写过游戏,可以继续前进把已经知道的和现在所学的结合起来创造一些东西。第3篇会通过在游戏的可视化方面和画面后面游戏进程之间搭建桥梁来帮助我们。接下来,我们将看看像缓冲动画和帧速同步这样的主题,以及如何创建自定义菜单和可视化装饰与控件。还会学习如何创建一个角色类,这样就可以给对象一些东西考虑。在第3篇结束前,还会学习如何在Internet上创建简单的C/S连接,以及创建一个基本的2-D游戏开发引擎。最后,会做好钻研一个完整游戏例子的准备,看看如何把所有的东西放在一起来创建一些完整的游戏。
所以,如果对第1篇和第2篇所讲述的内容感觉良好,那么可以直接进入第3篇。惟一阻碍我们进行全面控制的就是想象力。所以,我们像专业人士那样做,看看MediaTracker类和二维缓冲动画。