. 抽奖流程
其实我们的Flash只是一个显示作用,要转到哪个位置(中哪个奖品)是后台来完成的。而且每个奖品的概率是不同的,不是等概率的,我想没有转盘抽奖游戏是等概率的。从玩家点击“抽奖”开始到结束,与后台的交互如下:
转盘抽奖的大致流程是这样的:
F 玩家点击Flash中的“抽奖”按钮;
F Flash调用web页面中的Javascript函数,告诉它玩家开始抽奖了。当然Flash调用JS的时候是带了参数的,比如是谁在抽奖等详细信息; web页面中的JavaScript函数,通知后台(可以是C++、Python、PHP、Java、C#等等)玩家开始抽奖了。这里也是带了参数的!
F 后台返回结果,JavaScript函数通过调用Flash提供的接口,告诉抽奖结果;
F Flash拿到结果之后,就开始转到,最终转到指定位置。抽奖介绍!
JavaScript在flash和后台之间充当着桥梁的作用。Flash仅作展示的作用,概率和抽奖结果由后台控制。
2. AS3和JavaScript之间通信
ExternalInterface 类是外部 PI,在ActionScript和Flash Player的容器之间实现直接通讯的应用程序编程接口,例如:含有JavaScript的HTML页。推荐对所有JavaScript与ActionScript之间的通信使用ExternalInterface。
在HTML页中使用JavaScript,可以调用Flash Player中的ActionScript函数。ActionScript函数可以返回一个值,JavaScript会立即接收它作为该调用的返回值,反之亦然。此功能替代了较旧的fscommand()方法。
利用ActionScript,可以在HTML页上执行以下操作:
F 调用任何JavaScript 函数。
F 传递任意数量、具有任意名称的参数。
F 传递各种数据类型(Boolean、Number、String 等等)。
F 接收来自JavaScript 函数的返回值。
通过在HTML页上使用JavaScript,可以:
F 调用ActionScript函数。
F 使用标准的函数调用表示法传递参数。
F 将值返回给JavaScript函数。
2.1. ExternalInterface.available属性
ExternalInterface.available属性指示当前的Flash Player是否位于提供外部接口的容器中。如果外部接口可用,则此属性为true;否则,为false。在使用ExternalInterface类中的任何其它功能之前,应始终进行检查以确保当前容器支持外部接口通信,如下所示:
if (ExternalInterface.available)
{
// 在此执行 ExternalInterface 方法调用。
}
注意ExternalInterface.available属性报告当前容器是否为支持ExternalInterface连接的容器类型。它不会报告当前浏览器中是否启用了JavaScript。
通过使用ExternalInterface.objectID属性,您可以确定Flash Player实例的唯一标识符(具体来说,是指Internet Explorer中object标签的id属性,或者是指使用NPRuntime接口的浏览器中embed标签的name 属性)。这个唯一的ID代表浏览器中的当前SWF文档,并可用于对SWF文档进行引用,例如:在容器HTML页中调用JavaScript函数时进行引用。当Flash Player容器不是Web浏览器时,此属性为null。
2.2. 从ActionScript中调用外部代码
ExternalInterface.call()方法执行容器应用程序中的代码。它至少需要一个参数,即包含容器应用程序中要调用函数的名称的字符串。传递给ExternalInterface.call()方法的其它任何参数均作为函数调用的参数传递给容器。
//调用外部函数"addNumbers"
//传递两个参数并将该函数的结果
//赋给变量"result"
var param1:uint = 3;
var param2:uint = 7;
var result:uint = ExternalInterface.call("addNumbers", param1, param2);
如果容器为HTML页,此方法将调用具有指定名称的JavaScript函数,必须在包含HTML页中的script元素中定义该函数。JavaScript函数的返回值被传递回ActionScript。
<script language="JavaScript">
//加上两个数字,然后将结果发送回ActionScript
function addNumbers(num1, num2)
{
return (num1 + num2);
}
</script>
如果容器为其它的ActiveX容器,此方法将导致Flash PlayerActiveX 控件调度它的FlashCall事件。Flash Player将指定的函数名及所有参数序列化为一个XML字符串。容器可以在事件对象的request属性中访问该信息,并用它来确定如何执行它自己的代码。为了将值返回ActionScript,容器代码调用ActiveX对象的SetReturnValue()方法,并将结果(序列化为一个XML字符串)作为该方法的参数进行传递。
无论容器为Web浏览器还是为其它ActiveX容器,只要调用失败或容器方法没有指定返回值,都将返回null。如果包含环境属于调用代码无权访问的安全沙箱,ExternalInterface.call()方法将引发SecurityError 异常。可以通过在包含环境中为allowScriptAccess设置合适的值来解决此问题。例如,要在HTML页中更改allowScriptAccess 的值,请编辑object和embed标签中的相应属性。
2.3. 从容器中调用ActionScript代码
容器只能调用函数中的ActionScript代码,而不能调用任何其它ActionScript代码。要从容器应用程序调用ActionScript函数,必须执行两项操作:向ExternalInterface类注册函数,然后从容器的代码调用它。
首先,必须注册ActionScript函数,指示其应能够为容器所用。使用ExternalInterface.addCallback()方法,如下所示:
function callMe(name:String):String
{
return "busy signal";
}
ExternalInterface.addCallback("myFunction", callMe);
addCallback()方法采用两个参数。第一个参数为String类型的函数名,容器将籍此名称得知要调用的函数。第二个参数为容器调用定义的函数名时要执行的实际ActionScript函数。由于这些名称是截然不同的,因此可以指定将由容器使用的函数名,即使实际的ActionScript 函数具有不同的名称。这在函数名未知的情况下特别有用,例如:指定了匿名函数或需要在运行时确定要调用的函数。
一旦向ExternalInterface类注册了ActionScript函数,容器就可以实际调用该函数。完成该操作的具体方法依容器的类型而定。例如,在Web浏览器的JavaScript代码中,使用已注册的函数名调用ActionScript函数,就像它是Flash Player浏览器对象的方法(即,一个表示object或embed标签的JavaScript对象的方法)。也就是说,将传递参数并返回结果,就如同调用本地函数一样。
<script language="JavaScript">
// callResult gets the value "busy signal"
var callResult = flashObject.myFunction("my name");
</script>
...
<object id="flashObject"...>
...
<embed name="flashObject".../>
</object>
或者,在运行于计算机应用程序中的SWF文件中调用ActionScript函数时,必须将已注册的函数名及所有参数序列化为一个XML格式的字符串。然后,将该XML字符串作为一个参数来调用ActiveX控件的CallFunction()方法,以实际执行该调用。
不管是哪种情况,ActionScript函数的返回值都被传递回容器代码,当调用方为浏览器中的JavaScript代码时直接作为值返回,而当调用方为ActiveX容器时则会序列化为XML格式字符串。
3. 转盘旋转原理与实现
转盘旋转的设计和实现才是本文的重点,与服务器的交互将不再介绍,而且与服务器的交互也不止通过JavaScript的方法,还可以通过cgi的方式。接下来的内容,假设已经经历了Flash(AS3)àJavaScript(cgi等)à后台à JavaScript(cgi等),即Flash已经告诉后台玩家已经开始了抽奖,并且后台返回了结果——中奖物品(指针停止位置)。我们要做的工作就是,让指针旋转起来,并最终停留在指定位置。
3.1. 数学知识
这里可以说是用上了高中的数学知识了,指针从一个位置旋转到另一个位置,相当箭头的顶点绕着中心做圆周运动,运动过程中顶点的坐标变化公式如下:
F x += radius * sin ᶱ
F y += radius * cos ᶱ
其中radius为半径(即指针长度)、ᶱ为指针旋转的角度。
我们要做的就是不断的改变旋转角度,直到达到指定的位置,将旋转位置连续起来达到运动的效果(使用Event.ENTER_FRAME或TimerEvent.TIMER,参见走在网页游戏开发的路上(六))。
3.2. 指针运动
以开始给出的转盘为例,转盘中有8个物品,指针角度为0时指向物品1,角度为45时指向物品2…,以360/8=45为间隔。现在假设指针其实为值为物品1,中奖结果为物品5,即指针要旋转180度。为了设计动画效果,我们监听Event.ENTER_FRAME事件,每帧使指针旋转一个小角度,比如5,直到角度为180度停止,这样就可以达到旋转的效果。
那我们现在是每帧根据上面的公式,改变指针顶点的位置吗?不是,在ActionScript中,显示对象有个rotation属性,顾名思义旋转角度的意思。这样每帧改变这个属性值就可以,在Event.ENTER_FRAME事件处理函数中,使指针的rotation += 45,直到rotation等于180。(rotation的取值范围是:(-180, 180])
说明:如果是一个小球绕着某个点,做圆周运动,改变rotation属性显然是不行的,这时必须得通过上面介绍的公式改变小球的x、y坐标达到运动的效果,这也是介绍上面公式的原因。
似乎这样做已经满足了基本要求,但是为了让用户感觉更真实,往往会要求指针至少转几圈,不会在一圈之内就到达指定位置停止了。那么这时指针的指针需要旋转度数:旋转圈数 * 360 + (指针当前位置与指定位置的偏差值)。
3.3. 缓动效果
很显然,上面实现的动画效果比较生硬,需要加入缓动效果。现实实际效果也是如此,高速转到的指针不可能突然停止,其中有个减速的效果,当速度减到0时,指针才会停止。这其实也好办,我们在Event.ENTER_FRAME事件处理函数中,调整指针rotation的增加值从大到小变动,直到这个增量为0停止,这里我就不实现了。下面我要介绍一个非常有用的缓动效果库——TweenMax,如果做flash webgame开发的话,肯定会用用到这个库。
3.3.1TweenMax库简介
TweenMax库包含了很多缓动效果,它是建立在TweenLite核心类及TweenFilterLite基础之上(它们同样包含了一些缓动效果)。但是TweenMax新增功能:
F 进行贝塞尔缓动
F 连续的缓动(序列化的缓动)
F 对对象数组中的对象进行同意的缓动使用allTo()或allFrom()
F 缓动中的暂停/继续功能,使用pause()和resume()方法,或“paused”属性
F 跳转至缓动的任何时段,使用“progress”属性。输入一个0~1之间的数值
F 对16进制的颜色进行缓动,使用hexColors属性
F 获取缓动效果的实例数组,该数组中包括了加在一个指定目标对象上的所有的缓动效果的实例,TweenMax.getTweensOf(mc);如果该mc应用了多个缓动效果,则返回一个数组,数组中是不同的缓动效果的实例
F 获取TweenMax、TweenLite和TweenFilterLite的实例数组,使用静态函数getAllTweens()
F 终止所有的缓动
F 暂停/继续全部的缓动
3.3.2常用方法
l 构造函数:public function TweenMax(target:Object, duration:Number, vars:Object)
l target:目标对象,即需要缓动效果的对象,我们的例子中就是转盘的指针
l duration:持续的时间(单位:秒);
l vars:包含想要缓动的的属性值,缓动的常用属性包括
n alpha:Number:目标对象在缓动结束时的alpha
n delay:Number:延迟缓动
n ease:Function:缓动函数
n easeParames:Array:缓动函数中的参数
n autoAlpha:Number:用来代替alpha属性,可获得一些附加小伙,实现透明度缓动效果
n volume:Number:改变MovieClip或者SoundChannel的音量,将缓动结束时的音量值调整为指定的值
n tint:Number:改变可显示对象的色调/颜色
n frame:Number:将MovieClip缓动到指定的帧频
n bezier:Array:Bezier缓动,允许你以非线醒的方式进行缓动
n bezierThrough:Array:贝赛尔曲线要经过的位置点
n orientToBezier:Array:使MovieClip自动调整自身的方向,使之符合贝塞尔路径[x,y,rotation,ang](rotation:旋转属性,ang:旋转的度数
n hexColors:Object:缓动指定对象中相应颜色属性的值(TweenMax.to(my_obj,{hexColors:{mycolor:0Xff0000}}))
n onStart:Function:在缓动开始时想要执行的某个函数
n onStartParams:Array:缓动开始时要执行函数的参数
n onUpdate:Function:缓动过程中,每次更新属性值时,要执行的函数
n onUpdateParams:Array:同上
n onComplete:Function:缓动结束时要执行的函数
n onCompleteParams:Array:同上
n renderOnStart:Boolean:阻止缓动的渲染效果直到缓动真正开始
n overwrite:Boolean:缓动效果是否可以被覆盖
n blurFilter:Object:应用模糊滤镜,需要传递一个具有下列属性的对象作为参数:blurX(横向的模糊度),blurY(纵向的模糊度),quality(品质,默认值为2)
n glowFilter:Object:应用发光滤镜,需要传递一个带有以下属性的对象:alpha,blurX,blurY,color,strength(强度),quality,inner(内侧发光),knockout(挖空)
n colorMatrixFilter:Object:应用颜色矩阵滤镜,需要传递一个带有以下属性的对象:colorize(色调),amount(总量),contrast(对比度),brightness(亮度),saturation(饱和度),hue(色相),threshold(阀值),relative(相关性),matrix(颜色矩阵)
n dropShadowFilter:Object:应用阴影滤镜,需要传递一个带有以下属性的对象:alpha,angle(角度),blurX,blurY,color,distance(距离),strength,quality
n bevelFilter:Object:应用斜角滤镜,需要传递一个带有以下属性的对象:angle,blurX,blurY,distance,hightlightAlpha(高亮区的透明度),highlightColor(高亮区的颜色),shadowAlpha(阴影区的透明度),shadowColor(阴影区的颜色),strength(强度),quality
n progress:Number:缓动进程0~1
n paused:Boolean:是否停止缓动
l 函数:allTo(targets:Array, duration:Number, vars:Object):Array
返回的是一个数组保存了创建的所有TweenMax Object。
l 函数:allFrom(targets:Array, duration:Number, vars:Object):Array
跟allTo一样,只是定义的是运动对象的初始状态,运动到当前状态。
l 函数:complete(skipRender:Boolean = false, suppressEvents:Boolean = false):void
强制TweenMax到最后结束部分。如果第一个参数设为true,则不会渲染,TweenMax将停在调用那一刻。如果第二个参数设为true则不会触发onCompelte,onUpdate等事件。
l 函数:delayedCall(delay:Number, onComplete:Function, onCompleteParams:Array = null, useFrames:Boolean = false):TweenMax
延迟执行函数
l 函数:getTweensOf(target:Object):Array
返回运动物体正在运行的的TweenMax Object
l 函数:isTweening(target:Object):Boolean
判断是否正在缓动
l 函数:updateTo(vars:Object, resetDuration:Boolean = false):void
可以在运行中新增或改变原有的属性变化值。第二个参数设为false时将不重播缓动,而继续缓动到新的值;设为true将中断并重播缓动。
4. 最终代码实现
前面介绍了转盘的原理和TweenMax库,下面看最终实现的代码。不废话,上关键代码:
package
{
import flash.display.Sprite;
import com.greensock.TweenMax;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.utils.getDefinitionByName;
/**
* ...
* @author ...
*/
public class DialUI extends Sprite
{
private var _tween:TweenMax;
private var _view:*;
//物品个数;
private var _count:int = 8;
//角度;
private var _angle:Number = 360/_count;
//最少旋转圈数;
private var _rotateNum:int = 2;
public function DialUI()
{
initView();
}
private function initView():void
{
var cls:Class = getDefinitionByName("Dial") as Class;
if (cls != null)
{
_view = new cls();
_view.pointer.rotation = 0;
_view.btnStart.addEventListener(MouseEvent.CLICK, onClickHandler);
addChild(_view);
_tween = new TweenMax(_view.pointer, 2, {onComplete: onCompleteHandler});
}
}
private function onClickHandler(evt:MouseEvent):void
{
_view.btnStart.mouseEnabled = false;
var temp:uint = Math.floor(8 * Math.random());
trace(temp);
var rt = _angle * temp + (360 * _rotateNum);
_tween.updateTo({rotation: rt}, true)
}
private function onCompleteHandler():void
{
_view.btnStart.mouseEnabled = true;
}
}
}
写在最后
不知直觉,写到这么晚了,效率啊!最后大概浏览了一下,还是有很多传达的东西没有表现出来,只有大家意会了。声明:本文是我在公司半个月前所做东西
的总结,但并不涉及泄漏公司机密。感觉heaton导师的指导!要休息了,明天还要上班,不然明天要挂了。如果大家觉得还不错,就请推荐。