时钟的走动、移动、敲响的Flash教程(AS2.0 OOP)

2006-11-05 21:36 | Army


我是第一次用OOP方式写AS,以前一直在时间轴里嵌套写。先来说下我的感受吧:开始的确很不习惯,很多常用的东西往往不知道该如何用,可是研究一阵子后,我就发现,OOP的确在开发、维护和阅读等方面上存在很大的优势!所以,如果你正在使用Flash中的AS的话,尽快转向它吧!在一些小型的项目上面,OOP的优势或许并不怎么明显,可是当你构造一些大的东西时,就能够体会到它所带来的各种好处。

阅读这篇教程的人,需要有一定的Flash动画基础,至少要明白MC的建立、补间变换等原理。稍后你会看到,我在图形绘制方面会“惜墨如金”,因为大部分篇幅都将放在程序方面。另外,如果你有一些面向过程语言的基本功的话(如:C、VB等),读起来是很容易的。若是熟悉C++、Java、Python等其它OOP语言,那么这个对你来说简直就是小菜一碟~

依旧,在一些基本概念之前,我会先用蓝色字体介绍它的。

这个教程的源文件的地址是,关于图形的详细部分请自己翻阅(mx2004以上版本):

http://ff9.ffsky.cn/flash_teach\clock/exp.fla
http://ff9.ffsky.cn/flash_teach\clock/Clock.as

首先介绍下库中的所有文件(并非按名字顺序):



bar_fat是时钟上12个点里较大的4个,也就是0、3、6、9四个整点。bar_thin是其余的8个小点。它们都居中对齐并且再向上方移动一段距离。
btn和btn_movie是那个“点击”按钮,里面只有1段AS,稍后会介绍。
c_voice是包含voice声音的MC,它停留在第1帧。当点击那个“点击”按钮后这个MC就会跳到第2帧并播放敲响钟的声音。
hour、min、sec是时、分、秒指针,它们都是居中底部对齐,为什么呢?因为后面要制作它们的旋转,这样旋转中心以底部为原点,指针走动的效果就作出来了。如图,以秒针为例:



hour_movie等3个是制作了它们由透明度0变成100的影片剪辑,最后变成100时用stop();停在了最后一帧。
clock_bg是把4个大点和8个小点放上去形成钟面,可能你会问这样直接放上去岂不是都挤在一块了?不要忘了,可以通过旋转一定角度使它们等分成一个圆——这也是为什么一开始要让bar_fat和bar_thin居中后再向上移一段距离的原因,这段距离就等于钟的半径。把clock_bg拖到clock_movie里面,再把时分秒指针也拖进去,时分秒指针各自取名为hourp、minp、secp。再把clock_movie拖到舞台上并以相同名字命名。
最后解释下这12点随机闪烁的效果。bar_fat_m和bar_thin_m是各自的MC实现,在它们的第3帧之后制作了一个透明度渐变的效果,而在第2帧加了如下AS语句:

var ra = Math.floor(Math.random() * 50);
if(ra == 1) this.gotoAndPlay(3);
else this.gotoAndPlay(1);


第2帧上的AS调用了内部随机函数,Math.random()生成[0, 1)之间的小数,注意它的开闭区间。* 50后再用Math.floor舍去小数部分取整,因此ra的可能取值就是0、1、2、……、49。当ra等于1时这个MC走到第3帧,否则回到第1帧继续走。这样就形成了随即闪烁的效果,图片如下:



现在到了重头戏了,为了让初识OOP的人有个概念,我先来举一个例子吧,有水平的人可以直接跳过。

OOP就是面向对象,那么究竟什么是对象呢?抛弃伦理道德方面,只是为了举例的话,我们拿人造人来说。
要想创建对象,首先要有类,类是一个基本的单位。
假如现在有一个人的模型,它的各项器官都准备好了,只是还没将其按照设计者的想法“组装”起来。现在,我把它拼装上,并且设定它的性别是男,年龄很小,再给它起个名字叫“正太米”,让它变成一个活生生的人。于是——正太米就产生了!
当然,模型不止这一个,我有许多这样的模型。我再将另外一个模型拼装上,性别设定为女,年龄也很小,再给它起个名字叫“萝莉米”。如何?年轻漂亮迷倒N多人的loli米又有了!
很有趣吧,仅有这一种模型,我就可以创建出无数的人:大叔米、爷爷米、御姐米、大姨妈米……
打住、打住,这如果是现实实在是太可怕了,我一定会消灭其它所有的米,只保留一个loli米,以备……哇哈哈哈……

