zoukankan      html  css  js  c++  java
  • 大家快来玩转盘抽奖游戏(走在网页游戏开发的路上(七))

    抽奖转盘

    0.  前言

    每逢过年过节各大游戏都会搞些小活动来刺激、吸引、黏住玩家,比如转盘抽奖活动,有的公司年底也喜欢搞抽奖活动。本文介绍如何设计一个flash转盘抽奖程序。先上效果,看如下Flash抽奖转盘:

    注:转盘看上去比较丑,使出了吃奶劲才弄出这效果,毕竟不是专业美工人员,大家凑合着看。觉得还行鼓鼓掌,按按推荐;觉得不行,也不要拍砖,无视就行了☺。

    预备知识:事件机制(可以参考走在网页游戏开发的路上(五))、flash动画原理(可以参考走在网页游戏开发的路上(六))、AS3文档类等。

    1.  抽奖流程

    其实我们的Flash只是一个显示作用,要转到哪个位置(中哪个奖品)是后台来完成的。而且每个奖品的概率是不同的,不是等概率的,我想没有转盘抽奖游戏是等概率的。从玩家点击“抽奖”开始到结束,与后台的交互如下:

    clip_image002

    转盘抽奖的大致流程是这样的:

    F  玩家点击Flash中的“抽奖”按钮;

    F  Flash调用web页面中的Javascript函数,告诉它玩家开始抽奖了。当然Flash调用JS的时候是带了参数的,比如是谁在抽奖等详细信息; web页面中的JavaScript函数,通知后台(可以是C++PythonPHPJavaC#等等)玩家开始抽奖了。这里也是带了参数的!

    F  后台返回结果,JavaScript函数通过调用Flash提供的接口,告诉抽奖结果;

    F  Flash拿到结果之后,就开始转到,最终转到指定位置。抽奖介绍!

    JavaScriptflash和后台之间充当着桥梁的作用。Flash仅作展示的作用,概率和抽奖结果由后台控制。

    2.  AS3JavaScript之间通信

    ExternalInterface 类是外部 PI,在ActionScriptFlash Player的容器之间实现直接通讯的应用程序编程接口,例如:含有JavaScriptHTML页。推荐对所有JavaScriptActionScript之间的通信使用ExternalInterface

    HTML页中使用JavaScript,可以调用Flash Player中的ActionScript函数。ActionScript函数可以返回一个值,JavaScript会立即接收它作为该调用的返回值,反之亦然。此功能替代了较旧的fscommand()方法。

    利用ActionScript,可以在HTML页上执行以下操作:

    F  调用任何JavaScript 函数。

    F  传递任意数量、具有任意名称的参数。

    F  传递各种数据类型(BooleanNumberString 等等)。

    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 Explorerobject标签的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 的值,请编辑objectembed标签中的相应属性。

    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浏览器对象的方法(即,一个表示objectembed标签的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的方式。接下来的内容,假设已经经历了FlashAS3àJavaScriptcgi等)à后台à JavaScriptcgi等),即Flash已经告诉后台玩家已经开始了抽奖,并且后台返回了结果——中奖物品(指针停止位置)。我们要做的工作就是,让指针旋转起来,并最终停留在指定位置。

    3.1.    数学知识

    这里可以说是用上了高中的数学知识了,指针从一个位置旋转到另一个位置,相当箭头的顶点绕着中心做圆周运动,运动过程中顶点的坐标变化公式如下:

    F  x += radius * sin

    F  y += radius * cos

    其中radius为半径(即指针长度)、为指针旋转的角度。

    clip_image004

    我们要做的就是不断的改变旋转角度,直到达到指定的位置,将旋转位置连续起来达到运动的效果(使用Event.ENTER_FRAMETimerEvent.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属性显然是不行的,这时必须得通过上面介绍的公式改变小球的xy坐标达到运动的效果,这也是介绍上面公式的原因。

    似乎这样做已经满足了基本要求,但是为了让用户感觉更真实,往往会要求指针至少转几圈,不会在一圈之内就到达指定位置停止了。那么这时指针的指针需要旋转度数:旋转圈数 * 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  获取TweenMaxTweenLiteTweenFilterLite的实例数组,使用静态函数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:ArrayBezier缓动,允许你以非线醒的方式进行缓动

    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导师的指导!要休息了,明天还要上班,不然明天要挂了。如果大家觉得还不错,就请推荐。

    参考文献:

    • FLASH抽奖程序中的细节问题[上]、[下]
    • TweenMax官方文档
    • Flash帮助文档
  • 相关阅读:
    Java实现 LeetCode 27 移除元素
    Java实现 LeetCode 26 删除排序数组中的重复项
    Java实现 LeetCode 26 删除排序数组中的重复项
    Java实现 LeetCode 26 删除排序数组中的重复项
    Java实现 LeetCode 25 K个一组翻转链表
    Java实现 LeetCode 25 K个一组翻转链表
    Java实现 LeetCode 25 K个一组翻转链表
    Java实现 LeetCode 24 两两交换链表中的节点
    Java实现 LeetCode 24 两两交换链表中的节点
    Java实现 LeetCode 24 两两交换链表中的节点
  • 原文地址:https://www.cnblogs.com/skynet/p/2081106.html
Copyright © 2011-2022 走看看