当有人点击我的Flash活动时,有时会触发大量计算。如果用户再次单击(例如双击),则在繁重计算完成后,此事件将排队并调度。如果我多次点击,问题就会复杂化 - 如果点击速度足够快,排队繁重的计算会在十秒后完成,每次clickEvent都会慢慢地运出下一个任务。
我有两个问题。
首先:如何获得点击发生的准确时间?在下面的示例中,我发现快速点击事件很快就会发生点击(sp?)。
第二:收集每一次点击的好设计模式是什么?我想我应该
推迟任何计算直到下一个EnterFrame事件,但如果有人在EnterFrame事件的计算过程中点击......那么,我遇到了同样的问题!
我认为将重度计算分解为伪线程是另一种解决方案,但是根据处理器的速度,很难找到粒度。
在第一次点击后添加标记以忽略下次点击...但此解决方案不允许我跟踪用户在被锁定时尝试执行的操作。第一个问题的解决方案是我在这里需要的。
感谢您的任何建议。以下是一些示例代码来演示此问题:
package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
public class clicky extends Sprite
{
private static var _lastTraceTime:Number = new Date().getTime();
private var _sp:Sprite;
private var _state1:Boolean;
public function clicky( ):void
{ super( );
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_state1 = true;
_sp = new Sprite( );
addChild( _sp );
_sp.graphics.beginFill( 0xFF00AA, 1 );
_sp.graphics.drawRect( 10, 10, 100, 100 );
_sp.graphics.endFill( );
_sp.addEventListener(MouseEvent.MOUSE_DOWN, mDnCb, false, 0, true);
}
private function mDnCb( evt:MouseEvent ):void
{ traceTime( "click" );
_state1 = !_state1;
var c:uint = 0xFF0000;
if (_state1)
{ c = 0x00FFAA;
}
paintThatRect( c );
killTime( );
}
private function paintThatRect( c:uint ):void
{
_sp.graphics.beginFill( c, 1 );
_sp.graphics.drawRect( 10, 10, 100, 100 );
_sp.graphics.endFill( );
}
private function killTime( ):void
{ var r:Rectangle = new Rectangle( 0, 0, 100, 100 );
for (var i:uint = 0; i < 500000; i++)
{
var t:Rectangle = new Rectangle( i, i, i, i );
if (t.intersects(r) || r.containsRect(t) || t.containsRect(r))
{ r = t.union(r);
}
}
}
public static function traceTime( note:String ):Number
{ var nowTime:Number = new Date().getTime();
var diff:Number = (nowTime-_lastTraceTime);
trace( "[t" + diff + "] " + note );
_lastTraceTime = nowTime;
return diff;
}
}
}
答案 0 :(得分:1)
我过去的一个项目涉及快速按下按钮,所以我做了一项调查,看看我通常期望玩家有多快。我能找到的最快的点击器每秒不能超过10次。
这样做的结果是您可以假设帧速率总是快于点击率。在几乎所有情况下,帧速率为10fps或更低是不可接受的。设置程序,以便将检测到的任何火灾事件添加到队列中。每个帧只处理队列中的一个火灾事件。
有时会触发很多计算。
不要那样做。
如果一个过程需要超过十分之一秒才能完成,那么每秒执行它的次数绝对不可能超过10次。 (至少在AS3中)你所做的任何和所有处理都必须设计成不会严重延迟下一帧。它不仅看起来很不稳定,而且你会开始出现饥饿问题。
如何获得点击发生的准确时间?
保持良好的帧速率。
答案 1 :(得分:0)
如果播放器忙于运行killTime方法,您将无法获得单击事件的“准确”时间戳。当您的方法阻止动作脚本代码执行时,这些事件将不会及时处理(或者更确切地说,将调用您的处理程序)。
做你想做的事情(或者我认为你想要做的事情)的唯一方法是将重型加工部件分解成更小的部分,有点像绿线,如你所说。如果您使用google actionscript +绿色线程,有很多关于如何实现此功能的示例。有些人为问题添加了更多的结构,有些则更加简单,但他们都归结为相同的基本想法。处理块,检查您是否超过某个阈值;当你/如果你这样做,从你的功能返回并等待再次被召唤从你离开的地方接你。您可以使用计时器或订阅EnterFrame。
根据您的游戏情况,这可能会解决您的问题或将其移至其他位置。如果Gunslinger47的这一点适用于你的游戏,这种方法将无法真正起作用:
如果一个过程需要十分之一以上 一秒钟完成,简单来说 不可能执行超过10 每秒次数
然而,这是一个可能的实现草图,假设情况并非如此。我从示例代码中删除了一些内容并添加了其他内容。我认为代码很容易理解,但无论如何我只会解释一下。
我正在使用内部类Context
来跟踪进度,我还使用它来保存导致进程运行的点击的时间戳。您可以使用它来存储与每个进程相关的其他数据。每次用户单击该按钮时,都会创建其中一个上下文对象并将其推送到队列中。如果队列中只有一个项目(我们刚添加的项目),我们立即启动该过程并设置定时器/间隔。如果队列中有更多的东西,这意味着已经有一个进程在运行,所以我们暂时不做任何事情。
每次执行计时器机制时,它都会从上一次迭代中的位置开始拾取。先前的状态存储在队列的第一项中。我正在存储一个计数器,但你可能需要在那里保存其他数据。在这个函数中,我正在检查是否超过了时间阈值。这涉及调用getTimer(),它比使用Date对象更轻量级,但您可能不希望每次迭代都调用它。你可以只检查每N次循环的时间,但这取决于你。此外,这个最大时间有点任意。你应该稍微调整一下,虽然对于20 FPS swf来说20 ms似乎是合理的(假设代码和渲染每帧有50毫秒的理论值)。
当进程完成时,队列被移位。然后我们检查是否还有待处理的项目。如果有,我们只是让事情再次运行。否则,我们停止删除EnterFrame。
请注意使用这种“绿色线程”方法会有一些开销,因为只需一次运行相同的代码就会明显加快。所以它并不完美,但它通常是在处理过程中保持应用可用的唯一方法。
package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
import flash.events.Event;
import flash.utils.getTimer;
public class clicky extends Sprite {
private static var _lastTraceTime:Number = new Date().getTime();
private var _sp:Sprite;
private var _queue:Array;
private const MAX_TIME:int = 20;
public function clicky( ):void {
super( );
stage.align=StageAlign.TOP_LEFT;
stage.scaleMode=StageScaleMode.NO_SCALE;
_queue = [];
_sp = new Sprite( );
addChild( _sp );
_sp.graphics.beginFill( 0xFF00AA, 1 );
_sp.graphics.drawRect( 10, 10, 100, 100 );
_sp.graphics.endFill( );
_sp.addEventListener( MouseEvent.MOUSE_DOWN, mDnCb, false, 0, true );
}
private function mDnCb( evt:MouseEvent ):void {
_queue.push(new Context(new Date()));
if(_queue.length == 1) {
initProcess();
}
}
private function initProcess():void {
trace("initProcess");
killTime();
addEventListener(Event.ENTER_FRAME,run);
}
private function processDone():void {
trace("processDone, " + _queue[0].clickTime);
_queue.shift();
if(_queue.length == 0) {
removeEventListener(Event.ENTER_FRAME,run);
}
}
private function run(e:Event):void {
killTime();
}
private function paintThatRect( c:uint ):void {
_sp.graphics.beginFill( c, 1 );
_sp.graphics.drawRect( 10, 10, 100, 100 );
_sp.graphics.endFill( );
}
private function killTime():void {
var r:Rectangle=new Rectangle(0,0,100,100);
var initTime:int = getTimer();
var runningTime:int = 0;
var loops:int = 500000;
var ctx:Context = _queue[0];
for(var i:int = ctx.i; i < loops; i++) {
var t:Rectangle=new Rectangle(i,i,i,i);
if (t.intersects(r)||r.containsRect(t)||t.containsRect(r)) {
r=t.union(r);
}
runningTime = getTimer() - initTime;
if(runningTime >= MAX_TIME) {
break;
}
}
ctx.i = i;
if(i == loops) {
trace(i);
processDone();
}
}
}
}
class Context {
public var i:int = 0;
public var clickTime:Date;
public function Context(clickTime:Date) {
this.clickTime = clickTime;
}
public function reset():void {
i = 0;
}
}