<?xml version="1.0" encoding="utf-8" ?>
<!-- generator="FEEDCREATOR_VERSION" -->
<rss version="2.0" xmlns:sns="http://blog.sina.com.cn/sns">
    <channel>
        <title>Flash AS 基理殿堂</title>
        <description></description>
        <link>http://blog.sina.com.cn/yyy98</link>
        <lastBuildDate>Mon, 06 Jul 2009 23:46:11 GMT+8</lastBuildDate>
        <generator>FEEDCREATOR_VERSION</generator>
        <language>zh-cn</language>
        <copyright>Copyright 1996 - 2009 SINA Inc. All Rights Reserved.</copyright>
        <pubDate>Mon, 06 Jul 2009 15:46:11 GMT+8</pubDate>
        <item>
            <title>[杂谈九] Java 几种对象的解释 [转]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100b2u3.html</link>
            <description><![CDATA[<span STYLE="font-weight: bold;">PO（Persistant
Object）：</SPAN>持久对象，可以看成是与数据库中的表相映射的 java 对象。最简单的 PO
就是对应数据库中某个表中的一条记录，多个记录可以用 PO 的集合。PO 中应该不包含任何对数据库的操作。<br />
<br />
<span STYLE="font-weight: bold;">BO（Business
Object）：</SPAN>业务对象，封装业务逻辑的 java 对象。通过调用 DAO 方法，结合PO,VO
进行业务操作。<br />
<br />
<span STYLE="font-weight: bold;">VO（Value
Object）：</SPAN>值对象。通常用于业务层之间的数据传递，和 PO
一样也是仅仅包含数据而已。但应是抽象出的业务对象，可以和表对应，也可以不，这要根据业务的需要来定。个人觉得同
DTO（数据传输对象），在 web 上传递。<br />
<br />
<span STYLE="font-weight: bold;">DAO（Data Access
Object）：</SPAN>数据访问对象，此对象用于访问数据库。通常和 PO 结合使用，DAO
中包含了各种数据库的操作方法。通过它的方法，结合 PO 对数据库进行相关的操作。<br />
<br />
<span STYLE="font-weight: bold;">POJO（Plain Ordinary Java
Object）：</SPAN>简单无规则 java 对象。个人觉得它和其他不是一个层面上的东西，VO 和 PO
应该都属于它。<br />
<br />
—————————————————————————————————————————<br />
<span STYLE="font-weight: bold;">PO（持久对象）：</SPAN><br />
最形象的理解就是一个 PO 数据库中的一条记录。<br />
好处是可以把一条记录作为一个对象处理，可以方便的转为其它对象。<br />
<br />
<span STYLE="font-weight: bold;">BO（业务对象）：</SPAN><br />
主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。<br />
比如一个简历，有教育经历、工作经历、社会 关系等等。<br />
我们可以把教育经历对应一个 PO，工作经历对应一个 PO，社会关系对应一个 PO。<br />
建立一个对应简历的 BO 对象处理简历，每个 BO 包含这些 PO。<br />
这样处理业务逻辑时，我们就可以针对 BO 去处理。<br />
<br />
<span STYLE="font-weight: bold;">VO（值对象／表现层对象）：</SPAN><br />
主要对应界面显示的数据对象。对于一个 WEB 页面，或者 AWT、SWING 的一个界面，用一个 VO
对象对应整个界面的值。<br />
<br />
<span STYLE="font-weight: bold;">DTO（数据传输对象）：</SPAN><br />
主要用于远程调用等需要大量传输对象的地方。<br />
比如我们一张表有 100 个字段，那么对应的 PO 就有 100 个属性。<br />
但是我们界面上只要显示 10 个字段，客户端用 WEB service 来获取数据，没有必要把整个 PO
对象传递到客户端，这时我们就可以用只有这 10 个属性的 DTO
来传递结果到客户端，这样也不会暴露服务端表结构。到达客户端以后，如果用这个对象来对应界面显示，那此时它的身份就转为 VO。<br />
<br />
<span STYLE="font-weight: bold;">POJO（简单 Java 对象）：</SPAN><br />
个人感觉 POJO 是最常见最多变的对象，是一个中间对象，也是我们最常打交道的对象。一个POJO 持久化以后就是
PO，直接用它传递、传递过程中就是 DTO，直接用来对应表示层就是 VO。<br />
<br />
<span STYLE="font-weight: bold;">DAO（数据访问对象）：</SPAN><br />
这个大家最熟悉，和上面几个 O 区别最大，基本没有互相转化的可能性和必要。主要用来封装对数据库的访问。通过它可以把 POJO 持久化为
PO，用 PO 组装出来 VO、DTO。<br />
<br />
总结下，我认为一个对象究竟是什么O要看具体环境，在不同的层、不同的应用场合，对象的身份也不一样，而且对象身份的转化也是很自然的。就像你对老婆来说你就是老公，对父母来说就是子女。设计这些概念的初衷不是为了唬人而是为了更好的理解和处理各种逻辑，让大家能更好的去用面向对象的方式处理问题。<br />]]></description>
            <author>FL基础理论大师</author>
            <category>杂谈系列</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100b2u3.html#comment</comments>
            <pubDate>Sat, 04 Oct 2008 14:42:47 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100b2u3.html</guid>
        </item>
        <item>
            <title>[AS3 狂想曲 K.07] BlazeDS 入门及配置 [FL 车在臣]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100b2qu.html</link>
            <description><![CDATA[&nbsp;&nbsp;&nbsp;
无论是哪种服务器端技术都能够很轻松地应用 XML，因为 XML 完全是一个基于文本的协议。像 XML
这样基于文本的协议的缺点是额外的数据抽象层的编写与维护工作相对很沉重。此外，如果数据需要序列化和反序列化，那么这个数据抽象层在客户端和服务器端都会占用很多资源。<br />

&nbsp;&nbsp;&nbsp; Flash Player
可以支持另外一种传输协议，这个协议能够缓解由基于文本的协议导致的传输瓶颈问题，能够帮助开发者以更简单的方式与服务器交互。这就是
AMF（Action Message Format）。它是一个二进制格式的协议，能够替代用于传输XML的基于文本的协议而在 HTTP
协议之上交换数据。采用 AMF
的应用中，数据抽象层完全可以省去，客户与服务器间的通讯效率比传统的应用基于文本的协议传输要高得多。<br />
&nbsp;&nbsp;&nbsp; BlazeDS 中包含了
AMF 的 Java 实现，可以用来与服务器端的 Java 对象远程交互，也可以用来在客户端之间传递消息。开发人员可以借助
BlazeDS 的远程技术简单地调用 POJO、Spring
服务或EJB方法。开发人员可以通过其消息系统从客户端向服务器端发送消息，当然也可从服务器端向客户端发送消息。<br />
&nbsp;&nbsp;&nbsp; BlazeDS
也可以与其他一些消息系统结合使用，比如JMS、ActiveMQ。由于其远程技术与消息系统采用的方式是在 HTTP 协议上传输 AMF
数据，BlazeDS 因此在性能上拥有很大优势，同时也避免了额外的数据抽象层的处理工作。BlazeDS 在很多基于 Java
的应用服务器环境下都能正常工作，这些服务器包括 Tomcat、WebSphere、WebLogic、JBoss 以及
ColdFusion。此外，无论是 web（在 Flash Player 中运行）还是桌面（在Adobe AIR下运行）的 Flex
应用程序中，BlazeDS 的使用都很简单。<br />
&nbsp;&nbsp;&nbsp; 简单来说
BlazeDS是一个基于服务器的 Java 远程调用和 Web 消息传递技术，它能够让后台的 Java 应用程序和运行在浏览器上的
Flex 应用程序相互通信。BlazeDS 是 Adobe 的一个开源项目，里面提供了 Java 封装 AMF3
格式的方法。下面通过一个 HelloWorld 的例子对它做一个初步的了解。<br />
<br />
<span STYLE="font-weight: bold;">一、所需的软件：</SPAN><br />
MyEclipse + Tomcat 6.0 + Flex Builder 3 + BlazeDS，软件的下载请直接问
<span STYLE="font-weight: bold;">goole</SPAN>。<br />
<br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">二、配置服务器端（MyEclipse + Tomcat 6.0 +
BlazeDS）</SPAN><br />
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static2.photo.sina.com.cn/orignal/3ecb9b11t586b644626d1" TARGET="_blank"><img SRC="http://static2.photo.sina.com.cn/bmiddle/3ecb9b11t586b644626d1" /></A><br />

1. 先将 BlazeDS 压缩包解开，然后找到 blazeds.war 再用 winrar 解开。<br />
<br />
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static13.photo.sina.com.cn/orignal/3ecb9b11t586b6555ad3c" TARGET="_blank"><img SRC="http://static13.photo.sina.com.cn/bmiddle/3ecb9b11t586b6555ad3c" />
</A><br />
2. 打开 MyEclipse，配置 Tomcat 服务器：Window -&gt; Preferences
-&gt; MyEclipse -&gt; Application Servers
-&gt; Tomcat -&gt; Tomcat 6.x 配置 Tomcat
home directory 为 Tomcat 的安装路径，同时选择 Tomcat server -&gt;
Enable。<br />
<br />
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static1.photo.sina.com.cn/orignal/3ecb9b11t6f3df0a9e210" TARGET="_blank"><img SRC="http://static1.photo.sina.com.cn/bmiddle/3ecb9b11t6f3df0a9e210" /></A><br />

3. 接下来，在 MyEclipse 中创建一个 Web Project，工程名 blazeds_project。<br />
<br />
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static9.photo.sina.com.cn/orignal/3ecb9b11t586b67edab28" TARGET="_blank"><img SRC="http://static9.photo.sina.com.cn/bmiddle/3ecb9b11t586b67edab28" /></A><br />

5. 在左侧视图中选中 WebRoot 右键 -&gt; Import -&gt;
FileSystem -&gt; 选择 BlazeDS 安装目录下的 blazeds
文件夹。选中后打勾，Finish。出现 Override 提示框，选择 Yes To All。<br />
<br />
6. 下面在本工程的 src 目录下编写一个 Java 类（包名 blog.sina.yyy98，类名
HelloWorld）：<br />
package blog.sina.yyy98;<br />
<br />
public class HelloWorld {<br />
&nbsp;&nbsp;&nbsp; public
String getHello() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; return
"Hello World";<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
7. java 类写好了，下面要在 remoting-config.xml 中声明这个类（黑体字部分）：<br />
&lt;?xml version="1.0"
encoding="UTF-8"?&gt;<br />
&lt;service id="remoting-service"<br />
&nbsp;&nbsp;&nbsp;
class="flex.messaging.services.RemotingService"&gt;<br />

<br />
&nbsp;&nbsp;&nbsp;
&lt;adapters&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&lt;adapter-definition id="java-object"
class="flex.messaging.services.remoting.adapters.JavaAdapter"
default="true"/&gt;<br />
&nbsp;&nbsp;&nbsp;
&lt;/adapters&gt;<br />
<br />
&nbsp;&nbsp;&nbsp;
&lt;default-channels&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&lt;channel ref="my-amf"/&gt;<br />
&nbsp;&nbsp;&nbsp;
&lt;/default-channels&gt;<br />
<br />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&lt;destination
id="helloWorld"&gt;</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&lt;properties&gt;</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&lt;!-- 这里要写完整路径：包名 + 类名
--&gt;</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&lt;source&gt;blog.sina.yyy98.HelloWorld&lt;/source&gt;</SPAN><br STYLE="font-weight: bold;" />

<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&lt;/properties&gt;</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&lt;/destination&gt;</SPAN><br />
&lt;/service&gt;<br />
<br />
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static1.photo.sina.com.cn/orignal/3ecb9b11t586b69c1ea00" TARGET="_blank"><img SRC="http://static1.photo.sina.com.cn/bmiddle/3ecb9b11t586b69c1ea00" /></A><br />

8. 好了，大功告成，现在将这个项目部属到 Tomcat 中，然后启动 Tomcat。<br />
<br />
<span STYLE="font-weight: bold;">三、创建 Flex 客户端</SPAN><br />
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static14.photo.sina.com.cn/orignal/3ecb9b11t586b6b06740d" TARGET="_blank"><img SRC="http://static14.photo.sina.com.cn/bmiddle/3ecb9b11t586b6b06740d" />
</A><br />
1. 新建一个 Flex 项目，名称为 flex_client，选择 Application server
type：J2EE。<br />
<br />
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static5.photo.sina.com.cn/orignal/3ecb9b11t586b6c435bf4" TARGET="_blank"><img SRC="http://static5.photo.sina.com.cn/bmiddle/3ecb9b11t586b6c435bf4" /></A><br />

2. 这是关键的一步，Root folder 为刚刚在 Tomcat 中部署的 blazeds_project
项目路径。为了保持一致性，Root URL 和 Context root 中的相对路径的名称都与该 web
项目名称保持相同。这三项内容填写好后，需要点击 “Validate Configuration” 按钮，进行配置验证（此时最好让
Tomcat 处于开启状态）。验证完成后点击 Finish。<br />
<br />
3. 最后编写客户端（flex_client.mxml）：<br />
<table STYLE="width: 619px; height: 588px;" BORDER="1" CELLPADDING="3" CELLSPACING="1">
<tbody>
<tr>
<td>&nbsp;&lt;?xml version="1.0"
encoding="utf-8"?&gt;<br />
&lt;mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"&gt;<br />
&nbsp;&nbsp;&nbsp;
&lt;mx:Script&gt;<br />
&nbsp;&nbsp;&nbsp;
&lt;![CDATA[<br />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
import mx.rpc.events.FaultEvent;</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
import mx.controls.Alert;</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
import mx.rpc.events.ResultEvent;</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
private function resultHandler(event:ResultEvent):void
{</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Alert.show(event.result.toString(), "成功");</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
}</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
private function faultHandler(event:FaultEvent):void
{</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Alert.show(event.fault.toString(), "失败");</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
}</SPAN><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
]]&gt;<br />
&nbsp;&nbsp;&nbsp;
&lt;/mx:Script&gt;<br />
&nbsp; &nbsp;<br />
&nbsp;&nbsp;&nbsp;
&lt;!-- destination 属性值要和 remoting-config.xml 中
destination 的 id 一致--&gt;<br />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&lt;mx:RemoteObject id="remoteObject"</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
destination="helloWorld"</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
result="resultHandler(event)"</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
fault="faultHandler(event)"/&gt;</SPAN><br />
&nbsp; &nbsp;<br />
&nbsp;&nbsp;&nbsp;
&lt;mx:Button label="发送消息" click="<span STYLE="font-weight: bold;">remoteObject.getHello()</SPAN>" x="256.5"
y="197"/&gt;<br />
&lt;/mx:Application&gt;</TD>
</TR>
<tr>
<td>&nbsp;AS 表示法：<br />
&nbsp;&nbsp;&nbsp; var
remoteObject:RemoteObject = new RemoteObject();<br />
&nbsp;&nbsp;&nbsp;
remoteObject.destination = "helloWorld";<br />
&nbsp;&nbsp;&nbsp;
remoteObject.addEventListener(ResultEvent.RESULT,
resultHandler);<br />
&nbsp;&nbsp;&nbsp;
remoteObject.addEventListener(FaultEvent.FAULT,
faultHandler);<br /></TD>
</TR>
</TBODY>
</TABLE>
<br />
<br />
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static12.photo.sina.com.cn/orignal/3ecb9b11t586b6d4d1dcb" TARGET="_blank"><img SRC="http://static12.photo.sina.com.cn/bmiddle/3ecb9b11t586b6d4d1dcb" />
</A><br />
4. Ok，完成。运行 felx_client，收到服务器端返回的 “Hello World” 字符串。<br />]]></description>
            <author>FL基础理论大师</author>
            <category>AS3 狂想曲</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100b2qu.html#comment</comments>
            <pubDate>Sat, 04 Oct 2008 10:57:43 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100b2qu.html</guid>
        </item>
        <item>
            <title>[AS3 狂想曲 K.06] flash 聊天室 [FL 车在臣]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100b1jl.html</link>
            <description><![CDATA[&nbsp;&nbsp;&nbsp; 今天我们来制作
Flash 聊天室，学习使用 Socket 类，用 Java 作后台，开发一个 C / S
架构的多人聊天程序。首先，设计用户界面。<br />
<br />
<span STYLE="font-weight: bold;">一、界面设计</SPAN><br />
<div STYLE="text-align: center;"><a href="http://blog.photo.sina.com.cn/showpic.html#url=http://static2.photo.sina.com.cn/orignal/3ecb9b11t581eabaaba11" TARGET="_blank"><img SRC="http://static2.photo.sina.com.cn/bmiddle/3ecb9b11t581eabaaba11" /></A><br /></DIV>
界面的元件全部使用 Flash CS3 自带的组件：<br />
&nbsp;&nbsp;&nbsp; 首先，放入
TextInput 组件（实例名 input_txt），作为用户输入；<br />
&nbsp;&nbsp;&nbsp; 再放入 Button
组件（实例名 submit_btn），用于提交输入的信息；<br />
&nbsp;&nbsp;&nbsp; 最后放入
TextArea 组件（实例名 output_txt），显示聊天信息。<br />
<br />
<span STYLE="font-weight: bold;">二、组件参数初始化</SPAN><br />
&nbsp;&nbsp;&nbsp;
由于客户端代码不是很多，我们这次就写在动作帧上：<br />
// ************ 组件参数初始化 ************<br />
submit_btn.label = "发送消息";<br />
output_txt.editable = false;<br />
<br />
// 设置各组件中字体的大小<br />
input_txt.setStyle("textFormat", new TextFormat(null, 15));<br />
output_txt.setStyle("textFormat", new TextFormat(null, 15));<br />
submit_btn.setStyle("textFormat", new TextFormat(null, 15, null,
true));<br />
<br />
// 当按下回车或点击 submit_btn 按钮后调用事件处理函数<br />
submit_btn.addEventListener(MouseEvent.CLICK, sendMessage);<br />
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);<br />
<br />
// 事件处理函数<br />
function onKeyDown(evt:KeyboardEvent):void {<br />
&nbsp;&nbsp;&nbsp; if
(evt.keyCode == Keyboard.ENTER) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
sendMessage(null);<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
function sendMessage(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp; // 测试：将
input_txt 的内容输出到 output_txt 中<br />
&nbsp;&nbsp;&nbsp;
output_txt.appendText(input_txt.text + "\n");<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; // 清空
input_txt，并设置焦点到 input_txt<br />
&nbsp;&nbsp;&nbsp;
input_txt.text = "";<br />
&nbsp;&nbsp;&nbsp; stage.focus
= input_txt;<br />
}<br />
<br />
<span STYLE="font-weight: bold;">三、编写客户端 Socket</SPAN><br />
1. 首先，Socket 连接非常简单：<br />
&nbsp;&nbsp;&nbsp; var
socket:Socket = new Socket();<br />
&nbsp;&nbsp;&nbsp;
socket.connect("127.0.0.1", 8888);<br />
其中 connect() 方法中的两个参数分别为是主机名和端口号（端口号尽量用 1024
以上）。好了，这样就连接上了。接下来是读写的问题。<br />
<br />
2. 向服务器端写入字符串：我们在 sendMessage() 方法中进行写入操作，注意写出的字串必需以回车（\n）结束：<br />
function sendMessage(evt:Event):void {<br />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
var bytes:ByteArray = new ByteArray();</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
bytes.writeUTFBytes(input_txt.text + "\n");</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
socket.writeBytes(bytes);</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
socket.flush();</SPAN><br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; // 清空
input_txt，并设置焦点到 input_txt<br />
&nbsp;&nbsp;&nbsp;
input_txt.text = "";<br />
&nbsp;&nbsp;&nbsp; stage.focus
= input_txt;<br />
}<br />
<br />
3. 读取服务器端写回的字符串：最后将服务器发回的字符串输出到 output_txt 文本域中：<br />
// 当客户端 socket 收到数据后会调用 readMessage() 函数<br />
socket.addEventListener(ProgressEvent.SOCKET_DATA,
readMessage);<br />
<br />
function readMessage(evt:ProgressEvent):void {<br />
&nbsp;&nbsp;&nbsp;
output_txt.appendText(socket.readUTF() + "\n");<br />
}<br />
<br />
<span STYLE="font-weight: bold;">四、Flash 客户端全部脚本</SPAN><br />
// ************ 组件参数初始化 ************<br />
submit_btn.label = "发送消息";<br />
output_txt.editable = false;<br />
<br />
input_txt.setStyle("textFormat", new TextFormat(null, 15));<br />
output_txt.setStyle("textFormat", new TextFormat(null, 15));<br />
submit_btn.setStyle("textFormat", new TextFormat(null, 15, null,
true));<br />
<br />
submit_btn.addEventListener(MouseEvent.CLICK, sendMessage);<br />
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);<br />
<br />
function onKeyDown(evt:KeyboardEvent):void {<br />
&nbsp;&nbsp;&nbsp; if
(evt.keyCode == Keyboard.ENTER) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
sendMessage(null);<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
function sendMessage(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp; var
bytes:ByteArray = new ByteArray();<br />
&nbsp;&nbsp;&nbsp;
bytes.writeUTFBytes(input_txt.text + "\n");<br />
&nbsp;&nbsp;&nbsp;
socket.writeBytes(bytes);<br />
&nbsp;&nbsp;&nbsp;
socket.flush();<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
input_txt.text = "";<br />
&nbsp;&nbsp;&nbsp; stage.focus
= input_txt;<br />
}<br />
<br />
// ************ 客户端 Socket ************<br />
var socket:Socket = new Socket();<br />
socket.connect("127.0.0.1", 8888);<br />
socket.addEventListener(ProgressEvent.SOCKET_DATA,
readMessage);<br />
<br />
function readMessage(evt:ProgressEvent):void {<br />
&nbsp;&nbsp;&nbsp;
output_txt.appendText(socket.readUTF() + "\n");<br />
}<br />
<br />
<span STYLE="font-weight: bold;">五、编写 Java 服务器端 Socket</SPAN><br />
&nbsp;&nbsp;&nbsp; 最后，我们需要编写
Java 后台服务器端的代码。<br />
&nbsp;&nbsp;&nbsp; 首先，创建一个
ServerSocket 作为Socket 服务器。当有客户端连接后通过 accept() 方法即可得到客户端的
Socket：<br />
<br />
ServerSocket socketServer = new ServerSocket(8888);<br />
System.out.println("服务器已启动，等待客户端连接");<br />
<br />
// accept() 方法是阻塞式的，当有客户端连接成功后才继续执行<br />
Socket socket = socketServer.accept();<br />
System.out.println("客户端连接成功");<br />
<br />
然后得到与客户端的输入流和输出流（输入流是客户端连接到服务器的管道，输出流则是服务器到客户端的管道）：<br />
// 获得输入流和输出流，输入流为 BufferedReader 类型，输出流为 DataOutputStream 类型<br />
BufferedReader reader =<br />
&nbsp;new BufferedReader(new
InputStreamReader(socket.getInputStream(), "UTF-8"));<br />
DataOutputStream writer = new
DataOutputStream(socket.getOutputStream());&nbsp;&nbsp;&nbsp;<br />

<br />
拿到输入输出流以后，就可以通过输入流（InputStream）读取 Flash
客户端发来的字符串，通过输出流（OutputStream）向 Flash 客户端写字符串：<br />
while (true) {<br />
&nbsp;&nbsp;&nbsp; //
readLine() 方法也是阻塞式的，当客户端有消息发来就读取，否则就一直等待<br />
&nbsp;&nbsp;&nbsp; String msg =
reader.readLine();<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; //
当客户端发送的字符串为 null 时，说明客户端已经关闭，此时退出循环<br />
&nbsp;&nbsp;&nbsp; if (msg ==
null) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
System.out.println("客户端已离开");<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; break;<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; //
将读入的信息加工后再写回客户端<br />
&nbsp;&nbsp;&nbsp;
writer.writeUTF("写回客户端的" + msg);<br />
}<br />
<br />
&nbsp;&nbsp;&nbsp;
以上是ServerSocket 与 AS 3 Socket
通信的基本原理。在实际应用中，会有多个客户端连接这个ServerSocket，因此要创建一个多线程的 Socket
服务器。<br />
&nbsp;&nbsp;&nbsp; 下面简述一下多线程
Socket 服务器原理：当socketServer.accept()
之后就需要实例化一个线程对象，在该对象中持有socketServer.accept() 返回的 Socket
对象，然后让线程跑起来执行读写操作。如果再来一个客户端就再跑一个线程，同样执行读写操作。同时，用一个 List
容器来管理这些对象。<br />
&nbsp;&nbsp;&nbsp;
最终服务器端的代码如下：<br />
import java.io.BufferedReader;<br />
import java.io.DataOutputStream;<br />
import java.io.IOException;<br />
import java.io.InputStreamReader;<br />
import java.io.UnsupportedEncodingException;<br />
import java.net.ServerSocket;<br />
import java.net.Socket;<br />
import java.util.ArrayList;<br />
import java.util.Iterator;<br />
import java.util.List;<br />
<br />
public class FlashScoket {<br />
&nbsp;&nbsp;&nbsp; private
List&lt;Client&gt; clientList = new
ArrayList&lt;Client&gt;();<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public
static void main(String[] args) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; new
FlashScoket().runSocket();<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; private void
runSocket() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; try {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; ServerSocket
socketServer = new ServerSocket(8888);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
System.out.println("服务器已启动，等待客户端连接");<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; while (true)
{<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; // accept()
方法是阻塞式的，当有客户端连接成功后才继续执行<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; Socket
socket = socketServer.accept();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
System.out.println("客户端连接成功");<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; // 实例化一个
Client 对象，并启动该线程<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; Client
client = new Client(socket);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
clientList.add(client);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
client.start();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } catch
(IOException e) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
e.printStackTrace();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; class Client
extends Thread {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
Socket socket;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
BufferedReader reader;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
DataOutputStream writer;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
Client(Socket socket) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; this.socket
= socket;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; try {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
// 获得输入流和输出流，输入流为 BufferedReader 类型，输出流为 DataOutputStream 类型<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; reader = new
BufferedReader(new InputStreamReader(socket.getInputStream(),
"UTF-8"));<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; writer = new
DataOutputStream(socket.getOutputStream());<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } catch
(UnsupportedEncodingException e) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
e.printStackTrace();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } catch
(IOException e) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
e.printStackTrace();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
@Override<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; public void
run() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; try {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; while (true)
{<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //
readLine() 方法也是阻塞式的，当客户端有消息发来就读取，否则就一直等待<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; String msg =
reader.readLine();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //
当客户端发送的字符串为 null 时，说明客户端已经关闭，此时退出循环<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; if (msg ==
null) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
clientList.remove(this);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
System.out.println("客户端已离开");<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; break;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //
将读入的内容写给每个客户端<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; for
(Iterator&lt;Client&gt; it =
clientList.iterator(); it.hasNext();) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; Client
client = it.next();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
client.getWriter().writeUTF(msg);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } catch
(IOException e) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
e.printStackTrace();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } finally
{<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; // 关闭 socket
及相关资源<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; try {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; if (reader
!= null) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
reader.close();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } catch
(IOException e) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
e.printStackTrace();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } finally
{<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; try {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; if (writer
!= null) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
writer.close();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } catch
(IOException e) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
e.printStackTrace();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } finally
{<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; try {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; if (socket
!= null) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
socket.close();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } catch
(IOException e) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
e.printStackTrace();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; public
DataOutputStream getWriter() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; return
writer;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
<span STYLE="font-weight: bold;">六、补充技术</SPAN><br />
1. 如何测试？<br />
* 首先要编译FlashScoket —— javac FlashScoket<br />
* 然后启动FlashScoket —— java FlashScoket<br />
* 最后将 Flash 发布为 exe 文件格式，同时开启多个即可。<br />
<br />
2. 自动跟踪到最后一行：当收到新的消息时自动滚动到最后一行，在 readMessage() 方法中加入：<br />
output_txt.verticalScrollBar.scrollPosition =
output_txt.verticalScrollBar.maxScrollPosition;<br />
<br />
3. 当出现输入/输出错误并导致发送或加载操作失败时提示用户，加入对IO_ERROR 的侦听：<br />
socket.addEventListener(IOErrorEvent.IO_ERROR,
socketIOError);<br />
function socketIOError(evt:IOErrorEvent):void {<br />
&nbsp;&nbsp;&nbsp;
output_txt.appendText("服务器端尚未开启，请稍后再试\n");<br />
}<br />
<br />
4. 在sendMessage() 中加入对空字符串的验证，如果为空则 return：<br />
if (input_txt.text == "") {<br />
&nbsp;&nbsp;&nbsp;
return;<br />
}<br />
<br />
5. 在消息前面显示用户名：大家可以制作一个登录页面，输入用户名，假设已将输入的用户名存放在 userName
变量中，在写入时加进去：<br />
&nbsp;&nbsp;&nbsp; var
userName:String = "FL 车在臣";<br />
在 sendMessage() 中相应改为：<br />
&nbsp;&nbsp;&nbsp;
bytes.writeUTFBytes(userName + " : " + input_txt.text +
"\n");<br />
<br />
6. 使用output_txt .htmlText 输出文字，那么相应的写入格式可以调整为：<br />
bytes.writeUTFBytes("&lt;font
color='#0000FF'&gt;" + userName + " :
&lt;/font&gt;" + input_txt.text +
"\n");<br />
<br />
<span STYLE="font-weight: bold;">七、源文件及代码下载：</SPAN><br />
<a HREF="http://d.download.csdn.net/down/659792" TARGET="_blank">http://d.download.csdn.net/down/659792</A><br />
<br />
<span STYLE="font-weight: bold;">八、分享一下我再学校机房部属的客户端界面（仅供参考）</SPAN><br />
<div STYLE="text-align: center;"><a href="http://blog.photo.sina.com.cn/showpic.html#url=http://static16.photo.sina.com.cn/orignal/3ecb9b11t581ec5e8148f" TARGET="_blank"><img SRC="http://static16.photo.sina.com.cn/bmiddle/3ecb9b11t581ec5e8148f" /></A><br /></DIV>]]></description>
            <author>FL基础理论大师</author>
            <category>AS3 狂想曲</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100b1jl.html#comment</comments>
            <pubDate>Tue, 30 Sep 2008 15:22:36 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100b1jl.html</guid>
        </item>
        <item>
            <title>[AS3 狂想曲 K.05] Mp3 Player [FL 车在臣]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100b1jc.html</link>
            <description><![CDATA[今天我们制作的音乐播放器，主要实现以下功能：<br />
<a href="http://blog.photo.sina.com.cn/showpic.html#url=http://static14.photo.sina.com.cn/orignal/3ecb9b11t581e64451d1d" TARGET="_blank"><img SRC="http://static14.photo.sina.com.cn/bmiddle/3ecb9b11t581e64451d1d" /></A><br />
1、选歌 —— 使用 ComboBox 组件，读取外部 xml 歌曲列表；<br />
2、音量控制 —— 使用 Slider 组件；<br />
3、显示声音波形 —— 用 AS 3 提供的 SoundMixer 类实现。<br />
&nbsp;&nbsp;&nbsp;
下面开始吧，先来制作用户界面。<br />
<br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">一、界面设计</SPAN><br />
<div STYLE="text-align: center;"><a href="http://blog.photo.sina.com.cn/showpic.html#url=http://static6.photo.sina.com.cn/orignal/3ecb9b11t581e66c77345" TARGET="_blank"><img SRC="http://static6.photo.sina.com.cn/bmiddle/3ecb9b11t581e66c77345" /></A></DIV>
&nbsp;&nbsp;&nbsp; 新建 flash
文件，在舞台上放入两个组件：ComboBox（实例名：music_list），Slider（实例名：volume_bar）。music_list用来显示歌曲列表，volume_bar
用来控制音量。<br />
<div STYLE="text-align: center;"><a href="http://blog.photo.sina.com.cn/showpic.html#url=http://static8.photo.sina.com.cn/orignal/3ecb9b11t581e69825c17" TARGET="_blank"><img SRC="http://static8.photo.sina.com.cn/bmiddle/3ecb9b11t581e69825c17" /></A></DIV>
&nbsp;&nbsp;&nbsp; 选中
volume_bar 组件，打开参数面板将 maximum 设置为 1，minimum 设置为 0。snapIterval 设置为
0.1。意思是最大值为 1，最小值为 0，滑动滑块儿的增加或减小量为 0.1。<br />
&nbsp;&nbsp;&nbsp; 关于
music_list 的初始化，我们把它放到 Actionscript 文档类中完成。<br />
<div STYLE="text-align: center;"><a href="http://blog.photo.sina.com.cn/showpic.html#url=http://static12.photo.sina.com.cn/orignal/3ecb9b11t581e6c5a3dbb" TARGET="_blank"><img SRC="http://static12.photo.sina.com.cn/bmiddle/3ecb9b11t581e6c5a3dbb" /></A><br /></DIV>
&nbsp;&nbsp;&nbsp;
最后，绘制一个长方形，用来显示声音波形，保存为影片剪辑（注册点：中下方）。在库中选中该元件，右键
-&gt;“链接”-&gt; “导出” -&gt; 类名为
SoundBar。好了，准备工作完成，下面开始写代码。<br />
<br />
<span STYLE="font-weight: bold;">二、初始化播放列表</SPAN><br />
为了达到最灵活的配置，我们采用读取 xml 的方式来填充播放列表。<br />
先来创建musicList.xml：<br />
&lt; ?xml version="1.0" encoding="UTF-8"
?&gt;<br />
&lt;musicList&gt;<br />
&nbsp;&nbsp;&nbsp;
&lt;music label="Select one and Play" url="stop"
/&gt;<br />
&nbsp;&nbsp;&nbsp;
&lt;music label="Vitas-OperaII"
url="music/vitas-opreaII.mp3" /&gt;<br />
&nbsp;&nbsp;&nbsp;
&lt;music label="Vitas-Star" url="music/vitas-star.mp3"
/&gt;<br />
&lt;/musicList&gt;<br />
&nbsp;&nbsp;&nbsp; 请注意，标签中的url
属性是一个相对路径，所有的音乐文件都要放到当前目录下的 music 文件夹中。每个标签必须有一个名为 label 的属性，它会被
ComboBox 自动解析为显示在列表中的名称。<br />
&nbsp;&nbsp;&nbsp; 下面对这个 Combox
组件的实例 music_list 进行数据的初始化（文档类 SoundPlayer.as）：<br />
package {<br />
&nbsp;&nbsp;&nbsp; import
flash.display.Sprite;<br />
&nbsp;&nbsp;&nbsp; import
flash.net.URLLoader;<br />
&nbsp;&nbsp;&nbsp; import
flash.net.URLRequest;<br />
&nbsp;&nbsp;&nbsp; import
flash.events.Event;<br />
&nbsp;&nbsp;&nbsp; import
flash.media.Sound;<br />
&nbsp;&nbsp;&nbsp; import
fl.data.DataProvider;<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public class
SoundPlayer extends Sprite {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; public
function SoundPlayer() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
init();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
function init():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
loader:URLLoader = new URLLoader();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
loader.load(new URLRequest("musicList.xml"));<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
loader.addEventListener(Event.COMPLETE, initSoundList);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
function initSoundList(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; <span STYLE="font-weight: bold;">music_list.dataProvider = new
DataProvider(XML(evt.target.data));</SPAN><br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
music_list.addEventListener(Event.CHANGE, changeSound);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
function changeSound(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
url:String = ComboBox(evt.target).selectedItem.url;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
trace(url);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
首先使用 URLLoader 读取外部 musicList.xml 文件。读取成功后调用 initSoundList，将 xml
的包装成一个 DataProvider 对象赋给 music_list.dataProvider。音乐列表初始化完成。<br />
&nbsp;&nbsp;&nbsp;
下面加入一个侦听器，当鼠标点击了列表中的某一项时，会触发 Event.CHANGE 事件，调用 changeSound
函数，这里我们只为了测试，打印出相应的 url 属性，接下来要在这个函数中实现音乐的播放。<br />
<br />
<span STYLE="font-weight: bold;">三、播放音乐</SPAN><br />
要想在 flash 中播放音乐，非常简单：<br />
&nbsp;&nbsp;&nbsp; sound = new
Sound(new URLRequest(url));<br />
&nbsp;&nbsp;&nbsp;
sound.play();<br />
现在 url 已经得到了，在 changeSound 方法中直接加入这两句（别忘了导入 import
flash.media.Sound 类）：<br />
&nbsp;&nbsp;&nbsp; private
function changeSound(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
url:String = ComboBox(evt.target).selectedItem.url;<br />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
sound:Sound = new Sound(new URLRequest(url));</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
sound.play();</SPAN><br />
&nbsp;&nbsp;&nbsp; }<br />
OK，这样就可以点播了。但是当我们切换音乐时，两个会同时播放，这显然不对。那么就应该在切换时判断一下，是否已经有音乐在播放，如果有就把之前的声音关掉，再播放当前选中的音乐。<br />

&nbsp;&nbsp;&nbsp; 在 AS 3
中，Sound 类只负责加载和播放，声道控制和停止播放声音由 SoundChannel 类负责。获得 SoundChannel
对象的方法也比较特殊，并不是像平常一样 new 一个对象出来。而是在 Sound 对象调用 play() 方法时作为返回值，返回一个
SoundChannel 对象，拿到这个对象就可以控制当前 Sound 对象的音量或关闭声音了。由于我们每次都只播放一个
Sound，所以 SoundChannel 对象也只需要一个，把它作为一个成员变量（别忘了导入 import
flash.media.SoundChannel）：<br />
&nbsp;&nbsp;&nbsp; <span STYLE="font-weight: bold;">private var
soundChannel:SoundChannel;</SPAN><br />
下面在 changeSound() 方法中加入判断：<br />
private function changeSound(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp; var
url:String = ComboBox(evt.target).selectedItem.url;<br />
&nbsp;&nbsp;&nbsp;<br />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
if (soundChannel != null) {</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
soundChannel.stop();</SPAN><br STYLE="font-weight: bold;" />
<span STYLE="font-weight: bold;">&nbsp;&nbsp;&nbsp;
}</SPAN><br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; var
sound:Sound = new Sound(new URLRequest(url));<br />
&nbsp;&nbsp;&nbsp; <span STYLE="font-weight: bold;">soundChannel = sound.play();</SPAN><br />
}<br />
这样就可以了。大家不必担心同一个音乐会被重复播放，因为 ComboBox
会自动判断当前点击的条目是否已被选中，如果是，就不发出响应事件了。<br />
&nbsp;&nbsp;&nbsp;
最后再提醒一下，如果出现：<br />
Error #2044: 未处理的 IOErrorEvent:。 text=Error #2032: 流错误。<br />
&nbsp;&nbsp;&nbsp; 这样的错误，很有可能是
xml 配置的 url 有问题。对于这种错误最好把错误信息打印出来（需要导入类 import
flash.events.IOErrorEvent;）：<br />
sound.addEventListener(IOErrorEvent.IO_ERROR,
function(e:IOErrorEvent):void{ trace(e); });<br />
&nbsp;&nbsp;&nbsp;
这样一来，错误就一目了然了。<br />
<br />
<span STYLE="font-weight: bold;">四、音量控制</SPAN><br />
&nbsp;&nbsp;&nbsp; 在界面设计时，我们已经把
volume_bar 滑动条放置在舞台上，并且通过参数面板设置其最大值为 1，最小值为 0，每次滑动变化 0.1。在 AS 3 中诸如
scaleX, scaleY, alpha 等原来以 0 ～ 100 为区间的属性，现在都以 0 ～ 1
为区间，我们的音量（volume）属性也是如此。只要拿到volume_bar 滑块的 value，将它赋为音量值即可。<br />
&nbsp;&nbsp;&nbsp; 首先在 init
函数中为滑块添加一个侦听器，当滑块滑动后将音量调整为滑块的值：<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
volume_bar.addEventListener(Event.CHANGE, adjustVolume);<br />
&nbsp;&nbsp;&nbsp;
然后创建adjustVolume 方法：<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
private function adjustVolume(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
soundChannel.soundTransform = new SoundTransform(<span STYLE="font-weight: bold;">evt.target.value</SPAN>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
}<br />
同时还需要导入import
flash.media.SoundTransform。好了，音量控制也完成了，下面来看如何输出声音波形。<br />
<br />
<span STYLE="font-weight: bold;">五、输出声音波形</SPAN><br />
&nbsp;&nbsp;&nbsp; 读取声音波形使用
SoundMixer 类的静态方法computeSpectrum()。下面看一下帮助中对于该方法的描述：<br />
computeSpectrum(outputArray:ByteArray, FFTMode:Boolean = false,
stretchFactor:int = 0):void<br />
[static] 获取当前声音波形的快照，并将其放在指定的 ByteArray 对象中。<br />
&nbsp;&nbsp;&nbsp; 此方法的第一个参数是一个
ByteArray 字节数组，它会将波形写入到 ByteArray 中。<br />
字节数组中的值为标准浮点值（范围为 -1.0 到 1.0）格式。刚好，用得到的 -1.0 ～ 1.0 直接就可以赋值给声波条
SoundBar（我们在界面设计时绘制的一个影片剪辑）对象的 scaleY 属性了。第三个参数的值可以改变采样率，默认为 0，按
44.1 KHz 对数据进行采样。<br />
&nbsp;&nbsp;&nbsp;
computeSpectrum() 方法填充的 ByteArray 大小固定为512 个浮点值。其中前 256 个值表示左声道，后
256 个值表示右声道。<br />
&nbsp;&nbsp;&nbsp; 既然有 512
个浮点值，为了简单起见，我们就用 512 个 SoundBar 来显示它们。首先加入一些成员变量：<br />
&nbsp;&nbsp;&nbsp; // 保存 512
个声音波形的快照<br />
&nbsp;&nbsp;&nbsp; <span STYLE="font-weight: bold;">private var bytes:ByteArray = new
ByteArray();</SPAN><br />
&nbsp;&nbsp;&nbsp; // SoundBar
的个数<br />
&nbsp;&nbsp;&nbsp; <span STYLE="font-weight: bold;">private var barNum:uint = 512;</SPAN><br />
&nbsp;&nbsp;&nbsp; // 保存所有
SoundBar 对象的引用<br />
&nbsp;&nbsp;&nbsp; <span STYLE="font-weight: bold;">private var soundBars:Array = new
Array();</SPAN><br />
<br />
下面在 <span STYLE="font-weight: bold;">init()</SPAN> 函数中将声音条初始化，并添加
onEnterFrame 侦听：<br />
&nbsp;&nbsp;&nbsp; // 每个
SoundBar 的宽度：舞台宽度 / SoundBar 的个数<br />
&nbsp;&nbsp;&nbsp; var
barWidth:Number = stage.stageWidth / barNum;<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; // 初始化
SoundBar，放入舞台并加入数组<br />
&nbsp;&nbsp;&nbsp; for (var
i:uint = 0; i &lt; barNum; i++) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
soundBar:SoundBar = new SoundBar();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
soundBar.width = barWidth;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; soundBar.x =
i * barWidth;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; soundBar.y =
stage.stageHeight;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
addChild(soundBar);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
soundBars.push(soundBar);<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; //
注册用来处理波形的函数<br />
&nbsp;&nbsp;&nbsp;
addEventListener(Event.ENTER_FRAME, onEnterFrame);<br />
<br />
最后是 onEnterFrame 函数：<br />
&nbsp;&nbsp;&nbsp; private
function onEnterFrame(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
SoundMixer.computeSpectrum(bytes, false, 0);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; for (var
i:uint = 0; i &lt; barNum; i++) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
soundBars[i].scaleY = bytes.readFloat();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
<br />
<span STYLE="font-weight: bold;">六、SoundPlayer.as 完整代码</SPAN><br />
package {<br />
&nbsp;&nbsp;&nbsp; import
flash.display.Sprite;<br />
&nbsp;&nbsp;&nbsp; import
flash.net.URLLoader;<br />
&nbsp;&nbsp;&nbsp; import
flash.net.URLRequest;<br />
&nbsp;&nbsp;&nbsp; import
flash.events.Event;<br />
&nbsp;&nbsp;&nbsp; import
flash.media.Sound;<br />
&nbsp;&nbsp;&nbsp; import
fl.data.DataProvider;<br />
&nbsp;&nbsp;&nbsp; import
flash.media.Sound;<br />
&nbsp;&nbsp;&nbsp; import
flash.media.SoundChannel;<br />
&nbsp;&nbsp;&nbsp; import
flash.media.SoundTransform;<br />
&nbsp;&nbsp;&nbsp; import
flash.media.SoundMixer;<br />
&nbsp;&nbsp;&nbsp; import
flash.utils.ByteArray;<br />
&nbsp;&nbsp;&nbsp; import
flash.events.IOErrorEvent;<br />
<br />
&nbsp;&nbsp;&nbsp; public class
SoundPlayer extends Sprite {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private var
soundChannel:SoundChannel;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; // 保存 512
个声音波形的快照<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private var
bytes:ByteArray = new ByteArray();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; // SoundBar
的个数<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private var
barNum:uint = 512;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; // 保存所有
SoundBar 的引用<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private var
soundBars:Array = new Array();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; public
function SoundPlayer() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
init();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
function init():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; // 每个
SoundBar 的宽度：舞台宽度 / SoundBar 的个数<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
barWidth:Number = stage.stageWidth / barNum;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; // 初始化
SoundBar，放入舞台并加入数组<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; for (var
i:uint = 0; i &lt; barNum; i++) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
soundBar:SoundBar = new SoundBar();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
soundBar.width = barWidth;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; soundBar.x =
i * barWidth;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; soundBar.y =
stage.stageHeight;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
addChild(soundBar);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
soundBars.push(soundBar);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //
注册用来处理波形的函数<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
addEventListener(Event.ENTER_FRAME, onEnterFrame);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
loader:URLLoader = new URLLoader();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
loader.load(new URLRequest("musicList.xml"));<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
loader.addEventListener(Event.COMPLETE, initSoundList);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
volume_bar.addEventListener(Event.CHANGE, adjustVolume);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
function initSoundList(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
music_list.dataProvider = new
DataProvider(XML(evt.target.data));<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
music_list.addEventListener(Event.CHANGE, changeSound);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
function changeSound(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
url:String = ComboBox(evt.target).selectedItem.url;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; if
(soundChannel != null) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
soundChannel.stop();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; if (url ==
"stop") {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
return;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
sound:Sound = new Sound(new URLRequest(url));<br />
//sound.addEventListener(IOErrorEvent.IO_ERROR,
function(e:IOErrorEvent):void{ trace(e); });<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; soundChannel
= sound.play();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
function adjustVolume(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
soundChannel.soundTransform = new
SoundTransform(evt.target.value);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
function onEnterFrame(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
SoundMixer.computeSpectrum(bytes, false, 0);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; for (var
i:uint = 0; i &lt; barNum; i++) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
soundBars[i].scaleY = bytes.readFloat();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
<span STYLE="font-weight: bold;">七、补充说明</SPAN><br />
&nbsp;&nbsp;&nbsp;
需要注意的是这个程序最好还是在单机上运行，如果上传到网络上 Sound.load()
和SoundMixer.computeSpectrum() 都存在跨域访问和安全沙箱问题，除非您有自己的服务器。<br />
&nbsp;&nbsp;&nbsp; 通常来讲 512
个SoundBar 有些太耗费资源了，如果隔一个取一次就只需要 256 个，或者也可以在算法上加以改良。<br />
<br />
<span STYLE="font-weight: bold;">八、源文件下载：</SPAN><br />
<a HREF="http://d.download.csdn.net/down/659762/" TARGET="_blank">http://d.download.csdn.net/down/659762/</A><br />]]></description>
            <author>FL基础理论大师</author>
            <category>AS3 狂想曲</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100b1jc.html#comment</comments>
            <pubDate>Tue, 30 Sep 2008 14:26:41 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100b1jc.html</guid>
        </item>
        <item>
            <title>[AS3 狂想曲 K.04]Flash与 jsp/servlet [FL 车在臣]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100awak.html</link>
            <description><![CDATA[<span STYLE="font-weight: bold;">在 AS2 中通常使用 LoadVars 类与 Web
服务器交换数据：</SPAN><br />
var msg:LoadVars = new LoadVars();<br />
var msgSent:LoadVars = new LoadVars();<br />
<br />
msg.var1 = "one";<br />
msg.var2 = "two";<br />
<br />
msgSent.onLoad = function(success:Boolean):Void {<br />
&nbsp;&nbsp; &nbsp;if (success)
{<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;trace("Message sent.");<br />
&nbsp;&nbsp; &nbsp;}<br />
&nbsp;&nbsp; &nbsp;else {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;trace("Message failed.");<br />
&nbsp;&nbsp; &nbsp;}<br />
};<br />
msg.sendAndLoad("http://127.0.0.1:8080/communicate_flash/index.jsp",
msgSent);<br />
===================================================================================<br STYLE="font-weight: bold;" />

<span STYLE="font-weight: bold;">在 AS3 中，等同于：</SPAN><br />
var scriptRequest:URLRequest = new
URLRequest("http://127.0.0.1:8080/communicate_flash/index.jsp");<br />

var scriptLoader:URLLoader = new URLLoader();<br />
var scriptVars:URLVariables = new URLVariables();<br />
<br />
scriptLoader.addEventListener(Event.COMPLETE,
handleLoadSuccessful);<br />
scriptLoader.addEventListener(IOErrorEvent.IO_ERROR,
handleLoadError);<br />
<br />
scriptVars.var1 = "one";<br />
scriptVars.var2 = "two";<br />
<br />
scriptRequest.method = URLRequestMethod.POST;<br />
scriptRequest.data = scriptVars;<br />
<br />
scriptLoader.load(scriptRequest);<br />
<br />
function handleLoadSuccessful(evt:Event):void {<br />
&nbsp;&nbsp;
&nbsp;trace("Message sent.");<br />
&nbsp;&nbsp;
&nbsp;trace("DataReceived:" + evt.target.data);<br />
}<br />
<br />
function handleLoadError(evt:IOErrorEvent):void {<br />
&nbsp;&nbsp;
&nbsp;trace("Message failed.");<br />
}<br />
===================================================================================<br />

scriptLoader.load(scriptRequest): 实际上就是向服务器发送了这样一个请求：<br />
<span STYLE="font-weight: bold;">http://127.0.0.1:8080/communicate_flash/index.jsp?var1=one&amp;var2=two</SPAN><br />

===================================================================================<br />

其中 communicate_flash/index.jsp 是我们在 Tomcat 中 webapps 下部署的一个 web
应用程序：<br />
index.jsp 文件如下：<br />
&lt;%@ page language="java" import="java.util.*"
pageEncoding="GB18030"%&gt;<br />
&lt;%<br />
&nbsp;&nbsp;
&nbsp;System.out.println("----- connected
-----");<br />
&nbsp;&nbsp; &nbsp;<br />
&nbsp;&nbsp;&nbsp; // 在和 flash
通信时，请保证字符集为 UTF-8，否则传输中文会产生乱码<br />
&nbsp;&nbsp;&nbsp;
request.setCharacterEncoding("UTF-8");<br />
&nbsp;&nbsp;&nbsp;
response.setCharacterEncoding("UTF-8");<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
out.print("result1=天天"+request.getParameter("var1")+",");<br />
&nbsp;&nbsp;&nbsp;
out.print("result2=快乐"+request.getParameter("var2"));<br />
%&gt;<br />
===================================================================================<br />

收到 jsp 页面返回的数据后，会调用 handleLoadSuccessful() 函数：通过 evt.target.data
得到数据：<br />
result1=天天one,result2=快乐two<br />
===================================================================================<br />

最后别忘了 jsp 就是 servlet，因此能和 jsp 通信也就能和 servlet 通信。当然与 php, asp
通信方法也都是这样的。以上就是 flash 与后台通信最简单、最直接的方法。<br />]]></description>
            <author>FL基础理论大师</author>
            <category>AS3 狂想曲</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100awak.html#comment</comments>
            <pubDate>Mon, 15 Sep 2008 08:37:04 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100awak.html</guid>
        </item>
        <item>
            <title>[杂谈八] 从红警到星际——编程思想的大转变[转]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100amc6.html</link>
            <description><![CDATA[　　我最开始玩的是红色警戒，那时候一般只造一个兵营和一个机械厂，然后疯狂点兵，等着1个1个慢慢造出来（我只打电脑）。后来接触星际了，开始非常的不习惯，因为它没有一个固定的界面，可以造各种单位，而是要先选取一个建筑，然后在屏幕右下角选兵来造。<br />

<br />
　　后来又发现，星际里，都是要先选一个东西，然后右下角出现这个东西可以进行的操作，习惯了之后，觉得星际这样的设计更加合理，更加符合世界的本来面貌。<br />

<br />
　　恰好那时学了C++
这门课，一直不太明白面向对象程序设计是什么意思，和面向过程有什么区别，突然我想到了星际，这不就是面向对象程序设计的经典案例吗？<br />

<br />
　　让我们来看看 C++ 和星际的共同点。<br />
<br />
　　面向对象程序设计的核心概念就是类和对象，星际里，一种单位就是一个类，这个单位的实例就是对象，比如 zealot
是个类，而游戏中你造出的一个 zealot 就是一个对象。<br />
<br />
　　类有数据成员和成员函数，星际里的一个单位，也有属性和它的方法，还是以zealot为例，他的属性有血量、攻击力、盔甲、是否被辐射等等很多很多，他的方法有移动，攻击，hold等等，就是右下角里显示的。<br />

　　这些属性都是只读的，体现了类的封装性，你不可以直接修改zealot的血量，除非你通过方法，比如你 A
他，或者给他补血。<br />
<br />
　　类的另一个重要特征是继承性，星际里，很多单位都有共同属性，比如血量，盔甲，是否中绿毒等等，还有很多共同方法，如move，stop等等。将这些特征抽象出来，组成一个基类，有这些特征的单位继承这个基类，当然这个基类在游戏中是不会出现实例的。<br />

<br />
　　类还可以组合，如航母包括本体和小飞机，tank 包括底座和炮台。<br />
<br />
　　类还有多态性，比如对函数重载来说，同时点右键，却可以是移动，或攻击或修理，这取决于点中的单位是什么，好比调用相同名称的函数，具体是哪一个由参数决定。<br />

<br />
　　还有运行时的多态，比如相同的方法，不同的对象执行他有不同的含义，好比同是 A 键，有些单位是attack，mm
却是补血。<br />
<br />
　　红警是不是用面向过程语言写的我不敢肯定，但是他的操作方式无疑很符合面向过程的思想，他不是先选一个单位，然后进行操作，而是直接先选操作，然后看操作什么单位。相当于这个操作是个全局函数，参数是各个单位。<br />

<br />
　　显然，这是将操作与数据脱离了的，因为操作不属于任何一个类。<br />
<br />
　　总结：星际这款游戏体现了面向对象思想的精髓，如果你打算学习 C++ 等面向对象语言，我建议你先学会打星际。]]></description>
            <author>FL基础理论大师</author>
            <category>杂谈系列</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100amc6.html#comment</comments>
            <pubDate>Sun, 17 Aug 2008 14:34:05 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100amc6.html</guid>
        </item>
        <item>
            <title>[AS 专题] Think in ActionScript 3.0&amp;#8546;[FL 车在臣]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100ambr.html</link>
            <description><![CDATA[<font STYLE="font-size: 24px; font-weight: bold;">8.面向对象程序设计</FONT><br />
<font STYLE="font-size: 22px; font-weight: bold;">8.1
类和对象的概念</FONT><br />
&nbsp;&nbsp;&nbsp;
类：类是用来创建同一类型的对象的“模板”，在一个类中定义了该类对象所应具有的成员变量以及方法。<br />
&nbsp;&nbsp;&nbsp;
对象：对象是类的实例。<br />
<font STYLE="font-size: 22px; font-weight: bold;"><br />
8.2 类之间的关系</FONT><br />
&nbsp;&nbsp;
&nbsp;系统中的类有那些关系：依赖、关联（聚合、合成）、泛化、实现。<br />
&nbsp;&nbsp;
&nbsp;1.依赖：对于外部类或对象的引用；<br />
&nbsp;&nbsp;&nbsp;
5.关联：关联暗示两个类在概念上位于相同的级别；<br />
&nbsp;&nbsp;&nbsp;
6.聚合：表示一种“拥有”关系，是两个类之间一种整体 / 局部的关系；<br />
&nbsp;&nbsp;&nbsp;
7.合成：表示一种更强“拥有”关系，就像人和腿的关系一样。组合而成的新对象对组成部分的内容分配和释放有绝对责任；<br />
&nbsp;&nbsp;&nbsp; 8.泛化：表现为继承
extends；<br />
&nbsp;&nbsp;&nbsp; 9.实现：表现为实现
implements。<br />
<font STYLE="font-size: 22px; font-weight: bold;"><br />
8.3 面向对象程序设计（OOP）</FONT><br />
&nbsp;&nbsp;
&nbsp;在面向对象出现以前，结构化程序设计是程序设计的主流，结构化程序设计又称为面向过程的程序设计。这种设计方法开发的软件稳定性、可修改性和可重用性都比较差。<br />

&nbsp;&nbsp;
&nbsp;与过程相比对象是稳定的。面向对象的软件系统是由对象组成的，复杂的对象是由比较简单的对象组合而成的。也就是说，面向对象方法学使用对象分解取代了传统的功能分解。<br />

&nbsp;&nbsp;
&nbsp;面向对象的精髓在于考虑问题的思路是从现实世界人类思维习惯出发的，只要领会了这一点，就领会了面向对象的思维方法。万事万物皆为对象，大至日月星辰，小至沙粒微尘，都是对象。对象包容了一切事物，不仅仅是那些看得见摸得着的是实体，如：地球、汽车、树叶，还包括那些客观存在的事物，如：社会、互联网、朋友圈子等等，包罗万象。<br />

&nbsp;&nbsp;
&nbsp;以开车为例，用面向过程的思想去考虑，那么你先得知道怎么启动，怎么踩油门，怎么挂档。这些应该是司机的活，你要把这些步骤都实现出来。如果用面向对象的思想，把自己看成领导，只需要下达命令，告诉它你要去哪里就行了（例如，调用
drive() 方法），具体怎么开，怎么踩油门，怎么挂档，不需要我们去管。<br />
&nbsp;&nbsp;&nbsp; 那么 dirve()
这个方法放到车里是否合适呢，是不是应该放到“司机”类更合理呢？封装是很灵活的，没有对与错之分，只有好与更好，需要具体问题具体分析。因为
dirve() 方法要用到油门和车档，而这些东西都在车里面，因此如果将它封装到车这个类里面可能更好些。<br />
&nbsp;&nbsp;
&nbsp;下面我们通过对比面向过程和面向对象的设计方式体会什么才是面向对象的思维。<br />
<br />
<font STYLE="font-size: 22px; font-weight: bold;">8.4 出圈游戏 —— 面向过程
VS 面向对象</FONT><br />
<font STYLE="font-size: 20px; font-weight: bold;">8.4.1
游戏规则</FONT><br />
&nbsp;&nbsp; &nbsp;假设有 5
个小孩儿手拉手围成一圈。从第一个小孩儿开始以顺时针方向依次报数 —— “1，2，3”，报 3 的人出列，第四个人从 1
开始重新报数，报到 3 时再出列。如此下去，直到所有人全部出列为止，要求按照出列的顺序输出他们的序号。<br />
&nbsp;&nbsp; &nbsp;下面来看图理解，首先有
5 个小孩围成一个圈：<br />
<img STYLE="font-size: 20px;" SRC="http://static5.photo.sina.com.cn/bmiddle/3ecb9b11t6eebe34b7cb4" /><br />

图一、 5 个小孩儿围成一圈<br />
<img STYLE="font-size: 20px;" SRC="http://static4.photo.sina.com.cn/bmiddle/3ecb9b11t5536e1fb81e3" /><br />

图二、 数到 3 的小孩儿退出去<br />
<img STYLE="font-size: 20px;" SRC="http://static14.photo.sina.com.cn/bmiddle/3ecb9b11t5536e28d091d" />
<br />
图三、再从 2 号开始数三个人， 5 号退出，然后是 2 号，最后是 4 号。<br />
&nbsp;&nbsp; &nbsp;最终输出的顺序应该是
3、1、5、2、4。<br />
<br />
<font STYLE="font-size: 20px; font-weight: bold;">8.4.2 出圈游戏 ——
面向过程（cirgame/ CircleGame1.as）</FONT><br />
&nbsp;&nbsp;
&nbsp;下面用面向过程的思想写这个程序，通过读注释先来看一下这个程序：<br />
// 有 5 个小孩儿围成的圈<br />
var array:Array = new Array(5);<br />
for (var i = 0; i &lt; array.length; i++) {<br />
&nbsp;&nbsp; &nbsp;// 如果元素值为
true 表示他在圈内，如果是 false 表示不在圈内<br />
&nbsp;&nbsp; &nbsp;array[i] =
true;<br />
}<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;<br />
// 圈内还剩多少人，最开始人都在，等于 array.length<br />
var leftCount:int = array.length;<br />
// 当前所报的数，初始为 0<br />
var countNum:int = 0;<br />
// 圈子的数组下标，表示当前指向的是谁<br />
var index:int = 0;<br />
<br />
while(leftCount &gt; 0) {<br />
&nbsp;&nbsp; &nbsp;if
(array[index] == true) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;// 如果当前这个人在圈内则报数<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;countNum++;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;if (countNum == 3) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;// 如果所报的数是 3 则出列，剩余人数减1，并且下一次从新开始报数<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;trace("out " + (index + 1));<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;array[index] = false;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;leftCount--;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;countNum = 0;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;}<br />
&nbsp;&nbsp; &nbsp;}<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;<br />
&nbsp;&nbsp; &nbsp;//
数组下标增加<br />
&nbsp;&nbsp;
&nbsp;index++;<br />
&nbsp;&nbsp; &nbsp;if (index ==
array.length) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;// 如果下标是最后一个位则归 0，因为这个圈是圆的<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;index = 0;<br />
&nbsp;&nbsp; &nbsp;}<br />
}<br />
&nbsp;&nbsp; &nbsp;用 array
数组代表这个围成的圈，开始让圈数组中的每个元素都为 true，表示它们都在圈内，如果设为 false
则表示不在圈内，后面报数的时候就不予考虑了。<br />
&nbsp;&nbsp;
&nbsp;接下来定义三个变量分别表示圈内还剩多少人，所报的数是多少和数组下标。<br />
&nbsp;&nbsp; &nbsp;下面 while
循环开始，只有圈内还有人（leftCount &gt; 0）就进行循环，首先判断当前 index
所指的元素是否为真，如果是则报数加 1，再判断是不是加到 3 了，如果是则打印出当前的数组下标，再将该元素设为
false，剩余人数减1，下一次从新开始报数。<br />
&nbsp;&nbsp; &nbsp;最后让数组下标加
1，当指到最后时，将数组下标置为 0，因为这是一个圈，要用循环的数组来表示。<br />
<br />
<font STYLE="font-size: 20px; font-weight: bold;">8.4.3 出圈游戏 ——
面向对象（cirgame/ CircleGame2.as）</FONT><br />
&nbsp;&nbsp;
&nbsp;回顾上一个例子，在面向过程的程序中，明明是围成的一个圈儿，却要看成是一个数组；明明是一个个小孩儿却要看成是数组的一个个元素。这不就是为了让计算机看懂吗？但是，面向对象是更加接近人类的思维模式，我们在现实中看到的就是一个个小孩儿，怎么能说是数组？那么这一个个小孩儿就是一个个对象，他们都是
Kid。围成的这个圈，就是一个 KidCircle。很自然吧，比大自然还自然！下面来体会面向对象的设计思想：<br />
package cirgame {<br />
&nbsp;&nbsp; &nbsp;public class
CircleGame2 {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;public function CircleGame2() {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;var kc:KidCircle = new KidCircle(5);<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;var countNum:int = 0;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;var k:Kid = kc.head;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;while (kc.count &gt; 0) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;countNum++;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;if (countNum == 3) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;trace(k.id + 1);<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;kc.remove(k);<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;countNum = 0;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;}<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;k = k.right;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;}<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;}<br />
&nbsp;&nbsp; &nbsp;}<br />
}<br />
<br />
// 每个 Kid 都有自己的 id，并且左右手还拉着其它的 Kid<br />
class Kid {<br />
&nbsp;&nbsp; &nbsp;var
id:uint;<br />
&nbsp;&nbsp; &nbsp;var
left:Kid;<br />
&nbsp;&nbsp; &nbsp;var
right:Kid;<br />
}<br />
<br />
// 这个圈子里可以加入或移除一些 Kid<br />
class KidCircle {<br />
&nbsp;&nbsp; &nbsp;var
count:uint = 0;<br />
&nbsp;&nbsp; &nbsp;var
head:Kid;<br />
&nbsp;&nbsp; &nbsp;var
rear:Kid;<br />
&nbsp;&nbsp; &nbsp;<br />
&nbsp;&nbsp; &nbsp;function
KidCircle(n:uint) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;for (var i = 0; i &lt; n; i++)
{<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;var kid:Kid = new Kid();<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;add(kid);<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;}<br />
&nbsp;&nbsp; &nbsp;}<br />
&nbsp;&nbsp; &nbsp;<br />
&nbsp;&nbsp; &nbsp;function
add(kid:Kid):void {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;kid.id = count;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;if (count == 0) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;head = kid;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;rear = kid;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;kid.left = kid;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;kid.right = kid;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;} else {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;rear.right = kid;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;kid.left = rear;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;kid.right = head;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;head.left = kid;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;rear = kid;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;}<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;count++;<br />
&nbsp;&nbsp; &nbsp;}<br />
&nbsp;&nbsp; &nbsp;<br />
&nbsp;&nbsp; &nbsp;function
remove(kid:Kid):void {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;if (count &lt;= 0) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;return;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;} else if (count == 1) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;head = rear = null;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;} else {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;kid.left.right = kid.right;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;kid.right.left = kid.left;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;if (kid == head) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;head = kid.right;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;} else if (kid == rear) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;rear = kid.left;<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;}<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;}<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;count--;<br />
&nbsp;&nbsp; &nbsp;}<br />
}<br />
&nbsp;&nbsp;
&nbsp;这段程序中设计了两个类，代表两类客观事物 —— 小孩（Kid）和圈子（KidCircle）。从
8.4.1 的图中可以确切地看到。Kid 有三个属性：id 号、左手和右手，左手拉着一个 Kid，右手拉着一个 Kid，因此 left
和 right 存放两个 Kid 的引用。<br />
&nbsp;&nbsp; &nbsp;下面是
KidCircle 类，代表围成的圈子，这个圈子可以加入或移除一些 Kid，因此有 add 和 remove 两个方法。head 和
rear 两个成员变量用于指向队首和队尾的两个 Kid，因为些添加的 Kid
要放在队尾（rear）的后面，因为这是一个圈子，所以还需要让队尾的小孩儿拉住队首（head）的小孩儿，因此需要保存这两个成员变量。<br />

&nbsp;&nbsp;
&nbsp;最后是测试类，主要的逻辑和前一个例子比较像，这里就不多解释了。<br />
&nbsp;&nbsp;
&nbsp;通过这个例子大家可以看出，我们无形之间就完成了一个数据结构 ——
双向循环链表。而前面面向过程的例子，实际上就是一个顺序的存储结构 —— 线性表。<br />
&nbsp;&nbsp;
&nbsp;后面，我会带大家写一个贪吃蛇的游戏，目的是学习面向对象编程的思想（并非该游戏本身），在贪吃蛇中我们就会运用到类似于单向链表的结构，如果双向链表掌握了，那么单向一定没问题。<br />

&nbsp;&nbsp;
&nbsp;通过本节请大家认真体会面向对象的设计思想。一次学会，终身受用。<br />
<br />
OOP 专题全部源代码下载：<br />
<a HREF="http://d.download.csdn.net/down/585235/alan98" TARGET="_blank">http://download.csdn.net/source/585235</A><br />]]></description>
            <author>FL基础理论大师</author>
            <category>AS3_OOP_专题</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100ambr.html#comment</comments>
            <pubDate>Sun, 17 Aug 2008 14:03:03 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100ambr.html</guid>
        </item>
        <item>
            <title>[AS 专题] Think in ActionScript 3.0&amp;#8545;[FL 车在臣]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100amam.html</link>
            <description><![CDATA[<p><font STYLE="font-weight: bold; font-size: 24px;">5.
多态（Polymorphism）</FONT><br />
<font STYLE="font-weight: bold; font-size: 22px;">5.1
多态的概念</FONT><br />
&nbsp;&nbsp;&nbsp;&nbsp;
面向对象的三大特性：封装、继承、多态。从一定角度来看，封装和继承几乎都是为多态而准备的。这是我们最后一个概念，也是最重要的知识点。<br />

&nbsp;&nbsp;&nbsp;&nbsp;
多态的定义：指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。（发送消息就是函数调用）<br />

&nbsp;&nbsp;&nbsp;&nbsp;
实现多态的技术称为：动态绑定（dynamic
binding），是指在执行期间判断所引用对象的实际类型，根据其实际的类型调用其相应的方法。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
多态的作用：消除类型之间的耦合关系。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
现实中，关于多态的例子不胜枚举。比方说按下 F1 键这个动作，如果当前在 Flash 界面下弹出的就是 AS 3
的帮助文档；如果当前在 Word 下弹出的就是 Word 帮助；在 Windows 下弹出的就是 Windows
帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。<br />
下面是多态存在的三个必要条件，要求大家做梦时都能背出来！<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">5.2
多态存在的三个必要条件</FONT><br />
一、要有继承；<br />
二、要有重写；<br />
三、父类引用指向子类对象。<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">5.3
TestPolymoph.as —— 多态的应用，体会多态带来的好处</FONT><br />
package {<br />
&nbsp;&nbsp; &nbsp;public class
TestPolymoph {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;public function TestPolymoph() {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;var cat:Cat = new Cat("MiMi");<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;var lily:Lady = new Lady(cat);<br />
<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;// var dog:Dog = new Dog("DouDou");<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;// var lucy:Lady = new Lady(dog);<br />
<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;lady.myPetEnjoy();<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;}<br />
&nbsp;&nbsp; &nbsp;}<br />
}<br />
<br />
class Animal {<br />
&nbsp;&nbsp; &nbsp;private var
name:String;<br />
&nbsp;&nbsp; &nbsp;function
Animal(name:String) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;this.name = name;<br />
&nbsp;&nbsp; &nbsp;}<br />
&nbsp;&nbsp; &nbsp;public
function enjoy():void {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;trace("call...");<br />
&nbsp;&nbsp; &nbsp;}<br />
}<br />
<br />
class Cat extends Animal {<br />
&nbsp;&nbsp; &nbsp;function
Cat(name:String) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;super(name);<br />
&nbsp;&nbsp; &nbsp;}<br />
&nbsp;&nbsp; &nbsp;override
public function enjoy():void {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;trace("Miao Miao...");<br />
&nbsp;&nbsp; &nbsp;}<br />
}<br />
<br />
class Dog extends Animal {<br />
&nbsp;&nbsp; &nbsp;function
Dog(name:String) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;super(name);<br />
&nbsp;&nbsp; &nbsp;}<br />
&nbsp;&nbsp; &nbsp;override
public function enjoy():void {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;trace("Wang Wang...");<br />
&nbsp;&nbsp; &nbsp;}<br />
}<br />
<br />
// 假设又添加了一个新的类 Bird<br />
class Bird extends Animal {<br />
&nbsp;&nbsp; &nbsp;function
Bird(name:String) {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;super(name);<br />
&nbsp;&nbsp; &nbsp;}<br />
&nbsp;&nbsp; &nbsp;override
public function enjoy():void {<br />
&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;trace("JiJi ZhaZha");<br />
&nbsp;&nbsp; &nbsp;}<br />
}<br />
<br />
class Lady {<br />
&nbsp;&nbsp; &nbsp;private var
pet:Animal;<br />
&nbsp;&nbsp; &nbsp;<br />
&nbsp;&nbsp;&nbsp; function
Lady(pet:Animal) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; this.pet =
pet;<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public
function myPetEnjoy():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //
试想如果没有多态<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //if (pet is
Cat) { Cat.enjoy() }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //if (pet is
Dog) { Dog.enjoy() }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //if (pet is
Bird) { Bird.enjoy() }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
pet.enjoy();<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp; 首先，定义 Animal
类包括：一个 name 属性（动物的名字），一个 enjoy() 方法（小动物玩儿高兴了就会叫）。接下来，定义 Cat, Dog
类它们都继承了 Animal 这个类，通过在构造函数中调用父类的构造函数可以设置 name
这个属性。猫应该是“喵喵”叫的，因此对于父类的 enjoy() 方法进行重写（override），打印出的叫声为 “Miao
Maio…”。Dog 也是如此，重写 enjoy 方法，叫声为 “Wang Wang…”。<br />
&nbsp;&nbsp;&nbsp; 再定义一个 Lady
类，设置一个情节：假设这个 Lady 是一个小女孩儿，她可以去养一只宠物，这个小动物可能是 Cat, Dog，或是 Animal
的子类。在 Lady 类中设计一个成员变量 pet，存放着宠物的引用。具体是哪类动物不清楚，但肯定是 Animal 的子类，因此
pet 的类型为 Animal，即 pet:Animal。注意这是父类引用，用它来指向子类对象。<br />
&nbsp;&nbsp;&nbsp; 最后在 Lady
类里面有一个成员函数 myPetEnjoy()，这个方法中只有一句 pet.enjoy()，调用 pet 的 enjoy()
方法。<br />
&nbsp;&nbsp;&nbsp; 现在来看测试类。new
出来一只 Cat，new 出来一个 Lady，将 Cat 的对象传给 Lady。现在 Lady 中的成员变量应该是
pet:Animal = new Cat(“MiMi”)。下面，调用 lady.myPetEnjoy() 方法，实际就是在调用
pet.enjoy()，打印出 Miao Miao。pet 的类型明明是 Animal，但被调的方法却是 Cat 的
enjoy()，而非 Animal 的
enjoy()，这就叫动态绑定——“在执行期间判断所引用对象的实际类型，根据其实际的类型调用其相应的方法”。<br />
&nbsp;&nbsp;&nbsp;
想象一下，如果没有多态的话，myPetEnjoy() 中方法可能要做这样的一些判断：<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
if (pet is Cat) { new Cat(“c”).enjoy() }<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
if (pet is Dog) { new Dog(“d”).enjoy() }<br />
&nbsp;&nbsp;&nbsp; 判断如果 pet
是Cat 类型的话调用 new Cat().enjoy()，如果是 Dog 的话调用 new
Dog().enjoy()。假设有一天我要传入一个 Bird，那还得手动加上：<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
if (pet is Bird) { new Bird (“b”).enjoy() }<br />
&nbsp;&nbsp;&nbsp;
新加入什么类型的都要重新修改这个方法，这样的程序可扩展性差。但是现在我们运用了多态，可以随意地加入任何类型的对象，只要是 Animal
的子类就可以。例如，var lily:Lady = new Lady(new
Bird(“dudu”))，直接添加进去就可以了，不需要修改其它任何地方。这样就大大提升的代码的可扩展性，通过这个例子好好体会一下多态带来的好处。<br />

&nbsp;&nbsp;&nbsp;
最后再补充一点，在使用父类引用指向子类对象时，父类型的对象只能调用是在父类中定义的，如果子类有新的方法，对于父类来说是看不到的。拿我们这个例子来说，如果
Animal 类不变，在 Cat 和 Dog 中都新定义出一个 run()
方法，这个方法是父类中没有的。那么这时要使用父类型的对象去调用子类新添加的方法就不行了。<br />
&nbsp;&nbsp;&nbsp;
下面看一个这个例子的内存图。<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">5.4 TestPolymoph
内存分析</FONT></P>
<p><a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static14.photo.sina.com.cn/orignal/3ecb9b11t54a80f79bdfd" TARGET="_blank"><img SRC="http://static14.photo.sina.com.cn/bmiddle/3ecb9b11t54a80f79bdfd" /></A></P>
<p>&nbsp;&nbsp;&nbsp;
在内存中，一个个方法就是一段段代码，因此它们被存放在代码段中。上例中的 pet 是 Animal 类型的成员变量，但是它指向的是一个
Cat 类型的具体对象，同时 Cat 又是它的子类，并且重写了 enjoy() 方法，满足了多态存在的三个必要条件。那么当调用
pet.enjoy() 的时候，调用的就是实际对象 Cat 的 enjoy() 方法，而非引用类型 Animal 的 enjoy()
方法。<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">5.5
多态的好处</FONT><br />
&nbsp;&nbsp;&nbsp;
多态提升了代码的可扩展性，我们可以在少量修改甚至不修改原有代码的基础上，轻松加入新的功能，使代码更加健壮，易于维护。<br />
&nbsp;&nbsp;&nbsp;
在设计模式中对于多态的应用比比皆是，面向对象设计（OOD）中有一个最根本的原则叫做“开放 &ndash; 关闭”原则（Open-Closed
Principle&nbsp;
OCP），意思是指对添加开放，对修改关闭。看看上面的例子，运用了多态以后我们要添加一个 Bird 只需要再写一个 Bird
类，让它继承自 Animal，然后 new 出来一个对象把它传给 lily 即可。<br />
&nbsp;&nbsp;&nbsp;
我们所做的就是添加新的类，而对原来的结构没有做任何的修改，这样代码的可扩展性就非常好了！因为我们遵循了“开放-关闭”原则 ——
添加而不是修改。<br />
&nbsp;&nbsp;&nbsp;
前面这个例子中还有一个地方需要说明，Animal 这个类，实际上应该定义为一个抽象类，里面的 enjoy()
方法，事实上不需要实现，也没法实现。想一想，Animal 的叫声？！你能想象出 Animal
是怎么叫的吗？显然，这个方法应该定义为一个抽象方法，留给它的子类去实现，它自己不需要实现，那么一旦这个类中有一个方法抽象的，那么这个类就应该定义为抽象类。但是很遗憾
AS 3 不支持抽象类，因为它没有 abstract
关键字。但是抽象类也是一个比较重要的概念，因此下面还要给大家补充一下。<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">5.6
抽象类的概念</FONT><br />
&nbsp;&nbsp;&nbsp;&nbsp;
一个类如果只声明方法而没有方法的实现，则称为抽象类。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
含有抽象方法的类必须被声明为抽象类，抽象类必须被继承，抽象方法必须被重写。如果重写不了，应该声明自己为抽象。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
抽象类不能被实例化。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
抽象方法只需声明，而不需实现。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
ActionScript 3.0
不支持抽象类（abstract），以后肯定会支持的，相信我，那只是时间问题。因此这里只介绍一下抽象类的概念。<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">5.7
对象转型（Casting）</FONT><br />
&nbsp;&nbsp;&nbsp;&nbsp;
一个基类类型变量可以“指向”其子类的对象。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
一个基类的引用不可以访问其子类对象新增加的成员（属性和方法）。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
可以使用“变量 is 类名”来判断该引用型变量所“指向”的对象是否属于该类或该类的子类。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
子类的对象可以当作基类的对象来使用称作向上转型（upcasting），反之称为向下转型（downcasting）。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
每说到转型，就不得不提到“里氏代换原则（LSP）”。里氏代换原则说，任何基类可以出现的地方，子类一定可以出现。里氏代换原则是对“开放—关闭”原则的补充。<br />

&nbsp;&nbsp;&nbsp;&nbsp;
里氏代换原则准确的描述：在一个程序中，将所有类型为 A 的对象都转型为 B 的对象，而程序的行为没有变化，那么类型 B 是类型 A
的子类型。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
比如，假设有两个类：Base 和 Extender，其中 Extender 是 Base 的子类。如果一个方法可以接受基类对象 b
的话: method(b:Base) 那么它必然可以接受一个子类对象 e，即有
method(e)。注意，里氏代换原则反过来不能成立。使用子类对象的地方，不一定能替换成父类对象。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
向上转型是安全的，可以放心去做。但是在做向下转型，并且对象的具体类型不明确时通常需要用 instanceof 判断类型。下面看一个例子
TestPolymoph.as：<br />
package {<br />
&nbsp;&nbsp;&nbsp; public class
TestCast {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; public
function TestCast() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //
-------------- UpCasting --------------<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var cat:Cat
= new Cat();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var dog:Dog
= new Dog();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
animal:Animal = Animal(cat);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
animal.call();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
animal.sleep();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
//animal.eat(); // 不能调用父类中没有定义的方法<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; //
------------- DownCasting -------------<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; if (animal
is Cat) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; cat =
Cat(animal);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
cat.eat();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; } else if
(animal is Dog) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; dog =
Dog(animal);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
dog.eat();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
class Animal {<br />
&nbsp;&nbsp;&nbsp; public
function call():void{};<br />
&nbsp;&nbsp;&nbsp; public
function sleep():void{};<br />
}<br />
class Cat extends Animal {<br />
&nbsp;&nbsp;&nbsp; override
public function call():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Cat
Call");<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; override
public function sleep():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Cat
Sleep");<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; public
function eat():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Cat
Eat");<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
class Dog extends Animal {<br />
&nbsp;&nbsp;&nbsp; override
public function call():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Dog
Call");<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; override
public function sleep():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Dog
Sleep");<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; public
function eat():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Dog
Eat");<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp; 首先创建 Animal
类，定义两个方法 call() 和 sleep()，它的子类 Cat 和 Dog 分别重写了这两个方法，并且都扩展了出了一个新的方法
eat()。<br />
&nbsp;&nbsp;&nbsp; 来看测试类，new
出来一个 cat，再将它向上转型 animal:Animal =
Animal(cat)。由于向上转型是安全的，所以这样做没有问题，但是当它转型成了父类对象后，就不能再调用 eat()
方法了，因为在父类中只有call() 和 sleep() 方法，父类对象不能调用子类扩展出的新方法。<br />
&nbsp;&nbsp;&nbsp;
接下来一段代码是在进行向下转型，animal 这个对象可以是一个放一个 dog 也可以放一个
cat，当这两种情况都有可能时，进行向下转型就要判断一下当然对象到底是哪个类型的，使用“is”进行判断，看看该对象是不是一个 Cat
或 Dog，如果是 Cat 就将它向下转型为一个 Cat，这样就可以安全地调用 Cat 的 eat() 方法了。<br />
&nbsp;&nbsp;&nbsp; 最后再举一个现实中的例子
TestEventCast.as ：<br />
package {<br />
&nbsp;&nbsp;&nbsp; import
flash.display.Sprite;<br />
&nbsp;&nbsp;&nbsp; import
flash.events.Event;<br />
&nbsp;&nbsp;&nbsp; public class
TestEventCast extends Sprite {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; public
function TestEventCast() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
ball:Sprite = new Sprite();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
ball.graphics.beginFill(0xff0000);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
ball.graphics.drawCircle(0,0,50);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
ball.graphics.endFill();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; ball.y =
150;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; ball.x =
150;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
addChild(ball);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
ball.addEventListener(Event.ENTER_FRAME, onEnterFrame);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; private
function onEnterFrame(evt:Event):void {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
// evt.target 是 Object 类型的，需要转型成为实际类型才能使用 x 属性<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
ball:Sprite = Sprite(evt.target);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; ball.x +=
5;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp; 构造函数中创建一个
Sprite 类的对象，并在里面绘制一个圆，加入 ENTER_FRAME 侦听，在 onEnterFrame 函数中，var
ball:Sprite = Sprite(evt.target)
这里我们必须做向上转型，如果不做的话系统会报错，为什么呢？<br />
&nbsp;&nbsp;&nbsp;
查看一下帮助文档，Event 类 target 属性的实现：public function get
target():Object。这是一个只读属性，它返回的是一个 Object 类型的对象。由于 AS 3
是单根继承的，因此任何一个对象都可以向上转型成 Object 类型的。因此每次要拿到这个 evt.target
的时候都要将它向下转型成为该对象的实际类型才能放心使用。<br />
<br />
<font STYLE="font-weight: bold; font-size: 24px;">6.
接口（Interface）</FONT><br />
<font STYLE="font-weight: bold; font-size: 22px;">6.1
接口的概念</FONT><br />
&nbsp;&nbsp;&nbsp;&nbsp;
每次说到接口，我都会想到现在很流行的一句话 ——
“三流的企业卖产品，二流的企业卖服务，一流的企业卖标准”。接口就是在“卖标准”。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
接口是方法声明的集合，让不相关的对象能够彼此通信。接口是实现“多继承”的一种手段。因此这一节非常重要。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
接口仅包含一组方法声明，没有具体的代码实现。实现接口的类必须按照接口的定义实现这些方法，因此，实现同一个接口的类都具有这个接口的特征。<br />

&nbsp;&nbsp;&nbsp;&nbsp;
接口与类的区别：接口中只能定义成员方法，不能定义成员变量。接口中的方法都是抽象方法（没有具体实现）。<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">6.2
依赖倒转原则（Dependence Inversion Principle）</FONT><br />
&nbsp;&nbsp;&nbsp;
如果说“开放—关闭”原则是面对对象设计的目标，那么依赖倒转原则就是这个面向对象设计的主要机制。<br />
&nbsp;&nbsp;&nbsp;
依赖倒转原则讲的是：要依赖于抽象，不要依赖于具体。<br />
&nbsp;&nbsp;&nbsp;
依赖倒转原则的另一种表述是：要针对接口编程，不要针对实现编程。针对接口编程意思就是说，应当使用接口或抽象类来编程。它强调一个系统内实体间关系的灵活性。如果设计者要遵守“开放—关闭”原则，那么依赖倒转原则便是达到此要求的途径，它是面向对象设计的核心原则，设计模式的研究和应用均以该原则为指导原则。<br />

<br />
<font STYLE="font-weight: bold; font-size: 22px;">6.3
实现接口的原则</FONT><br />
在实现接口的类中，实现的方法必须（选自帮助文档）：<br />
（1）使用 public 访问控制标识符。<br />
（2）使用与接口方法相同的名称。<br />
（3）拥有相同数量的参数，每一个参数的数据类型都要与接口方法参数的数据类型相匹配。<br />
（4）使用相同的返回类型。<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">6.4
TestInterFaceAccess.as —— 实现多个接口</FONT><br />
package {<br />
&nbsp;&nbsp;&nbsp; public class
TestInterFaceAccess {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; public
function TestInterFaceAccess() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
duck:Duck = new Duck();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
duck.run(56);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
duck.fly();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
interface Runnable {<br />
&nbsp;&nbsp;&nbsp; function
run(meter:uint):void;<br />
}<br />
<br />
interface Flyable {<br />
&nbsp;&nbsp;&nbsp; function
fly():void;<br />
}<br />
<br />
class Duck implements Runnable, Flyable {<br />
&nbsp;&nbsp;&nbsp; public
function run(meter:uint):void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("I can
run " + meter + " meters");<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public
function fly():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("I can
fly");<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp;
这个例子很简单，首先定义两个接口 Runnable 和 Flyable。Runnable 中定义了一个抽象的 run()
方法，Flyable 中定义了一个抽象的 fly()
方法。我们知道，接口是在定义标准，它自己不需要实现，具体的实现交给实现该接口的类去完成。<br />
&nbsp;&nbsp;&nbsp; Duck
类实现（implements）了 Runnable 和
Flyable，因此它必需去实现这两个接口中定义的所有方法。并且方法名，参数类型，返回值类型要与接口中定义的完全一致，权限修饰符必需是
public。大家可以试一试其它的访问权限，例如，private, internal 看看能不能测试通过。结论是不能，请查看 6.3
节实现接口的原则。<br />
&nbsp;&nbsp;&nbsp;
其实这些结论大家通过动手实验就能得出结论。比如说如果实现了该接口的类的方法权限不是 public
或者方法返回值、参数类型、参数个数与接口中定义的不同，是否可以测试通过呢？如果在定义接口时在 function
前面加入了访问权限修饰符，可以不可以以呢？类似这些问题不需要查书或去问别人，自己动手做实验是最快最高效的学习方法，编译器会告诉你，行还是不行，直接问它就可以了！以上做法都行不通。<br />

&nbsp;&nbsp;&nbsp;&nbsp;
为了更好地保证接口的实现不出差错，通常最保险的做法就将该方法复制（ctrl + c）过来，并在前面加上
public，再去实现。<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">6.5
接口用法总结</FONT><br />
&nbsp;&nbsp;&nbsp;&nbsp;
通过接口可以实现不相关类的相同行为，而不需要考虑这些类之间的层次关系。（就像人拥有一项本领）。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
通过接口可以指明多个类需要实现的方法。（描述这项本领的共同接口）。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
通过接口可以了解对象的交互界面，而不需要了解对象所对应的类。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
我们通常所说的“继承”广义来讲，它不仅是指 extends ，还包括 implements，回想 5.2
节中说到多态存在的三个必要条件，这里面所说的就广义的继承。说得更明确一点就是：要有继承（或实现相同接口），要有重写（或实现接口），父类引用指向子类对象（或接口类型指向实现类的对象）。下面来看最后一个知识点，使用接口实现多态。<br />

<br />
<font STYLE="font-weight: bold; font-size: 22px;">6.6
TestInterFacePoly.as —— 接口实现多态</FONT><br />
package {<br />
&nbsp;&nbsp;&nbsp; public class
TestInterFacePoly {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; public
function TestInterFacePoly() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var cat:Cat
= new Cat();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
duck:Duck = new Duck();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
racing:Racing = new Racing(cat);<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
racing.go();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
interface Runnable {<br />
&nbsp;&nbsp;&nbsp; function
run():void;<br />
}<br />
<br />
interface Swimmable {<br />
&nbsp;&nbsp;&nbsp; function
swim():void;<br />
}<br />
<br />
interface Flyable {<br />
&nbsp;&nbsp;&nbsp; function
fly():void;<br />
}<br />
<br />
class Cat implements Runnable, Swimmable {<br />
&nbsp;&nbsp;&nbsp; public
function run():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Cat
run");<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public
function swim():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Cat
swim");<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public
function climb():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Cat
Climb");<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
class Duck implements Runnable, Flyable {<br />
&nbsp;&nbsp;&nbsp; public
function run():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Duck
run");<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public
function fly():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("Duck
fly");<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
class Racing {<br />
&nbsp;&nbsp;&nbsp; var
runner:Runnable;<br />
&nbsp;&nbsp;&nbsp; public
function Racing(r:Runnable) {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; runner =
r;<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; public
function go():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
runner.run();<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp;
使用接口实现多态，和前面通过继承实现多态几乎是相同的，只不过这次是把父类引用改成了接口类型的引用。实现了同一接口的类的对象表示它们都具有这一项相同的能力，当我们只关心某些这项能力，而并不关心具体对象的类型时，使用多态可以更好地保证代码的灵活性，这就是向上抽象的作用。下面来解释一下这个例子。<br />

&nbsp;&nbsp;&nbsp;&nbsp;
首先，定义三个接口 Runnable（会跑的），Swimmable（会游的），Flyable（会飞的）。让 Cat
类实现Runnable, Swimmable，让 Duck 类实现Runnable,
Flyable。实现了某个接口就代表拥有了某项（或几项）技能。Cat 类中除了实现这两个接口之外，它还有自己的 climb()
方法。<br />
&nbsp;&nbsp;&nbsp; 在测试类中，创建一个
Cat 类的对象 cat，一个 Dog 类的对象 dog。<br />
&nbsp;&nbsp;&nbsp; 接下来，加入一个
Racing（跑步比赛）类的实例，将 cat 传进去，赋给 runner 变量（Runnable 接口类型的引用），Racing 类的
go() 方法中调用了实现了 Runnable 接口的对象，并调用它的 run() 方法，这里就有了多态，动态绑定到实际对象的
run() 方法。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
成员变量 runner 是 Runnable 接口类型的，说明我们只关心它是能跑的对象（拥有run()
方法），具体它是怎么跑的我不管，反正你实现了 Runnable 接口，就肯定有 run() 方法，我只要你的 run()
方法，其它的我不管。<br />
&nbsp;&nbsp;&nbsp; 注意，在
Runnable 中只定义 run() 方法，对于 runner 来说只能看到 Runnable
接口里定义的方法，在此接口以外的方法一律看不到，如果要让 runner 调用 Cat 对象的 climb() 或 swim()
方法是行不通的，与 5.3 节最后一段说明的道理是一样的。<br />
&nbsp;&nbsp;&nbsp;
接口在设计模式中应用广泛，下面请出策略模式。<br />
<br />
<font STYLE="font-weight: bold; font-size: 22px;">6.7 策略模式（Strategy
Pattern）</FONT><br />
&nbsp;&nbsp;&nbsp;
同样，不直接给出最终的答案，先看下面这个例子：<br />
package {<br />
&nbsp;&nbsp;&nbsp; public class
TestStrategy {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; public
function TestStrategy() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; var
rabbit:Rabbit = new Rabbit();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
rabbit.run();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
rabbit.jump();<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
interface Runnable {<br />
&nbsp;&nbsp;&nbsp; function
run():void;<br />
}<br />
<br />
interface Jumpable {<br />
&nbsp;&nbsp;&nbsp; function
jump():void;<br />
}<br />
<br />
class Rabbit implements Runnable, Jumpable {<br />
&nbsp;&nbsp;&nbsp; public
function run():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("I can
run fast");<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; public
function jump():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("I can
jump 5m");<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp; 这个例子很简单，让
Rabbit 实现 Runnable, Jumpable 接口，让它能跑能跳。<br />
&nbsp;&nbsp;&nbsp; 现在如果要让
Rabbit 跳不起来，那么就要修改它的 jump() 方法，打印出 “I can’t jump”。如果要让它能跑 1000
m，并且还能跨栏，那么还要修改 run() 方法的实现。还记得 OO 设计的最根本原则吗？“开放—关闭”原则 ——
对添加开放，对修改关闭。下面来看看策略模式是怎样做到“开放—关闭”原则的。以下是 TestStrategy.as：<br />
interface Runnable {<br />
&nbsp;&nbsp;&nbsp; function
run():void;<br />
}<br />
<br />
interface Jumpable {<br />
&nbsp;&nbsp;&nbsp; function
jump():void;<br />
}<br />
<br />
class FastRun implements Runnable {<br />
&nbsp;&nbsp;&nbsp; public
function run():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("I can
run fast");<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
class JumpHigh implements Jumpable {<br />
&nbsp;&nbsp;&nbsp; public
function jump():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("I can
jump 5m");<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
class JumpNoWay implements Jumpable {<br />
&nbsp;&nbsp;&nbsp; public
function jump():void {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; trace("I
can't jump");<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
<br />
class Rabbit {<br />
&nbsp;&nbsp;&nbsp; var
runBehavior:Runnable = new FastRun();<br />
&nbsp;&nbsp;&nbsp; var
jumpBehavior:Jumpable = new JumpHigh();&nbsp; // new
JumpNoWay();<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; public
function run() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
runBehavior.run();<br />
&nbsp;&nbsp;&nbsp; }<br />
&nbsp;&nbsp;&nbsp; public
function jump() {<br />
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
jumpBehavior.jump();<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp; 现在 Rabbit
中加入了两个成员变量 runBehavior，jumpBehavior 分别是 Runnable 和 Jumpable
类型的引用，又是父类引用（接口）指向子类对象。Rabbit 的 run 和 jump 直接调用了 runBehavior.run()
和 jumpBehavior.jump()。而 runBehavior，jumpBehavior 指向的是两个实现了 Runnable
和 Jumpable 接口的类 FastRun 类和 JumpHigh 类。而在这两个类中分别实现了 Runnable 和
Jumpable 接口，run() 和 jump() 的具体实现被放到 FastRun 和 JumpHigh
这两个类中去了。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
这样做有什么好处呢？首先，如果将来的策略发生了变化让兔子跳不起来，那么只需要添加一个新的类（策略）：JumpNoWay 同样让它实现
Jumpable 接口，jump 方法中打印出 "I can't jump"，然后将 Rabbit 类中的new JumpHigh()
改为 new JumpNoWay()
即可，这样就实现了“添加而不是修改”的原则，我们只添加了一个新的策略（类），对原来的策略没有任何修改，最后只是替换了一个策略而以（当然这种修改是必要的）。另一个好处是，将来如果要修改
JumpHigh 的算法，让它可以跳 150 米，那么直接去修改 JumpHigh 里的 jump() 就可以，而不会影响到
Rabbit，从而降低了耦合度，这是封装算法所带来的好处。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
这一切的灵活性都是多态所带来了，因此在很多的设计模式中都会用到多态，为的就是降低耦合度，增加程序的灵活性以及提高扩展性。以下是该模式的
UML 类图：<br />
&nbsp;<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static4.photo.sina.com.cn/orignal/3ecb9b11t54a812ec1383" TARGET="_blank"><img SRC="http://static4.photo.sina.com.cn/bmiddle/3ecb9b11t54a812ec1383" /></A><br />

&nbsp;&nbsp;&nbsp;&nbsp;
策略模式（Strategy
Pattern）属于对象行为型模式，体现了两个非常基本的面向对象设计的基本原则：封装变化的概念；编程中使用接口，而不是对接口实现。策略模式的定义如下：<br />

&nbsp;&nbsp;&nbsp;&nbsp;
定义一组算法，将每个算法都封装起来，并且使它们之间可以互换。每一个算法封装到具有共同接口的独立的类中，策略模式使这些算法在客户端调用它们的时候能够互不影响地变化。<br />

&nbsp;&nbsp;&nbsp;&nbsp;
策略模式使开发人员能够开发出由许多可替换的部分组成的软件，并且各个部分之间是弱连接的关系。弱连接的特性使软件具有更强的可扩展性，易于维护；<br />

策略模式中有三个对象：<br />
（1）环境对象：该类中实现了对抽象策略中定义的接口或者抽象类的引用。<br />
（2）抽象策略对象：它可由接口或抽象类来实现。<br />
（3）具体策略对象：它封装了实现不同功能的不同算法。<br />
<br />
<font STYLE="font-weight: bold; font-size: 24px;">7.浅谈设计模式</FONT><br />
&nbsp;&nbsp;&nbsp;
前面我们已经介绍了两个设计模式以及一些面向对象设计（OOD）的原则，那么到底还有多少种原则呢？下面我们一起简单地了解一下：<br />
1. 单一职责原则：一个类，最好只做一件事，只有一个引起它变化得原因。<br />
2. 开放封闭原则：软件实体应当对修改关闭，对扩展开放。<br />
3. 依赖倒置原则：依赖于抽象，而不要依赖于具体，因为抽象相对稳定。<br />
4. 接口隔离原则：尽量应用专门的接口，而不是单一得总接口，接口应该面向用户，将依赖建立在最小得接口上。<br />
5. 里氏替换原则：子类必须能够替换其基类。<br />
6.
合成/聚合复用原则：在新对象中聚合已有对象，使之成为新对象的成员，从而通过操作这些对象达到复用得目的。合成方式较继承方式耦合更松散，所以应该少继承，多聚合。<br />

7. 迪米特法则（又叫最少知识原则）：软件实体应该尽可能少的和其他软件实体发生相互作用。<br />
&nbsp;&nbsp;&nbsp;&nbsp;
设计原则是基本的工具，应用这些规则可使代码更加灵活、更容易维护，更容易扩展。基本原则：封装变化；面向接口变成而不是实现；优先使用组合而非继承。<br />

&nbsp;&nbsp;&nbsp;&nbsp;
对于开发人员而言，学习使用设计模式是很有必要的，而且越早学越好。尽早地了解它，能让我们脑子里先有这个概念，在日后写程序时或许就能联系上某个模式，可以对比一下人家的设计比我的设计强在哪儿，从而形成条件反射。<br />

&nbsp;&nbsp;&nbsp;&nbsp;
我在论坛上看到有人说“设计模式不用学，自己平时写写程序就会了”，楼下居然还有人跟帖表示赞同。设计模式最早由 GoF
提出，集结了这四位专家多年的心血才提炼出了 30
多种设计模式，难道说光凭你一个人的力量能够超越这些位专家经过多少年得到的成就？<br />
&nbsp;&nbsp;&nbsp;
作为一名知识的传播者，我认为讲授知识点是必要的，但更重要的是讲授学习的方法。比讲授学习方法更重要的是传授正确思考问题的方法。<br />
</P>]]></description>
            <author>FL基础理论大师</author>
            <category>AS3_OOP_专题</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100amam.html#comment</comments>
            <pubDate>Sun, 17 Aug 2008 12:57:59 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100amam.html</guid>
        </item>
        <item>
            <title>[AS 专题] Think in ActionScript 3.0&amp;#8544;[FL 车在臣]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100am9k.html</link>
            <description><![CDATA[<p><font STYLE="font-size: 32px;"><strong>Think in ActionScript
3.0</STRONG></FONT></P>
<p><font STYLE="font-size: 24px;"><strong>1.类的由来</STRONG></FONT><br />
<font STYLE="font-size: 22px;"><strong>1.1&nbsp; C
语言中的结构体</STRONG></FONT><br />
&nbsp;&nbsp;&nbsp;
这部分属于历史问题，与技术无关，了解历史可以让我们更好地把握现在和将来。C 语言中的结构体 struct
可以说是类的最原始的雏形。只有 int, float, char
这些基本数据类型是不够的，有时需要用不同的数据类型组合成一个有机的整体来使用。例如一个学生有学号和姓名就可以定义一个 Student
的结构体：<br />
struct Student {<br />
&nbsp;&nbsp;&nbsp; int
id;<br />
&nbsp;&nbsp;&nbsp; char[20]
name;<br />
} student;</P>
<p>main() {<br />
&nbsp;// 可以使用“对象名.属性” 的方式来操作数据<br />
&nbsp;&nbsp;&nbsp; student.id =
5;<br />
&nbsp;&nbsp;&nbsp; student.name
= “ZhangSan”;<br />
}</P>
<p><br />
<font STYLE="font-size: 22px;"><strong>1.2 从结构体到类的演化（C ——
C++）</STRONG></FONT></P>
<table STYLE="width: 80%;" BORDER="1" CELLPADDING="3" CELLSPACING="1">
<tbody>
<tr>
<td>&nbsp;C 中的结构体</TD>
<td>&nbsp;C++ 中的结构体</TD>
</TR>
<tr>
<td>struct 结构名 {<br />
&nbsp;&nbsp;&nbsp; 数据成员<br />
};</TD>
<td>struct 结构名 {<br />
&nbsp;&nbsp;&nbsp; 数据成员<br />
&nbsp;&nbsp;&nbsp; 成员函数<br />
}</TD>
</TR>
</TBODY>
</TABLE>
<p>&nbsp;&nbsp;&nbsp; C++
首次允许在结构体中定义成员函数！那么再将 struct 关键字换成 class 不就是我们现在所看到的类的形态了吗？<br />
class Student {<br />
&nbsp;private:<br />
&nbsp;&nbsp;int id;<br />
&nbsp;&nbsp;char[20] name;<br />
&nbsp;public:<br />
&nbsp;&nbsp;void gotoSchool() {}<br />
}<br />
&nbsp;&nbsp;&nbsp; C++
最初的名字叫做“C with class”（带类的C），经过长时间的发展，最终敲定将其命名为 C++，“++”表示加一操作，代表它比
C 语言更进步，更高级。<br />
&nbsp;&nbsp;&nbsp;
面向过程的编程就是在处理一个个函数，而现在的面向对象编程处理是函数加数据，形式上就这么点儿差别。也许刚刚接触时它时会感到有些困难，这很正常。一旦你真正了解它，那你一定会爱上它。所以，请大家不要畏惧，技术永远向着更方便，更简单，更高效的方向发展，而不会向越来越难，越来越复杂发展。对于面向对象程序设计（OOP）而言，代表着越来越接近人类的自然语言，越来越接近人类的思维，因此一切都会变得越来越简单。<br />

&nbsp;&nbsp;&nbsp;
从结构体到类的演变过程中我们看到，类中是可以定义函数的。因此，引出了面向对象三大特性之一，封装。</P>
<p><br />
<font STYLE="font-size: 24px;"><strong>2.封装（Encapsulation）</STRONG></FONT><br />

<font STYLE="font-size: 22px;"><strong>2.1
封装的概念</STRONG></FONT><br />
&nbsp;&nbsp;&nbsp;
封装的定义：把过程和数据包围起来，对数据的访问只能通过已定义的界面。在程序设计中，封装是指将数据及对于这些数据有关的操作放在一起。<br />

&nbsp;&nbsp;&nbsp;
知道这些定义，并不能代表技术水平有多高。但是如果去参加面试也许会用得着。简单解释一下，它的意思是指把成员变量和成员函数放在一个类里面，外面要想访问该类的成员变量只能通过对外公开的成员函数来访问。用户不需要知道对象行为的实现细节，只需根据对象提供的外部接口访问对象即可。<br />

&nbsp;&nbsp;&nbsp;
这里有一个原则叫做“信息隐藏”—— 通常应禁止直接访问成员变量，而应该通过对外公开的接口来访问。下面，看一个小例子：<br />
class Father {<br />
&nbsp;&nbsp;&nbsp; private var
money:int = 10000000;<br />
&nbsp;&nbsp;&nbsp; public
takeMoney():int {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
money -= 100;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
return 100;<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
&nbsp;&nbsp;&nbsp; 定义名为 Father
的类（一个有钱的父亲），类中有一个成员变量 money 它的访问权限为 private 意思是说 money 是 Father
私有的，private 是指只有在这个类里面才能访问，出了这个类就无法访问该成员了，稍后会有关于访问权限更多的解释。<br />
&nbsp;&nbsp;&nbsp; 类中还定义了
takeMoney() 这个方法，它是 public 的，可以说是对外公开的一个接口。<br />
&nbsp;&nbsp;&nbsp;
从这个例子中可以看出，任何人要想从 Father 类取一些 money 的话，都只能通过 takeMoney()
这个方法去拿，而不能直接访问 money 这个属性，因为它是私有的。只有通过 takeMoney() 这个公开的方法从能修改
Father 类的成员变量让 money -= 100 —— 每次只能给你 100 元。对外只能看到 takeMoney()
这个方法，该方法如何实现的别人不知道，反正你每次只能得到 100 块。</P>
<p><br />
<font STYLE="font-size: 22px;"><strong>2.2
封装的好处</STRONG></FONT><br />
&nbsp;&nbsp;&nbsp;
封装的好处：保证了模块具有较高的独立性，使得程序的维护和修改更加容易。对应程序的修改仅限于类的内部，将程序修改带来的影响减少到最低。</P>
<p><br /></P>
<p><font STYLE="font-size: 22px;"><strong>2.3
封装的目的<br /></STRONG></FONT>（1）隐藏类的实现细节；<br />
（2）迫使用户通过接口去访问数据；<br />
（3）增强代码的可维护性。</P>
<p><br />
<font STYLE="font-size: 22px;"><strong>2.4
封装的技巧</STRONG></FONT><br />
&nbsp;&nbsp;&nbsp;
按照纯面向对象编程的思想，类中所有的成员变量都应该是 private
的，要操作这些私有的成员变量只能通过对外公开的函数来完成。实际工作中，有时也常把变量的访问权限设置为 public
目的就是为了调用起来方便。在 AS 3 中提供了 get/set 关键字，它能让我们以函数调用的方式处理属性。按照封装的原则，再结合
get/set 如何去操作一个成员变量呢？看下面这个例子：<br />
class Person {<br />
&nbsp;private var _age:int;<br />
&nbsp;<br />
&nbsp;public function get age():int {<br />
&nbsp;&nbsp;return _age;<br />
&nbsp;}<br />
&nbsp;<br />
&nbsp;public function set age(a:int):void {<br />
&nbsp;&nbsp;if (a &lt; 0)
{<br />
&nbsp;&nbsp;&nbsp;trace("age
不能小于 0！");<br />
&nbsp;&nbsp;&nbsp;return;<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;_age = a;<br />
&nbsp;}<br />
}<br />
&nbsp;&nbsp;&nbsp; 首先，实例化出该类的对象
var person:Peron = new person()。如果要设置成员变量 _age 就要通过调用 set age()
来实现：person.age = 5。实际上，我们是在调用 set age(5) 这个方法，但是由于有了 set
修饰符，操作的方法就是给对象的属性赋值，但实际上是在调用函数。但是如果这么写 person.age =
-20，这样合理吗？一个人的年龄等于 -20 岁，显然不对。因此在调用 set age()
方法中，可以进行一下判断，如果给的参数不对就发出提示，这项工作只能由函数来完成，这是属性没办法到的。<br />
回想一下封装的定义，它的用意就在于数据（成员变量）是核心，不可以随随便便去改，要想改只能去调用公开的函数，而在函数可以进行各种判断，保证对数据的修改是经过思考的，合理的。<br />

说到 get/set 现在去看看 ActionScript 3 是怎么做的。我们知道 TextField 类有一个 text
属性，查看帮助文档，会看到：<br /></P>
<table STYLE="width: 60.15%; height: 123px;" BORDER="1" CELLPADDING="3" CELLSPACING="1">
<tbody>
<tr>
<td>
<p><strong>text 属性</STRONG><br />
实现<br />
&nbsp;&nbsp;&nbsp; public
function get text():String<br />
&nbsp;&nbsp;&nbsp; public
function set text(value:String):void</P>
</TD>
</TR>
</TBODY>
</TABLE>
<p>&nbsp;&nbsp;&nbsp;
可见这个属性也是通过 get/set 方法实现的，虽然我们是用“对象名.text”方式去操作 text 属性，但实际上是在调用
get/set text()，那么这两个函数具体怎么实现的呢？Who knows！这就叫隐藏实现。AS 3
里面所有类的属性都是这样实现的，只有 get 没有 set 的是只读属性。<br />
&nbsp;&nbsp;&nbsp;
封装时还要考虑，这个方法应该归属哪个类。首先，逻辑上要说得过去，例如制作一个贪吃蛇的游戏，有一个方法叫
eat()，“吃”这个方法应该给谁？给文档类？给食物？当然应该放到蛇这个类里面，蛇去吃食物这才合理。然后，还要注意该方法要用到哪些属性。例如，一个人拿粉笔在黑板上画圆，那么画圆这个方法应该归属哪个类。人？黑板？粉笔？画圆的时候需要知道圆的半径和圆心，而这些属性都在圆这个类里面，所以画圆这个方法应该放在圆这个类里面。<br />

&nbsp;&nbsp; 下面，学习访问控制修饰符。</P>
<p><br />
<font STYLE="font-size: 24px;"><strong>3. 访问控制（Access
Control）</STRONG></FONT></P>
<table STYLE="width: 80%;" BORDER="1" CELLPADDING="3" CELLSPACING="1">
<tbody>
<tr>
<td>&nbsp;修饰符</TD>
<td>&nbsp;类内可见</TD>
<td>&nbsp;包内可见</TD>
<td>&nbsp;子类可见</TD>
<td>&nbsp;任何地方</TD>
</TR>
<tr>
<td>&nbsp;private</TD>
<td>&nbsp;&nbsp;&nbsp; Yes</TD>
<td>&nbsp;</TD>
<td>&nbsp;</TD>
<td>&nbsp;</TD>
</TR>
<tr>
<td>&nbsp;protected<br /></TD>
<td>&nbsp;&nbsp;&nbsp; Yes</TD>
<td>
&nbsp;&nbsp;&nbsp;<br /></TD>
<td>&nbsp;&nbsp;&nbsp;
Yes<br /></TD>
<td>&nbsp;</TD>
</TR>
<tr>
<td>&nbsp;internal（默认）</TD>
<td>&nbsp;&nbsp;&nbsp; Yes</TD>
<td>&nbsp;&nbsp;&nbsp;
Yes<br /></TD>
<td>&nbsp;&nbsp;&nbsp; Yes</TD>
<td>&nbsp;</TD>
</TR>
<tr>
<td>&nbsp;public</TD>
<td>&nbsp;&nbsp;&nbsp; Yes</TD>
<td>&nbsp;&nbsp;&nbsp; Yes</TD>
<td>&nbsp;&nbsp;&nbsp; Yes</TD>
<td>&nbsp;&nbsp;&nbsp; Yes</TD>
</TR>
</TBODY>
</TABLE>
<p>（1）private：仅当前类可访问，称为私有成员；<br />
（2）internal（默认）：包内可见，在同一文件夹下可访问（如果不写权限修饰符，默认权限也是它）；<br />
（3）protected：子类可访问，与包无关。如果不是该类的子类，那么 protected 和 private
是一样的；<br />
（4）public：完全公开。任何地方、任何对象都可以访问这个类的成员。</P>
<p><br />
<font STYLE="font-size: 24px;"><strong>4.
继承（Inheritance）</STRONG></FONT><br />
<font STYLE="font-size: 22px;"><strong>4.1
继承的概念</STRONG></FONT><br />
&nbsp;&nbsp;&nbsp;
下面进入面向对象的第二大特性 —— 继承。<br />
&nbsp;&nbsp;&nbsp;
继承的定义：继承是一个类可以获得另一个类的特性的机制，继承支持层次概念。<br />
&nbsp;&nbsp;&nbsp;
继承是一种代码重用的形式，允许程序员基于现有类开发新类。现有类通常称为"基类"或"超类"，新类通常称为"子类"或"派生类"。通过继承还可以在代码中利用多态。<br />

&nbsp;&nbsp;&nbsp;
继承的好处：继承最大的好处是代码的重用，与它同样重要的是它带来了多态。关于多态后面会给大家非常详细的讲解，我们现在只讨论继承。<br />

&nbsp;&nbsp;&nbsp; AS 3 和 Java
一样都是单根继承的。在 AS 3 中不论哪个类都有一个相同的唯一的老祖宗 —— Object。拿我们最熟悉的 Sprite
类来说，查看一下帮助文档：<br /></P>
<table STYLE="width: 90%;" BORDER="1" CELLPADDING="3" CELLSPACING="1">
<tbody>
<tr>
<td>包 flash.display<br />
类 public class Sprite<br />
继承
Sprite→DisplayObjectContainer→InteractiveObject→DisplayObject→EventDispatcher→Object<br />

子类&nbsp; FLVPlayback, FLVPlaybackCaptioning, MovieClip,
UIComponent</TD>
</TR>
</TBODY>
</TABLE>
&nbsp;&nbsp;&nbsp;
从这里可以清楚地看到，Sprite 的父类是 DisplayObjectContainer，而
DisplayObjectContainer 的父类是 InteractiveObject。一直往下找，最终都能够找到
Object类，不仅是 Sprite，所有的类皆如此，这就叫单根继承。AS 单根继承的思想应该说是从 Java
借鉴来的，实践证明单根继承取得了很大的成功。Java 发明的单根继承摒弃了 C++ 的多继承所带来的很多问题。<br />
&nbsp;&nbsp;&nbsp; 虽然在 AS 3
中不能直接利用多继承，但是可以通过实现多个接口（interface）达到相同的目的，关于接口后面会单独讲解。单继承和多继承相比，无非多敲些代码而已，但是它带来的巨大好处是潜移默化的。具体有哪些好处这里就不详细讨论了，总之是非常非常多，大家如果有兴趣可以到网上搜索一下相关内容。<br />

&nbsp;&nbsp;&nbsp; 我们知道 Sprite
类有 x 和 y 属性，现在请大家在帮助文档中查找出 Sprite 类的这两个属性。你一定是先去打开 Sprite
这个类，但只看到五个属性，里面没有我没要找到 x, y
属性！怎么回事？这时候应该首先想到的是继承，一定是它的父类里面有，这两个属性是从父类中继承过来的！OK，没错，就是这样，那么就去
DisplayObjectContainer 找找，可是还没找到，再找父类的父类 InteractiveObject
还没有，再找父类的父类的父类 DisplayObject
找到了吧。学会查帮助文档非常重要，知道某个类有某个属性或方法，如果在这个类中找不到就要去它的父类中去找，再找不到就去父类的父类中找，最远到
Object 不信你找不到。<br />
&nbsp;&nbsp;&nbsp;
下面看一些例子。<br />
<br />
<font STYLE="font-size: 22px;"><strong>4.2 TestPerson.as ——
属性与方法的继承</STRONG></FONT><br />
package {<br />
&nbsp;public class TestStudent {<br />
&nbsp;&nbsp;public function TestPerson()
{<br />
&nbsp;&nbsp;&nbsp;var
student:Student = new Student();<br />
&nbsp;&nbsp;&nbsp;student.name
= "ZhangSan";<br />
&nbsp;&nbsp;&nbsp;student.age =
18;<br />
&nbsp;&nbsp;&nbsp;student.school
= "114";<br />
&nbsp;&nbsp;&nbsp;trace(student.name,
student.age, student.school);<br />
&nbsp;&nbsp;}<br />
&nbsp;}<br />
}
<p>class Person {<br />
&nbsp;private var _name:String;<br />
&nbsp;private var _age:int;<br />
&nbsp;<br />
&nbsp;public function get name():String{<br />
&nbsp;&nbsp;return _name;<br />
&nbsp;}<br />
&nbsp;<br />
&nbsp;public function set name(n:String):void {<br />
&nbsp;&nbsp;if (n == "") {<br />
&nbsp;&nbsp;&nbsp;trace("name
不能为空！");<br />
&nbsp;&nbsp;&nbsp;return;<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;_name = n;<br />
&nbsp;}<br />
&nbsp;<br />
&nbsp;public function get age():int {<br />
&nbsp;&nbsp;return _age;<br />
&nbsp;}<br />
&nbsp;<br />
&nbsp;public function set age(a:int):void {<br />
&nbsp;&nbsp;if (a &lt; 0)
{<br />
&nbsp;&nbsp;&nbsp;trace("age
不能小于 0！");<br />
&nbsp;&nbsp;&nbsp;return;<br />
&nbsp;&nbsp;}<br />
&nbsp;&nbsp;_age = a;<br />
&nbsp;}<br />
}</P>
<p>class Student extends Person {<br />
&nbsp;private var _school:String;<br />
&nbsp;<br />
&nbsp;public function get school():String {<br />
&nbsp;&nbsp;return _school;<br />
&nbsp;}<br />
&nbsp;<br />
&nbsp;public function set school(s:String):void {<br />
&nbsp;&nbsp;_school = s;<br />
&nbsp;}<br />
}<br />
&nbsp;&nbsp;&nbsp; 注意，package
中包的是用来测试的类，为了演示方便Person 和 Student 跟这个测试类放在了一个 as 文件中。<br />
&nbsp;&nbsp;&nbsp; 首先定义一个
Person 类，人都有名字（name）和年龄（age），下面让 Student 类继承 Person
类，也就是说学生继承了人的所有特性，或者说“学生是一个人”，这话没错吧！ “Student is a Person”，满足
is-a，那么就可以使用继承。Student 在 Person 的基础上还多出了 school
属性，记录着他所在学校的名称。<br />
&nbsp;&nbsp;&nbsp; 在测试类中创建了一个
Student 对象，使用 student.name, student.age,
student.school，设置学生的姓名，年龄和学校。虽然 Student 类中没有定义 name, age
属性，但这两个属性是它从父类 Person 继承而来的，因此实现了代码的复用，因此 Student 也就拥有了父类属性和方法。</P>
<p><br />
<font STYLE="font-size: 22px;"><strong>4.3 TestExtends.as ——
继承的限制</STRONG></FONT><br />
package {<br />
&nbsp;public class TestExtends {<br />
&nbsp;&nbsp;public function TestExtends()
{<br />
&nbsp;&nbsp;&nbsp;new
Son();<br />
&nbsp;&nbsp;}<br />
&nbsp;}<br />
}</P>
<p>class Father {<br />
&nbsp;private var money:int = 1000000;<br />
&nbsp;public var car:String = "BMW";<br />
&nbsp;private function work():void {<br />
&nbsp;&nbsp;trace("writing");<br />
&nbsp;}<br />
}</P>
<p>class Son extends Father {<br />
&nbsp;public var bike:String = "YongJiu";<br />
&nbsp;<br />
&nbsp;// 没有继承因此谈不上重写<br />
&nbsp;private function work():void {<br />
&nbsp;&nbsp;trace("studying");<br />
&nbsp;};<br />
&nbsp;<br />
&nbsp;function Son (){<br />
&nbsp;&nbsp;trace(bike);<br />
&nbsp;&nbsp;trace(car);<br />
&nbsp;&nbsp;//trace(money); // private
的属性不能被继承<br />
&nbsp;&nbsp;work();<br />
&nbsp;}<br />
}<br />
&nbsp;&nbsp;&nbsp;
这个例子要演示哪些属性或方法不能被继承。本例中定义了一个 Father 类，其中包括 private 的money 属性，public
的 car 属性和 private 的 work() 方法。<br />
&nbsp;&nbsp; &nbsp;然后定义一个 Son
类继承自 Father 类，在 Son 类中新增加一个 bike 属性，同样也有一个 work() 方法。在 Son
的构造函数中打印出 bike, car属性或调用 work() 方法都没问题。但是不能打印出 money 属性，因为它是 Father
类私有的，回想一下第三节所讲的“访问控制”查一下那个表，可以看到 private 的成员只能在类内访问，不能被继承。那么父类中还有一个
work() 方法也是 private 的，因此也不会被继承，所以在子类中再定义一个 work()
方法也不会有冲突，也就谈不上重写。关于重写，后面还有相关的例子。<br />
&nbsp;&nbsp;&nbsp; 学习 OOP
编程时，如果可以理解程序执行时内存的结构，那将对我们学习 OOP 编程有莫大好处。下面就来了解一下内存的结构以及各部分的作用。</P>
<p><br />
<font STYLE="font-size: 22px;"><strong>4.4
内存分析</STRONG></FONT><br />
&nbsp;<br />
&nbsp;根据不同的操作系统内存的结构可能有所差异，但是通常在程序执行中会把内存分为四部分：</P>
<p><a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static8.photo.sina.com.cn/orignal/3ecb9b11t54a7f5d5f1f7" TARGET="_blank"><img SRC="http://static8.photo.sina.com.cn/bmiddle/3ecb9b11t54a7f5d5f1f7" /></A><br />

（1）heap 堆：存放 new 出来的对象；<br />
（2）stack 栈：局部变量；<br />
（3）data segment 数据段：静态变量和字符串常量；<br />
（4）code segment 代码段：存放代码。<br />
&nbsp;&nbsp;&nbsp;
后面会在一些例子中利用内存图来帮助理解。先看下面一个例子 this 和 super。</P>
<p><br />
<font STYLE="font-size: 22px;"><strong>4.5 this 和 super
关键字</STRONG></FONT><br />
<font STYLE="font-size: 20px;"><strong>4.5.1 TestSuper.as —— this 和
super 以及 override</STRONG></FONT><br />
package {<br />
&nbsp;public class TestSuper {<br />
&nbsp;&nbsp;public function TestSuper()
{<br />
&nbsp;&nbsp;&nbsp;new
Extender();<br />
&nbsp;&nbsp;}<br />
&nbsp;}<br />
}</P>
<p>class Base {<br />
&nbsp;public var name:String;<br />
&nbsp;public var age:uint;<br />
&nbsp;<br />
&nbsp;//function Base(){<br />
&nbsp;&nbsp;//
即使不写构造函数，系统也会自动添加的无参构造函数<br />
&nbsp;//}<br />
&nbsp;<br />
&nbsp;public function work():void {<br />
&nbsp;&nbsp;trace("Writing!");<br />
&nbsp;}<br />
}</P>
<p>class Extender extends Base {<br />
&nbsp;//public var name:String; // name
属性已从父类继承，不能重复定义<br />
&nbsp;<br />
&nbsp;function Extender() {<br />
&nbsp;&nbsp;// super(); // 即使不写
super()，系统也会添加的无参的 super();<br />
&nbsp;&nbsp;super.work();<br />
&nbsp;&nbsp;this.work();<br />
&nbsp;}<br />
&nbsp;<br />
&nbsp;override public function work():void {<br />
&nbsp;&nbsp;trace("Programming!");<br />
&nbsp;}<br />
}<br />
&nbsp;&nbsp;&nbsp; 这个例子演示了 this
与 super 关键字的使用，以及重写（override）的概念。<br />
&nbsp;&nbsp;&nbsp; 首先，定义 Base
类代表基类，它被 Extender 类继承。Base 类中定义两个 public 的属性 age 和 name，以及一个公有的方法
work()。Extender 类继承了 Base 类的这些属性和方法，但是如果要想在 Extender 类中再重复定义 name 或
age 属性就不行了，因为它已经继承了这两个属性，不能再重复定义。但是方法可以被重新定义，前提是要在方法前面加上 override
关键字，显示地说明要重写（覆盖）基类的 work() 方法。<br />
&nbsp;&nbsp;&nbsp; 这一样来就有两个
work() 方法了如何区分它们呢？我们可以在方法名前面显示地加上 super 或 this 关键字，见 Extender
的构造函数。super.work() 表示调用父类的 work() 方法，this.work() 表示调用该类中的 work()
方法。<br />
&nbsp;&nbsp;&nbsp; override
关键字表示对父类方法的重写，override 是利用继承实现多态的必要手段。<br />
何时使用重写呢？当我们对父类的方法不满意、或者同样的一个方法对于两个类来说实现的功能不相同时，就要考虑重写，把父类的方法冲掉，让它改头换面。<br />

下面我们看一下内存图中，this 和 super 各代表着什么。</P>
<p><br />
<font STYLE="font-size: 20px;"><strong>4.5.2 this 和 super
的内存分析</STRONG></FONT><br />
&nbsp;<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static10.photo.sina.com.cn/orignal/3ecb9b11t54a7f7258759" TARGET="_blank"><img SRC="http://static10.photo.sina.com.cn/bmiddle/3ecb9b11t54a7f7258759" /></A><br />

&nbsp;&nbsp;&nbsp; 测试类中有一条语句
new Extender()。new 出来的对象放在堆内存（Heap Segment）中。在对象内部隐藏着两个对象：this 和
super。<br />
&nbsp;&nbsp; this 持有当前对象的引用；super
持有父类的引用。<br />
&nbsp;&nbsp; 上例中我们有两种调用的方法：super.work() 和
this.work()。如果不加修饰，只写 work() ，那么系统默认调用的是 this.work()。<br />
&nbsp;&nbsp;&nbsp; 既然系统默认调用的就是
this.work()，那还要 this
有什么用？比如，当局部变量与成员变量同名时，如果要在属于这个局部变量的上下文中引用成员变量，那么就要显示地调用“this.成员变量”名来指定引用的是成员变量而非局部变量。请看下面一个例子。</P>
<p><br />
<font STYLE="font-size: 20px;"><strong>4.5.3 TestThis.as —— 用 this
区分局部变量与成员变量</STRONG></FONT><br />
package {<br />
&nbsp;public class TestThis {<br />
&nbsp;&nbsp;public function TestThis()
{<br />
&nbsp;&nbsp;&nbsp;var p:Person
= new Person();<br />
&nbsp;&nbsp;&nbsp;p.setInfo("ZhangSan",
20);<br />
&nbsp;&nbsp;}<br />
&nbsp;}<br />
}</P>
<p>class Person {<br />
&nbsp;private var name:String;<br />
&nbsp;private var age:int;<br />
&nbsp;<br />
&nbsp;public function setInfo(name:String, age:int)
{<br />
&nbsp;&nbsp;this.name = name;<br />
&nbsp;&nbsp;this.age = age;<br />
&nbsp;}<br />
}<br />
&nbsp;&nbsp;&nbsp; 本例中就是用
this.name 指定是成员变量 name，而非传进来的局部变量的 name。age 也是如此。下面请看内存分析。</P>
<p><a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static7.photo.sina.com.cn/orignal/3ecb9b11t54a806c2ebc6" TARGET="_blank"><img SRC="http://static7.photo.sina.com.cn/bmiddle/3ecb9b11t54a806c2ebc6" /></A><br />

&nbsp;&nbsp;&nbsp; 传进去的两个变量
name 和 age 属于临时变量存放在栈内存（Stack Segment），setInfo() 方法中的 this.name 和
this.age 则指的是该对象的成员变量 name 和 age。由于 name 和 age 都是基元数据类型，因此直接传值，把
“zhang” 和 20 赋给了 Person 的成员变量。最后在 setInfo()
执行完成后，为该方法分配的临时变量全部释放，赋值工作完成。</P>
<p><a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static4.photo.sina.com.cn/orignal/3ecb9b11t54a7fa3f2f13" TARGET="_blank"><img SRC="http://static4.photo.sina.com.cn/bmiddle/3ecb9b11t54a7fa3f2f13" /></A></P>
<p><br />
<font STYLE="font-size: 22px;"><strong>4.6
初始化顺序</STRONG></FONT><br />
&nbsp;&nbsp;&nbsp; 下面 Think in
Java 中的一段演示代码，见 TestSandwich.as：<br />
package {<br />
&nbsp;public class TestSandwich {<br />
&nbsp;&nbsp;public function TestSandwich()
{<br />
&nbsp;&nbsp;&nbsp; new
Sandwich();<br />
&nbsp;&nbsp;}<br />
&nbsp;}<br />
}</P>
<p>class Water {<br />
&nbsp;//static var w = trace("static water");<br />
&nbsp;function Water() {<br />
&nbsp;&nbsp;trace("Water");<br />
&nbsp;}<br />
}</P>
<p>class Meal {<br />
&nbsp;//static var w = trace("static meal");<br />
&nbsp;function Meal() {<br />
&nbsp;&nbsp;trace("Meal");<br />
&nbsp;}<br />
}</P>
<p>class Bread {<br />
&nbsp;function Bread() {<br />
&nbsp;&nbsp;trace("Bread");<br />
&nbsp;}<br />
}</P>
<p>class Cheese {<br />
&nbsp;function Cheese() {<br />
&nbsp;&nbsp;trace("Cheese");<br />
&nbsp;}<br />
}</P>
<p>class Lettuce{<br />
&nbsp;function Lettuce() {<br />
&nbsp;&nbsp;trace("Lettuce");<br />
&nbsp;}<br />
}</P>
<p>class Lunch extends Meal {<br />
&nbsp;function Lunch() {<br />
&nbsp;&nbsp;trace("Lunch");<br />
&nbsp;}<br />
}</P>
<p>class PortableLunch extends Lunch {<br />
&nbsp;//static var w = trace("static lunck");<br />
&nbsp;function PortableLunch() {<br />
&nbsp;&nbsp;trace("PortableLunch");<br />
&nbsp;}<br />
}</P>
<p>class Sandwich extends PortableLunch {<br />
&nbsp;var bread:Bread = new Bread();<br />
&nbsp;var cheese:Cheese = new Cheese();<br />
&nbsp;var lettuce:Lettuce = new Lettuce();<br />
&nbsp;<br />
&nbsp;//static var good = trace("static
sandwich");<br />
&nbsp;<br />
&nbsp;function Sandwich() {<br />
&nbsp;&nbsp;trace("Sandwich");<br />
&nbsp;}<br />
}<br />
&nbsp;&nbsp;&nbsp;
测试类很简单只有一句：new Sandwich()。构造出 Sandwich 类一个实例。<br />
&nbsp;&nbsp;&nbsp; Sandwich
类继承了 PortableLunch
这个类。现在有一个问题，是先有子类还是先有父类？是先有父亲后有儿子，还是先有儿子后有父亲？肯定是先有父亲。那么怎么有的父亲？需要先构造出来。怎么构造？调用构造函数！<br />

&nbsp;&nbsp;&nbsp;
因此，我们说在构造子类之前，要先将它的父类构造出来，如果父类还有父类，就要先把父类的父类构造出来。在这段程序中每个类在构造出来后都会打印出该类的类名。下面请看执行结果：<br />

Bread<br />
Cheese<br />
Lettuce<br />
Meal<br />
Lunch<br />
PortableLunch<br />
Sandwich<br />
&nbsp;&nbsp;&nbsp;
我们看到，最先打印出来的是 Bread, Cheese, Lettuce。这是 Sandwich
类的三个成员变量，可见在调用构造函数之前，要新将该类的成员变量构造出来，然后再去构造这个类本身。<br />
&nbsp;&nbsp;&nbsp;
前面提到，要构造这个类就先要构造它的父类，Sandwich 的父类是 PortableLunch，而 PortableLunch
还有父类叫 Lunch，而 Lunch 还有父类叫 Meal，到了 Meal 就终止了。这时将执行 Meal 的构造函数，有了
Meal 之后就可以构造Lunch 了，有了 Lunch，PortableLunch 就可以构造，有了 PortableLunch
我们的 Sandwich 才被构造出来。<br />
&nbsp;&nbsp;&nbsp;
现在我们得到的结论就是：类的成员变量先被初始化，然后才是构造函数。<br />
&nbsp;&nbsp;&nbsp;
下面，请大家把代码中注释掉的部分全部打开。现在，又新加入了一些 static
的成员变量，我们来实验一下静态的成员变量是何时被调用的，执行结果如下：<br />
static water<br />
static meal<br />
static lunck<br />
static sandwich<br />
Bread<br />
Cheese<br />
Lettuce<br />
Meal<br />
Lunch<br />
PortableLunch<br />
Sandwich<br />
&nbsp;&nbsp;&nbsp;
我们看到，所有静态的成员都先于非静态成员变量被构造出来！最上面有一个 Water
类，虽然没有地方会用到它，但是它也被打印出来了。结论是：当类被加载时静态的属性和方法就会被初始化。注意，什么叫类被加载时？这是指类被加载到内存里面。可见，我们整个这个
as 文件中的所有类都被加载到了内存中了，只要这个类被读入到内存中，那么它的所有静态成员就会被初始化。<br />
&nbsp;&nbsp;&nbsp; 最终的结论 ——
初始化顺序：<br />
&nbsp;&nbsp;&nbsp;
（1）当类被加载时该类的静态的属性和方法就会被初始化<br />
&nbsp;&nbsp;&nbsp;
（2）然后初始化成员变量<br />
&nbsp;&nbsp;&nbsp;
（3）最后构造出这个类本身<br />
&nbsp;&nbsp;&nbsp;&nbsp;OK，既然这里提到了静态成员，下面我们就来了解一下它。</P>
<p><br />
<font STYLE="font-size: 20px;"><strong>4.7
静态属性与方法</STRONG></FONT><br />
<font STYLE="font-size: 20px;"><strong>4.7.1
static的概念</STRONG></FONT><br />
&nbsp;&nbsp;&nbsp;
静态属性与方法属于这个类，而不属于该类的实例。<br />
&nbsp;&nbsp;&nbsp;
静态属性只生成一份。同类对象的静态属性值都相同。改变一个类的静态属性值会影响该类所有的对象。静态属性可以节省内存，方便调用。一个方法即使不声明为静态的实际上也只生成一份，因为除了方法所处理的数据不同外，方法本身都相同的。<br />

&nbsp;&nbsp;&nbsp;
静态属性和静态方法中不能存在非静态的属性和方法，或者说 static 的属性和方法中不能出现 this。</P>
<p><br />
<font STYLE="font-size: 20px;"><strong>4.7.2 TestStatic.as ——
static 属于这个类，不属于该类实例<br /></STRONG></FONT>package {<br />
&nbsp;public class TestStatic {<br />
&nbsp;&nbsp;public function TestStatic()
{<br />
&nbsp;&nbsp;&nbsp;var b:Ball =
new Ball();<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;//
静态属性或方法属于这个类，而不属于该类对象，只能用类名引用<br />
&nbsp;&nbsp;&nbsp;//
b.sMethod();<br />
&nbsp;&nbsp;&nbsp;//
trace(b.color);<br />
&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;Ball.sMethod();<br />

&nbsp;&nbsp;&nbsp;trace(Ball.color);<br />

&nbsp;&nbsp;}<br />
&nbsp;}<br />
}</P>
<p>class Ball {<br />
&nbsp;public var xpos:Number = 300;</P>
<p>&nbsp;public static var color:uint = 0xff0000;</P>
<p>&nbsp;public function changeColor():void {<br />
&nbsp;&nbsp;color = 0;<br />
&nbsp;}<br />
&nbsp;<br />
&nbsp;public static function sMethod():void {<br />
&nbsp;&nbsp;trace("I'm a static
Method");<br />
&nbsp;&nbsp;//trace(xpos); //
静态方法中不能调用非静态成员变量<br />
&nbsp;&nbsp;//changeColor(); //
静态方法中不能调用非静态成员函数<br />
&nbsp;}<br />
}<br />
&nbsp;&nbsp;&nbsp; 在 Ball
这个类中有一个静态属性 color，一个静态方法 sMethod()。注意这条原则：静态属性与方法属于这个类，而不属于该类的实例。在
sMethod 中不能出现非静态的成员变量或方法。反之可以，在非静态的成员变量或方法中可以调用静态的成员变量或方法。<br />
&nbsp;&nbsp;&nbsp; 注意，在测试类
TestStatic
中，要访问静态的成员变量或方法只能通过“类名.方法（或属性）”的形式去调用，而不能通过“实例名.方法（或属性）”的形式调用。<br />

&nbsp;&nbsp;
&nbsp;由于静态属性只生成一份，所有该类对象都共享这一个，因此可以节省一部分内存，并且可以一改全改。既然大家都用一份属性，那就不能存在差别化了，这也是声明静态属性的一个原则，当所有对象都共用一个相同的属性时，可考虑将其声明为静态属性。而静态方法带来的好处就是方便引用。<br />

&nbsp;&nbsp;&nbsp;
下面，我们看看在设计模式中是如何使用 static 的，有请单例模式。</P>
<p><br />
<font STYLE="font-size: 20px;"><strong>4.7.4 单例模式（Singleton
Pattern）</STRONG></FONT></P>
<p>&nbsp;&nbsp;&nbsp;
单例模式是指“只能有一个该类的对象存在”。<br />
&nbsp;&nbsp;&nbsp;
单例模式有什么用处？例如系统的缓存、注册表、日志、显卡驱动、回收站等等都是独一无二的，都只有一个。如果造出了多个实例，就会导致系统出问题。这时就需要用到单例模式，原则就是该类的对象只能有一个。<br />

&nbsp;&nbsp;&nbsp;
首先，如果一个类的构造函数是公开的，那么就有可能被其它地方 new 出来，首先将构造函数变为私有的。遗憾的是 ActionScript
3 中，构造函数只能是 public 的，不能为 private。以下是 AS 3 版本的单例模式：<br />
package {<br />
&nbsp;&nbsp;&nbsp; public class
Singleton {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
static private var instance:Singleton;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
public function Singleton(singletonEnforcer:SingletonEnforcer)
{}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
public static function getInstance():Singleton {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
if (instance == null) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
instance = new Singleton(new SingletonEnforcer());<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
return instance;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
}<br />
&nbsp;&nbsp;&nbsp; }<br />
}<br />
class SingletonEnforcer { }</P>
<p>&nbsp;&nbsp;&nbsp; 这里用
SingletonEnforcer 类作为 Singleton 类构造函数的参数，由于这两个类放在一个 as
文件中，instance这个类只能被 Singleton 访问到，保证其它类得不到 SingletonEnforcer
的实例，用这种方法达到私有构造函数的作用。<br />
&nbsp;&nbsp;&nbsp;
这就是单例模式，一个最简单的设计模式！单例模式还可分为饿汉式／懒汉式。上面演示的是懒汉式单例模式，它在第一次调用
getInstance() 方法时才 new
出这个对象来。没人调用它的话，它永远不会主动去做，是不是很懒？！另一种是饿汉式的，instance 一上来就 new
出来该类的实例。</P>]]></description>
            <author>FL基础理论大师</author>
            <category>AS3_OOP_专题</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100am9k.html#comment</comments>
            <pubDate>Sun, 17 Aug 2008 10:18:52 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100am9k.html</guid>
        </item>
        <item>
            <title>[视频教程] ActionScript 3.0 OOP 专题</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100aij5.html</link>
            <description><![CDATA[<p>ActionScript 3.0 OOP 专题开始陆续发表在博客上，这集视频会在本月发表</P>
<p><div><object id="articlevblog" width="480" height="370" > <param name="allowScriptAccess" value="always" /> <embed pluginspage="http://www.macromedia.com/go/getflashplayer" src="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0</embed></object></div></P>
<p><div><object id="articlevblog" width="480" height="370" > <param name="allowScriptAccess" value="always" /> <embed pluginspage="http://www.macromedia.com/go/getflashplayer" src="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0</embed></object></div></P>
<p><div><object id="articlevblog" width="480" height="370" > <param name="allowScriptAccess" value="always" /> <embed pluginspage="http://www.macromedia.com/go/getflashplayer" src="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0</embed></object></div></P>
<p><div><object id="articlevblog" width="480" height="370" > <param name="allowScriptAccess" value="always" /> <embed pluginspage="http://www.macromedia.com/go/getflashplayer" src="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0</embed></object></div></P>
<p>----下周继续<br /></P>]]></description>
            <author>FL基础理论大师</author>
            <category>视频教程</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100aij5.html#comment</comments>
            <pubDate>Wed, 06 Aug 2008 10:58:36 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100aij5.html</guid>
        </item>
        <item>
            <title>大师新书 AdvancED ActionScript 3.0 Animation</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100aihn.html</link>
            <description><![CDATA[<p>I’ve mentioned this to a few people here and there, but here’s
the official announcement: I’m working on a new book, which will be
a successor to <a HREF="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2FFoundation-Actionscript-3-0-Animation-Making%2Fdp%2F1590597915&amp;tag=bit101-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=9325">
<strong>Foundation ActionScript 3.0 Animation: Making Things
Move!</STRONG></A><strong><img STYLE="MARGIN: 0px; BORDER-TOP-STYLE: none! important; BORDER-RIGHT-STYLE: none! important; BORDER-LEFT-STYLE: none! important; BORDER-BOTTOM-STYLE: none! important" HEIGHT="1" ALT="" SRC="http://www.assoc-amazon.com/e/ir?t=bit101-20&amp;l=ur2&amp;o=1" WIDTH="1" BORDER="0" /></STRONG></P>
<p>&nbsp;</P>
<p>I’m not sure of the full title yet, I think the first part will
be something like “Advanced ActionScript Animation…” No, it’s not
Foundation ActionScript 4.0 Animation… since AS4 only exists in a
still-evolving specification so far. Not sure if we’ll tack on a
“Making More Things Move” or something yet.</P>
<p>Basically, when I originally wrote Making Things Move, there was
just a whole lot of material I wanted to cover. It wound up at 400
pages and 19 chapters. So there was a fair amount that had to be
left out. When I started re-writing the book for AS3, I was hoping
to squeeze in some new subjects. But as it turned out, I decided to
use a full object oriented approach, which made the code samples
longer, and required a more in-depth explanation of classes, etc.
So even with the same amount of material and chapters, it came in
at a hefty 568 pages!</P>
<p>In addition to the things I had to leave out, there have been
other areas I’ve learned about in the years since I first wrote out
the table of contents for the first book. So back in April, I sat
down with Ben Renow-Clarke from Friends of ED while at FiTC in
Toronto, and started working out a spec for a sequel.</P>
<p>I’m not going to give away the full table of contents yet, but
we’re looking at 10 chapters and 300 pages. You might have noticed
me posting some isometric screen shots in the last week or so. That
has to do with Chapter 3. <img CLASS="wp-smiley" ALT=":)" SRC="http://www.bit-101.com/blog/wp-includes/images/smilies/icon_smile.gif" />
</P>
<p>I’m pretty excited about the book. There’s a really cool range
of topics, which I’ll leak out here and there in the coming weeks.
A lot of the things are stuff I’ve touched on, but don’t consider
myself a complete expert in, so it’s taking a fair amount of
research and I’m learning so much. I’ve always said one of the best
ways to learn something is to write about or teach it. In order to
make something understandable to someone else, you have to have a
personal understanding it on a level far beyond what you would
probably ever have just in using that thing.</P>
<p><strong>Update:</STRONG><br />
OK, the book is actually described on the Friend of ED website:</P>
<p><a HREF="http://www.friendsofed.com/book.html?isbn=9781430216087"><strong>http://www.friendsofed.com/book.html?isbn=9781430216087</STRONG></A></P>
<p>&nbsp;</P>
<p>
----------------------------------------------------------------------------------------------</P>
<p><span ID="btAsinTitle"><font STYLE="FONT-SIZE: 20px">AdvancED
ActionScript 3.0 Animation 一书简介</FONT></SPAN></P>
<p><span>（国外预计上市时间为 2008 年 12 月 1 日）</SPAN></P>
<p><span>Flash has always been about animation. Back in the early
days it was all done through tweening on the timeline, but with
ActionScript people realized they could control and empower their
animation to a much greater degree using code. With the speed
boosts that came with ActionScript 3.0, the latest version of
Flash's scripting language, Flash finally had the power to model a
lot more complex programmatic animations, enabling 3D animation and
physics modeling to become a reality.</SPAN></P>
<p>With the follow-up to his best-selling <em>Foundation
ActionScript Animation: Making Things Move!</EM>, Keith Peters
takes you further up on the road to becoming a Flash animation
master. He describes the brand new features available in Flash
Player 10, and how you can take advantage of them in your
animations. He also describes much more advanced techniques, such
as pathfinding routines and flocking behaviors that will be a
massive boon for any game developers struggling with AI. If you
want to take your programmatic animation skills to the next level,
this is the only book for you!</P>
<ul>
<li>Covers brand new ActionScript features to make use of Flash
Player 10</LI>
<li>Features vital game creation techniques</LI>
<li>Includes advanced physics formulas</LI>
</UL>
<h3>What you'll learn</H3>
<ul>
<li>ActionScript for Flash Player 10 3D and Inverse Kinematics</LI>
<li>New drawing API commands and PixelBender</LI>
<li>Isometric world creation</LI>
<li>Artificial intelligence including pathfinding and
steering/flocking behaviors</LI>
<li>Numerical integration for real world physics</LI>
<li>Advanced collision detection</LI>
</UL>
<h3>Who is this book for?</H3>
<p>This book is for Flash designers wishing to increase their
ActionScript abilities, and Flash games programmers wanting to pick
up some incredible new tricks.</P>
<p><br />
<b>About the Author</B><br />
Keith lives in the vicinity of Boston, MA, in the USA with his wife
Kazumi and their new daughter Kristine. He has been working with
Flash since 1999, and has co-authored many books for friends of ED,
including Flash MX Studio, Flash MX Most Wanted, and the
ground-breaking Flash Math Creativity.<br />
<br />
In 2001 he started the experimental Flash site, BIT-101
(www.bit-101.com), which strives for a new, cutting edge, open
source experiment each day. The site recently won an award at the
Flashforward 2003 Flash Film Festival in the Experimental category.
In addition to the experiments on the site, there are several
highly regarded Flash tutorials which have been translated into
many languages and are now posted on web sites throughout the
world. Keith is currently working full time doing freelance and
contract Flash development and various writing projects.</P>
<p>&nbsp;</P>
<p>新书的内容很诱人，可以说是 Making Things Move!
的高级版。非常期待，不知道什么时候才能拿到该书的电子版！</P>]]></description>
            <author>FL基础理论大师</author>
            <category>Making Things Move!</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100aihn.html#comment</comments>
            <pubDate>Wed, 06 Aug 2008 08:47:17 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100aihn.html</guid>
        </item>
        <item>
            <title>[AS3 狂想曲 K.03] 让 &quot;Flash&quot; 写文件 [FL 车在臣]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100a8cs.html</link>
            <description><![CDATA[<p>&nbsp;&nbsp;&nbsp; 目前，出于安全考虑
Flash 不支持写文件的操作，在 AS 3 的 API 中找不到相应的方法。</P>
<p>但是我们可以去看看其它的编程语言的文档，比如 VB，VC++，Java。它们都支持对文件的操作。因</P>
<p>此，我们的策略就是让 Flash 去调用一个执行写文件操作的应用程序来实现我们的计划。</P>
<p>&nbsp;</P>
<p>这里有两个知识点：<br />
1. <strong>fscommand("exec", "Write_File.exe")</STRONG> 这句话的意思就是让
Flash 去调用一个名为</P>
<p>Write_File.exe 的应用程序，这里有两点要非常注意：第一，被调用的程序必须在fscommand子文件</P>
<p>夹中，并且调用时不能传递参数；第二，Flash 影片必需发布为一个 .exe 的可执行文件，即让一个</P>
<p>可执行文件调用另一个可执行文件。</P>
<p>2. <strong>System.setClipboard(string:String)</STRONG>
调用这句话时，可以根据给定的 String 参数来设置剪贴</P>
<p>板。</P>
<p>&nbsp;</P>
<p>下面就开始制作吧，今天我们的 ActionScript 尤其简单哟！<br />
<strong>步骤1：</STRONG>在舞台中放入一个输入文本框：实例名"input_txt"；一个按钮：实例名"Submit_btn"；<br />

<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static15.photo.sina.com.cn/orignal/3ecb9b11451cc018adc1e" TARGET="_blank"><img SRC="http://static15.photo.sina.com.cn/bmiddle/3ecb9b11451cc018adc1e" />
</A></P>
<p><strong>步骤2：</STRONG>在第一帧中加入 ActionScript；<br />
// 提示用户在此输入要写入的信息<br />
<strong>input_txt.text = "在此写入一些内容，点击按钮后提交！";</STRONG></P>
<p>// 点击按钮后执行 onClick 函数<br />
<strong>Submit_btn.addEventListener(MouseEvent.CLICK,
onClick);</STRONG><br />
<strong>function onClick(evt:MouseEvent):void {</STRONG><br />
&nbsp;// 将文本内容写入到剪贴板(Clipboard)中<br />
&nbsp;<strong>System.setClipboard(String(input_txt.text));</STRONG><br />

&nbsp;// 执行用于写文件的应用程序（这里不用写 fscommand
路径名，程序默认到这里取）<br />
&nbsp;<strong>fscommand("exec",
"Write_File.exe");</STRONG><br />
<strong>}</STRONG></P>
<p>&nbsp;</P>
<p>
<strong>步骤3：</STRONG>在菜单中选择“文件”-&gt;“发布设置”，勾选"Windows
放映文件（.exe）"，点击"发布"；</P>
<p>&nbsp;</P>
<p><strong>步骤4：</STRONG>在当前目录下，创建命为 fscommand 的子目录（必需）；</P>
<p>&nbsp;</P>
<p><strong>步骤5：</STRONG>万事具备，只欠东风！只要在 fscommand 子目录下再加入
Write_File.exe 就搞定了。您</P>
<p>可以使用任何喜欢的语言去编写，如 VB, VC 甚至可以是 Java（用包装软件将 jar 包装成 exe，但</P>
<p>不推荐这样使用，因为可能需要客户端去下载 jvm）。这里我使用 VC++ 制作一个 MFC 来生成</P>
<p>Write_File.exe。整体思想很简单：读取剪贴板内容，创建文件，将读入的字符串写入文件。最终会</P>
<p>在应用程序的同一目录下生成Save_Text.txt 文件，其内容就是我们在 Flash 的文本框中输入的。</P>
<p>以下是 VC++ 代码摘要：</P>
<p>// 导入 fstream 库文件<br />
#include "fstream.h"</P>
<p>// TODO: Add extra initialization here<br />
// 在窗口的初始化中加入<br />
//***************************************************************************<br />

&nbsp;// 获得存储路径<br />
&nbsp;CString strPath;<br />
&nbsp;GetCurrentDirectory(MAX_PATH,strPath.GetBuffer(MAX_PATH));<br />

&nbsp;strPath.ReleaseBuffer();<br />
&nbsp;CString fullPath = strPath + '\\' +
"Save_Text.txt";</P>
<p>&nbsp;// 读取剪贴板<br />
&nbsp;char * buffer = NULL;<br />
&nbsp;CString fromClipboard;<br />
&nbsp;if (OpenClipboard()) {<br />
&nbsp;&nbsp;&nbsp; HANDLE hData
= GetClipboardData(CF_TEXT);<br />
&nbsp;&nbsp;&nbsp; char *
buffer = (char*)GlobalLock(hData);<br />
&nbsp;&nbsp;&nbsp;
fromClipboard = buffer;<br />
&nbsp;&nbsp;&nbsp;
GlobalUnlock(hData);<br />
&nbsp;&nbsp;&nbsp;
CloseClipboard();<br />
&nbsp;}</P>
<p>&nbsp;// 写文件<br />
&nbsp;ofstream outfile;<br />
&nbsp;outfile.open(fullPath);<br />
&nbsp;if (outfile.is_open()) {<br />
&nbsp;&nbsp;&nbsp; outfile
&lt;&lt; fromClipboard
&lt;&lt; endl;<br />
&nbsp;&nbsp;&nbsp;
outfile.close();<br />
&nbsp;}<br />
//**************************************************************************</P>
<p>&nbsp;</P>
<p><strong>步骤6：</STRONG>如果您不想使用其它语言编写也没关系，您可以直接使用下面我已经写好的这个 exe
文件（其中</P>
<p>附有 Flash 源文件）:</P>
<p>&nbsp;<a HREF="http://www.fs2you.com/files/7bf1fb75-4fcb-11dd-aa18-0014221b798a/" TARGET="_blank">http://www.fs2you.com/files/7bf1fb75-4fcb-11dd-aa18-0014221b798a/</A></P>
<p>&nbsp;</P>
<p><strong>结语：</STRONG><br />
&nbsp;&nbsp;&nbsp;
好了，完成！我们实际上就是在使用剪贴板作为中间媒介来传送消息，这也是一种进程间通</P>
<p>信的方式，因为所有进程都共享着同一个剪贴板。大家如果感兴趣可以把他制作成一个日记本的程序</P>
<p>—— 每次生成文件的文件名都以系统日期和时间命名。<br />
&nbsp;&nbsp;&nbsp;
本次教程中，没有太多实质的内容，甚至我们的主角都不是 Flash，但是它可以给我们一些</P>
<p>启发。如果某些任务 Flash 无法完成，是否能让其它人来助我们一臂之力呢？一个好汉三个帮！</P>]]></description>
            <author>FL基础理论大师</author>
            <category>AS3 狂想曲</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100a8cs.html#comment</comments>
            <pubDate>Sat, 12 Jul 2008 04:19:46 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100a8cs.html</guid>
        </item>
        <item>
            <title>ActionScript 3.0 相关资料下载 [FL 基理殿堂]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100a6ut.html</link>
            <description><![CDATA[<p><strong>FlashCS3 简体中文帮助文档 (中文 chm)</STRONG><br />
下载链接: <a HREF="http://www.blueidea.com/articleimg/2007/08/4876/FlashCS3Help_cn.rar" TARGET="_blank">http://www.blueidea.com/articleimg/2007/08/4876/FlashCS3Help_cn.rar</A></P>
<p><strong>ActionScript 3.0 Cookbook (中文 pdf)</STRONG><br />
下载链接: <a HREF="http://www.fs2you.com/files/fdffee21-4cb1-11dd-b208-00142218fc6e/" TARGET="_blank">http://www.fs2you.com/files/fdffee21-4cb1-11dd-b208-00142218fc6e/</A></P>
<p><strong>ActionScript 3 及组件 官方指南 (中文 pdf)</STRONG><br />
下载链接: <a HREF="http://www.fs2you.com/files/08ecb991-4cb3-11dd-ac76-001143e7b41c/" TARGET="_blank">http://www.fs2you.com/files/08ecb991-4cb3-11dd-ac76-001143e7b41c/</A></P>
<p><strong>XML 轻松学习手册 (中文 chm)</STRONG><br />
下载链接: <a HREF="http://www.fs2you.com/files/9d11d417-4cb3-11dd-8131-0014221f4662/" TARGET="_blank">http://www.fs2you.com/files/9d11d417-4cb3-11dd-8131-0014221f4662/</A></P>
<p><strong>Essential ActionScript3.0 (英文 pdf)</STRONG><br />
下载链接: <a HREF="http://www.fs2you.com/files/000cb9a1-4cb4-11dd-ada0-00142218fc6e/" TARGET="_blank">http://www.fs2you.com/files/000cb9a1-4cb4-11dd-ada0-00142218fc6e/</A></P>
<p><strong>Foundation Flash CS3 for Designers (英文
pdf)</STRONG><br />
下载链接: <a HREF="http://www.fs2you.com/files/a6d38f63-4cb4-11dd-a92a-00142218fc6e/" TARGET="_blank">http://www.fs2you.com/files/a6d38f63-4cb4-11dd-a92a-00142218fc6e/</A></P>
<p><strong>AS3 Design Patterns (英文 pdf)</STRONG><br />
下载链接: <a HREF="http://www.fs2you.com/files/b3bae097-4cb2-11dd-8d04-001143e7b41c/" TARGET="_blank">http://www.fs2you.com/files/b3bae097-4cb2-11dd-8d04-001143e7b41c/</A></P>
<p><strong>AS3 API (pdf)</STRONG><br />
下载链接: <a HREF="http://www.fs2you.com/files/e2c1ac6b-4cb4-11dd-99af-001143e7b41c/" TARGET="_blank">http://www.fs2you.com/files/e2c1ac6b-4cb4-11dd-99af-001143e7b41c/</A></P>
<p><strong>ActionScript 3.0 Game Programming University (英文
pdf)</STRONG><br />
下载链接: <a HREF="http://www.fs2you.com/files/5bb37d7d-4cb5-11dd-ade3-00142218fc6e/" TARGET="_blank">http://www.fs2you.com/files/5bb37d7d-4cb5-11dd-ade3-00142218fc6e/</A></P>]]></description>
            <author>FL基础理论大师</author>
            <category>杂谈系列</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100a6ut.html#comment</comments>
            <pubDate>Tue, 08 Jul 2008 06:19:09 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100a6ut.html</guid>
        </item>
        <item>
            <title>Making Things Move!  结束语 [FL 基理文]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100a6h2.html</link>
            <description><![CDATA[<p><font STYLE="FONT-SIZE: 18px">&nbsp;&nbsp;&nbsp;
历经四个月的时间，今天终于把 Foundation Actionscript 3.0 Animation —— Making
Things Move! 一书全部译完。非常感谢本书作者 Keith Peters
带给我们这么好的教材，他对本书的内容可以说是精雕细琢，不断提炼。为了能让这本经典教材呈现出它特有的魅力，我不惜花费大量的时间研究、揣摩作者的意图，尽可能地表述完整、准确。我的译文中拒绝出现蹩脚、拗口、晦涩、难懂的词句，尤其是在原理的阐述上更要清楚、明确，不能让读者在学习知识的过程中再去推敲这段话想表述什么意思。因此，我采取的策略就是转译。尽可能地用我们的思维习惯和语言方式来表示作者的意图，立争用最清晰、明快的语言将原理阐述明白，这是对您的时间和精力的最大尊重。非常感谢亲爱的网友们这么长期以来的支持，忠心地感谢大家，让我们一起为
Flash 事业而奋斗！</FONT></P>
<p ALIGN="right"><font STYLE="FONT-SIZE: 18px">Flash AS 基理殿堂
2008年7月7日</FONT></P>
<p ALIGN="right"><a HREF="http://blog.sina.com.cn/yyy98"><font STYLE="FONT-SIZE: 18px">http://blog.sina.com.cn/yyy98</FONT></A>&nbsp;</P>]]></description>
            <author>FL基础理论大师</author>
            <category>Making Things Move!</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100a6h2.html#comment</comments>
            <pubDate>Mon, 07 Jul 2008 07:05:39 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100a6h2.html</guid>
        </item>
        <item>
            <title>第十九章 实用技巧 &amp;#8546; [FL 基理译]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100a6a8.html</link>
            <description><![CDATA[<p><font STYLE="FONT-SIZE: 22px"><strong>实用公式</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
统领全书，我们已经有了各种运动和效果的公式。我已经提取出了最实用和最常用的公式、方程、以及代码的摘录，并将它们列在本章的最后。我认为将它们放到同一个地方应该对大家非常有帮助，因此我将这些我认为最需要的内容放到一起作为整体的一个参考资料。我将会在这一页夹上书签。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第三章</STRONG></FONT><br/>
<strong>基础三角函数的计算：<br/></STRONG>角的正弦值
= 对边 / 斜边<br/>
角的余弦值 = 邻边 / 斜边<br/>
角的正切值 = 对边 / 邻边</P>
<p>&nbsp;</P>
<p>
<strong>弧度转换为角度以及角度转换为弧度：</STRONG><br/>

弧度 = 角度 * Math.PI / 180<br/>
角度 = 弧度 * 180 / Math.PI</P>
<p>&nbsp;</P>
<p>
<strong>向鼠标（或者任何一个点）旋转：</STRONG><br/>

// 用要旋转到的 x, y 坐标替换 mouseX, mouseY<br/>
dx = mouseX - sprite.x;<br/>
dy = mouseY - sprite.y;<br/>
sprite.rotation = Math.atan2(dy, dx) * 180 / Math.PI;</P>
<p>&nbsp;</P>
<p><strong>创建波形：</STRONG><br/>
// 将 x, y 或其它属性赋值给 Sprite
影片或影片剪辑，<br/>
// 作为绘图坐标，等等。<br/>
public function onEnterFrame(event:Event){<br/>
&nbsp;value = center + Math.sin(angle) * range;<br/>
&nbsp;angle += speed;<br/>
}</P>
<p>&nbsp;</P>
<p><strong>创建圆形：</STRONG><br/>
// 将 x, y 或其它属性赋值给 Sprite
影片或影片剪辑，<br/>
// 作为绘图坐标，等等。<br/>
public function onEnterFrame(event:Event){<br/>
&nbsp;xposition = centerX + Math.cos(angle) *
radius;<br/>
&nbsp;yposition = centerY + Math.sin(angle) *
radius;<br/>
&nbsp;angle += speed;<br/>
}</P>
<p>&nbsp;</P>
<p><strong>创建椭圆：</STRONG><br/>
// 将 x, y 或其它属性赋值给 Sprite
影片或影片剪辑，<br/>
// 作为绘图坐标，等等。<br/>
public function onEnterFrame(event:Event){<br/>
&nbsp;xposition = centerX + Math.cos(angle) *
radiusX;<br/>
&nbsp;yposition = centerY + Math.sin(angle) *
radiusY;<br/>
&nbsp;angle += speed;<br/>
}</P>
<p>&nbsp;</P>
<p><strong>获得两点间的距离：</STRONG><br/>
// x1, y1 和 x2, y2 是两个点<br/>
// 也可以是 Sprite / MovieClip
坐标，鼠标坐标，等等。<br/>
dx = x2 &ndash; x1;<br/>
dy = y2 &ndash; y1;<br/>
dist = Math.sqrt(dx*dx + dy*dy);</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第四章</STRONG></FONT></P>
<p><strong>十六进制转换为十进制：</STRONG><br/>
trace(hexValue);</P>
<p>&nbsp;</P>
<p><strong>十进制转换为十六进制：</STRONG><br/>
trace(decimalValue.toString(16));</P>
<p>&nbsp;</P>
<p><strong>颜色组合：</STRONG><br/>
color24 = red &lt;&lt; 16 | green
&lt;&lt; 8 | blue;<br/>
color32 = alpha &lt;&lt; 24 | red
&lt;&lt; 16 | green
&lt;&lt; 8 | blue;</P>
<p>&nbsp;</P>
<p><strong>颜色提取：</STRONG><br/>
red = color24 &gt;&gt; 16;<br/>
green = color24 &gt;&gt; 8
&amp; 0xFF;<br/>
blue = color24 &amp; 0xFF;<br/>
alpha = color32 &gt;&gt; 24;<br/>
red = color32 &gt;&gt; 16 &amp;
0xFF;<br/>
green = color32 &gt;&gt; 8
&amp; 0xFF;<br/>
blue = color232 &amp; 0xFF;</P>
<p>&nbsp;</P>
<p><strong>穿过某点绘制曲线：</STRONG><br/>
// xt, yt 是我们想要穿过的一点<br/>
// x0, y0 以及 x2, y2 是曲线的两端<br/>
x1 = xt * 2 &ndash; (x0 + x2) / 2;<br/>
y1 = yt * 2 &ndash; (y0 + y2) / 2;<br/>
moveTo(x0, y0);<br/>
curveTo(x1, y1, x2, y2);</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第五章</STRONG></FONT><br/>
<strong>角速度转换为 x, y 速度：</STRONG><br/>
vx = speed * Math.cos(angle);<br/>
vy = speed * Math.sin(angle);</P>
<p>&nbsp;</P>
<p><strong>角加速度（作用于物体上的 force）转换为
x, y 加速度：</STRONG><br/>
ax = force * Math.cos(angle);<br/>
ay = force * Math.sin(angle);</P>
<p>&nbsp;</P>
<p><strong>将加速度加入速度：</STRONG><br/>
vx += ax;<br/>
vy += ay;</P>
<p>&nbsp;</P>
<p><strong>将速度加入坐标：</STRONG><br/>
movieclip._x += vx;<br/>
sprite.y += vy;</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第六章</STRONG></FONT><br/>
<strong>移除出界对象：</STRONG><br/>
if(sprite.x - sprite.width / 2 &gt; right ||<br/>
sprite.x + sprite.width / 2 &lt; left ||<br/>
sprite.y &ndash; sprite.height / 2 &gt; bottom ||<br/>
sprite.y + sprite.height / 2 &lt; top)<br/>
{<br/>
&nbsp;// 删除影片的代码<br/>
}</P>
<p>&nbsp;</P>
<p><strong>重置出界对象：</STRONG><br/>
if(sprite.x - sprite.width / 2 &gt; right ||<br/>
sprite.x + sprite.width / 2 &lt; left ||<br/>
sprite.y &ndash; sprite.height / 2 &gt; bottom ||<br/>
sprite.y + sprite.height / 2 &lt; top)<br/>
{<br/>
&nbsp;// 重置影片的位置和速度<br/>
}</P>
<p>&nbsp;</P>
<p><strong>屏幕环绕出界对象：</STRONG><br/>
if(sprite.x - sprite.width / 2 &gt; right)<br/>
{<br/>
&nbsp;sprite.x = left - sprite.width / 2;<br/>
}<br/>
else if(sprite.x + sprite.width / 2 &lt; left)<br/>
{<br/>
&nbsp;sprite.x = right + sprite.width / 2;<br/>
}<br/>
if(sprite.y &ndash; sprite.height / 2 &gt; bottom)<br/>
{<br/>
&nbsp;sprite.y = top &ndash; sprite.height / 2;<br/>
}<br/>
else if(sprite.y + sprite.height / 2 &lt; top)<br/>
{<br/>
&nbsp;sprite.y = bottom + sprite.height / 2;<br/>
}</P>
<p>&nbsp;</P>
<p><strong>摩擦力应用（正确方法）：</STRONG><br/>
speed = Math.sqrt(vx * vx + vy * vy);<br/>
angle = Math.atan2(vy, vx);<br/>
if(speed &gt; friction)<br/>
{<br/>
&nbsp;speed -= friction;<br/>
}<br/>
else<br/>
{<br/>
&nbsp;speed = 0;<br/>
}<br/>
vx = Math.cos(angle) * speed;<br/>
vy = Math.sin(angle) * speed;</P>
<p>&nbsp;</P>
<p><strong>摩擦力应用（简便方法）：</STRONG><br/>
vx *= friction;<br/>
vy *= friction;</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第八章：</STRONG></FONT><br/>
<strong>简单缓动运动，长形：</STRONG><br/>
var dx:Number = targetX - sprite.x;<br/>
var dy:Number = targetY - sprite.y;<br/>
vx = dx * easing;<br/>
vy = dy * easing;<br/>
sprite.x += vx;<br/>
sprite.y += vy;</P>
<p>&nbsp;</P>
<p><strong>简单缓动运动，中形：</STRONG><br/>
vx = (targetX - sprite.x) * easing;<br/>
vy = (targetY - sprite.y) * easing;<br/>
sprite.x += vx;<br/>
sprite.y += vy;</P>
<p>&nbsp;</P>
<p><strong>简单缓动运动，短形：</STRONG><br/>
sprite.x += (targetX - sprite.x) * easing;<br/>
sprite.y += (targetY - sprite.y) * easing;</P>
<p>&nbsp;</P>
<p><strong>简单弹性运动，长形：</STRONG><br/>
var ax:Number = (targetX - sprite.x) * spring;<br/>
var ay:Number = (targetY - sprite.y) * spring;<br/>
vx += ax;<br/>
vy += ay;<br/>
vx *= friction;<br/>
vy *= friction;<br/>
sprite.x += vx;<br/>
sprite.y += vy;</P>
<p>&nbsp;</P>
<p><strong>简单弹性运动，中形：</STRONG><br/>
vx += (targetX - sprite.x) * spring;<br/>
vy += (targetY - sprite.y) * spring;<br/>
vx *= friction;<br/>
vy *= friction;<br/>
sprite.x += vx;<br/>
sprite.y += vy;</P>
<p>&nbsp;</P>
<p><strong>简单弹性运动，短形：</STRONG><br/>
vx += (targetX - sprite.x) * spring;<br/>
vy += (targetY - sprite.y) * spring;<br/>
sprite.x += (vx *= friction);<br/>
sprite.y += (vy *= friction);</P>
<p>&nbsp;</P>
<p><strong>偏移弹性运动：</STRONG><br/>
var dx:Number = sprite.x - fixedX;<br/>
var dy:Number = sprite.y - fixedY;<br/>
var angle:Number = Math.atan2(dy, dx);<br/>
var targetX:Number = fixedX + Math.cos(angle) * springLength;<br/>
var targetY:Number = fixedX + Math.sin(angle) * springLength;<br/>
// 如前例弹性运动到 targetX, targetY</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第九章</STRONG></FONT><br/>
<strong>距离碰撞检测：</STRONG><br/>
// 从影片 spriteA 和 spriteB 开始<br/>
//
如果使用一个空白影片，或影片没有半径（radius）属性<br/>

// 可以用宽度或高度除以 2。<br/>
var dx:Number = spriteB.x - spriteA.x;<br/>
var dy:Number = spriteB.y - spriteA.y;<br/>
var dist:Number = Math.sqrt(dx * dx + dy * dy);<br/>
if(dist &lt; spriteA.radius + spriteB.radius)<br/>
{<br/>
&nbsp;// 处理碰撞<br/>
}</P>
<p>&nbsp;</P>
<p><strong>多物体碰撞检测：</STRONG><br/>
var numObjects:uint = 10;<br/>
for(var i:uint = 0; i &lt; numObjects - 1; i++)<br/>
{<br/>
&nbsp;// 使用变量 i 提取引用<br/>
&nbsp;var objectA = objects[i];<br/>
&nbsp;for(var j:uint = i+1; j<br/>
&nbsp;{<br/>
&nbsp;&nbsp;// // 使用变量 j
提取引用<br/>
&nbsp;&nbsp;var objectB = objects[j];<br/>
&nbsp;&nbsp;// perform collision
detection<br/>
&nbsp;&nbsp;// between objectA and
objectB<br/>
&nbsp;}<br/>
}</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第十章</STRONG></FONT><br/>
<strong>坐标旋转：</STRONG><br/>
x1 = Math.cos(angle) * x - Math.sin(angle) * y;<br/>
y1 = Math.cos(angle) * y + Math.sin(angle) * x;</P>
<p>&nbsp;</P>
<p><strong>反坐标旋转：</STRONG><br/>
x1 = Math.cos(angle) * x + Math.sin(angle) * ;y<br/>
y1 = Math.cos(angle) * y - Math.sin(angle) * x;</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第十一章</STRONG></FONT><br/>
<strong>动量守恒的数学表达式：</STRONG><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
(m0 &ndash; m1) * v0 + 2 * m1 * v1<br/>
v0Final = ----------------------------------------------<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
m0 + m1<br/>
&nbsp;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
(m1 &ndash; m0) * v1 + 2 * m0 * v0<br/>
v1Final = ---------------------------------------------<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
m0 + m1</P>
<p><br/>
<strong>动量守恒的 ActionScript
表达式，短形:</STRONG><br/>
var vxTotal:Number = vx0 - vx1;<br/>
vx0 = ((ball0.mass - ball1.mass) * vx0 +<br/>
2 * ball1.mass * vx1) /<br/>
(ball0.mass + ball1.mass);<br/>
vx1 = vxTotal + vx0;</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第十二章</STRONG></FONT><br/>
<strong>引力的一般公式：</STRONG><br/>
force = G * m1 * m2 / distance<sup>2</SUP></P>
<p>&nbsp;</P>
<p><strong>ActionScript 实现万有引力：</STRONG><br/>
function gravitate(partA:Ball, partB:Ball):void<br/>
{<br/>
&nbsp;var dx:Number = partB.x - partA.x;<br/>
&nbsp;var dy:Number = partB.y - partA.y;<br/>
&nbsp;var distSQ:Number = dx * dx + dy * dy;<br/>
&nbsp;var dist:Number = Math.sqrt(distSQ);<br/>
&nbsp;var force:Number = partA.mass * partB.mass /
distSQ;<br/>
&nbsp;var ax:Number = force * dx / dist;<br/>
&nbsp;var ay:Number = force * dy / dist;<br/>
&nbsp;partA.vx += ax / partA.mass;<br/>
&nbsp;partA.vy += ay / partA.mass;<br/>
&nbsp;partB.vx -= ax / partB.mass;<br/>
&nbsp;partB.vy -= ay / partB.mass;<br/>
}</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第十四章</STRONG></FONT><br/>
<strong>余弦定理</STRONG><br/>
a<sup>2</SUP> = b<sup>2</SUP> + c<sup>2</SUP> - 2 * b * c * cos
A<br/>
b<sup>2</SUP> = a<sup>2</SUP> + c<sup>2</SUP> - 2 * a * c * cos
B<br/>
c<sup>2</SUP> = a<sup>2</SUP> + b<sup>2</SUP> - 2 * a * b * cos
C</P>
<p>&nbsp;</P>
<p><strong>ActionScript 的余弦定理:</STRONG><br/>
A = Math.acos((b * b + c * c - a * a) / (2 * b * c));<br/>
B = Math.acos((a * a + c * c - b * b) / (2 * a * c));<br/>
C = Math.acos((a * a + b * b - c * c) / (2 * a * b));</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>第十五章</STRONG></FONT><br/>
<strong>基本透视法：</STRONG><br/>
scale = fl / (fl + zpos);<br/>
sprite.scaleX = sprite.scaleY = scale;<br/>
sprite.alpha = scale; // 可选<br/>
sprite.x = vanishingPointX + xpos * scale;<br/>
sprite.y = vanishingPointY + ypos * scale;</P>
<p>&nbsp;</P>
<p><strong>Z 排序：<br/></STRONG>// 假设有一个带有 zpos
属性的 3D 物体的数组<br/>
objectArray.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);<br/>
for(var i:uint = 0; i &lt; numObjects; i++)<br/>
{<br/>
&nbsp;setChildIndex(objectArray[i], i);<br/>
}</P>
<p>&nbsp;</P>
<p><strong>坐标旋转：</STRONG><br/>
x1 = cos(angleZ) * xpos - sin(angleZ) * ypos;<br/>
y1 = cos(angleZ) * ypos + sin(angleZ) * xpos;<br/>
x1 = cos(angleY) * xpos - sin(angleY) * zpos;<br/>
z1 = cos(angleY) * zpos + sin(angleY) * xpos;<br/>
y1 = cos(angleX) * ypos - sin(angleX) * zpos;<br/>
z1 = cos(angleX) * zpos + sin(angleX) * ypos;</P>
<p>&nbsp;</P>
<p><strong>3D 距离：</STRONG><br/>
dist = Math.sqrt(dx * dx + dy * dy + dz * dz);</P>]]></description>
            <author>FL基础理论大师</author>
            <category>Making Things Move!</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100a6a8.html#comment</comments>
            <pubDate>Sun, 06 Jul 2008 14:16:36 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100a6a8.html</guid>
        </item>
        <item>
            <title>第十九章 实用技巧 &amp;#8545; [FL 基理译]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100a6a7.html</link>
            <description><![CDATA[<p><font STYLE="FONT-SIZE: 22px"><strong>基于计时器与时间的动画</STRONG></FONT><br/>

&nbsp;&nbsp;&nbsp;
到目前为止本书的所有例子都是通过把运动代码放到
onEnterFrame 方法中并将它赋给一个 enterFrame
事件的处理函数来实现的。我一直认为这是最简单的一种方式，因为帧的概念在
Flash
中根深蒂固，它就是给我们准备的；我猜我们大多都习以为常了。<br/>

&nbsp;&nbsp;&nbsp;
然而，对于那些从非 Flash
编程环境转来的朋友，对于这种模式可能并不习惯。对于他们来说，时序动画模型（使用
Interval 或
Timer）似乎可以更加精准地控制动画。<br/>
&nbsp;&nbsp;
&nbsp;稍后，我们要来看看“基于时间的动画”，一种即能用作帧又能用作计时器的技术。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>基于计时器的动画</STRONG></FONT><br/>

&nbsp;&nbsp;&nbsp;
作为计时器动画使用的关键类，不出意料，它就是
flash.utils.Timer。同时我们还需要 flash.events.TimerEvent
类。<br/>
&nbsp;&nbsp;
&nbsp;使用计时器实际上与使用 enterFrame
没什么两样。只需要我们去创建一个计时器(Timer)，告诉它多久“滴答响”一声，并侦听
TimerEvent.TIMER 事件，就像对 Event.ENTER_FRAME
事件的侦听一样。哦，还要告诉计时器何时开始！接下来，计时器就会每隔一段时间广播一个计时事件，它将调用赋给它的函数进行处理。计时器触发的间隔以毫秒为单位，在创建该计时器时指定。让我们来看一个简单的例子（可在
Timer1.as 中找到）：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;import flash.utils.Timer;<br/>
&nbsp;import flash.events.TimerEvent;<br/>
&nbsp;public class Timer1 extends Sprite {<br/>
&nbsp;&nbsp;private var timer:Timer;<br/>
&nbsp;&nbsp;public function Timer1()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
<strong>&nbsp;&nbsp;&nbsp;timer
= new Timer(30);<br/>
&nbsp;&nbsp;&nbsp;timer.addEventListener(TimerEvent.TIMER,
onTimer);<br/>
&nbsp;&nbsp;&nbsp;timer.start();</STRONG><br/>

&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function
onTimer(timer:TimerEvent):void {<br/>
<strong>&nbsp;&nbsp;&nbsp;trace("timer!");</STRONG><br/>

&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
重要的部分加粗表示。我们创建一个计时器，告诉它每隔
30 毫秒触发一次，意味着每秒约 33
次。添加一个事件的侦听器并将它起动。 onTimer
方法与我们以前用的 onEnterFrame 类似。<br/>
&nbsp;&nbsp;&nbsp;
这是我们所要知道计时器的大部分内容。它还有其它两个漂亮的特征。一个是在创建计时器时，可以通过第二个参数，repeatCount，告诉它运行的次数。假设我们要让计时器每秒运行一次，总共执行
5 秒。就可以这样做：<br/>
&nbsp;&nbsp;&nbsp; timer = new
Timer(1000, 5);<br/>
如果没有指定重复的次数，或传入
0，那么计时器将无限地运行。<br/>
&nbsp;&nbsp;
&nbsp;另一个好东西是可以让计时器在某个点上启动或停止，只需要调用
timer.stop 或 timer.start
即可。在某些例子中，这样做比删除和重新加入事件侦听器更简单一些。<br/>

&nbsp;&nbsp; &nbsp;与
enterFrame
相比，很多朋友更喜欢使用计时器的原因是，理论上讲，计时器可以让我们控制动画的速度——这是对于帧的不精确性的一个重大改进。我之所以说“理论上讲”，是因为这里有些事情需要弄清楚。<br/>

&nbsp;&nbsp;&nbsp;
首先，实际上计时器要依赖于帧频。另一个原因是计时器的事件函数中的代码会增加整个计时器间隔。稍后我会解释一下第二点。现在，让我们看看计时器是如何与帧频相关联的。下面是文档类
Timer2.as，使用到我们著名的 Ball 类。<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;import flash.utils.Timer;<br/>
&nbsp;import flash.events.TimerEvent;<br/>
&nbsp;public class Timer2 extends Sprite {<br/>
&nbsp;&nbsp;private var timer:Timer;<br/>
&nbsp;&nbsp;private var ball:Ball;<br/>
&nbsp;&nbsp;public function Timer2()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;stage.frameRate
= 1;<br/>
&nbsp;&nbsp;&nbsp;ball = new
Ball();<br/>
&nbsp;&nbsp;&nbsp;ball.y =
stage.stageHeight / 2;<br/>
&nbsp;&nbsp;&nbsp;ball.vx =
5;<br/>
&nbsp;&nbsp;&nbsp;addChild(ball);<br/>

&nbsp;&nbsp;&nbsp;timer = new
Timer(20);<br/>
&nbsp;&nbsp;&nbsp;timer.addEventListener(TimerEvent.TIMER,
onTimer);<br/>
&nbsp;&nbsp;&nbsp;timer.start();<br/>

&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function
onTimer(event:TimerEvent):void {<br/>
&nbsp;&nbsp;&nbsp;ball.x +=
ball.vx;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
这里我们把创建出来的小球放在舞台的左侧。让它以
vx 为 5 的速度穿过舞台。然后设置一个 20
毫秒的计时器，每秒约调用 50
次。同时设置影片的帧频为 1
就是为了看看帧频是否会对计时器有影响。<br/>
&nbsp;&nbsp;&nbsp;
测试后，我们会看到小球没有平稳地穿过屏幕，而是每秒钟跳一下
—— 以帧的频率。每跳一次都会大于 5
像素。为什么呢？<br/>
&nbsp;&nbsp;
&nbsp;回想一下一、二章的动画基础，我们知道模型是需要更新的，所以屏幕要根据新的模型被刷新。这里时间间隔函数确实将更新了模型并将小球每次移动
5 像素，但是只有在 Flash
进入新的一帧时才进行刷新。仅仅运行一个函数不会驱使
Flash 进行重绘。<br/>
&nbsp;&nbsp;
&nbsp;幸运的是，Macromedia (现在的 Adobe)
的好人们看到了这个问题并给了我们另一个小工具：updateAfterEvent。最初是在
Flash MX 中介绍的，现在它是传给计时器事件函数中
TimerEvent
对象的一个方法。就像它的名字一样，在事件之后刷新屏幕的。当然，由于它是
TimerEvent
类的一个方法，所以只有在收到一个事件后才能被调用。（事实上，它也是
KeyboardEvent 和 MouseEvent
的方法，因此也能在这些处理函数中调用。）<br/>
这样一来，我们可以修正一下 onTimer 事件：<br/>
private function onTimer(event:TimerEvent):void {<br/>
&nbsp;ball.x += ball.vx;<br/>
&nbsp;event.updateAfterEvent();<br/>
}<br/>
现在一切有所好转了。非常流畅。但是如果您意识到小球应该每秒更新
50 次，我们看到的基本上应该是一个 50 fps
的动画。这就意味着小球的运动应该比第四章创建的
fps 小于 50 的 enterFrame
事件的例子更为流畅。但是实际的运动更为缓慢。<br/>

&nbsp;&nbsp;&nbsp;
问题出来了，计时器在某种程度上要依赖于帧频。通过我的测算，在帧频为
1 fps
时，我们所得到的计时器运行的最快间隔大约为 100
毫秒。<br/>
&nbsp;&nbsp;
&nbsp;我已经听到了嘲笑：每帧只得到了 10
次间隔。所以，试将帧频改为 5。它允许每秒更新
50
次。在我看来，仍然不是很流畅。如果不大于每秒
10
帧的话是不会达到真正流畅的效果。因此，我们可以看到使用计时器并不能完全让我们从帧频的铐链中解脱出来。<br/>

&nbsp;&nbsp;
&nbsp;下一个问题是计时器内部是怎样工作的，它会对计时的精确度产生多大的影响。当
timer.start() 被调时，实际上发生了什么，Flash
等待一段指定的时间，然后广播事件，运行与该计时器相关的处理函数。只有当函数执行完成后计时器才开始定时下一次计时。看一个例子，假设我们有一个每
20
毫秒运行一次计时器。假设在处理函数中有大量的代码需要执行
30
毫秒。下一轮定时只有在所有的代码都运行完成后才开始。这样一来，我们的函数会在大约每
50
毫秒调用一次。因为在用户的机器上没法精确地知道代码会运行多快，所以多数情况下，计时器动画不会比帧动画精确多少。<br/>

&nbsp;&nbsp;&nbsp;如果您需要真正的精确，那么基于时间的动画则是我们的必经之路。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>基于时间的动画</STRONG></FONT><br/>

&nbsp;&nbsp;&nbsp;
如果要让动画中物体的速度是一致的，那么基于时间的动画就是我们要使用的方法。这种方法在一些游戏中比较常用。我们知道，帧和计时器动画都不能以特定的速率播放。一个复杂的动画在一台又老又慢的电脑上运行可能要比最初设计的速度慢上许多。我们马上会看到，使用基于时间的动画无论最终动画运行的帧频如何，都将获得可靠的速度。<br/>

&nbsp;&nbsp;&nbsp;
首先要改变考虑速度的方法。到目前为止，在我说
vx = 5 时，我们使用的单位是像素每帧（pixels per
frame）。换句话讲，每进入新的一帧物体都将在 x
轴上运动 5
像素。在计时器动画中，当然，就应该是每次定时间隔移动
5 像素。<br/>
&nbsp;&nbsp;&nbsp;
对于时间动画，我们将使用真正的时间单位，如秒。由于我们是处理完整的一秒，而非其中的一部分，因此这个速度值就要更大一些。如果某个物体的速度是每帧
10 像素，每秒 30 帧的速度运动，大约每秒 300
像素。比如下面这个例子，我从第六章的 Bouncing2.as
文档类中截取了一部分并进行了一些变化，见下面粗体部分（也可在
TimeBased.as 中找到）：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;import flash.events.Event;<br/>
<strong>&nbsp;import
flash.utils.getTimer;</STRONG><br/>
&nbsp;public class TimeBased extends Sprite {<br/>
&nbsp;&nbsp;private var ball:Ball;<br/>
&nbsp;&nbsp;private var vx:Number;<br/>
&nbsp;&nbsp;private var vy:Number;<br/>
&nbsp;&nbsp;private var bounce:Number =
-0.7;<br/>
<strong>&nbsp;&nbsp;private var
time:Number;</STRONG><br/>
&nbsp;&nbsp;public function TimeBased()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
<strong>&nbsp;&nbsp;&nbsp;stage.frameRate
= 10;</STRONG><br/>
&nbsp;&nbsp;&nbsp;ball = new
Ball();<br/>
&nbsp;&nbsp;&nbsp;ball.x =
stage.stageWidth / 2;<br/>
&nbsp;&nbsp;&nbsp;ball.y =
stage.stageHeight / 2;<br/>
&nbsp;&nbsp;&nbsp;vx =
300;<br/>
&nbsp;&nbsp;&nbsp;vy =
-300;<br/>
&nbsp;&nbsp;&nbsp;addChild(ball);<br/>

<strong>&nbsp;&nbsp;&nbsp;time
= getTimer();</STRONG><br/>
&nbsp;&nbsp;&nbsp;addEventListener(Event.ENTER_FRAME,
onEnterFrame);<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function
onEnterFrame(event:Event):void {<br/>
<strong>&nbsp;&nbsp;&nbsp;var
elapsed:Number = getTimer() - time;<br/>
&nbsp;&nbsp;&nbsp;time =
getTimer();<br/>
&nbsp;&nbsp;&nbsp;ball.x += vx
* elapsed / 1000;<br/>
&nbsp;&nbsp;&nbsp;ball.y += vy
* elapsed / 1000;</STRONG><br/>
&nbsp;&nbsp;&nbsp;var
left:Number = 0;<br/>
&nbsp;&nbsp;&nbsp;var
right:Number = stage.stageWidth;<br/>
&nbsp;&nbsp;&nbsp;var
top:Number = 0;<br/>
&nbsp;&nbsp;&nbsp;var
bottom:Number = stage.stageHeight;<br/>
&nbsp;&nbsp;&nbsp;if (ball.x +
ball.radius &gt; right) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;ball.x
= right - ball.radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;vx
*= bounce;<br/>
&nbsp;&nbsp;&nbsp;} else if
(ball.x - ball.radius &lt; left) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;ball.x
= left + ball.radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;vx
*= bounce;<br/>
&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;if (ball.y +
ball.radius &gt; bottom) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;ball.y
= bottom - ball.radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;vy
*= bounce;<br/>
&nbsp;&nbsp;&nbsp;} else if
(ball.y - ball.radius &lt; top) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;ball.y
= top + ball.radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;vy
*= bounce;<br/>
&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
如上所描述，我改变了对速度的计算，让它们使用固定的值，而非随机值。然后我创建了一个名为
time 的变量，让它等于 flash.utils.getTimer
函数的结果。getTimer
函数非常简单。它返回影片已经运行了的毫秒数
——
这就是它全部的工作。我们没有办法清除它，重启它，改变它，等等。它只是一个计数器。<br/>

&nbsp;&nbsp;
&nbsp;看起来它似乎用处不大，但是如果先调用一次
getTimer
将值保存起来，稍后再调用它一次，将两个结果相减，我们就能知道确切的——
毫秒 ——这两次调用之间经过了多少时间。<br/>
&nbsp;&nbsp;
&nbsp;这就是策略：在每帧的开始时调用
getTimer
计算与上一帧间隔了多长时间。如果将它除以
1,000，我们将得到以秒为单位的间隔时间，这是个以秒为单位的分数。由于我们的
vx 和 vy
现在是以像素每秒来计算的，因此可以让它们去乘以这个分数，这样就知道要对物体移动多少了。同样，不要忘记重新设置
time 变量的值，以便让下一帧进行计算。<br/>
&nbsp;&nbsp;
&nbsp;测试一下，我们将看到小球移动的速度几乎与最初的速度相同！但是真正令人激动的是我们可以以任何帧频来发布这个影片，它仍然可以以同样的速度运动！通过修改
stage.frameRate 的值，试验一下高到 1,000 fps，低到 10
fps，你会看到小球的速度是相同的。当然，较高的频率会让动画更加流畅，而较低的频率则会十分不连贯，但是速度是一致的。<br/>

&nbsp;&nbsp;&nbsp;
大家可以把这个技术应用在本书任何一个包含速度的例子中。如果这样的话，还需要将相似的技术应用在加速度或外力上，如重力，因为它们也是基于时间的。加速度肯定要比转前要大很多，因为加速度被定义为速度对时间的变化率。例如，重力大约为
32 英尺／秒／秒。<br/>
&nbsp;&nbsp;&nbsp;
如果在一个 30 fps 帧的动画中，重力为 0.5
的话，那么现在就应该是 450。计算方法 0.5 * 30 *
30。然后像这样将它加入：<br/>
&nbsp;&nbsp;&nbsp;
&nbsp;vy += gravity * elapsed / 1000;<br/>
在最后一个例子中加入 450
重力后测试一下。我们会看到它与帧动画中加入重力
0.5
的效果是相同的。使用这种技术的一个技巧是将帧频设置得非常高，如
100。虽然没有机器能够与真正的帧频相吻合，但是基于时间的动画将保证每个人看到的影片运行得都是最流畅的。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 22px"><strong>同质量物体的碰撞</STRONG></FONT><br/>

&nbsp;&nbsp;&nbsp;
回忆一下第十一章的动量守恒。那里有非常严谨的代码。不过，当两个相同质量的物体发生碰撞时，我们实现起来可以更简单一些。基本原理是，两个物体沿着碰撞的线路交换它们的速度。同时还要用坐标旋转来决定碰撞的线路，以及物体的速度，这样就摆脱了复杂的动量守恒公式。来看看它是如何工作的，让我们回到文件
MultiBilliard2.as 中，将用它作为下一个例子 SameMass.as
的基础。我就不再列出原先所有的代码了，因为它实在是一个很大的文件。但是，我们要来看一下创建所有小球的
for 循环：<br/>
for(var i:uint = 0; i &lt; numBalls; i++) {<br/>
<strong>var radius:Number = Math.random() * 50 + 20;</STRONG><br/>
&nbsp;var ball:Ball = new Ball(radius);<br/>
&nbsp;ball.mass = radius;<br/>
&nbsp;ball.x = Math.random() * stage.stageWidth;<br/>
&nbsp;ball.y = Math.random() * stage.stageHeight;<br/>
&nbsp;ball.vx = Math.random() * 10 - 5;<br/>
&nbsp;ball.vy = Math.random() * 10 - 5;<br/>
&nbsp;addChild(ball);<br/>
&nbsp;balls.push(ball);<br/>
}<br/>
对于新的文件来说，要把粗体字的部分改为这一行：<br/>

&nbsp;&nbsp;&nbsp; var
radius:Number = 30;<br/>
让所有小球大小都相同，消除了质量的概念，相当于给它们相同的质量。<br/>

接下来，看一下 checkCollision
函数。请找到这一部分：<br/>
// 旋转 ball0 的速度<br/>
var vel0:Point = rotate(ball0.vx,<br/>
ball0.vy,<br/>
sin,<br/>
cos,<br/>
true);<br/>
// 旋转 ball1 的速度<br/>
var vel1:Point = rotate(ball1.vx,<br/>
ball1.vy,<br/>
sin,<br/>
cos,<br/>
true);<br/>
// 碰撞反应<br/>
var vxTotal:Number = vel0.x - vel1.x;<br/>
vel0.x = ((ball0.mass - ball1.mass) * vel0.x +<br/>
2 * ball1.mass * vel1.x) /<br/>
(ball0.mass + ball1.mass);<br/>
vel1.x = vxTotal + vel0.x;<br/>
这一部分找出了碰撞线路上的速度，根据物体的质量求出碰撞的结果。“碰撞反应”部分是动量守恒的要素，这就是我们可以消除的部分。我们可以让
vel0 和 vel1
进行交换，就可以很容易地替换它了。整个代码段如下：<br/>

// 旋转 ball0 的速度<br/>
var vel0:Point = rotate(ball0.vx,<br/>
ball0.vy,<br/>
sin,<br/>
cos,<br/>
true);<br/>
// 旋转 ball1 的速度<br/>
var vel1:Point = rotate(ball1.vx,<br/>
ball1.vy,<br/>
sin,<br/>
cos,<br/>
true);<br/>
<strong>// 交换两个速度<br/>
var temp:Point = vel0;<br/>
vel0 = vel1;<br/>
vel1 = temp;</STRONG><br/>
这里甚至还可以再优化，但是为了代码的清晰我就不做改变了。现在我们已经摆脱了一小块儿数学问题，测试一下修改前与修改后的文件，所得的结果是相同的。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 22px"><strong>声音整合</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
本书一直没有提到声音的使用。因为声音并不是动画的直接组成部分，优质的声音效果可以让
Flash 影片更加真实、引人入胜。<br/>
&nbsp;&nbsp;
&nbsp;我们可以使用不同的方法来加入声音。回溯到
Flash IDE
最早的程序版本，我们有一种使用声音的特殊方式
——
将声音导入到库，再把它加入到帧里面。这不是我要介绍的方法。我将介绍在
AS 3 中使用声音的一些基础。<br/>
&nbsp;&nbsp;&nbsp; AS 3 的
Sound
类有了很大的变化，而且还有几个额外的类可以帮助我们对其进行修饰。这里没有太多的空间进行深入的讨论，但是有一个方面我想应该对于我们这本书来说会很有用。这就是当动画中发生某种事件时，应该播放声音。最明显的就应该是碰撞了。一个小球碰到墙上或其它小球上，我们会听到“砰”、“啵嘤”、“啪”或其它什么声音。因此，我们需要掌握通过
ActionScript 来启动声音的能力。<br/>
&nbsp;这个例子中，我们还要回到
Bouncing2.as，小球会与墙壁产生碰撞。每次碰撞到墙壁时，我想让它发出声音。新的类请见
SoundEvents.as。<br/>
&nbsp;&nbsp;&nbsp;
首先，需要有声音效果。在网上有许多免费的声音效果资源。其中最火的
Flash 声音网站是 FlashKit。他们的音乐文件除了有
loop 以外，还有一个声音效果库 <a HREF="http://www.flashkit.com/soundfx/">www.flashkit.com/soundfx/</A>。这些效果被分类为
Cartoon，Interfaces，Mechanical
等等，而且这个网站有超过 6,000
多个声音效果文件，所以您应该能够找到适合自己的音效。我们可以在页面上直接进行预览（preview），找到自己喜欢的文件后，以
MP3
格式进行下载。将它保存到硬盘上与最终发部的影片放在同一目录下。<br/>

&nbsp;&nbsp;
&nbsp;有时我们要重新给文件一个更为简单的名字。例如，我下载了一个“boing”声音，我就将它重命名为
boing.mp3。<br/>
&nbsp;&nbsp;&nbsp; 在 AS 3
中使用声音的基础实际上要比 AS 2
中简单一些。<br/>
首先，我们需要创建声音对象。假设在类中已经声明了一个名为
boing 的变量：<br/>
&nbsp;&nbsp;&nbsp;&nbsp;private
var boing:Sound;<br/>
创建一个声音对象就这么简单：<br/>
&nbsp;&nbsp; &nbsp;boing = new
Sound();<br/>
当然，如同大多数 AS 3 的类一样，Sound
类也在包中，flash.media 包，因此要确保先导入
flash.media.Sound。</P>
<p>&nbsp;&nbsp;&nbsp;
连接一个外部声音对象最简单的方法是在构造函数中传入一个请求（request）。<br/>

&nbsp;&nbsp;&nbsp;&nbsp;就像读取外部图像（第四章）一样，我们不能直接传入外部声音文件的
URL。而是要将它包装到 URLRequest
中（flash.net.URLRequest，需要导入它）。应该像这样：<br/>

&nbsp;&nbsp;&nbsp;&nbsp;boing
= new Sound(new URLRequest("boing.mp3"));<br/>
全部内容就是这样。现在声音已经准备好。我们要做的就是：<br/>

&nbsp;&nbsp;&nbsp;
mySound.play();<br/>
无论在哪儿都会播放出这个音效。在 play
中有一些可选参数，如偏移的毫秒数，以及播放的次数，但是默认情况下是从声音的起始位置播放一次声音，这已经满足了我们通常的需求。以下是
SoundEvents.as 的全部代码，展示了 Sound
对象的创建，无论何时小球碰撞到墙上，都会播放声音。<br/>

package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;import flash.events.Event;<br/>
&nbsp;import flash.media.Sound;<br/>
&nbsp;import flash.net.URLRequest;<br/>
&nbsp;public class SoundEvents extends Sprite {<br/>
&nbsp;&nbsp;private var ball:Ball;<br/>
&nbsp;&nbsp;private var vx:Number;<br/>
&nbsp;&nbsp;private var vy:Number;<br/>
&nbsp;&nbsp;private var bounce:Number =
-0.7;<br/>
<strong>&nbsp;&nbsp;private var
boing:Sound;</STRONG><br/>
&nbsp;&nbsp;public function SoundEvents()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
<strong>&nbsp;&nbsp;&nbsp;boing
= new Sound(new URLRequest("boing.mp3"));</STRONG><br/>
&nbsp;&nbsp;&nbsp;ball = new
Ball();<br/>
&nbsp;&nbsp;&nbsp;ball.x =
stage.stageWidth / 2;<br/>
&nbsp;&nbsp;&nbsp;ball.y =
stage.stageHeight / 2;<br/>
&nbsp;&nbsp;&nbsp;vx =
Math.random() * 10 - 5;<br/>
&nbsp;&nbsp;&nbsp;vy =
-10;<br/>
&nbsp;&nbsp;&nbsp;addChild(ball);<br/>

&nbsp;&nbsp;&nbsp;addEventListener(Event.ENTER_FRAME,
onEnterFrame);<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function
onEnterFrame(event:Event):void {<br/>
&nbsp;&nbsp;&nbsp;ball.x +=
vx;<br/>
&nbsp;&nbsp;&nbsp;ball.y +=
vy;<br/>
&nbsp;&nbsp;&nbsp;var
left:Number = 0;<br/>
&nbsp;&nbsp;&nbsp;var
right:Number = stage.stageWidth;<br/>
&nbsp;&nbsp;&nbsp;var
top:Number = 0;<br/>
&nbsp;&nbsp;&nbsp;var
bottom:Number = stage.stageHeight;<br/>
&nbsp;&nbsp;&nbsp;if (ball.x +
ball.radius &gt; right) {<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;boing.play();</STRONG><br/>

&nbsp;&nbsp;&nbsp;&nbsp;ball.x
= right - ball.radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;vx
*= bounce;<br/>
&nbsp;&nbsp;&nbsp;} else if
(ball.x - ball.radius &lt; left) {<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;boing.play();</STRONG><br/>

&nbsp;&nbsp;&nbsp;&nbsp;ball.x
= left + ball.radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;vx
*= bounce;<br/>
&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;if (ball.y +
ball.radius &gt; bottom) {<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;boing.play();</STRONG><br/>

&nbsp;&nbsp;&nbsp;&nbsp;ball.y
= bottom - ball.radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;vy
*= bounce;<br/>
&nbsp;&nbsp;&nbsp;} else if
(ball.y - ball.radius &lt; top) {<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;boing.play();</STRONG><br/>

&nbsp;&nbsp;&nbsp;&nbsp;ball.y
= top + ball.radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;vy
*= bounce;<br/>
&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
测试一下影片看一看 … …
听一听拥有声音以后带来的不同感受。当然，要找到正确的声音用在正确的环境下，也不要加得太多，因为这本身也是一门艺术。</P>]]></description>
            <author>FL基础理论大师</author>
            <category>Making Things Move!</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100a6a7.html#comment</comments>
            <pubDate>Sun, 06 Jul 2008 14:08:23 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100a6a7.html</guid>
        </item>
        <item>
            <title>第十九章 实用技巧 &amp;#8544; [FL 基理译]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100a6a3.html</link>
            <description><![CDATA[<p><font STYLE="FONT-SIZE: 24px"><strong>第十九章&nbsp;
实用技巧</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
现在您已经来到了最后一章。我将所有想要介绍的一些小东西都放在了这一章，它们不太合适放在其它地方，或者说与前面章节的主线有些脱离。<br/>

&nbsp;&nbsp;
&nbsp;本章，我还重组了前面每章课后列出的公式，因此可以当作这些公式的一个参考点。<br/>

&nbsp;&nbsp;&nbsp;
由于这些课题都是比较零碎的概念，所以我没有办法将这些许许多多的内容组织起来。因此每一节都是一个独立的单元。好了，不多说了，让我们开始吧。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 22px"><strong>布朗（随机）运动</STRONG></FONT><br/>

&nbsp;&nbsp;&nbsp;
先讲讲历史。一天，一个名叫罗伯特-布朗（Robert
Brown）的植物学家正在观察一滴水中的花粉颗粒，随后他发现这些花粉是在随机运动的。虽然它们不是水流或水的运动，但是这些小小的颗粒却永远不会停下来。他发现同样的事情会发生在微尘中，但它们不会像花粉那样游泳。虽然他不知道为什么会有这种现象，其实不只是他还有其他所有人在几十年内都不能给出解释，但是他却将这种现象用自己的名字命名
—— 只是为了能意识到它！<br/>
&nbsp;&nbsp;&nbsp;
当今，我们对布朗运动的解释是大量的水分子在一滴水中不断运动，虽然水滴看上去是静止的。这些水分子与花粉和灰尘发生碰撞，将一些动量传给它们。因为即使是一颗小小的灰尘都要比一个水分子重上一百万倍，所以一次碰撞不会带来多大的影响。但是当每秒有几百万次的碰撞时，那么这些动量就会累计起来。<br/>

&nbsp;&nbsp;&nbsp;
现在，一些水分子也许撞到了灰尘的一边，而另一些则撞在了另一边。最终，它们会达到总的平均值。但是，随着时间的变化，受到更多撞击的一边就会产生波动，假设为左边，那么这个粒子就会向右运动一点。底部所受撞击越多，则粒子向上运动得就越多。最后所有的值趋于平均，最终的结果通常不会在任何一个方向产生太多的动量。这就是随机悬浮动作。<br/>

&nbsp;&nbsp;&nbsp;
我们可以在 Flash
中轻松地模拟出这种效果。在每一帧中，计算一个随机数加在运动物体的
x 和 y
速度中。随机的数值应该即可以是正数也可以是负数，并且一般来说都非常小，比如范围从
-0.1 到 +0.1。形式如下：<br/>
&nbsp;&nbsp; &nbsp;vx +=
Math.random() * 0.2 - 0.1;<br/>
&nbsp;&nbsp; &nbsp;vy +=
Math.random() * 0.2 - 0.1;<br/>
用 0.2 乘以一个随机的小数，所得的值从 0.0 到
0.2。再减去 0.1 则值变为 -0.1 到
0.1。在这里加入一些摩擦力（friction）很重要，否则速度会增大，并产生不自然的加速。在
Brownian1.as 中，我创建了 50
个粒子并让它们以布朗运动的形式悬浮。粒子就是我们熟悉的
Ball
类的实例，让它们为黑色并缩小。以下是代码：<br/>

package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;import flash.events.Event;<br/>
&nbsp;public class Brownian1 extends Sprite {<br/>
&nbsp;&nbsp;private var numDots:uint =
50;<br/>
&nbsp;&nbsp;private var friction:Number =
0.95;<br/>
&nbsp;&nbsp;private var dots:Array;<br/>
&nbsp;&nbsp;public function Brownian1()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;dots = new
Array();<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
dot:Ball = new Ball(1, 0);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= Math.random() * stage.stageWidth;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= Math.random() * stage.stageHeight;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.vx
= 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.vy
= 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;addChild(dot);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;dots.push(dot);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;addEventListener(Event.ENTER_FRAME,
onEnterFrame);<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function
onEnterFrame(event:Event):void {<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
dot:Ball = dots[i];<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;dot.vx
+= Math.random() * 0.2 - 0.1;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.vy
+= Math.random() * 0.2 - 0.1;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.x
+= dot.vx;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.y
+= dot.vy;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.vx
*= friction;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.vy
*= friction;</STRONG><br/>
&nbsp;&nbsp;&nbsp;&nbsp;if
(dot.x &gt; stage.stageWidth) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}
else if (dot.x &lt; 0) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= stage.stageWidth;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;if
(dot.y &gt; stage.stageHeight) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}
else if (dot.y &lt; 0) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= stage.stageHeight;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}</P>
<p>
这里大多部分内容都不是什么新知识，我已将有关的部分用加粗表示。<br/>

如图 19-1 所示，代码运行时屏幕上的显示。<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static6.photo.sina.com.cn/orignal/3ecb9b114515b5824d415" TARGET="_blank"><img SRC="http://static6.photo.sina.com.cn/bmiddle/3ecb9b114515b5824d415" /></A><br/>

<strong>图19-1</STRONG> 布朗运动<br/>
在 Brownian2.as 中，我将 numDots 减少到
20。然后将这行代码：<br/>
&nbsp;&nbsp;&nbsp;
graphics.lineStyle(0, 0, .5);<br/>
加入到 init 函数中，并在 onEnterFrame
中加入一些绘图的代码。<br/>
private function onEnterFrame(event:Event):void {<br/>
&nbsp;for (var i:uint = 0; i &lt; numDots;
i++) {<br/>
&nbsp;&nbsp;var dot:Ball = dots[i];<br/>
<strong>&nbsp;&nbsp;graphics.moveTo(dot.x,
dot.y);</STRONG><br/>
&nbsp;&nbsp;dot.vx += Math.random() * 0.2 -
0.1;<br/>
&nbsp;&nbsp;dot.vy += Math.random() * 0.2 -
0.1;<br/>
&nbsp;&nbsp;dot.x += dot.vx;<br/>
&nbsp;&nbsp;dot.y += dot.vy;<br/>
&nbsp;&nbsp;dot.vx *= friction;<br/>
&nbsp;&nbsp;dot.vy *= friction;<br/>
<strong>&nbsp;&nbsp;graphics.lineTo(dot.x,
dot.y);</STRONG><br/>
&nbsp;}<br/>
}<br/>
这样就在每个 dot
移动前与移动后的位置之间绘制了一条线。也就绘制出了自己的运动路径，如图
19-2
所示。如果您知道“布朗运动”这个词的话，那么一定会常常看到这种图像。<br/>

<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static12.photo.sina.com.cn/orignal/3ecb9b114515b594cec0b" TARGET="_blank"><img SRC="http://static12.photo.sina.com.cn/bmiddle/3ecb9b114515b594cec0b" />
</A><br/>
<strong>图19-2</STRONG> 带有行迹的布朗运动<br/>
&nbsp;&nbsp;&nbsp;
当我们要让物体以无意识、无外力的形式运动时，那么布朗运动将是最为实用的。也可以将它加入到一个带有其它运动的影片中，这样会给人一种随意（randomness）的感觉。例如到处乱飞的苍蝇或蜜蜂。也许这些影片已经有了它们各自的运动路径，但是在加入了一些随机运动后会让它看起来更加栩栩如生。</P>
<p>随机分布<br/>
&nbsp;&nbsp;
&nbsp;有时候我们会创建一些物体并将它们随机放置。您已经在本书中看到了很多这样的例子，但是下面我们要来关注几种不同的方法，获得不同的结果。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>方形分布</STRONG></FONT><br/>
&nbsp;&nbsp;
&nbsp;如果你想让物体随机分布在整个舞台上，那是相当简单的。只需要选择一个舞台宽度的随机数作为
x，一个高度的随机数作为
y。事实上，我们上一节就是这么做的：<br/>
for (var i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;var dot:Ball = new Ball(1, 0);<br/>
&nbsp;dot.x = Math.random() * stage.stageWidth;<br/>
&nbsp;dot.y = Math.random() * stage.stageHeight;<br/>
&nbsp;...<br/>
}<br/>
但是如果说我们要让这些点集中分布在舞台中心，比如舞台中心上方
100 像素为顶，下方 100
像素为底。我们可以这样做，可见 Random1.as：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;public class Random1 extends Sprite {<br/>
&nbsp;&nbsp;private var numDots:uint =
50;<br/>
&nbsp;&nbsp;public function Random1()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
dot:Ball = new Ball(1, 0);<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= stage.stageWidth / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.random()
* 200 - 100;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= stage.stageHeight / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.random()
* 200 - 100;</STRONG><br/>
&nbsp;&nbsp;&nbsp;&nbsp;addChild(dot);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
这里创建了一个从 -100 到 +100
的随机数，并将它加到舞台中心点上，因此所有的点在每个轴上都不能超过
100 像素。如图 19-3 所示。<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static7.photo.sina.com.cn/orignal/3ecb9b114515b5d2867f6" TARGET="_blank"><img SRC="http://static7.photo.sina.com.cn/bmiddle/3ecb9b114515b5d2867f6" /></A><br/>

<strong>图19-3</STRONG> 随机安排的点<br/>
&nbsp;&nbsp;&nbsp;
不赖嘛。但是如果让它们挤在一起，增加点的数量（300）并减少随机范围为
50，我们将看到一些奇怪的事情。以下代码来自
Random2.as：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;public class Random2 extends Sprite {<br/>
&nbsp;&nbsp;private var numDots:uint =
300;<br/>
&nbsp;&nbsp;public function Random2()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
dot:Ball = new Ball(1, 0);<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= stage.stageWidth / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.random()
* 100 - 50;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= stage.stageHeight / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.random()
* 100 - 50;</STRONG><br/>
&nbsp;&nbsp;&nbsp;&nbsp;addChild(dot);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
图 19-4 为运行结果。<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static2.photo.sina.com.cn/orignal/3ecb9b114515b60da65a1" TARGET="_blank"><img SRC="http://static2.photo.sina.com.cn/bmiddle/3ecb9b114515b60da65a1" /></A><br/>

<strong>图19-4</STRONG>
这种方法形成了一个方形。看起来不再像是随机安排的<br/>

&nbsp;&nbsp;&nbsp;
如您所见，这些点形成了一个正方形。这样没问题，但是如果我们要制作一些爆炸效果或星系效果等，正方形看起来就不那么自然了。如果正方形分布不是您真正想要的，那么就继续下一项技术吧。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 22px"><strong>圆形分布</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
虽然圆形分布比方形分布稍稍复杂一点，但是它真的也不难。<br/>

&nbsp;&nbsp;
&nbsp;首先，我们需要知道圆的半径。为了与上一个例子呼应，这里仍然使用
50。这将是从舞台中心开始能够放置的最大半径。我们将从
0 到 50 之间取一个随机数作为点的位置。然后从 0
到 PI * 2 个弧度 (360
度)选择一个随机的角度，再使用三角形函数找出点的
x 和 y 坐标。以下是 Random3.as 的代码：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;public class Random3 extends Sprite {<br/>
&nbsp;&nbsp;private var numDots:uint =
300;<br/>
&nbsp;&nbsp;private var maxRadius:Number =
50;<br/>
&nbsp;&nbsp;public function Random3()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
dot:Ball = new Ball(1, 0);<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;var
radius:Number = Math.random() * maxRadius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
angle:Number = Math.random() * (Math.PI * 2);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= stage.stageWidth / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.cos(angle)
* radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= stage.stageHeight / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.sin(angle)
* radius;</STRONG><br/>
&nbsp;&nbsp;&nbsp;&nbsp;addChild(dot);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
运行结果如图 19-5 所示。<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static9.photo.sina.com.cn/orignal/3ecb9b114515b62836218" TARGET="_blank"><img SRC="http://static9.photo.sina.com.cn/bmiddle/3ecb9b114515b62836218" /></A><br/>

<strong>图19-5</STRONG> 圆形随机分布<br/>
&nbsp;&nbsp;&nbsp;
这就是我前面提到的那种更加自然的分布形式。大家也许注意到了这些点似乎更集中于圆形的中心。因为点是沿着半径平均的分布的，这就会使中心的点与边上的点同样多。但是因为中心的空间小，也就会显得更加拥挤。<br/>

&nbsp;&nbsp;&nbsp;
这样的效果也许对于某些程序来说很好，但是
O’Shell (<a HREF="http://www.pixelwit.com/">www.pixelwit.com</A>)
的 Sean
曾给我一次挑战，让我给出一种方法将这些点均匀地分布成圆形。不得不承认我被难倒了，我的解决方法非常复杂。最后他给了我一个非常简单的办法，可见
Random4.as：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;public class Random4 extends Sprite {<br/>
&nbsp;&nbsp;private var numDots:uint =
300;<br/>
&nbsp;&nbsp;private var maxRadius:Number =
50;<br/>
&nbsp;&nbsp;public function Random4()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
dot:Ball = new Ball(1, 0);<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;var
radius:Number = Math.sqrt(Math.random()) *
maxRadius;</STRONG><br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
angle:Number = Math.random() *<br/>
&nbsp;&nbsp;&nbsp;&nbsp;(Math.PI
* 2);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= stage.stageWidth / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.cos(angle)
* radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= stage.stageHeight / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.sin(angle)
* radius;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;addChild(dot);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
通过取随机数的平方根，所得的结果偏向 1 而远离
0，这样做足以使分布变均匀。运行结果如图 19-6
所示。Sean，好人呀！<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static4.photo.sina.com.cn/orignal/3ecb9b114515b64713083" TARGET="_blank"><img SRC="http://static4.photo.sina.com.cn/bmiddle/3ecb9b114515b64713083" /></A><br/>

<strong>图19-6</STRONG> 更为均衡的分布</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 22px"><strong>偏向分布</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
最后，我们也许还想让物体自由地分布在整个舞台上，但是要让它们趋于在中心区域分布。我们已经找到了分布在边缘的方法，而现在要让它们越接近中心越多。这就有点像第一个圆形分配的例子，只不过这次要运用在矩形区域中。<br/>

&nbsp;&nbsp;&nbsp;
我们为每个坐标生成一个随机数，然后求它们的平均数得到最终的值。例如，假设舞台有
500 像素宽。如果为每个对象随机生成一个 x
坐标，那么每个对象分布在哪里的概率都是相同的。但是如果从
0 到 500
产生两个随机数，再求平均，那么被置在舞台中心的机会就会略高一些。<br/>

&nbsp;&nbsp;&nbsp;
让我们更深入地看一看这个问题。我们有一定的概率让两个数都在较“高”的范围内，假设从
300 到
500。让两个数都得到较低范围的概率也几乎相同，从
0 到
200。但是相比而言，一个数较高而另一个数较低，或者一个数居中另一个数较高或较低，或者都处于中等水平的概率要更高。所有这些可能性的平均值将使大多数点更靠近舞台中心。<br/>

&nbsp;&nbsp;&nbsp;
OK，让我们来看代码。像平常一样，从一维的开始。下面是代码（可以在
Random5.as 中找到）：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;public class Random5 extends Sprite {<br/>
&nbsp;&nbsp;private var numDots:uint =
300;<br/>
&nbsp;&nbsp;public function Random5()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
dot:Ball = new Ball(1, 0);<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;var
x1:Number = Math.random() * stage.stageWidth;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
x2:Number = Math.random() * stage.stageWidth;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= (x1 + x2) / 2;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= stage.stageHeight / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.random()
* 50 - 25;</STRONG><br/>
&nbsp;&nbsp;&nbsp;&nbsp;addChild(dot);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
这里我们创建了两个随机数 x1 和 x2，并设置 dot 的
x 坐标为它们的平均值。y
坐标只是邻近舞台中心点的一个简单的随机数。运行结果如图
19-7 所示。<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static10.photo.sina.com.cn/orignal/3ecb9b114515b67979e49" TARGET="_blank"><img SRC="http://static10.photo.sina.com.cn/bmiddle/3ecb9b114515b67979e49" />
</A><br/>
<strong>图19-7</STRONG> 一次迭代的偏向分布<br/>
&nbsp;&nbsp;&nbsp;
虽然这个效果不是很明显，但是我们已经可以看到中心位置比边缘位置的点更多一些。创建更多的随机值，再取它们的平均值，会使效果更加明显。我们可以将它放入一个
for 循环中动态执行（Random6.as）：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;public class Random6 extends Sprite {<br/>
&nbsp;&nbsp;private var numDots:uint =
300;<br/>
&nbsp;&nbsp;private var maxRadius:Number =
50;<br/>
<strong>&nbsp;&nbsp;private var
iterations:uint = 6;</STRONG><br/>
&nbsp;&nbsp;public function Random6()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
dot:Ball = new Ball(1, 0);<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;var
xpos:Number = 0;</STRONG><br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;for
(var j:uint = 0; j &lt; iterations; j++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xpos
+= Math.random() * stage.stageWidth;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= xpos / iterations;</STRONG><br/>
&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= stage.stageHeight / 2 +<br/>
&nbsp;&nbsp;&nbsp;&nbsp;Math.random()
* 50 - 25;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;addChild(dot);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
这里 iterations
变量控制要取多少个数的平均值。开始让 xpos
变量等于
0，然后将每个随机数都加在上面。最后，用这个结果除以
iterations 得到最终的值。运行结果如图 19-8。<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static8.photo.sina.com.cn/orignal/3ecb9b114515b688d6e67" TARGET="_blank"><img SRC="http://static8.photo.sina.com.cn/bmiddle/3ecb9b114515b688d6e67" /></A><br/>

<strong>图19-8</STRONG> 六次迭代的偏向分布<br/>
&nbsp;&nbsp;&nbsp; 现在，要
y 轴也做同样的事就非常简单了，Random7.as：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;public class Random7 extends Sprite {<br/>
&nbsp;&nbsp;private var numDots:uint =
300;<br/>
&nbsp;&nbsp;private var maxRadius:Number =
50;<br/>
&nbsp;&nbsp;private var iterations:uint =
6;<br/>
&nbsp;&nbsp;public function Random7()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; numDots; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
dot:Ball = new Ball(1, 0);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
xpos:Number = 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(var j:uint = 0; j &lt; iterations; j++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xpos
+= Math.random() * stage.stageWidth;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;dot.x
= xpos / iterations;<br/>
<strong>&nbsp;&nbsp;&nbsp;&nbsp;var
ypos:Number = 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(j = 0; j &lt; iterations; j++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ypos
+= Math.random() * stage.stageHeight;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;dot.y
= ypos / iterations;</STRONG><br/>
&nbsp;&nbsp;&nbsp;&nbsp;addChild(dot);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
这种分布如图 19-9 所示。<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static8.photo.sina.com.cn/orignal/3ecb9b114515b69776fd7" TARGET="_blank"><img SRC="http://static8.photo.sina.com.cn/bmiddle/3ecb9b114515b69776fd7" /></A><br/>

<strong>图19-9</STRONG> 二维的偏向分布<br/>
&nbsp;&nbsp;&nbsp;
在我看来，这是所有分布效果中最无规律，最具爆发性，最像星系的一种分布，虽然它也是最复杂的。好了，我们现在至少已经掌握了四种生成随机位置的方法。</P>]]></description>
            <author>FL基础理论大师</author>
            <category>Making Things Move!</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100a6a3.html#comment</comments>
            <pubDate>Sun, 06 Jul 2008 13:53:13 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100a6a3.html</guid>
        </item>
        <item>
            <title>第十八章 矩阵数学 [FL 基理译]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100a3fn.html</link>
            <description><![CDATA[<p><font STYLE="FONT-SIZE: 24px"><strong>第十八章&nbsp;
矩阵数学</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
本章我们不去介绍一些新的运动、物理学或渲染图形的方法。我要给大家介绍的是<em>矩阵（Matrix）</EM>，它给我们提供了一个新的可选方案。<br/>

&nbsp;&nbsp;&nbsp; 矩阵在 3D
系统中 3D
点的旋转，缩放以及平移（运动）中使用得非常频繁。在各种
2D 图形的变换上也很常用。您也许可以回想到
beginGradientFill
方法就是使用矩阵来设置位置，大小以及旋转比例的。<br/>

&nbsp;&nbsp;&nbsp;
本章大家将看到如何创建一个 3D
矩阵系统，用以操作 3D 的影片并且可以看到一些
Flash
中内置的矩阵。我很庆幸现在为止还没有一处提到
Keanu Reeves
[译注：基努-里维斯，尤指电影《黑客帝国》-- The
Matrix] 的电影。看看我还能坚持多久。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 22px"><strong>矩阵基础</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
矩阵最简单的定义是一个数字表格。它可以有一个或多个水平的行和一个或多个垂直的列。图
18-1 展示了一些矩阵。<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static15.photo.sina.com.cn/orignal/3ecb9b11450bae605d92e" TARGET="_blank"><img SRC="http://static15.photo.sina.com.cn/bmiddle/3ecb9b11450bae605d92e" />
</A><br/>
<strong>图18-1</STRONG> 一个 3&times;3 矩阵，一个 1&times;3
矩阵，一个 3&times;1 矩阵<br/>
&nbsp;&nbsp;&nbsp;
矩阵通常都是由一些变量来描述的，如
M。在矩阵中为表示一个特殊的单元，我们使用的变量里面通常要用行列的值作为脚标。例如，如果图
18-1 中的 3&times;3 矩阵叫做 M，那么 M2,3 就等于
6，因为它指向第二行，第三列。<br/>
&nbsp;&nbsp;&nbsp;
一个矩阵的单元不仅可以包含简单的数字，也可以是公式和变量。其实电子表格就是一个大的矩阵。我们可以用一个单元保存某一列的和，用另一个单元格将这个总和乘以一个分数，等等。我们看到这样的矩阵应该非常有用。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>矩阵运算</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
一个电子表格就像一个自由组合的矩阵，我们要处理的矩阵更加有结构，至于能用它们做什么以及如何生成都有各自的规则。<br/>

&nbsp;&nbsp;&nbsp;
我所见过的大多数矩阵数学的教材都只介绍两种方法的一种。首先学校讲的是矩阵运算的细节，使用的整个矩阵的几乎都是一些随机的数字。我们学习这些规则，但是不知道为什么要做这些事情或所得的结果代表什么。就像在玩把数字排列成漂亮形状的游戏。<br/>

&nbsp;&nbsp;&nbsp;
第二个方法是详细地描述矩阵的内容但是略过手工操作，如“将两个矩阵相乘得到这个结果…
…”让读者不知道乘法到底是怎么算的。<br/>
&nbsp;&nbsp;&nbsp;
为了保证大家都能了解矩阵是如何工作的，我选择一个两者兼具的方法（折衷），从介绍一些数值矩阵开始，然后描述如何做矩阵乘法。</P>
<p>矩阵加法<br/>
&nbsp;&nbsp;&nbsp;
矩阵更为通常的作用是操作 3D 点。一个 3D
点包涵了 x, y, z
坐标。我们可以简单地将它视为一个 1&times;3
的矩阵：<br/>
&nbsp;&nbsp;&nbsp;
x&nbsp; y&nbsp; z<br/>
现在假设要将这个点在空间中移动，或叫做点的平移。我们需要知道每个轴上移动多远。这时可以将它放入一个<em>转换矩阵（translation
matrix）</EM>中。它又是一个 1&times;3 的矩阵：<br/>
&nbsp;&nbsp;&nbsp;
dx&nbsp; dy&nbsp; dz<br/>
这里 dx, dy, dz
是每个轴移动的距离。现在我们要想办法将转换矩阵加到点矩阵上面。这就是矩阵加法，非常简单。我们只需要将相应的单元进行相加形成一个新的包含了每个单元之和的矩阵。很明显，要让两个矩阵相加，它们的大小都应该是相同的。转换方法如下：<br/>

&nbsp;&nbsp;&nbsp;
x&nbsp; y&nbsp; z&nbsp;
+&nbsp; dx&nbsp; dy&nbsp; dz =
(x + dx) (y + dy) (z + dz)<br/>
获得的矩阵可以叫做 x1, y1,
z1，转换之后包含了该点的新坐标。让我们用实数来试一下。假设点在
x, y, z 轴上的位置分别为 100, 50,
75，要让它们分别移动 -10, 20,
-35。则应该是这样的：<br/>
&nbsp;&nbsp;&nbsp;
100&nbsp; 50&nbsp; 75&nbsp;
+&nbsp; -10&nbsp; 20&nbsp; -35
= (100 - 10) (50 + 20) (75 - 35)<br/>
因此，当进行加法运算时，所得该点的新坐标就是
90, 70,
40。非常简单，不是吗？大家也许已经注意到了速度间的相互关系，每个轴上的速度都加到了另一个矩阵的相应位置上。公平交易嘛。<br/>

&nbsp;&nbsp;&nbsp;
如果我们有一个较大的矩阵，那么继续使用同样的方法，匹配每个单元。我们不会去处理大于
3&times;1
的矩阵加法，但是我会给大家这样一个抽象的例子：<br/>

&nbsp;&nbsp;&nbsp;
a&nbsp; b&nbsp;
c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
j&nbsp; k&nbsp;
l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
(a + j)&nbsp;(b + k)&nbsp;(c + l)<br/>
&nbsp;&nbsp;&nbsp;
d&nbsp; e&nbsp;
f&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;m&nbsp;
n&nbsp; o&nbsp;
&nbsp;=&nbsp; (d + m)&nbsp;(e +
n)&nbsp;(f + o)<br/>
&nbsp;&nbsp;&nbsp;
g&nbsp; h&nbsp;
i&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p&nbsp;
q&nbsp;
r&nbsp;&nbsp;&nbsp;
&nbsp; (g + p)&nbsp;(h +
q)&nbsp;(i + r)<br/>
以上就是我们需要知道矩阵加法的一切。在介绍了矩阵乘法之后，我将展示如何将现有的函数使用在矩阵
3D 引擎中。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>矩阵乘法</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp; 在 3D
转换中应用更为广泛的是<em>矩阵乘法（matrix
multiplication）</EM>，常用于缩放与旋转。在本书中我们实际上不会用到
3D 缩放，因为例子中的点缩放，影片也没有 3D
的“厚度”，因此只有二维的缩放。当然，大家可以建立一个可缩放整个
3D
立体模型的更为复杂的引擎。这就需要写一些根据新的影片大小改变
3D
点的函数。这些已经超出了我们讨论的范围，但是由于缩放是非常简单的，并且使用矩阵乘法很容易实现，因此我将带大家看一下这个例子。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 18px"><strong>使用矩阵进行缩放</STRONG></FONT><br/>

&nbsp;&nbsp;
首先，需要知道一个物体现有的宽度，高度和深度
——
换句话讲，它是三个轴上每个轴分量的大小。当然可以建立一个
3&times;1 的矩阵：<br/>
&nbsp;&nbsp;&nbsp;
w&nbsp; h&nbsp; d<br/>
我们知道 w, h, d
代表宽度（width），高度（height）和深度（depth）。下面需要缩放这个矩阵：<br/>

&nbsp;&nbsp;&nbsp;
sx&nbsp; 0&nbsp; 0<br/>
&nbsp;&nbsp;&nbsp;
0&nbsp; sy&nbsp; 0<br/>
&nbsp;&nbsp;&nbsp;
0&nbsp; 0&nbsp; sz<br/>
这里 sx, sy, sz
是对应轴上的缩放比例。它们都将是分数或小数，1.0
为 100%，2.0 为 200%，0.5 为
50%，等等。稍后大家会看到为什么矩阵是用这种形式分布的。<br/>

&nbsp;&nbsp;&nbsp;&nbsp;要知道，矩阵乘法是为了让两个矩阵相乘，第一个矩阵的列数必需与另一个矩阵的行数相同。只要符合这个标准，第一个矩阵可以有任意多个行，第二个矩阵可以有任意多个列。本例中，由于第一个矩阵有三列（w,
h,
d），因此缩放矩阵就有三行。那么它们如何进行乘法运算呢？让我们来看一下这个模式：</P>
<p></P>
<table STYLE="WIDTH: 32.26%; HEIGHT: 93px" CELLSPACING="1" CELLPADDING="3" BORDER="0">
<tbody>
<tr>
<td>&nbsp;</TD>
<td>&nbsp;</TD>
<td>&nbsp; sx&nbsp; 0&nbsp;
0</TD>
</TR>
<tr>
<td>&nbsp;&nbsp; w&nbsp;
h&nbsp; d</TD>
<td>*</TD>
<td>&nbsp; 0&nbsp; sy&nbsp;
0</TD>
</TR>
<tr>
<td>&nbsp;</TD>
<td>&nbsp;</TD>
<td>&nbsp; 0&nbsp; 0&nbsp;
sz</TD>
</TR>
</TBODY>
</TABLE>
<p></P>
<p>矩阵的计算结果如下：<br/>
&nbsp;&nbsp; &nbsp;(w*sx + h*0
+ d*0) (w*0 + h*sy + d*0) (w*0 + h*0 + d*sz)<br/>
删除所有等于 0 的数：<br/>
&nbsp;&nbsp; &nbsp;(w*sx)
(h*sy) (d*sz)<br/>
非常有合乎逻辑，因为我们是用宽度（x
轴分量）乘以 x 缩放系数，高度乘以 y
缩放系数，深度乘以 z
缩放系数。但是，我们究竟在做什么呢？那些所有等于
0
的数都像被遮盖上了，因此让我们将这个模式抽象得更清晰一点。</P>
<p></P>
<table STYLE="WIDTH: 29.69%; HEIGHT: 93px" CELLSPACING="1" CELLPADDING="3" BORDER="0">
<tbody>
<tr>
<td>&nbsp;</TD>
<td>&nbsp;</TD>
<td>&nbsp;a&nbsp; b&nbsp;
c</TD>
</TR>
<tr>
<td>&nbsp;u&nbsp; v&nbsp;
w</TD>
<td>&nbsp;*</TD>
<td>&nbsp;d&nbsp; e&nbsp;
f</TD>
</TR>
<tr>
<td>&nbsp;</TD>
<td>&nbsp;</TD>
<td>&nbsp;g&nbsp; h&nbsp;
i</TD>
</TR>
</TBODY>
</TABLE>
<p></P>
<p>现在可以看到该模式的结果为：<br/>
&nbsp;&nbsp;&nbsp; (u*a + v*d +
w*g) (u*b + v*e + w*h) (u*c + v*f + w*i)<br/>
我们将第一个矩阵的第一行（u, v,
w）与第二个矩阵每行的第一个元素相乘。将它们加起来就得到了结果的第一行的第一个元素。在第二个矩阵的第二列（b,
e,
h）中使用相同的方法就得到了第二列的结果。<br/>
&nbsp;&nbsp;&nbsp;
如果第一个矩阵的行数大于
1，就要在第二行中重复上述动作，就会得到第二行的结果：</P>
<p></P>
<table STYLE="WIDTH: 30.6%; HEIGHT: 93px" CELLSPACING="1" CELLPADDING="3" BORDER="0">
<tbody>
<tr>
<td>&nbsp;u&nbsp; v&nbsp;
w</TD>
<td>&nbsp;</TD>
<td>&nbsp;a&nbsp; b&nbsp;
c</TD>
</TR>
<tr>
<td>&nbsp;x&nbsp; y&nbsp;
z</TD>
<td>&nbsp;*</TD>
<td>&nbsp;d&nbsp; e&nbsp;
f</TD>
</TR>
<tr>
<td>&nbsp;</TD>
<td>&nbsp;</TD>
<td>&nbsp;g&nbsp; h&nbsp;
i</TD>
</TR>
</TBODY>
</TABLE>
就得到了这个 3&times;2 的矩阵：<br/>
&nbsp;&nbsp;&nbsp; (u*a + v*d +
w*g)&nbsp; (u*b + v*e + w*h)&nbsp; (u*c +
v*f + w*i)<br/>
&nbsp;&nbsp;&nbsp; (x*a + y*d +
z*g)&nbsp; (x*b + y*e + z*h)&nbsp; (x*c +
y*f + z*i)<br/>
现在让我们看一些实际中用到的矩阵乘法 ——
坐标旋转。希望通过这个缩放的例子会让它看起来更加清晰。
<p></P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 18px"><strong>使用矩阵进行坐标旋转</STRONG></FONT><br/>

首先，要挖出我们的 3D 点矩阵：<br/>
&nbsp;&nbsp;&nbsp;
x&nbsp; y&nbsp; z<br/>
它保存了该点所有的坐标。当然，还要有一个旋转矩阵。我们可以在三个轴的任意一轴上进行旋转。我们将分别创建每种旋转的矩阵。先从
x 轴旋转矩阵开始：<br/>
&nbsp;&nbsp;
&nbsp;1&nbsp;&nbsp;
0&nbsp;&nbsp;&nbsp;&nbsp;
0<br/>
&nbsp;&nbsp;
&nbsp;0&nbsp;
cos&nbsp;&nbsp; sin<br/>
&nbsp;&nbsp;
&nbsp;0&nbsp; -sin&nbsp;
cos<br/>
这里有一些正余弦值，“sin 和 cos
是什么？”很明显，这就是我们要旋转的角度的正余弦值。如果让这个点旋转
45 度，则这两个值就是 45
的正弦和余弦值。（当然，在代码中要使用弧度制）现在，我们让该矩阵与一个
3D 点的矩阵相乘，看一下结果。</P>
<p></P>
<table STYLE="WIDTH: 31.04%; HEIGHT: 93px" CELLSPACING="1" CELLPADDING="3" BORDER="0">
<tbody>
<tr>
<td>&nbsp;</TD>
<td>&nbsp;</TD>
<td>&nbsp;1&nbsp;&nbsp;
0&nbsp;&nbsp;&nbsp; 0</TD>
</TR>
<tr>
<td>&nbsp;x&nbsp; y&nbsp;
z</TD>
<td>&nbsp;*</TD>
<td>&nbsp;0&nbsp; cos&nbsp;
sin</TD>
</TR>
<tr>
<td>&nbsp;</TD>
<td>&nbsp;</TD>
<td>&nbsp;0&nbsp;
-sin&nbsp;cos</TD>
</TR>
</TBODY>
</TABLE>
由此得到：<br/>
&nbsp;&nbsp;&nbsp; (x*1 + y*0 +
z*0) (x*0 + y*cos - z*sin) (x*0 + y*sin + z*cos)<br/>
整理后结果如下：<br/>
&nbsp;&nbsp;&nbsp; (x) (y*cos -
z*sin) (z*cos + y*sin)<br/>
这句话用 ActionScript 大略可以翻译成：<br/>
&nbsp;&nbsp; &nbsp;x = x;<br/>
&nbsp;&nbsp; &nbsp;y =
Math.cos(angle) * y - Math.sin(angle) * z;<br/>
&nbsp;&nbsp;&nbsp; z =
Math.cos(angle) * z + Math.sin(angle) * y;<br/>
回忆一下第十章，在讨论坐标旋转时，我们会看到这实际上就是
x
轴的坐标旋转。不要惊讶，矩阵数学只是观察和组织各种公式和方程的不同方法。至此，要创建一个
y 轴旋转的矩阵就非常容易了：<br/>
&nbsp;&nbsp;&nbsp;
&nbsp;cos&nbsp; 0&nbsp;
sin<br/>
&nbsp;&nbsp;&nbsp;
&nbsp; 0&nbsp;&nbsp;
1&nbsp;&nbsp; 0<br/>
&nbsp;&nbsp;
&nbsp;-sin&nbsp; 0&nbsp;
cos<br/>
最后，z 轴的旋转为：<br/>
&nbsp;&nbsp; &nbsp;
cos&nbsp; sin&nbsp;&nbsp;
0<br/>
&nbsp;&nbsp;
&nbsp;-sin&nbsp;
cos&nbsp;&nbsp; 0<br/>
&nbsp;&nbsp;
&nbsp;&nbsp;
0&nbsp;&nbsp;&nbsp;
0&nbsp;&nbsp;&nbsp; 1<br/>
这是一个很好的尝试，用 x, y, z
的矩阵乘以每个旋转矩阵的单位，证明所得到的结果与第十章的坐标旋转公式完全相同。
<p></P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 22px"><strong>编写矩阵<br/></STRONG></FONT>&nbsp;&nbsp;
&nbsp;OK，现在大家已经有了足够的基础将这些知识转换为代码了。下面，我们对第十五章的
RotateXY.as 进行重新转换。这个类中有 rotateX 和
rotateY 两个方法，用以实现 3D
坐标旋转。我们要让它们以矩阵的方式工作。<br/>
&nbsp;&nbsp;&nbsp; 从 rotateX
函数开始。它会用到小球的 x, y, z
坐标，将它们放入 1&times;3
矩阵，然后创建一个给定角度的 x
旋转矩阵。这个矩阵将使用数组的形式表示。最后使用
matrixMultiply
函数让两个矩阵相乘，当然还需要创建这个函数！相乘后的矩阵还要用另一个数组进行保存，因为我们需要将这些数值再存回小球的
x, y, z 坐标中。下面是新版的方法：<br/>
private function rotateX(ball:Ball3D, angleX:Number):void {<br/>
&nbsp;var position:Array = [ball.xpos, ball.ypos,
ball.zpos];<br/>
&nbsp;var sin:Number = Math.sin(angleX);<br/>
&nbsp;var cos:Number = Math.cos(angleX);<br/>
&nbsp;var xRotMatrix:Array = new Array();<br/>
&nbsp;xRotMatrix[0] = [1, 0, 0];<br/>
&nbsp;xRotMatrix[1] = [0, cos, sin];<br/>
&nbsp;xRotMatrix[2] = [0, -sin, cos];<br/>
&nbsp;var result:Array = matrixMultiply(position,
xRotMatrix);<br/>
&nbsp;ball.xpos = result[0];<br/>
&nbsp;ball.ypos = result[1];<br/>
&nbsp;ball.zpos = result[2];<br/>
}<br/>
下面是矩阵乘法的函数：<br/>
private function matrixMultiply(matrixA:Array, matrixB:Array):Array
{<br/>
&nbsp;var result:Array = new Array();<br/>
&nbsp;result[0] = matrixA[0] * matrixB[0][0] +<br/>
&nbsp;matrixA[1] * matrixB[1][0] +<br/>
&nbsp;matrixA[2] * matrixB[2][0];<br/>
&nbsp;result[1] = matrixA[0] * matrixB[0][1] +<br/>
&nbsp;matrixA[1] * matrixB[1][1] +<br/>
&nbsp;matrixA[2] * matrixB[2][1];<br/>
&nbsp;result[2] = matrixA[0] * matrixB[0][2] +<br/>
&nbsp;matrixA[1] * matrixB[1][2] +<br/>
&nbsp;matrixA[2] * matrixB[2][2];<br/>
&nbsp;return result;<br/>
}<br/>
现在，这个矩阵乘法的函数是手工写出的一个 1&times;3
和 3&times;3
矩阵的乘法，这就是我们后面用在每个例子中的函数。大家也可以使用
for
循环创建出更为动态的可处理任何大小的矩阵函数，但是现在我要让代码保持简洁。<br/>

&nbsp;&nbsp;&nbsp; 最后创建
rotateY 函数。如果你了解 rotateX
函数，那么这个函数应该非常显而易见了。只需要创建一个
y 旋转矩阵来代替 x 旋转矩阵即可。<br/>
private function rotateY(ball:Ball3D, angleY:Number):void {<br/>
&nbsp;var position:Array = [ball.xpos, ball.ypos,
ball.zpos];<br/>
&nbsp;var sin:Number = Math.sin(angleY);<br/>
&nbsp;var cos:Number = Math.cos(angleY);<br/>
&nbsp;var yRotMatrix:Array = new Array();<br/>
&nbsp;yRotMatrix[0] = [ cos, 0, sin];<br/>
&nbsp;yRotMatrix[1] = [ 0, 1, 0];<br/>
&nbsp;yRotMatrix[2] = [-sin, 0, cos];<br/>
&nbsp;var result:Array = matrixMultiply(position,
yRotMatrix);<br/>
&nbsp;ball.xpos = result[0];<br/>
&nbsp;ball.ypos = result[1];<br/>
&nbsp;ball.zpos = result[2];<br/>
}<br/>
就是这样。大家也可以创建一个 rotateZ
函数，由于我们的例子中实际上不需要用到它，所以我将它作为练习留给大家完成。<br/>

&nbsp;&nbsp;&nbsp;
现在，运行一下
RotateXY.as，与第十五章的版本相比，它们看上去实际是一样的。在
AS 2
中，我发现非矩阵版本的运行得更为流畅一些。原因是我们为
3D
旋转和缩放执行了非常大量的数学运算。当我们使用矩阵数学进行计算时，会产生额外的计算。在进行矩阵乘法时，我们实际是做了四次乘以零的操作，并将这四个结果与其它数值相加。这八次数学运算实际上没有任何作用。将这些操作乘以
50 个对象，每帧旋转二次，每帧就多做了 800
次额外计算！这两个版本在 AS 3
中的运行时看不出任何的不同，这就是 Flash CS3 与
AS 3
强大的证明。但是，当加入的物体越来越多时，我们就要为这些巨大的计算量付出代价。我给大家的这些代码都非常基本的。你也许可以使它更加优化一些，让性能得到提升。<br/>

&nbsp;即使在 3D
中不使用矩阵，我们仍可以发现它们在其它方面的用途，我将在下面一节进行介绍。在
3D
中使用矩阵是一个很好的引子，因为这样可以让大家看到它们是如何与已知公式相关联的。同样，矩阵在其它语言的
3D 制作中应用得非常之广泛，而且比我们现在的
ActionScript
更为有效。在这些语言中，只需付出一点点 CPU
就可以得到矩阵所带来的组织良好的代码。如果大家试图在
Flash 以外的其它软件中进行 3D
动画编程，那么就一定要使用到矩阵。还是那句话，谁知道
Flash
播放器几年后会成为什么样？终会有一天，所有的这些技术都能与
Flash 完美地结合。</P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 22px"><strong>Matrix
类</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
刚刚提到，学习矩阵的一个很好的理由是它被用在许多
ActionScript
类的内核中。事实上，我们有一个内置矩阵类。浏览一下
Flash 帮助文档中的 flash.geom.Matrix
类，就会发现那里写得非常清楚详细。如果本章前面内容您都能理解，那么要掌握这些材料就一定没问题。文档写得非常好，我就不再浪费空间将这些内容重复一遍了，但是我会给大家一个快速的总结并举出两个例子。<br/>

&nbsp;&nbsp;&nbsp;
矩阵主要用于对显示对象的转换（旋转，缩放和平移）。现在，任何一个显示对象（Sprite，影片剪辑，文本类等）都有名为
transform （转换）的属性。这是 flash.geom.Transform
类的一个实例，它还包含有另一个名为 matrix
的属性。如果我们创建一个 Matrix
类的实例，并把它赋给显示对象的 transform.matrix
属性，那么它将会改变这个对象的形状、大小或位置。我们马上会看到一些具体的例子。<br/>

基本来说 Matrix 类的矩阵是一个 3&times;3
的矩阵，形式如下：<br/>
&nbsp;&nbsp;
&nbsp;a&nbsp; b&nbsp; tx<br/>
&nbsp;&nbsp;
&nbsp;c&nbsp; d&nbsp; ty<br/>
&nbsp;&nbsp;
&nbsp;u&nbsp; v&nbsp; w<br/>
其中 u, v, w 内部自动被设置为 0, 0,
1。而且它们是不可改变的，因此不需要管它们。（更为具体的解释请参见帮助文档）我们使用下述语法来创建一个新的
Matrix：<br/>
&nbsp;&nbsp; &nbsp;import
flash.geom.Matrix;<br/>
&nbsp;&nbsp; &nbsp;var
myMatrix:Matrix = new Matrix(a, b, c, d, tx, ty);<br/>
那么这些字母是什么意思呢？tx 和 ty
非常简单。它们通过改变矩阵来控制显示对象的 x
和 y 轴。而 a, b, c, d
有些难度，因为它们都相互依赖。如果设置 b 和 c
为 0，就可以使用 a 和 d，在 x 和 y
轴上缩放一个对象。如果设置 a 和 d 为
1，就可以使用 b 和 c，分别在 y 和 x
轴上倾斜一个对象。最后，可以用一种我们非常熟悉的方式来使用
a, b, c, d。在本例中，设置如下：<br/>
&nbsp;&nbsp;&nbsp;
cos&nbsp; sin&nbsp; tx<br/>
&nbsp;&nbsp; -sin&nbsp;
cos&nbsp; ty<br/>
&nbsp;&nbsp;&nbsp;
&nbsp;u&nbsp;&nbsp;&nbsp;
v&nbsp;&nbsp; w<br/>
当然，我们可以看到这里包含了一个旋转矩阵，它确实可以旋转一个物体。自然本例中的
cos&nbsp; 和 sin
代表我们想要旋转的某个角度的正弦和余弦值（弧度制）。让我们试验一下这个例子。<br/>

&nbsp;&nbsp;&nbsp; 这里可见
MatrixRotate.as，这个类中用红色正方形创建了一个简单影片。然后设置一个
enterFrame 处理函数，所有的动作都加在其中：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;import flash.events.Event;<br/>
&nbsp;import flash.geom.Matrix;<br/>
&nbsp;public class MatrixRotate extends Sprite {<br/>
&nbsp;&nbsp;private var angle:Number =
0;<br/>
&nbsp;&nbsp;private var box:Sprite;<br/>
&nbsp;&nbsp;public function MatrixRotate()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;box = new
Sprite();<br/>
&nbsp;&nbsp;&nbsp;box.graphics.beginFill(0xff0000);<br/>

&nbsp;&nbsp;&nbsp;box.graphics.drawRect(-50,
-50, 100, 100);<br/>
&nbsp;&nbsp;&nbsp;box.graphics.endFill();<br/>

&nbsp;&nbsp;&nbsp;addChild(box);<br/>

&nbsp;&nbsp;&nbsp;addEventListener(Event.ENTER_FRAME,
onEnterFrame);<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function
onEnterFrame(event:Event):void {<br/>
&nbsp;&nbsp;&nbsp;angle +=
.05;<br/>
&nbsp;&nbsp;&nbsp;var
cos:Number = Math.cos(angle);<br/>
&nbsp;&nbsp;&nbsp;var
sin:Number = Math.sin(angle);<br/>
&nbsp;&nbsp;&nbsp;box.transform.matrix
= new Matrix(cos, sin,<br/>
&nbsp;&nbsp;&nbsp;-sin,
cos,<br/>
&nbsp;&nbsp;&nbsp;stage.stageWidth
/ 2,<br/>
&nbsp;&nbsp;&nbsp;stage.stageHeight
/ 2);<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
这里有一个 angle
变量，每帧都会增加。代码求出了角度的正弦和余弦值并将它们赋给新的矩阵对象，以这种方式指定
rotation。我同时还设置了平移，根据舞台的宽度和高度把影片放置到中心。新的矩阵被赋给了影片的
transform.matrix
属性。测试该影片就得到了一个旋转的正方形。<br/>

&nbsp;&nbsp;&nbsp;
现在，也许有人会问，你什么不改变影片的 rotation
属性。在一个简单的例子中使用 rotation
没有问题，这是一个更为简单的解决方法。但是，也许在一些处理多个角度、弧度、正弦、余弦的例子中，相比将一切转换回角度制并改变
rotation
值而言，像这样的矩阵赋值确实要简单很多。<br/>
&nbsp;&nbsp;&nbsp;
再给大家一些实际的说明，让我们试一下倾斜。<em>倾斜（Skewing）</EM>意思是将物体在一个轴上进行拉伸以便使一个部分走一条路，另一个部分走另一条路。斜体字就是一个倾斜的例子。字母顶部的部分向右倾斜，而底部的部分向左倾斜。这是
Flash 中一个众所周知的一个难点，但是使用 Matrix
类将会惊人地简单。如同我前面所说，设置矩阵的
a 和 d 为 1。属性 b 是 y 轴倾斜的值，属性 c 控制 x
轴的倾斜值。让我们先来试一下 x 倾斜。在 SkewX.as
中，我几乎使用了与前一个例子完全相同的设置，只不过改变了
onEnterFrame 方法中矩阵的创建。<br/>
private function onEnterFrame(event:Event):void {<br/>
&nbsp;var skewX:Number = (mouseX - stage.stageWidth /
2) * .01;<br/>
&nbsp;box.transform.matrix = new Matrix(1, 0,<br/>
&nbsp;skewX, 1,<br/>
&nbsp;stage.stageWidth / 2,<br/>
&nbsp;stage.stageHeight / 2);<br/>
}<br/>
这里相对于鼠标的 x 坐标创建了一个 skewX
变量，以舞台的中心为偏移量。然后将它乘以 .01
让倾斜的值处于可控范围，并将此值赋给矩阵。<br/>

&nbsp;&nbsp;&nbsp;
测试影片后，我们将看到如何让一个完整的影片进行倾斜，如图
18-2 所示。有了 Matrix
类一切都变得可能，如果你知道有谁在试图做这样的事，那么就把上述代码拿给他们看，等着看他们开始流口水吧！如果您亲自测试了这段代码，那么肯定已经知道我的意思了。<br/>

<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static4.photo.sina.com.cn/orignal/3ecb9b11450bb17ff7253" TARGET="_blank"><img SRC="http://static4.photo.sina.com.cn/bmiddle/3ecb9b11450bb17ff7253" /></A><br/>

<strong>图18-2</STRONG> 影片在 x 轴上的倾斜<br/>
在 SkewXY 中，我在 y 轴上做了同样的事情：<br/>
private function onEnterFrame(event:Event):void {<br/>
&nbsp;var skewX:Number = (mouseX - stage.stageWidth /
2) * .01;<br/>
&nbsp;var skewY:Number = (mouseY - stage.stageHeight /
2) * .01;<br/>
&nbsp;box.transform.matrix = new Matrix(1, skewY,<br/>
&nbsp;skewX, 1,<br/>
&nbsp;stage.stageWidth / 2,<br/>
&nbsp;stage.stageHeight / 2);<br/>
}<br/>
从图18-3 中可以看到影片在两个轴上的倾斜<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static10.photo.sina.com.cn/orignal/3ecb9b11450bb1913f2f9" TARGET="_blank"><img SRC="http://static10.photo.sina.com.cn/bmiddle/3ecb9b11450bb1913f2f9" />
</A><br/>
<strong>图18-3</STRONG> 影片在两个轴上的倾斜<br/>
如此简单就能实现这样的效果的确让人惊喜。如果您不确定这种效果能用在哪里，那么我告诉您，倾斜效果在伪
3D
效果中使用得非常频繁。当我们在上个例子中移动鼠标时，如果这个图形正在倾斜并旋转，那么大家已经可以看到了它是如何显示出透视来的。这不是特别精确的
3D，但是它可以用在一些非常棒的效果中。在网上有一些这方面的教程，告诉我们如何使用倾斜实现这种伪
3D 效果。Matrix 的使用也许会将代码缩短一半。<br/>
&nbsp;&nbsp;&nbsp;
大家一定要为 Matrix
类查一查帮助文档，因为这里还有其它好多好东西。要知道上述这些并不是
AS 3 唯一使用矩阵的地方。大家还应该看看
ColorMatrixFilter，ConvolutionFilter，这些不同的制图 API
填充和渐变的方法，以及 flash.geom.Transform
类。所以说矩阵的应用非常广泛！</P>]]></description>
            <author>FL基础理论大师</author>
            <category>Making Things Move!</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100a3fn.html#comment</comments>
            <pubDate>Sat, 28 Jun 2008 14:20:33 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100a3fn.html</guid>
        </item>
        <item>
            <title>[视频教程] 连连看游戏设计 [FL 车在臣]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100a1wk.html</link>
            <description><![CDATA[<p></P>
<div><embed NAME="articlevblog" PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer" SRC="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0&amp;vid=14561576&amp;uid=1053530897&amp;pid=346&amp;tid=1" WIDTH="480" HEIGHT="370" TYPE="application/x-shockwave-flash" ALLOWFULLSCREEN="true" ALLOWSCRIPTACCESS="samedomain"></EMBED></DIV>
<p></P>
<p></P>
<div><embed NAME="articlevblog" PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer" SRC="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0&amp;vid=14562838&amp;uid=1053530897&amp;pid=346&amp;tid=1" WIDTH="480" HEIGHT="370" TYPE="application/x-shockwave-flash" ALLOWFULLSCREEN="true" ALLOWSCRIPTACCESS="samedomain"></EMBED></DIV>
<p></P>
<p></P>
<div><embed NAME="articlevblog" PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer" SRC="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0&amp;vid=14562439&amp;uid=1053530897&amp;pid=346&amp;tid=1" WIDTH="480" HEIGHT="370" TYPE="application/x-shockwave-flash" ALLOWFULLSCREEN="true" ALLOWSCRIPTACCESS="samedomain"></EMBED></DIV>
<p></P>
<p>&nbsp;</P>
<p>全屏观看地址：</P>
<p><a HREF="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0&amp;vid=14561576&amp;uid=1053530897&amp;pid=346&amp;tid=1" TARGET="_blank">视频1地址</A></P>
<p><a HREF="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0&amp;vid=14562838&amp;uid=1053530897&amp;pid=346&amp;tid=1" TARGET="_blank">视频2地址</A></P>
<p><a HREF="http://vhead.blog.sina.com.cn/player/outer_player.swf?auto=0&amp;vid=14562439&amp;uid=1053530897&amp;pid=346&amp;tid=1" TARGET="_blank">视频3地址</A></P>
<p>&nbsp;</P>
<p>源文件及 PPT 下载：</P>
<p><a HREF="http://www.fs2you.com/files/76b3bea8-41d3-11dd-ba6f-001143e7b41c/">
http://www.fs2you.com/files/76b3bea8-41d3-11dd-ba6f-001143e7b41c/</A></P>
<p>&nbsp;</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>代码公布（数字版）：</STRONG></FONT></P>
<p><font STYLE="FONT-SIZE: 20px"><strong>Card.as：</STRONG></FONT></P>
<p>package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;import flash.text.TextField;<br/>
&nbsp;import flash.text.TextFormat;<br/>
&nbsp;import flash.filters.*;<br/>
&nbsp;import flash.events.MouseEvent;</P>
<p>&nbsp;public class Card extends Sprite {<br/>
&nbsp;&nbsp;public var id:int;<br/>
&nbsp;&nbsp;public var W:Number = 30;<br/>
&nbsp;&nbsp;public var H:Number = 40;<br/>
&nbsp;&nbsp;public var i:int;<br/>
&nbsp;&nbsp;public var j:int;<br/>
&nbsp;&nbsp;public var
TargetCard:Card;<br/>
&nbsp;&nbsp;private var color:uint =
0xcccccc;<br/>
&nbsp;&nbsp;public var Path:Array = new
Array();<br/>
&nbsp;&nbsp;private var pressed:Boolean =
false;<br/>
&nbsp;&nbsp;<br/>
&nbsp;&nbsp;public var line:Sprite = new
Sprite();<br/>
&nbsp;&nbsp;public function Card(id:int)
{<br/>
&nbsp;&nbsp;&nbsp;this.id =
id;<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}</P>
<p>&nbsp;&nbsp;function init():void {<br/>
&nbsp;&nbsp;&nbsp;graphics.beginFill(color);<br/>

&nbsp;&nbsp;&nbsp;graphics.drawRect(0,0,W,H);<br/>

&nbsp;&nbsp;&nbsp;graphics.endFill();<br/>

&nbsp;&nbsp;&nbsp;var
Text:TextField = new TextField();<br/>
&nbsp;&nbsp;&nbsp;Text.text =
String(id);<br/>
&nbsp;&nbsp;&nbsp;Text.selectable
= false;<br/>
&nbsp;&nbsp;&nbsp;Text.autoSize
= "left";</P>
<p>&nbsp;&nbsp;&nbsp;var
tf:TextFormat = new TextFormat(null,25);<br/>
&nbsp;&nbsp;&nbsp;// tf.size =
25;</P>
<p>
&nbsp;&nbsp;&nbsp;Text.setTextFormat(tf);<br/>

&nbsp;&nbsp;&nbsp;addChild(Text);<br/>

&nbsp;&nbsp;&nbsp;<br/>
&nbsp;&nbsp;&nbsp;addEventListener(MouseEvent.MOUSE_OVER,
MouseOver);<br/>
&nbsp;&nbsp;&nbsp;addEventListener(MouseEvent.MOUSE_OUT,
MouseOut);<br/>
&nbsp;&nbsp;}</P>
<p>&nbsp;&nbsp;function setIndex(a:int,
b:int):void {<br/>
&nbsp;&nbsp;&nbsp;i = a;<br/>
&nbsp;&nbsp;&nbsp;j = b;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;function Pressed():void {<br/>
&nbsp;&nbsp;&nbsp;pressed =
true;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;function UnPressed():void
{<br/>
&nbsp;&nbsp;&nbsp;pressed =
false;<br/>
&nbsp;&nbsp;&nbsp;this.filters
= null;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;function
MouseOver(evt:MouseEvent):void {<br/>
&nbsp;&nbsp;&nbsp;var
blur:BevelFilter = new BevelFilter();<br/>
&nbsp;&nbsp;&nbsp;var
shadow:DropShadowFilter = new DropShadowFilter(10);<br/>
&nbsp;&nbsp;&nbsp;var f:Array =
new Array();<br/>
&nbsp;&nbsp;&nbsp;f.push(blur);<br/>

&nbsp;&nbsp;&nbsp;f.push(shadow);<br/>

&nbsp;&nbsp;&nbsp;this.filters
= f;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;function
MouseOut(evt:MouseEvent):void {<br/>
&nbsp;&nbsp;&nbsp;if (pressed
== false) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;this.filters
= null;<br/>
&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}</P>
<p>&nbsp;}<br/>
}</P>
<p>
＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝</P>
<p><font STYLE="FONT-SIZE: 20px"><strong>Main.as</STRONG></FONT></P>
<p>package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;import flash.events.MouseEvent;<br/>
&nbsp;import flash.events.Event;<br/>
&nbsp;public class Main extends Sprite {<br/>
&nbsp;&nbsp;var Row:uint = 4;<br/>
&nbsp;&nbsp;var Colum:uint = 5;<br/>
&nbsp;&nbsp;var MarginLeft:Number =
180;<br/>
&nbsp;&nbsp;var MarginTop:Number = 100;</P>
<p>&nbsp;&nbsp;var oldCard:Card;<br/>
&nbsp;&nbsp;var map:Array;</P>
<p>&nbsp;&nbsp;public function Main()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;<br/>
&nbsp;&nbsp;function init():void {<br/>
&nbsp;&nbsp;&nbsp;map = new
Array(Row + 2);</P>
<p>&nbsp;&nbsp;&nbsp;var
numArray:Array = new Array();<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; Row; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;numArray[i]
= new Array();<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
num:uint = 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(var j:uint = 0; j &lt; Colum; j++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;numArray[i][j]
= ++num;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;for (i = 0; i
&lt; Row; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(j = 0; j &lt; Colum; j++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var
Rani:uint = Math.floor(Math.random() * Row);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var
Ranj:uint = Math.floor(Math.random() * Colum);</P>
<p>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var
temp:uint = numArray[i][j];<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;numArray[i][j]
= numArray[Rani][Ranj];<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;numArray[Rani][Ranj]
= temp;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;}</P>
<p>&nbsp;&nbsp;&nbsp;for (i =
0; i &lt; Row + 2; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;map[i]
= new Array(Colum + 2);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(j = 0; j &lt; Colum + 2; j++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if
(i ==0 || j ==0 || i == Row + 1 || j == Colum + 1) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map[i][j]
= 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
else {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map[i][j]
= 1;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;<br/>
&nbsp;&nbsp;&nbsp;for (i = 0; i
&lt; Row; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(j = 0; j &lt; Colum; j++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var
card:Card = new Card(numArray[i][j]);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addChild(card);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;card.x
= MarginLeft + j * (card.W + 2);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;card.y
= MarginTop + i * (card.H + 2);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;card.setIndex(i
+ 1, j + 1);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;card.addEventListener(MouseEvent.CLICK,
onClick);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;function
onClick(evt:MouseEvent):void {<br/>
&nbsp;&nbsp;&nbsp;try {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
currentCard:Card = Card(evt.target.parent);<br/>
&nbsp;&nbsp;&nbsp;} catch
(error:Error) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;currentCard
= Card(evt.target);<br/>
&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;if (oldCard
== null) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;oldCard
= currentCard;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;oldCard.Pressed();<br/>

&nbsp;&nbsp;&nbsp;} else {</P>
<p>
&nbsp;&nbsp;&nbsp;&nbsp;oldCard.TargetCard
= currentCard;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;//
successfully matched!<br/>
&nbsp;&nbsp;&nbsp;&nbsp;if
(isMatched()) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addChild(oldCard.line)<br/>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldCard.line.graphics.clear();<br/>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldCard.line.graphics.lineStyle(2);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br/>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var
node:Object = oldCard.Path.shift();<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldCard.line.graphics.moveTo(MarginLeft
+ node.y * oldCard.W - oldCard.W / 2, MarginTop + node.x *
oldCard.H - oldCard.H / 2);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br/>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldCard.addEventListener(Event.ENTER_FRAME,
ToLink);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldCard
= null;</P>
<p>
&nbsp;&nbsp;&nbsp;&nbsp;}
else {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//
Can not Match!<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldCard.UnPressed();<br/>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldCard
= null;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}</P>
<p>&nbsp;&nbsp;function
ToLink(evt:Event):void {<br/>
&nbsp;&nbsp;&nbsp;var card:Card
= Card(evt.target);<br/>
&nbsp;&nbsp;&nbsp;if
(card.Path.length &gt; 0) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
node:Object = card.Path.shift();<br/>
&nbsp;&nbsp;&nbsp;&nbsp;card.line.graphics.lineTo(MarginLeft
+ node.y * card.W - card.W / 2, MarginTop + node.x * card.H -
card.H / 2);<br/>
&nbsp;&nbsp;&nbsp;} else
{<br/>
&nbsp;&nbsp;&nbsp;&nbsp;<br/>

&nbsp;&nbsp;&nbsp;&nbsp;map[card.i][card.j]
= 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;map[card.TargetCard.i][card.TargetCard.j]
= 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;card.removeEventListener(Event.ENTER_FRAME,
ToLink);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;removeChild(card);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;removeChild(card.line);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;removeChild(card.TargetCard);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;<br/>
&nbsp;&nbsp;function isMatched():Boolean
{<br/>
&nbsp;&nbsp;&nbsp;if (oldCard
== oldCard.TargetCard || oldCard.id != oldCard.TargetCard.id)
{<br/>
&nbsp;&nbsp;&nbsp;&nbsp;return
false;<br/>
&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;<br/>
&nbsp;&nbsp;&nbsp;var x1:uint =
oldCard.i;<br/>
&nbsp;&nbsp;&nbsp;var y1:uint =
oldCard.j;<br/>
&nbsp;&nbsp;&nbsp;var x2:uint =
oldCard.TargetCard.i;<br/>
&nbsp;&nbsp;&nbsp;var y2:uint =
oldCard.TargetCard.j;<br/>
&nbsp;&nbsp;&nbsp;<br/>
&nbsp;&nbsp;&nbsp;var
node:Object = new Object();<br/>
&nbsp;&nbsp;&nbsp;var
tempPath:Array = new Array();<br/>
&nbsp;&nbsp;&nbsp;<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; Colum + 2; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
count:uint = 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;tempPath.splice(0);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;<br/>

&nbsp;&nbsp;&nbsp;&nbsp;var
step:int = (y1 &gt; i) ? -1 : 1;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(var j = y1; j != i; j += step) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count
+= map[x1][j];<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node
= {x:x1,y:j};<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempPath.push(node);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;}</P>
<p>
&nbsp;&nbsp;&nbsp;&nbsp;step
= (x1 &gt; x2) ? -1 : 1;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(j = x1; j != x2; j += step) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count
+= map[j][i];<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node
= {x:j,y:i};<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempPath.push(node);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;}</P>
<p>
&nbsp;&nbsp;&nbsp;&nbsp;step
= (i &lt; y2) ? 1 : -1;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(j = i; j != y2; j+= step) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count
+= map[x2][j];<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node
= {x:x2,y:j};<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempPath.push(node);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;}</P>
<p>
&nbsp;&nbsp;&nbsp;&nbsp;if
(count == 1) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if
(oldCard.Path.length == 0 || tempPath.length &lt;
oldCard.Path.length) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldCard.Path
= tempPath.slice();<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;}</P>
<p>&nbsp;&nbsp;&nbsp;for (i =
0; i &lt; Row + 2; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;count
= 0;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;tempPath.splice(0);</P>
<p>
&nbsp;&nbsp;&nbsp;&nbsp;step
= (i &lt; x1) ? -1 : 1;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(j = x1; j != i; j += step) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count
+= map[j][y1];<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node
= {x:j,y:y1};<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempPath.push(node);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;step
= (y2 &gt; y1) ? 1 : -1;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(j = y1; j != y2; j += step) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count
+= map[i][j];<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node
= {x:i,y:j};<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempPath.push(node);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;step
= (x2 &gt; i) ? 1 : -1;<br/>
&nbsp;&nbsp;&nbsp;&nbsp;for
(j = i; j != x2; j += step) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count
+= map[j][y2];<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node
= {x:j,y:y2};<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempPath.push(node);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;if
(count == 1) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if
(oldCard.Path.length == 0 || tempPath.length &lt;
oldCard.Path.length) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;oldCard.Path
= tempPath.slice();<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;&nbsp;}<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;<br/>
&nbsp;&nbsp;&nbsp;if
(oldCard.Path.length &gt; 0) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;node
= {x:x1, y:y1};<br/>
&nbsp;&nbsp;&nbsp;&nbsp;oldCard.Path.unshift(node);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;node
= {x:x2, y:y2};<br/>
&nbsp;&nbsp;&nbsp;&nbsp;oldCard.Path.push(node);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;return
true;<br/>
&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;return
false;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}</P>]]></description>
            <author>FL基础理论大师</author>
            <category>视频教程</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100a1wk.html#comment</comments>
            <pubDate>Tue, 24 Jun 2008 09:53:22 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100a1wk.html</guid>
        </item>
        <item>
            <title>第十七章 背面剔除与 3D  灯光&amp;#8545;[FL 基理译]</title>
            <link>http://blog.sina.com.cn/s/blog_3ecb9b110100a0lb.html</link>
            <description><![CDATA[<p><font STYLE="FONT-SIZE: 22px"><strong>3D
灯光</STRONG></FONT><br/>
&nbsp;&nbsp;&nbsp;
刚刚这个例子近乎可以让我们的渲染达到完美的效果，但是它似乎还缺少点儿什么。有些单调。OK，OK，大家看到标题就已经知道了，下面就让我们加入
3D 的灯光效果吧。<br/>
&nbsp;&nbsp;&nbsp;
同背面剔除一样，3D
灯光的细节也是相当复杂并且需要数学运算的。我实在没有太多的空间讨论每个漂亮的细节，但是通过快速的网络搜索大家可以获得非常更多的相关资料，也许这些资料多得我们一生也看不完。在这里，我给大家的都是一些基础的需要用到的函数。<br/>

&nbsp;&nbsp;&nbsp;
首先，需要一个光源。一个最简单的光源只有两个属性：位置和亮度（brightness）。在更加复杂的
3D
系统中，它也能够指向某个方向，并且还带有颜色，衰减率（falloff
rate），圆锥区域等等。但是这些都超出了本例的范围。<br/>

&nbsp;&nbsp;
&nbsp;让我们从制作一个 Light
灯光类开始。它会持有我们刚说的那两个属性 ——
位置和亮度。<br/>
package {<br/>
&nbsp;public class Light {<br/>
&nbsp;&nbsp;public var x:Number;<br/>
&nbsp;&nbsp;public var y:Number;<br/>
&nbsp;&nbsp;public var z:Number;<br/>
&nbsp;&nbsp;private var
_brightness:Number;<br/>
&nbsp;&nbsp;public function Light(x:Number
= -100,<br/>
&nbsp;&nbsp;y:Number = -100,<br/>
&nbsp;&nbsp;z:Number = -100,<br/>
&nbsp;&nbsp;brightness:Number = 1) {<br/>
&nbsp;&nbsp;&nbsp;this.x =
x;<br/>
&nbsp;&nbsp;&nbsp;this.y =
y;<br/>
&nbsp;&nbsp;&nbsp;this.z =
z;<br/>
&nbsp;&nbsp;&nbsp;this.brightness
= brightness;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;public function set
brightness(b:Number):void {<br/>
&nbsp;&nbsp;&nbsp;_brightness =
Math.max(b, 0);<br/>
&nbsp;&nbsp;&nbsp;_brightness =
Math.min(_brightness, 1);<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;public function get
brightness():Number {<br/>
&nbsp;&nbsp;&nbsp;return
_brightness;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
现在可以在主类的 init
方法中创建一个新的默认灯光：<br/>
&nbsp;&nbsp;&nbsp; var
light:Light = new Light();<br/>
或者可以创建一个指定位置和区域的灯光：<br/>
&nbsp;&nbsp;&nbsp; var
light:Light = new Light(100, 200, 300, .5);<br/>
这里有两个重要的地方需要注意。一个是位置，仅用于计算灯光的角度。灯光的亮度不会因为距离而衰减。因此改变
x, y, z 到 &ndash;1,000,000 或 -1
对于照射在物体上的灯光的亮度是没有区别的。<br/>

&nbsp;&nbsp;&nbsp; 只有
brightness
属性才会改变灯光的特性。我们当然可以加入一个函数用以判断灯光与物体间的距离来计算灯光的亮度值（brightness）。不会很难，现在已经介绍得差不多了，因此把这个函数留给大家去做。<br/>

&nbsp;&nbsp;&nbsp; brightness
必需是 0.0 到 1.0
之间的数。如果出了这个范围，会带来一些奇怪的结果。就是这个原因，我创建了一个私有属性
_brightness，并允许通过公共的 getter 和 setter 访问
brightness。这样做，允许我们传入的数值得到有效性的验证，确保这个数在有效范围内。<br/>

&nbsp;&nbsp;
&nbsp;一个理想的类不应该出现公有的属性，即使这些属性不需要验证，也只有私有属性通过
getter 和 setter
函数才能访问。这里我抄了近路，为的是让代码简洁并突出动画编程的原则。但是在本例中，额外添加的这一步是有必要的。<br/>

&nbsp;&nbsp;&nbsp;
下面，光源要做的就是根据灯光照射在多边形上的角度来改变三角形颜色的亮度值。因此如果一个多边形直接面对灯光，它就会显示出全部的颜色值。当离开灯光时，就会变得越来越暗。最终，当它完全离开光源时，它将完全变为阴影或黑色。<br/>

&nbsp;&nbsp;&nbsp; 由于
Triangle
类的成员知道自己的颜色是什么，并知道如何绘制自己，似乎每个三角形只需访问这个
light 就可以实现自己 draw
函数。因此，让我们给所有三角形一个 light
属性。我还要超个近路设置它们为公有属性：<br/>
&nbsp;&nbsp;&nbsp; public var
light:Light;<br/>
然后在主类中，创建这些三角形后，只需要循环它们把灯光的引用赋值给每个三角形：<br/>

var light:Light = new Light();<br/>
for(i = 0; i &lt; triangles.length; i++) {<br/>
&nbsp;triangles[i].light = light;<br/>
}<br/>
或者，我们也可以让 light 作为 Triangle
构造函数中的一个附加的参数，让每个三角形都有一个光源。我将这个方法留给大家去选择。<br/>

&nbsp;&nbsp;&nbsp;
现在，Triangle
需要一个关于其灯光颜色、角度、亮度的函数，并返回一个调整后的颜色值。以下是这个函数：<br/>

function getAdjustedColor():uint {<br/>
&nbsp;var red:Number = color
&gt;&gt; 16;<br/>
&nbsp;var green:Number = color
&gt;&gt; 8 &amp; 0xff;<br/>
&nbsp;var blue:Number = color &amp;
0xff;<br/>
&nbsp;var lightFactor:Number = getLightFactor();<br/>
&nbsp;red *= lightFactor;<br/>
&nbsp;green *= lightFactor;<br/>
&nbsp;blue *= lightFactor;<br/>
&nbsp;return red &lt;&lt; 16 |
green &lt;&lt; 8 | blue;<br/>
}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;
这个函数首先将三角形的基本颜色分为了 red, green,
blue 三个成分（见第四章）。然后调用另一个方法
getLightFactor，稍后会看到这个函数。现在，只需要知道它返回的是
0.0 到 1.0
之间的一个数，表示该颜色需要改变的大小，1.0
表示全部亮度，0.0 表示为全黑色。<br/>
&nbsp;&nbsp;&nbsp;&nbsp;
然后将每个颜色成分乘以这个滤光系数（light
factor），最后再将它们组合为一个 24
位的颜色值，并且将它作为调整后的颜色返回。它将成为灯光照射下三角形的颜色。<br/>

&nbsp;现在，如何得到这个 lightFactor
呢？让我们看一下：<br/>
private function getLightFactor():Number {<br/>
&nbsp;var ab:Object = new Object();<br/>
&nbsp;ab.x = pointA.x - pointB.x;<br/>
&nbsp;ab.y = pointA.y - pointB.y;<br/>
&nbsp;ab.z = pointA.z - pointB.z;<br/>
&nbsp;var bc:Object = new Object();<br/>
&nbsp;bc.x = pointB.x - pointC.x;<br/>
&nbsp;bc.y = pointB.y - pointC.y;<br/>
&nbsp;bc.z = pointB.z - pointC.z;<br/>
&nbsp;var norm:Object = new Object();<br/>
&nbsp;norm.x = (ab.y * bc.z) - (ab.z * bc.y);<br/>
&nbsp;norm.y = -((ab.x * bc.z) - (ab.z * bc.x));<br/>
&nbsp;norm.z = (ab.x * bc.y) - (ab.y * bc.x);<br/>
&nbsp;var dotProd:Number = norm.x * light.x +<br/>
&nbsp;norm.y * light.y +<br/>
&nbsp;norm.z * light.z;<br/>
&nbsp;var normMag:Number = Math.sqrt(norm.x * norm.x
+<br/>
&nbsp;norm.y * norm.y +<br/>
&nbsp;norm.z * norm.z);<br/>
&nbsp;var lightMag:Number = Math.sqrt(light.x * light.x
+<br/>
&nbsp;light.y * light.y +<br/>
&nbsp;light.z * light.z);<br/>
&nbsp;return Math.acos(dotProd / normMag * lightMag) /
Math.PI * light.brightness;<br/>
}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;
哇，好大一个函数不是吗？要想完全理解它，就一定要对高等向量学有较深的掌握，但是我也会试将基础的地方解释一下。<br/>

&nbsp;&nbsp;&nbsp;
&nbsp;首先，我们需要找到三角形的<em>法线</EM>（<em>normal</EM>）。它是一个向量，是三角形平面上的一条垂线，如图
17-5
所示。想象一下，我们拿着一块木制的三角板，然后从背后钉入一根钉子，它会从正面穿出。这根钉子就代表三角形平面的法线。如果您学过
3D
渲染和灯光的话，一定看过各种关于法线的资料。<br/>

<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static12.photo.sina.com.cn/orignal/3ecb9b114501b60e7202b" TARGET="_blank"><img SRC="http://static12.photo.sina.com.cn/bmiddle/3ecb9b114501b60e7202b" />
</A><br/>
<strong>图17-5</STRONG>
法线是到达三角形表面的一条垂线<br/>
&nbsp;&nbsp;&nbsp;
我们可以通过该平面的两个向量计算出它们的<em>外积</EM>（<em>cross
product</EM>）从而求出这条法线。两个向量的积是一条垂直于这两条向量的新向量。我们将使用的这两条向量是点
A 和 B，点 B 和 C 之间的连线。每个向量都用有带有
x, y, z 的 Object 持有。<br/>
var ab:Object = new Object();<br/>
ab.x = pointA.x - pointB.x;<br/>
ab.y = pointA.y - pointB.y;<br/>
ab.z = pointA.z - pointB.z;<br/>
var bc:Object = new Object();<br/>
bc.x = pointB.x - pointC.x;<br/>
bc.y = pointB.y - pointC.y;<br/>
bc.z = pointB.z - pointC.z;<br/>
然后计算法线，即另一个向量。求该对象的模（norm）。下面的代码用于计算向量
ab 和 bc 的外积：<br/>
var norm:Object = new Object();<br/>
norm.x = (ab.y * bc.z) - (ab.z * bc.y);<br/>
norm.y = -((ab.x * bc.z) - (ab.z * bc.x));<br/>
norm.z = (ab.x * bc.y) - (ab.y * bc.x);<br/>
我没有太多的篇幅来介绍这种计算方法的细节，这是计算向量外积的标准公式。如果您对它的推导感兴趣，可以随便找一本线性代数的正规参考书查一查。<br/>

&nbsp;&nbsp;&nbsp;
现在我们需要知道这条法线与灯光的角度。向量数学的另一个好东西叫做<em>内积</EM>（<em>dot
product</EM>），它与外积不同。我们有了法线的向量和灯光的向量。下面计算点积：<br/>

&nbsp;&nbsp;&nbsp; var
dotProd:Number = norm.x * light.x + norm.y * light.y + norm.z *
light.z;<br/>
我们看到，内积要比外积简单一些！<br/>
&nbsp;&nbsp;&nbsp;
OK，都差不多了！接下来，计算法线的量值，以及灯光的量值，大家应该还认识这个
3D 版的勾股定理吧：<br/>
var normMag:Number = Math.sqrt(norm.x * norm.x + norm.y * norm.y +
norm.z * norm.z);<br/>
var lightMag:Number = Math.sqrt(light.x * light.x + light.y *
light.y + light.z * light.z);<br/>
请注意，当一个三角形被渲染时，变量 lightMag
每次都要进行计算，这样就允许灯光是移动的。如果知道光源是固定的，我们可以在代码的一开始就加入这个变量，只需在创建灯光或为三角形赋值时进行一次计算。或者可以为
Light 类添加 lightMag 属性，让它可以在每次 x, y, z
属性发生变化时被计算。看，我已经给大家留出了各种发挥的空间！<br/>

最后，将前面计算出的这些数放入一个具有魔力公式中：<br/>

&nbsp;&nbsp;&nbsp; return
(Math.acos(dotProd / (normMag * lightMag)) / Math.PI) *
light.brightness;<br/>
其中 dotProd 是一个分量，而 normMag * lightMag
是另一个分量。两者相除得出一个比率。回忆一下第三章，一个角度的余弦给了我们一个比率，而一个比率的反余弦给了我们一个角度。这就是灯光照射在多边形表面上的角度。它的范围在
0 到 Math.PI 个弧度之间（0 到 180
度），也就是说灯光完全照射在物体前面上或完全照射在物体背面。<br/>

&nbsp;&nbsp;&nbsp;
用这个数除以 Math.PI 得出一个百分数，再用它乘以
brightness
的百分比就得出了最终用于改变底色的滤光系数。<br/>

&nbsp;&nbsp;&nbsp;
OK，所有这些仅仅给出了多边形表面颜色！此刻，在现有的代码中实现它就非常简单了。我们在
draw
方法中使用它。应该像这样直接使用这个调整后的颜色：<br/>

&nbsp;&nbsp;&nbsp;
g.beginFill(getAdjustedColor());<br/>
为了把上述内容综合起来，以下是全部最终的
Triangle.as 和 ExtrudedA.as
代码，列出了我们本章所有发生变化的部分：<br/>
首先是 Triangle：<br/>
package {<br/>
&nbsp;import flash.display.Graphics;<br/>
&nbsp;public class Triangle {<br/>
&nbsp;&nbsp;private var
pointA:Point3D;<br/>
&nbsp;&nbsp;private var
pointB:Point3D;<br/>
&nbsp;&nbsp;private var
pointC:Point3D;<br/>
&nbsp;&nbsp;private var color:uint;<br/>
<strong>&nbsp;&nbsp;public var
light:Light;</STRONG><br/>
&nbsp;&nbsp;public function
Triangle(a:Point3D, b:Point3D,<br/>
&nbsp;&nbsp;c:Point3D, color:uint) {<br/>
&nbsp;&nbsp;&nbsp;pointA =
a;<br/>
&nbsp;&nbsp;&nbsp;pointB =
b;<br/>
&nbsp;&nbsp;&nbsp;pointC =
c;<br/>
&nbsp;&nbsp;&nbsp;this.color =
color;<br/>
&nbsp;&nbsp;}<br/>
<strong>&nbsp;&nbsp;public function
draw(g:Graphics):void {<br/>
&nbsp;&nbsp;&nbsp;if
(isBackFace()) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;return;<br/>

&nbsp;&nbsp;&nbsp;}</STRONG><br/>

<strong>&nbsp;&nbsp;&nbsp;g.beginFill(getAdjustedColor());</STRONG><br/>

&nbsp;&nbsp;&nbsp;g.moveTo(pointA.screenX,
pointA.screenY);<br/>
&nbsp;&nbsp;&nbsp;g.lineTo(pointB.screenX,
pointB.screenY);<br/>
&nbsp;&nbsp;&nbsp;g.lineTo(pointC.screenX,
pointC.screenY);<br/>
&nbsp;&nbsp;&nbsp;g.lineTo(pointA.screenX,
pointA.screenY);<br/>
&nbsp;&nbsp;&nbsp;g.endFill();<br/>

&nbsp;&nbsp;}<br/>
<strong>&nbsp;&nbsp;private function
getAdjustedColor():uint {<br/>
&nbsp;&nbsp;&nbsp;var
red:Number = color &gt;&gt; 16;<br/>
&nbsp;&nbsp;&nbsp;var
green:Number = color &gt;&gt; 8
&amp; 0xff;<br/>
&nbsp;&nbsp;&nbsp;var
blue:Number =color &amp; 0xff;<br/>
&nbsp;&nbsp;&nbsp;var
lightFactor:Number = getLightFactor();<br/>
&nbsp;&nbsp;&nbsp;red *=
lightFactor;<br/>
&nbsp;&nbsp;&nbsp;green *=
lightFactor;<br/>
&nbsp;&nbsp;&nbsp;blue *=
lightFactor;<br/>
&nbsp;&nbsp;&nbsp;return red
&lt;&lt; 16 | green
&lt;&lt; 8 | blue;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function
getLightFactor():Number {<br/>
&nbsp;&nbsp;&nbsp;var ab:Object
= new Object();<br/>
&nbsp;&nbsp;&nbsp;ab.x =
pointA.x - pointB.x;<br/>
&nbsp;&nbsp;&nbsp;ab.y =
pointA.y - pointB.y;<br/>
&nbsp;&nbsp;&nbsp;ab.z =
pointA.z - pointB.z;<br/>
&nbsp;&nbsp;&nbsp;var bc:Object
= new Object();<br/>
&nbsp;&nbsp;&nbsp;bc.x =
pointB.x - pointC.x;<br/>
&nbsp;&nbsp;&nbsp;bc.y =
pointB.y - pointC.y;<br/>
&nbsp;&nbsp;&nbsp;bc.z =
pointB.z - pointC.z;<br/>
&nbsp;&nbsp;&nbsp;var
norm:Object = new Object();<br/>
&nbsp;&nbsp;&nbsp;norm.x =
(ab.y * bc.z) - (ab.z * bc.y);<br/>
&nbsp;&nbsp;&nbsp;norm.y =
-((ab.x * bc.z) - (ab.z * bc.x));<br/>
&nbsp;&nbsp;&nbsp;norm.z =
(ab.x * bc.y) - (ab.y * bc.x);<br/>
&nbsp;&nbsp;&nbsp;var
dotProd:Number = norm.x * light.x +<br/>
&nbsp;&nbsp;&nbsp;norm.y *
light.y +<br/>
&nbsp;&nbsp;&nbsp;norm.z *
light.z;<br/>
&nbsp;&nbsp;&nbsp;var
normMag:Number = Math.sqrt(norm.x * norm.x +<br/>
&nbsp;&nbsp;&nbsp;norm.y *
norm.y +<br/>
&nbsp;&nbsp;&nbsp;norm.z *
norm.z);<br/>
&nbsp;&nbsp;&nbsp;var
lightMag:Number = Math.sqrt(light.x * light.x +<br/>
&nbsp;&nbsp;&nbsp;light.y *
light.y +<br/>
&nbsp;&nbsp;&nbsp;light.z *
light.z);<br/>
&nbsp;&nbsp;&nbsp;return
Math.acos(dotProd / normMag * lightMag) / Math.PI *
light.brightness;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function
isBackFace():Boolean {<br/>
&nbsp;&nbsp;&nbsp;//
见</STRONG> <a HREF="http://www.jurjans.lv/flash/shape.html"><strong>http://www.jurjans.lv/flash/shape.html</STRONG></A><br/>

<strong>&nbsp;&nbsp;&nbsp;var
cax:Number = pointC.screenX - pointA.screenX;<br/>
&nbsp;&nbsp;&nbsp;var
cay:Number = pointC.screenY - pointA.screenY;<br/>
&nbsp;&nbsp;&nbsp;var
bcx:Number = pointB.screenX - pointC.screenX;<br/>
&nbsp;&nbsp;&nbsp;var
bcy:Number = pointB.screenY - pointC.screenY;<br/>
&nbsp;&nbsp;&nbsp;return cax *
bcy &gt; cay * bcx;<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;public function get
depth():Number {<br/>
&nbsp;&nbsp;&nbsp;var
zpos:Number = Math.min(pointA.z, pointB.z);<br/>
&nbsp;&nbsp;&nbsp;zpos =
Math.min(zpos, pointC.z);<br/>
&nbsp;&nbsp;&nbsp;return
zpos;<br/>
&nbsp;&nbsp;}</STRONG><br/>
&nbsp;}<br/>
<strong>}<br/></STRONG>然后是 ExtrudedA：<br/>
package {<br/>
&nbsp;import flash.display.Sprite;<br/>
&nbsp;import flash.events.Event;<br/>
&nbsp;public class ExtrudedA extends Sprite {<br/>
&nbsp;&nbsp;private var points:Array;<br/>
&nbsp;&nbsp;private var
triangles:Array;<br/>
&nbsp;&nbsp;private var fl:Number =
250;<br/>
&nbsp;&nbsp;private var vpX:Number =
stage.stageWidth / 2;<br/>
&nbsp;&nbsp;private var vpY:Number =
stage.stageHeight / 2;<br/>
&nbsp;&nbsp;public function ExtrudedA()
{<br/>
&nbsp;&nbsp;&nbsp;init();<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function init():void
{<br/>
&nbsp;&nbsp;&nbsp;points = new
Array();<br/>
&nbsp;&nbsp;&nbsp;points[0] =
new Point3D( -50, -250, -50);<br/>
&nbsp;&nbsp;&nbsp;points[1] =
new Point3D( 50, -250, -50);<br/>
&nbsp;&nbsp;&nbsp;points[2] =
new Point3D( 200, 250, -50);<br/>
&nbsp;&nbsp;&nbsp;points[3] =
new Point3D( 100, 250, -50);<br/>
&nbsp;&nbsp;&nbsp;points[4] =
new Point3D( 50, 100, -50);<br/>
&nbsp;&nbsp;&nbsp;points[5] =
new Point3D( -50, 100, -50);<br/>
&nbsp;&nbsp;&nbsp;points[6] =
new Point3D(-100, 250, -50);<br/>
&nbsp;&nbsp;&nbsp;points[7] =
new Point3D(-200, 250, -50);<br/>
&nbsp;&nbsp;&nbsp;points[8] =
new Point3D( 0, -150, -50);<br/>
&nbsp;&nbsp;&nbsp;points[9] =
new Point3D( 50, 0, -50);<br/>
&nbsp;&nbsp;&nbsp;points[10] =
new Point3D( -50, 0, -50);<br/>
&nbsp;&nbsp;&nbsp;points[11] =
new Point3D( -50, -250, 50);<br/>
&nbsp;&nbsp;&nbsp;points[12] =
new Point3D( 50, -250, 50);<br/>
&nbsp;&nbsp;&nbsp;points[13] =
new Point3D( 200, 250, 50);<br/>
&nbsp;&nbsp;&nbsp;points[14] =
new Point3D( 100, 250, 50);<br/>
&nbsp;&nbsp;&nbsp;points[15] =
new Point3D( 50, 100, 50);<br/>
&nbsp;&nbsp;&nbsp;points[16] =
new Point3D( -50, 100, 50);<br/>
&nbsp;&nbsp;&nbsp;points[17] =
new Point3D(-100, 250, 50);<br/>
&nbsp;&nbsp;&nbsp;points[18] =
new Point3D(-200, 250, 50);<br/>
&nbsp;&nbsp;&nbsp;points[19] =
new Point3D( 0, -150, 50);<br/>
&nbsp;&nbsp;&nbsp;points[20] =
new Point3D( 50, 0, 50);<br/>
&nbsp;&nbsp;&nbsp;points[21] =
new Point3D( -50, 0, 50);<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; points.length; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;points[i].setVanishingPoint(vpX,
vpY);<br/>
&nbsp;&nbsp;&nbsp;&nbsp;points[i].setCenter(0,
0, 200);<br/>
&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;triangles =
new Array();<br/>
&nbsp;&nbsp;&nbsp;triangles[0]
=new Triangle(points[0], points[1],<br/>
&nbsp;&nbsp;&nbsp;points[8],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[1]
=new Triangle(points[1], points[9],<br/>
&nbsp;&nbsp;&nbsp;points[8],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[2]
=new Triangle(points[1], points[2],<br/>
&nbsp;&nbsp;&nbsp;points[9],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[3]
=new Triangle(points[2], points[4],<br/>
&nbsp;&nbsp;&nbsp;points[9],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[4]
=new Triangle(points[2], points[3],<br/>
&nbsp;&nbsp;&nbsp;points[4],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[5]
=new Triangle(points[4], points[5],<br/>
&nbsp;&nbsp;&nbsp;points[9],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[6]
=new Triangle(points[9], points[5],<br/>
&nbsp;&nbsp;&nbsp;points[10],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[7]
=new Triangle(points[5], points[6],<br/>
&nbsp;&nbsp;&nbsp;points[7],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[8]
=new Triangle(points[5], points[7],<br/>
&nbsp;&nbsp;&nbsp;points[10],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[9]
=new Triangle(points[0], points[10],<br/>
&nbsp;&nbsp;&nbsp;points[7],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[10]
=new Triangle(points[0], points[8],<br/>
&nbsp;&nbsp;&nbsp;points[10],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[11]
=new Triangle(points[11], points[19],<br/>
&nbsp;&nbsp;&nbsp;points[12],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[12]
=new Triangle(points[12], points[19],<br/>
&nbsp;&nbsp;&nbsp;points[20],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[13]
=new Triangle(points[12], points[20],<br/>
&nbsp;&nbsp;&nbsp;points[13],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[14]
=new Triangle(points[13], points[20],<br/>
&nbsp;&nbsp;&nbsp;points[15],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[15]
=new Triangle(points[13], points[15],<br/>
&nbsp;&nbsp;&nbsp;points[14],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[16]
=new Triangle(points[15], points[20],<br/>
&nbsp;&nbsp;&nbsp;points[16],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[17]
=new Triangle(points[20], points[21],<br/>
&nbsp;&nbsp;&nbsp;points[16],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[18]
=new Triangle(points[16], points[18],<br/>
&nbsp;&nbsp;&nbsp;points[17],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[19]
=new Triangle(points[16], points[21],<br/>
&nbsp;&nbsp;&nbsp;points[18],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[20]
=new Triangle(points[11], points[18],<br/>
&nbsp;&nbsp;&nbsp;points[21],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[21]
=new Triangle(points[11], points[21],<br/>
&nbsp;&nbsp;&nbsp;points[19],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[22]
=new Triangle(points[0], points[11],<br/>
&nbsp;&nbsp;&nbsp;points[1],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[23]
=new Triangle(points[11], points[12],<br/>
&nbsp;&nbsp;&nbsp;points[1],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[24]
=new Triangle(points[1], points[12],<br/>
&nbsp;&nbsp;&nbsp;points[2],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[25]
=new Triangle(points[12], points[13],<br/>
&nbsp;&nbsp;&nbsp;points[2],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[26]
=new Triangle(points[3], points[2],<br/>
&nbsp;&nbsp;&nbsp;points[14],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[27]
=new Triangle(points[2], points[13],<br/>
&nbsp;&nbsp;&nbsp;points[14],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[28]
=new Triangle(points[4], points[3],<br/>
&nbsp;&nbsp;&nbsp;points[15],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[29]
=new Triangle(points[3], points[14],<br/>
&nbsp;&nbsp;&nbsp;points[15],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[30]
=new Triangle(points[5], points[4],<br/>
&nbsp;&nbsp;&nbsp;points[16],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[31]
=new Triangle(points[4], points[15],<br/>
&nbsp;&nbsp;&nbsp;points[16],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[32]
=new Triangle(points[6], points[5],<br/>
&nbsp;&nbsp;&nbsp;points[17],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[33]
=new Triangle(points[5], points[16],<br/>
&nbsp;&nbsp;&nbsp;points[17],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[34]
=new Triangle(points[7], points[6],<br/>
&nbsp;&nbsp;&nbsp;points[18],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[35]
=new Triangle(points[6], points[17],<br/>
&nbsp;&nbsp;&nbsp;points[18],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[36]
=new Triangle(points[0], points[7],<br/>
&nbsp;&nbsp;&nbsp;points[11],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[37]
=new Triangle(points[7], points[18],<br/>
&nbsp;&nbsp;&nbsp;points[11],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[38]
=new Triangle(points[8], points[9],<br/>
&nbsp;&nbsp;&nbsp;points[19],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[39]
=new Triangle(points[9], points[20],<br/>
&nbsp;&nbsp;&nbsp;points[19],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[40]
=new Triangle(points[9], points[10],<br/>
&nbsp;&nbsp;&nbsp;points[20],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[41]
=new Triangle(points[10], points[21],<br/>
&nbsp;&nbsp;&nbsp;points[20],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[42]
=new Triangle(points[10], points[8],<br/>
&nbsp;&nbsp;&nbsp;points[21],
0xcccccc);<br/>
&nbsp;&nbsp;&nbsp;triangles[43]
=new Triangle(points[8], points[19],<br/>
&nbsp;&nbsp;&nbsp;points[21],
0xcccccc);<br/>
<strong>&nbsp;&nbsp;&nbsp;var
light:Light = new Light();<br/>
&nbsp;&nbsp;&nbsp;for (i = 0; i
&lt; triangles.length; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;triangles[i].light
= light;<br/>
&nbsp;&nbsp;&nbsp;}</STRONG><br/>

&nbsp;&nbsp;&nbsp;addEventListener(Event.ENTER_FRAME,
onEnterFrame);<br/>
&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;private function
onEnterFrame(event:Event):void {<br/>
&nbsp;&nbsp;&nbsp;var
angleX:Number = (mouseY - vpY) * .001;<br/>
&nbsp;&nbsp;&nbsp;var
angleY:Number = (mouseX - vpX) * .001;<br/>
&nbsp;&nbsp;&nbsp;for (var
i:uint = 0; i &lt; points.length; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;var
point:Point3D = points[i];<br/>
&nbsp;&nbsp;&nbsp;&nbsp;point.rotateX(angleX);<br/>

&nbsp;&nbsp;&nbsp;&nbsp;point.rotateY(angleY);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
<strong>&nbsp;&nbsp;&nbsp;triangles.sortOn("depth",
Array.DESCENDING | Array.NUMERIC);</STRONG><br/>
&nbsp;&nbsp;&nbsp;graphics.clear();<br/>

&nbsp;&nbsp;&nbsp;for (i = 0; i
&lt; triangles.length; i++) {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;triangles[i].draw(graphics);<br/>

&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;}<br/>
&nbsp;}<br/>
}<br/>
&nbsp;&nbsp;&nbsp;
我们看到，在文档类中只有两个次要的变化。主要的工作都集中在
Triangle
中。同时，我还让所有的三角形使用相同的颜色，我认为这样做可以更好地观察灯光效果（见图
17-6）。<br/>
<a HREF="http://blog.photo.sina.com.cn/showpic.html#url=http://static7.photo.sina.com.cn/orignal/3ecb9b114501b707aa716" TARGET="_blank"><img SRC="http://static7.photo.sina.com.cn/bmiddle/3ecb9b114501b707aa716" /></A><br/>

<strong>图17-6</STRONG> 带有背面剔除，深度排序及 3D
灯光的三维立体模型</P>]]></description>
            <author>FL基础理论大师</author>
            <category>Making Things Move!</category>
            <comments>http://blog.sina.com.cn/s/blog_3ecb9b110100a0lb.html#comment</comments>
            <pubDate>Fri, 20 Jun 2008 16:12:15 GMT+8</pubDate>
            <guid>http://blog.sina.com.cn/s/blog_3ecb9b110100a0lb.html</guid>
        </item>
    </channel>
</rss>