如何,明白类和对象了吗?那个最基本的模型就是类,用类创建出来的无数活生生的人就是对象。

仅仅是开个玩笑的话你只对OOP有了个概念了解,下面我们通过一段程序来说明:

//1
class Person {
  var name:String;
  var sex:String;
  var age:Number;
  function print():Void {
    if(sex == "male") {
      if(age <= 15) trace(name + ":我是正太米!");
      else trace(name + ":我是大叔米!");
    }
    else {
      if(age <= 15) trace(name + ":我是萝莉米!");
      else trace(name + ":我是御姐米!");
    }
  }
  function Person(name:String, sex:String, age:Number) {
    this.name = name;
    this.sex = sex;
    this.age = age;
  }
}

//2
var p1:Person = new Person("Army", "male", 15);
var p2:Person = new Person("Army", "female", 21);
p1.print();
p2.print();

好了我来解释一下吧,第1段定义了一个Person类,里面有字符串型的name和sex(定义变量后加上:号是说明变量类型,这与一般语言正好倒过来),还有数字型的age。后面定义了一个Void(空返回值型)的print方法,通过if语句判断输出。最后的那个与类同名的Person是构造器(构造函数),它用来初始化对象的属性,关键字this指向对象本身。
因此,第2段则是创建了两个Person对象,一个是p1,一个是p2。p1的传入参数是("Army", "male", 15),它被构造器接收,然后初始化name、sex、age三个变量,p2的构造参数则是("Army", "female", 21)。
于是,再调用p1和p2的各自print()方法,输出的结果依次是:Army:我是正太米!Army:我是御姐米!


现在回到正题吧,新建的类不能放在Flash时间轴上,需要新建一个.as的类文件,可以自己建也可以通过“文件-新建-Action Script文件”来创建。注意创建的类文件名并非需要和Flash文件同名,但里面定义的类却要和这个类文件同名!
下面是我的代码:

class Clock {
  var hour:Number, min:Number, sec:Number, mc:MovieClip;
  var cRun, cMove, pMove;

  //3
  function pointRun():Void {
    mc.secp._rotation += 6;
    sec ++;
    if(sec >= 60)  {
      mc.minp._rotation += 6;
      sec = 0;
      min ++;
    }
    if(min >= 60)
    {
      mc.hourp._rotation += 6;
      min = 0;
    }
  }

  //4
  function clockMove():Void {
    var center_x:Number = 240, center_y:Number = 180;
    var left_x:Number = 140, right_x:Number = 340, top_y:Number = 120, bottom_y:Number = 240;
    if(_xmouse <= center_x)
      mc._x += (left_x - mc._x) * (center_x - _xmouse) / center_x * 0.02;
    else
      mc._x += (right_x - mc._x) * (_xmouse - center_x) / center_x * 0.02;
    if(_ymouse <= center_y)
      mc._y += (top_y - mc._y) * (center_y - _ymouse) / center_y * 0.015;
    else
      mc._y += (bottom_y - mc._y) * (_ymouse - center_y) / center_y * 0.015;
  }

  //5
  function clockRun():Void {
    cRun = setInterval(this, "pointRun", 1000);
    cMove = setInterval(this, "clockMove", 10);
  }

  //6
  function pointMove():Void {
    mc.secp._rotation += 6;
    mc.minp._rotation -= 3;
    mc.hourp._rotation += 1;
  }

  //7
  function clockVoice():Void {
    clearInterval(this.cRun);
    clearInterval(this.cMove);
    pMove = setInterval(this, "pointMove", 10);
    _root.cVoice.gotoAndPlay(2);
  }

  //8
  function Clock(hour:Number, min:Number, sec:Number, mc:MovieClip) {
    this.hour = hour % 12;
    this.min = min;
    this.sec = sec;
    this.mc = mc;
    
    mc.secp._rotation += 360 * sec / 60;
    mc.minp._rotation += 360 * min / 60 + sec / 180;
    mc.hourp._rotation += 360 * hour / 12 + 360 / 12 * min / 60;
  }
}


