精华内容
下载资源
问答
  • 幸运抽奖小软件

    2017-01-05 20:20:05
    为年会一个小型抽奖环节设计的抽奖软件,VS2010工程源码
  • 利用Authorware软件设计了平面设计大赛抽奖系统,随机抽取建立的2维表格数据库文本文件记录,以体现抽奖环节的公平性、公正性。
  • 大转盘抽奖活动设计

    千次阅读 2017-07-13 11:09:37
    核心代码实现待续数据库设计待续高并发下的优化转载:http://blog.csdn.net/qq_16681169/article/details/53750704 一. 项目思考由于项目发起了一个抽奖活动,发起活动之前给所有用户发短信提示他们购买了我们的...

    核心代码实现

    待续

    数据库设计

    待续

    高并发下的优化

    转载:http://blog.csdn.net/qq_16681169/article/details/53750704
    一. 项目思考

    由于项目发起了一个抽奖活动,发起活动之前给所有用户发短信提示他们购买了我们的产品有抽奖权益。然后用户上来进入抽奖页面点击爆增,过了一会儿页面就打不开了。后面查看了下各种日志,发现了瓶颈在数据库,由于读写冲突严重,导致响应变慢,有不少连接都超时了。后面看到监控和日志留下的数据,发现负责抽奖的微服务集群qps暴涨12倍,db的qps也涨了10倍。这很明显是一个高并发下如何摆脱数据库读写,I/O瓶颈的问题。
    整点开抢后瞬时巨量的请求同时涌入,即使我们Nginx端做过初步限流,整个业务逻辑校验阶段运作良好,但是系统的瓶颈就转移到其他环节:大量的读写请求,导致后面的请求全部排队等待,等前面一个update完成释放行锁后才能处理下一个请求,大量请求等待,占用了数据库的连接!一旦数据库同一时间片内的连接数被打满,就会导致这个时间片内其他后来的全部请求因拿不到连接而超时,导致访问此数据库的其他环节也出现问题!所以RT就会异常飙高!
    于是我们在思考着怎么优化这个高并发下的抽奖问题

    二. 优化思路

    听了经验丰富的师兄的经验,也借鉴了下网上的一些思路,能采用的有效措施主要是:降级,限流,缓存,消息队列。主要原则是:尽量不暴露db,把大部分请求在服务的系统上层处理了。

    三. 优化细节

    1. 抽奖详情页

    a. 线上开启缓存
    线上已写缓存逻辑,但是没有用switch开启。开启后可以减少数据库的并发IO压力,减少锁冲突。

    b. 关于本地缓存淘汰策略的细节处理
    缓存超过或等于限制大小全部清空。建议等于时不清空,而使用缓存淘汰算法:比如LRU,LFU,NRU等,这样不会出现缓存过大清空后,从数据库更新数据到缓存,缓存里数据依旧很大。导致缓存清空频率过高,反而降低系统的吞吐量。例如guava cache中的参数是

    //设置缓存容器的初始容量为10
    initialCapacity(10)
    //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
    maximumSize(100)

    1. 抽奖逻辑

    a.队列削峰
    用额外的单进程处理一个队列,下单请求放到队列里,一个个处理,就不会有qps的高并发问题了。场景中抽奖用户会在到点的时间涌入,DB瞬间就接受暴击压力,hold不住就会宕机,然后影响整个业务。队列的长度保持固定,对于如果请求排队在队伍中靠后,比如奖品100个的情况下,中奖率10%,队列里请求任务超过1000时,就直接将后续的抽奖请求返回不中奖。用tair记录排队数,如果奖品没发完,再请空tair,允许请求继续入队列。这样队列起到了降级和削峰的作用。

    b.将事务和行级悲观锁改成乐观锁
    原来的代码是通过悲观锁来控制超发的情况。(比如一共有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。)
    在原来的代码中用的是for update行锁,在高并发的情况下会很多这样的修改请求,每个请求都需要等待锁,某些线程可能永远都没有机会抢到这个锁,这种请求就会死在那里。同时,这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。
    可以采用乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。

    c.对于与抽奖无直接关系的流程采用异步
    比如抽奖成功之后的发短信功能另起一个线程池专门处理。这样可以提高请求的处理速率,提高qps上升后的乘载能力。

    d.数据库的读写分离
    现在的数据库查询都是读的主库。将数据库的大量查询改为从库,减轻主库的读写压力。主服务器进行写操作时,不影响查询应用服务器的查询性能,降低阻塞,提高并发。

    e.同一时间片内,采用信号量机制
    确保进来的人数不会过多导致系统响应超时: 信号量的采用,能够使得抽奖高峰期内,同一时间片内不会进入过多的用户,从底层实现上规避了系统处理大数据量的风险。这个可以配合队列进行限流处理。

    f. 消息存储机制
    将数据请求先添加到信息队列中(比如Tair存储的数据结构中),然后再写工具启动定时任务从Tair中取出数据去入库,这样对于db的并发度大大降低到了定时任务的频率。但是问题可能会出在保持数据的一致性和完整性上。

    g.必要时候采用限流降级的测流
    当并发过多时为了保证系统整体可用性,抛弃一些请求。对于被限流的请求视为抽不到奖。

    3.额外考虑

    a.防止黑客刷奖
    防止黑客恶意攻击(比如cc攻击)导致qps过高,可以考虑策略在服务入口为相同uid的账户请求限制每秒钟的最高访问数。

    b. 中奖数据预热
    中奖只是少数,大部分人并不会中奖,所以可以在第一步便限制只有少数用户的请求能够打到真正抽奖逻辑上。是否可以考虑在抽奖之前先用随机算法生成一批中奖候选人。然后当用户请求过来时如果其中绝大多数请求都非中奖候选人,则直接返回抽奖失败,不走抽奖拿奖品的流程。少部分用户请求是中奖候选人,则进入队列,排在队列前面的获得奖品,发完为止,先到先得。
    举个例子:10万个用户抽奖,奖品100个,先随机选出中奖候选人500个。用户请求过来时,不走抽奖查库逻辑的用户过滤掉99500个,剩余的候选人的请求用队列处理,先到先得。这样可以把绝大多数的请求拦截在服务上游不用查库,但是缺点是不能保证奖品一定会被抽完(可能抽奖候选人只有不到100人参与抽奖)。

    四.设计架构图
    这里写图片描述

    JS前端实现


    核心函数是一个jQuery中旋转rotate插件
    支持Internet Explorer 6.0+ 、Firefox 2.0 、Safari 3 、Opera 9 、Google Chrome,高级浏览器下使用Transform,低版本ie使用VML实现。

    • rotate(angle)angle参数:[Number] – 默认为 0

    根据给定的角度旋转图片例如:

    $("#img").rotate45);
    或 $('#img').rotate({angle:45})
    • rotate(parameters)

    parameters参数:[Object] 包含旋转参数的对象。支持的属性:

    1.angle属性:[Number] – default 0 – 旋转的角度数,并且立即执行例如:

    $("#img").rotate({angle:45});

    2.bind属性:[Object] 对象,包含绑定到一个旋转对象的事件。事件内部的 (this) (this).rotate(…)。例如 (click on arrow):

    $("#img").rotate({
    bind: {
    click: function () {
    $(this).rotate({
    angle: 0,
    animateTo: 180
    })
    }
    }
    });

    3.animateTo属性:[Number] – default 0 – 从当前角度值动画旋转到给定的角度值 (或给定的角度参数)例如: 结合上面的例子,请参阅使用。

    4.duration属性:[Number] – 指定使用animateTo的动画执行持续时间例如 (click on arrow):

    $("#img").rotate({
    bind: {
    click: function () {
    $(this).rotate({
    duration: 6000,
    angle: 0,
    animateTo: 100
    })
    }
    }
    });

    5.step属性:[Function] – 每个动画步骤中执行的回调函数,当前角度值作为该函数的第一个参数

    6.callback属性:[Function] 动画完成时执行的回调函数例如 (click on arrow):

    $("#img").rotate({bind: {
    click: function () {
    $(this).rotate({
    angle: 0,
    animateTo: 180,
    callback: function () {
    alert(1)
    }
    })
    }
    }
    });

    getRotateAngle

    这个函数只是简单地返回旋转对象当前的角度。

    $("#img").rotate({
    angle: 45,
    bind: {
    click: function () {
    alert($(this).getRotateAngle());
    }
    }
    });

    stopRotate

    这个函数只是简单地停止正在进行的旋转动画。

    $("#img").rotate({
    bind: {
    click: function () {
    $("#img").rotate({
    angle: 0,
    animateTo: 180,
    duration: 6000
    });
    setTimeout(function () {
    $("#img").stopRotate();
    }, 1000);
    }
    }
    });
    展开全文
  • 目录 1.抽奖系统的背景引入 2.结合具体业务需求分析抽奖系统 ...7.发放礼品环节进行限流削峰 8.系统架构设计总结 1、抽奖系统的背景引入 本文给大家分享一个之前经历过的抽奖系统的流量削峰...

    目录 

    1.抽奖系统的背景引入    

    2.结合具体业务需求分析抽奖系统    

    3.一个未经过优化的系统架构    

    4.负载均衡层的限流    

    5.Tomcat线程数量的优化    

    6.基于Redis实现抽奖业务逻辑    

    7.发放礼品环节进行限流削峰    

    8.系统架构设计总结    

     

    1、抽奖系统的背景引入

    本文给大家分享一个之前经历过的抽奖系统的流量削峰架构的设计方案。

     

    抽奖、抢红包、秒杀,这类系统其实都有一些共同的特点,那就是在某个时间点会瞬间涌入大量的人来点击系统,给系统造成瞬间高于平时百倍、千倍甚至几十万倍的流量压力。

     

    比如抽奖,有一种场景:某个网站或者APP规定好了在某个时间点,所有人都可以参与抽奖,那么可能百万级的用户会蹲守在那个时间点,到时间大家一起参与这个抽奖。

     

    抢红包,可能是某个电视节目上,突然说扫码可以抢红包,那么电视机前可能千万级的用户会瞬间一起打开手机扫码抢红包。

     

    秒杀更是如此,所谓秒杀,意思是让大家都在电脑前等着,在某个时间突然就可以抢购某个限量的商品

     

    比如某个手机平时卖5999,现在限量100台价格才2999,50%的折扣,可能百万级的用户就会蹲守在电脑前在比如凌晨12点一起点击按钮抢购这款手机。

     

    类似的场景其实现在是很多的,那么本文就用一个抽奖系统举例,说说应对这种瞬时超高并发的流量,应该如何设计流量削峰的架构来应对,才能保证系统不会突然跨掉?

     

     

    2、结合具体业务需求分析抽奖系统

    假设现在有一个抽奖的业务场景,用户在某个时间可以参与抽奖,比如一共有1万个奖,奖品就是某个礼物。

     

    然后参与抽奖的用户可能有几十万,一瞬间可能几十万请求涌入过来,接着瞬间其中1万人中奖了,剩余的人都是没中奖的。然后中奖的1万人的请求会联动调用礼品服务,完成这1万中奖人的礼品发放。

     

    简单来说,需求场景就是如此,然而这里就有很多的地方值得优化了。

     

     

    3、一个未经过优化的系统架构

    先来看一个未经过任何优化的系统架构,简单来说就是有一个负载均衡的设备会把瞬间涌入的超高并发的流量转发到后台的抽奖服务上。

     

    这个抽奖服务就是用普通的Tomcat来部署的,里面实现了具体的抽奖逻辑,假设刚开始最常规的抽奖逻辑是基于MySQL来实现的,接着就是基于Tomcat部署的礼品服务,抽奖服务如果发现中奖了需要调用礼品服务去发放礼品。

     

    如下图所示:

     

     

    4、负载均衡层的限流

     

    4.1 防止用户重复抽奖

    首先第一次在负载均衡层可以做的事情,就是防止重复抽奖。

     

    我们可以在负载均衡设备中做一些配置,判断如果同一个用户在1分钟之内多次发送请求来进行抽奖,就认为是恶意重复抽奖,或者是他们自己写的脚本在刷奖,这种流量一律认为是无效流量,在负载均衡设备那个层次就给直接屏蔽掉。

     

    举个例子,比如有几十万用户瞬间同时抽奖,最多其实也就几十万请求而已,但是如果有人重复抽奖或者是写脚本刷奖,那可能瞬间涌入的是几百万的请求,就不是几十万的请求了,所以这里就可以把无效流量给拦截掉。

     

    如下图所示:

     

     

    4.2 全部开奖后暴力拦截流量

    其实秒杀、抢红包、抽奖,这类系统有一个共同的特点,那就是假设有50万请求涌入进来,可能前5万请求就直接把事儿干完了,甚至是前500请求就把事儿干完了,后续的几十万流量是无效的,不需要让他们进入后台系统执行业务逻辑了。

     

    什么意思呢?

     

    举个例子,秒杀商品,假设有50万人抢一个特价手机,人家就准备了100台手机,那么50万请求瞬间涌入,其实前500个请求就把手机抢完了,后续的几十万请求没必要让他转发到Tomcat服务中去执行秒杀业务逻辑了,不是吗?

     

    抽奖、红包都是一样的 ,可能50万请求涌入,但是前1万个请求就把奖品都抽完了,或者把红包都抢完了,后续的流量其实已经不需要放到Tomcat抽奖服务上去了,直接暴力拦截返回抽奖结束就可以了。

     

    这样的话,其实在负载均衡这一层(可以考虑用Nginx之类的来实现)就可以拦截掉99%的无效流量。

     

    所以必须让抽奖服务跟负载均衡之间有一个状态共享的机制。

     

    就是说抽奖服务一旦全部开奖完毕,直接更新一个共享状态。然后负载均衡感知到了之后,后续请求全部拦截掉返回一个抽奖结束的标识就可以了。

     

    这么做可能就会做到50万人一起请求,结果就可能2万请求到了后台的Tomcat抽奖服务中,48万请求直接拦截掉了。

     

    我们可以基于Redis来实现这种共享抽奖状态,它非常轻量级,很适合两个层次的系统的共享访问。

     

    当然其实用ZooKeeper也是可以的,在负载均衡层可以基于zk客户端监听某个znode节点状态。一旦抽奖结束,抽奖服务更新zk状态,负载均衡层会感知到。

     

    下图展示了上述所说的过程:

     

     

    5、Tomcat线程数量的优化

    其次就是对于线上生产环境的Tomcat,有一个至关重要的参数是需要根据自己的情况调节好的,那就是他的工作线程数量。

     

    众所周知,对于进入Tomcat的每个请求,其实都会交给一个独立的工作线程来进行处理,那么Tomcat有多少线程,就决定了并发请求处理的能力。

     

    但是这个线程数量是需要经过压测来进行判断的,因为每个线程都会处理一个请求,这个请求又需要访问数据库之类的外部系统,所以不是每个系统的参数都可以一样的,需要自己对系统进行压测。

     

    但是给一个经验值的话,Tomcat的线程数量不宜过多。因为线程过多,普通虚拟机的CPU是扛不住的,反而会导致机器CPU负载过高,最终崩溃。

     

    同时,Tomcat的线程数量也不宜太少,因为如果就100个线程,那么会导致无法充分利用Tomcat的线程资源和机器的CPU资源。

     

    所以一般来说,Tomcat线程数量在200~500之间都是可以的,但是具体多少需要自己压测一下,不断的调节参数,看具体的CPU负载以及线程执行请求的一个效率。

     

    在CPU负载尚可,以及请求执行性能正常的情况下,尽可能提高一些线程数量。

     

    但是如果到一个临界值,发现机器负载过高,而且线程处理请求的速度开始下降,说明这台机扛不住这么多线程并发执行处理请求了,此时就不能继续上调线程数量了。

     

     

     

    6、基于Redis实现抽奖业务逻辑

    现在问题又来了,虽然在负载均衡那个层面,已经把比如50万流量中的48万都拦截掉了,但是可能还是会有2万流量进入抽奖服务

     

    此时抽奖服务自然是可以多机器来部署的,比如假设一台Tomcat可以抗500请求,那么2万并发就是40台机器。

     

    如果你是基于云平台来部署系统的,搞活动临时租用一批机器就可以了,活动结束了机器立马可以释放掉,现在云平台都很方便。

     

    但是有个问题,你的数据库MySQL能抗住2万的并发请求吗?

     

    如果你基于MySQL来实现核心的抽奖业务逻辑,40个Tomcat部署的抽奖服务频繁对MySQL进行增删改查,这一个MySQL实例也是很难抗住的。

     

    所以此时还得把MySQL给替换成Redis,通常这种场景下,建议是基于Redis来实现核心的业务逻辑。

     

    Redis单机抗2万并发那是很轻松的一件事情,所以在这里又需要做进一步的优化。如下图:

     

     

    7、发放礼品环节进行限流削峰

    接着问题又来了,假设抽奖服务在2万请求中有1万请求抽中了奖品,那么势必会造成抽奖服务对礼品服务调用1万次。

     

    礼品服务假设也是优化后的Tomcat,可以抗500并发,难道礼品服务也要去部署20台机器吗?

     

    其实这是没必要的,因为抽奖之后完全可以让礼品服务在后台慢慢的把中奖的礼品给发放出去,不需要一下子就立马对1万个请求完成礼品的发放逻辑。

     

    所以这里可以在抽奖服务和礼品服务之间,引入消息中间件,进行限流削峰

     

    也就是说,抽奖服务把中奖信息发送到MQ,然后礼品服务假设就部署两个Tomcat,慢慢的从MQ中消费中奖消息,然后慢慢完成1完礼品的发放就可以了。

     

    假设两个礼品服务实例每秒可以完成100个礼品的发放,那么1万个礼品也就是延迟100秒发放完毕罢了。

     

    也就是你抽奖之后,可能过了一两分钟,会看到自己的礼品发放的一些物流配送的进度之类的。

     

    而且礼品服务可能需要在MySQL数据库中做很多增删改查的操作,比如插入中奖纪录,然后进行礼品发货等等。

     

    此时因为礼品服务就2个Tomcat实例,所以对MySQL的并发读写不会太高,那么数据库层面也是可以抗住的。

     

    整个过程,如下图所示:

     

     

    8、系统架构设计总结

    其实对于商品秒杀、抽奖活动、抢红包类的系统而言,架构设计的思路很多都是类似的,核心思路都是对于这种瞬时超高流量的系统,尽可能在负载均衡层就把99%的无效流量拦截掉

     

    然后在1%的流量进入核心业务服务后,此时每秒并发还是可能会上万,那么可以基于Redis实现核心业务逻辑 ,抗住上万并发。

     

    最后对于类似秒杀商品发货、抽奖商品发货、红包资金转账之类的非常耗时的操作,完全可以基于MQ来限流削峰,后台有一个服务慢慢执行即可。

    展开全文
  • 在不同游戏中,经常有各种各样抽奖环节,比如每次登入游戏的免费抽奖,卡牌游戏中的抽不同颜色的卡牌英雄,不同品质的武器抽奖,十连抽等等。今天给大家讲解一下,比较传统的抽奖方式,就是转转盘的抽奖,包含抽奖...

    ****************************************************************************

    时间:2015-02-01

    作者:Sharing_Li

    转载出处:http://blog.csdn.net/sharing_li/article/details/43268877

    ****************************************************************************

     

         

            在不同游戏中,经常有各种各样抽奖的环节,比如每次登入游戏的免费抽奖,卡牌游戏中的抽不同颜色的卡牌英雄,不同品质的武器抽奖,十连抽等等。今天给大家讲解一下,比较传统的抽奖方式,就是转转盘的抽奖,包含抽奖界面动画的设计和抽奖概率的设计。由于内容稍微有点多,所以分两篇进行讲解,本篇先介绍转盘抽奖方式的界面设计。废话不多说,先上效果图:

     

    (。。。亮瞎了我的钛合金眼!)

    来看看大致的功能需求有哪些:

        1、一个转盘,一个指针,可以是转盘转,也可以是指针转,本篇是转盘转。

        2、转盘在转的时候,速度是先快后慢,然后停止。

        3、转盘在转的时候,各种粒子效果的动画,其中包括圆环状的闪光星星,还有以椭圆轨迹运动的小彗星。

        4、抽中奖品后,弹出抽中奖品的动画。

     

    看完功能需求,再来看看代码怎么写:

    先看简单的初始化代码:

    bool LotteryTurnTest::init()
    {
    	if (!Layer::init())
    	{
    		return false;
    	}
    
    	auto bgSize = Director::getInstance()->getWinSize();
    
    	m_pBg = Sprite::create("LotteryTurn/bg_big.png");
    	m_pBg->setPosition(Vec2(bgSize.width / 2,bgSize.height / 2));
    	this->addChild(m_pBg);
    
    	//添加标题
    	auto plabel = Label::createWithTTF("LotteryTurnTest","fonts/Marker Felt.ttf",30);
    	plabel->setPosition(Vec2(bgSize.width / 2,bgSize.height * 0.9));
    	m_pBg->addChild(plabel);
    
    	//添加转盘
    	m_turnBg = Sprite::create("LotteryTurn/turn_bg.png");
    	m_turnBg->setPosition(Vec2(bgSize.width / 2,bgSize.height / 2));
    	m_pBg->addChild(m_turnBg);
    
    	//添加指针
    	auto arrNor = Sprite::create("LotteryTurn/turn_arrow.png");
    	auto arrSel = Sprite::create("LotteryTurn/turn_arrow.png");
    	arrSel->setColor(Color3B(190,190,190));
    	m_turnArr = MenuItemSprite::create(arrNor,arrSel,CC_CALLBACK_1(LotteryTurnTest::onBtnCallback,this));
    	m_turnArr->setPosition(Vec2(bgSize.width / 2,bgSize.height * 0.557));
    	m_turnArr->setScale(0.7);
    	auto pMenu = Menu::createWithItem(m_turnArr);
    	pMenu->setPosition(Vec2::ZERO);
    	m_pBg->addChild(pMenu);
    
    	//添加中奖之后的简单界面
    	auto awardLayer = LayerColor::create(Color4B(0,0,0,100));
    	awardLayer->setPosition(Point::ZERO);
    	awardLayer->setTag(100);
    	m_pBg->addChild(awardLayer,10);
    	awardLayer->setVisible(false);
    
    	return true;
    }
    

     

    点击按钮,获取一个随机的旋转角度,转盘开始转,注意的是,转盘在转的时候,按钮要被设置成无效状态,以免多次点击。

    //防止多次点击
    	m_turnArr->setEnabled(false);
    
    	srand(unsigned(time(NULL)));
    	float angleZ = rand() % 720 + 720;
    	auto pAction = EaseExponentialOut::create(RotateBy::create(4,Vec3(0,0,angleZ)));
    	m_turnBg->runAction(Sequence::create(pAction,CallFunc::create(CC_CALLBACK_0(LotteryTurnTest::onTurnEnd,this)),NULL));

    这里,我们用的EaseExponentialOut来控制转盘旋转的速度。

    当然,转盘在转的时候,各种粒子效果开始行动啦,这里放到文章后面讲解,先看看中奖之后的动画:

    //弹出抽中奖品
    	((LayerColor *)m_pBg->getChildByTag(100))->setVisible(true);
    	auto award = Sprite::create("LotteryTurn/award.png");
    	award->setAnchorPoint(Vec2(0.5,0));
    	award->setPosition(Vec2(m_pBg->getPositionX(),m_pBg->getPositionY() * 2));
    	this->addChild(award);
    	auto bounce = EaseBounceOut::create(MoveBy::create(2,Vec2(0,-m_pBg->getPositionX() * 2)));
    	award->runAction(Sequence::createWithTwoActions(bounce,CallFuncN::create([=](Node * node){
    					award->removeFromParentAndCleanup(true);
    					((LayerColor *)m_pBg->getChildByTag(100))->setVisible(false);
    					m_turnArr->setEnabled(true);
    	})));


    再来看看咱们转盘中的粒子效果,有两种,第一种圆环的星星闪烁效果比较简单,设置下离中心的距离就好了,这里主要讲解以椭圆轨迹旋转的小彗星粒子效果。

    既然以椭圆为轨迹,其实也就是实时更新下粒子的位置,但是椭圆的坐标怎么计算呢?想必部分人都忘记了吧(我也忘记了。。。),直接去问度娘吧:

    咱们椭圆的中心即是转盘的中心,所以是一个标准的椭圆方程:

    ,对应的参数方程就是:

    那么答案就出来啦,只要我们实时改变参数φ的值,那么椭圆上的坐标就会实时更新。知道原理了,我们再来看看怎么设计这样一个椭圆类。既然沿椭圆轨迹运动,那么为什么不把这一种动作设计成跟cocos2dx引擎中的动作Action一样呢?在使用的时候,我们只需要调用runAction就可以了。我们可以参考cocos2dx引擎动作类的设计。

    来看头文件:

    #ifndef _ELLIPSEBY_H_
    #define _ELLIPSEBY_H_
    
    #include "cocos2d.h"
    
    USING_NS_CC;
    
    #define PI 3.14159
    
    //椭圆的参数信息
    struct EllipseConfig 
    {
    	//椭圆a的长度
    	float ellipseA;
    	//椭圆b的长度
    	float ellipseB;
    	//椭圆的中心坐标
    	Vec2 cenPos;
    	//是否逆时针旋转
    	bool isAntiClockwise;
    	//目标开始旋转的位置,默认位置是在椭圆长轴右方,即值为0
    	float startAngle;
    	//目标自身的角度
    	float selfAngle;
    };
    
    class EllipseBy : public ActionInterval
    {
    public:
    	EllipseBy();
    	~EllipseBy();
    
    	//初始化函数,参数t为持续时间,config为椭圆参数
    	static EllipseBy * create(float t,const EllipseConfig & config);
    	bool initWithDuration(float t,const EllipseConfig & config);
    
    	//每帧更新当前椭圆坐标
    	virtual void update(float time) override;
    	//在动作开始前调用
    	virtual void startWithTarget(Node *target) override;
    	//动作的拷贝
    	virtual EllipseBy * clone() const override;
    	//动作的逆序
    	virtual EllipseBy * reverse() const override;
    
    protected:
    	//获得椭圆上当前点坐标
    	inline Vec2 getPosWithEllipse(float t)
    	{
    		float angle = 2 * PI * ((m_config.isAntiClockwise ? t : (1 - t)) + m_config.startAngle / 360);
    		return Vec2(m_config.ellipseA * cos(angle),m_config.ellipseB * sin(angle));
    	}
    
    private:
    	EllipseConfig m_config;
    };
    
    #endif

    我们定义了一个椭圆参数的结构体EllipseConfig,前面4个比较好理解,后面2个:startAngle是开始旋转粒子出现的位置,值为角度值。比如下面例图所示:

    selfAngle是指把整个椭圆当成一个整体,这个整体的角度,类似于精灵的rotation属性。比如下面例图所示:

     接着再来看看从父类继承来的三个函数:startWithTarget、clone、reverse

    startWithTarget是用来设置是谁要执行动作,在动作开始前调用;后面两个,一个是动作的拷贝,一个是动作的逆序,因为在父类中是纯虚函数,所以要继承实现。

    再来看看函数getPosWithEllipse,这个是利用椭圆的参数方程,获得当前目标所处椭圆上的位置。因为要不停的调用,所以声明为内联函数。

    最后看看cpp文件的部分实现代码:

    EllipseBy * EllipseBy::clone() const
    {
    	auto pAction = new EllipseBy();
    	pAction->initWithDuration(_duration, m_config);
    	pAction->autorelease();
    	return pAction;
    }
    
    EllipseBy * EllipseBy::reverse() const
    {
    	EllipseConfig resConfig = m_config;
    	resConfig.isAntiClockwise = !m_config.isAntiClockwise;
    	return EllipseBy::create(_duration, m_config);
    }
    
    void EllipseBy::startWithTarget(Node *target)
    {
    	ActionInterval::startWithTarget(target);
    }
    
    void EllipseBy::update(float time)
    {
    	if (_target)
    	{
    		Vec2 curPos = this->getPosWithEllipse(time);
    		float tmpAngle = m_config.selfAngle / 180 * PI;
    		float newX = curPos.x * cos(tmpAngle) + curPos.y * sin(tmpAngle);
    		float newY = curPos.y * cos(tmpAngle) - curPos.x * sin(tmpAngle);
    		_target->setPosition(m_config.cenPos + Vec2(newX,newY));
    	}
    }

    其中最重要的部分就是update函数啦,getPosWithEllipse获得的坐标curPos是selfAngle为0时的坐标,如果我们设置了椭圆自身的角度,就要调整下curPos的坐标。有以下公式:

    如果椭圆自身旋转了β,即selfAngle = β 那么之后的坐标是:newX = xcosβ + ysinβ,newY = ycosβ - xsinβ

    这里给大家简单的分析一下公式,先看图:


    这里黑色的椭圆是没有设置selfAngle时的样子,当设置selfAngle为β后,就变成蓝色的椭圆。由于两个椭圆的中心都是圆心,所以椭圆上同一位置上的点到圆心的距离以一样,也就是上图中红线和绿线的长度相等,那么利用勾股定理,就是下面:

    a^2 + b^2 = x^2 + y^2 = x^2 * 1 + y^2 * 1

    因为cos& * cos& + sin& * sin& = 1,所以:

    上面公式 = x^2 * (cos(β)^2 + sin(β)^2) + y^2 * (cos(β)^2 + sin(β)^2)

    然后分解合并,就可以得到下面的公式啦:

    a^2 + b^2 = (xcosβ + ysinβ)^2 + (ycosβ - xsinβ)^2

    所以:a = xcosβ + ysinβ,b = ycosβ - xsinβ

     

    最后,我们只需要如下调用,就可以像使用引擎的动作一样:

    //椭圆旋转
    	EllipseConfig config;
    	config.ellipseA = 100;
    	config.ellipseB = 50;
    	config.cenPos = m_turnBg->getPosition();
    	config.isAntiClockwise = true;
    	config.startAngle = 0;
    	config.selfAngle = 45;
    
    	m_pElliRtt_1->runAction(RepeatForever::create( EllipseBy::create(2.5,config)));


    到这里,本篇上部分内容已经讲解完了,下一篇将讲解抽奖概率的设计。

     资源下载处:http://download.csdn.net/detail/sharing_li/8414387

    本篇第二篇地址:http://blog.csdn.net/sharing_li/article/details/43405569

    展开全文
  • 抽奖小程序的设计

    千次阅读 2010-04-01 09:48:00
    csdn俱乐部的活动中准备了抽奖的小环节为了娱乐一下,写了一个小程序;从txt文件中导入信息列表,并添加到listbox 控件中OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "文本文件(*.txt)|*.txt"; int i =...

    csdn俱乐部的活动中准备了抽奖的小环节为了娱乐一下,写了一个小程序;

    从txt文件中导入信息列表,并添加到listbox 控件中

    OpenFileDialog ofd = new OpenFileDialog();
                ofd.Filter = "文本文件(*.txt)|*.txt";
                int i = 0;
                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    FileStream fs = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read);
                    StreamReader sr = new StreamReader(fs);
                    string recorde;
                    while (sr.Peek() != -1)//循环写入
                    {
                        recorde = sr.ReadLine();
                        if (recorde.Length > 0)
                        {
                            if (i < 10)
                            {
                                this.listBoxCollection.Items.Add("0" + i + "|" + recorde);
                            }
                            else
                            {
                                this.listBoxCollection.Items.Add(i + "|" + recorde);
                            }
                        }
                        i++;
                    }
                }

    private int[] GetRandom(int number)  //得到不重复的数组
            {
                int[] sArray = new int[number];
                Random ra = new Random();
                for (int i = 0; i < number; i++)
                {
                    int  m=ra.Next(0,this.listBoxCollection.Items.Count);
                    this.listBoxCollection.SelectedIndex = m;
                     Thread.Sleep(1200);

                    int  mNum=0;
                    foreach (Control lb in this.panelResult.Controls)
                    {
                        if (lb.GetType().ToString().Trim() == "System.Windows.Forms.Button")
                        {
                            if (Convert.ToInt32(lb.Tag) == m)
                            {
                                mNum++;
                            }
                        }
                    }
                    for (int x = 0; x < i; x++)
                    {
                        if (sArray[x] == m)
                        {
                            mNum++;
                        }
                    }
                    if (mNum > 0)
                    {
                        i--;
                    }
                    else
                    {
                        sArray[i] = m;
                    }
                }
                return sArray;
            }

     

     

    动态加入相就的按钮控件

    int[] sArray = GetRandom(Convert.ToInt32(textBoxSum.Text));
                        Button[] lb = new Button[sArray.Length];
                        for (int n = 0; n < lb.Length; n++)  //根据选取随机数得到相应的值
                        {
                            lb[n] = new Button();
                            panelResult.Controls.Add(lb[n]);
                            if (colornum % 2 == 0)
                            {
                                lb[n].BackColor = Color.Brown;
                            }
                            else
                            {
                                lb[n].BackColor = Color.Black;
                            }
                            lb[n].ForeColor = Color.White;
                            lb[n].Text = GetLine(sArray[n]);
                            lb[n].Tag = sArray[n].ToString();
                            //lb[n].Font = new Font("宋体", 10F, System.Drawing.FontStyle.Regular);
                            lb[n].BringToFront();
                            lb[n].AutoSize = true;
                            lb[n].TextAlign = ContentAlignment.MiddleLeft;
                            lb[n].Left = 20;
                            lb[n].Top = (n + cursor) * 30;

                        }

     

     

    由于有及时响应所以查询时间长了一些

    下载网址   http://download.csdn.net/source/2190746

    展开全文
  • 本文给大家分享一个之前经历过的抽奖系统的流量削峰架构的设计方案。 抽奖、抢红包、秒杀,这类系统其实都有一些共同的特点,那就是在某个时间点会瞬间涌入大量的人来点击系统,给系统造成瞬间高于平时百倍、千倍...
  • 设计一个抽奖系统

    千次阅读 2016-11-08 21:57:31
    System.out.println("请进入幸运抽奖环节!"); System.out.println("请输入会员卡号:"); int b=input.nextInt(); int[] arr=new int[5]; for (int k=0;k;k++){ Random random=...
  • 注意,马上进入 「抽奖环节了 年终总结会汇报结束,老板临时提议将拿出几千块现场抽奖,规则很简单:100% 中奖,奖品大小全凭手气(100-500 元) 刹那间,经历一天汇报,正处于昏昏欲睡状态中的同事们,全体满血...
  • 目录 1.抽奖系统的背景引入 2.结合具体业务需求分析抽奖系统 ...7.发放礼品环节进行限流削峰 8.系统架构设计总结 1、抽奖系统的背景引入 本文给大家分享一个之前经历过的抽奖系统的流量削峰...
  • 周末的时候公司到一个学校做一场活动,活动中间有抽奖环节,抽奖是按照会议室坐位编号来的,就是把编有座位号的纸条折起来放在一个抽奖箱中。如何快速设计并打印出这些编号呢?这个问题难倒了我们美女设计。她是用...
  • 课堂上做的作业 设计首页点击进入抽奖环节,分为1、2、3等奖 获奖人数可设定 简单容易修改 图片换一下即可改为年会抽奖、班级抽奖等抽奖活动
  • 本文给大家分享一个之前经历过的抽奖系统的流量削峰架构的设计方案。 抽奖、抢红包、秒杀,这类系统其实都有一些共同的特点,那就是在某个时间点会瞬间涌入大量的人来点击系统,给系统造成瞬间高于平时百倍、千倍...
  • canvas设计刮刮卡 先随机生成一个奖项,然后通过 globalCompositeOperation 和之前学到的涂鸦知识,让显示重叠的透明处理。最后在刮到一定面积的的时候再自动把剩下的残渣自动清除。 主要代码 ctx....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,900
精华内容 760
关键字:

抽奖环节设计