首先来看下最后的第8段,虽然构造器的初始化是在最后,但首先介绍它是问了让新手更容易明白。前4句是把这个类的4个属性初始化,注意小时% 12运算是因为有24小时,钟却只有12个点,因此要取模运算。
后3句是将时分秒针初始化到正确的时间,这里面是通过一定的数学计算达到的,它们一开始都指向0点的位置,通过获得现在几点的数据旋转相应的角度。构造器传入的参数放到最后再说,它也很简单,你可以理解为一开始就能通过Flash得知现在几点几分几秒。

第3段定义了一个指针移动的方法,秒针不是每秒走1格吗?而1格就是6度,因此要自增6。后面两个if判断语句通过判定秒针和分针是否走完1圈后再决定分针和时针何时要走1格。

第4段定义了时钟移动的函数,别忘了它是跟随着鼠标移动的。这里有点小逻辑思维,如果你不想理解的话就可跳过。
如果仅仅是跟随鼠标移动的话,这个语句很好写:mc._x + = (_xmouse - mc._x) * 0.5;这里0.5数值可自己修改,这句的意思就是MC的横坐标值不断自增,自增的量就是鼠标横坐标值与自己横坐标值之差的一半!学过极限的人可以知道,这样的话MC就会一直向鼠标靠拢直到它的横坐标值和鼠标一样!纵坐标道理相同。
现在问题来了,我们规定时钟只能在一定范围内移动,比如说我这里定义的在以[140, 340](横坐标)、[120, 240](纵坐标)为限制的矩形内,那么该怎么写呢?
好了,现在我们就要判断了,设定一个矩形中心坐标。以横坐标为例,当鼠标在中心左边时:
mc._x += (left_x - mc._x) * (center_x - _xmouse) / center_x * 0.02;
(left_x - mc._x)是矩形左边界与MC的横坐标之差,它再乘以一个比例(center_x - _xmouse) / center_x——中心坐标减去鼠标坐标占中心坐标到舞台左边界的比值——以决定鼠标越靠左时MC移动越快,最后* 0.02是和上面那个0.5相同的意思。
好了,你明白了吗,这的确有点饶人。

第5段则是定义了clockRun函数,它的功能是不断执行上面第3段和第4段定义的指针移动和时钟行走的函数。注意setInterval的用法在类里和在时间轴上大有不同,这里this指明运行的对象是本身,运行的函数是对象里面定义的方法,最后一个是间隔时间,单位毫秒。

第6段定义了鼠标点击那个“点击”按钮后3个指针乱转的方法。

第7段则是执行第6段以及播放钟声还有清除时分秒针自身走动和时钟移动的方法。_root.cVoice.gotoAndPlay(2);就是叫舞台上那个名叫cVoice的影片剪辑播放第2帧(我在那里放了一个不断循环的钟声)。这些东西连起来可能有些逻辑混乱,你在阅读时务必要一个个慢慢来,否则很快就会转晕的。


让我们回到Flash中来吧,在时间轴上加入如下代码:

var now_date:Date = new Date();
var p1:Clock = new Clock(now_date.getHours(), now_date.getMinutes(), now_date.getSeconds(), clock_movie);
p1.clockRun();


觉得很少吧?那当然了,主要的程序都放在那个.as文件中。第1句是创建了一个Date对象,这是Flash内部提供支持的类,用于获取本地计算机的日期时间。后面那个就是用类创建对象了,传入的4个参数分别是通过Date获得时、分、秒时间以及放在舞台上的clock_movie影片剪辑名。
最后1句调用了对象的clockRun()方法,看看类里面定义的,它是让时分秒针走动以及时钟移动的。

现在我们已经完成了大部分的内容了,只差最后点击那个“点击”按钮后时钟停止移动、时分秒针停止走动并且开始旋转和播放钟声的效果了。还记得开头处我们说的按钮里的那段代码吗?那就是关键所在!
点选按钮之后打开动作脚本面板,里面就1句话:

on(release) {
  p1.clockVoice();
}


简单吧!调用p1对象的clockVoice()方法。如何,OOP就是如此神奇。当你具有一定水平,开发大型工程项目之时,通过OOP的继承多态回调接口等等等,你会发现它的优势所在。

好了,作为最后的建议,我要说的是良好的和严谨的编程风格绝对对你有益无害!不要想着把自己的代码伪装的多么花哨,多么迷人。请记住,代码被阅读以及被维护的时间永远大于被创建的!你能提供给别人阅读的同时其实也锻炼了你自己,这样别人在获益后也会记得分享他的成果。所以,如果通过这篇教程让你踏上了OOP的道路的话,请也来写出自己体会到的心得吧!