28. července 2008

Detekce vyběhnutí kursoru myši ze Stage Flash Animace v AS 2

(doplněno)

Řešil jsem problém, jak v ActionScriptu 2.0 detekovat událost, kdy myš opustí Stage, tzn. že uživatel přejede s kursorem mimo oblast Flash animace někam do okolní html stránky. Chtěl jsem se obejít bez programování nějakých pomocných funkčností mimo vlastní Flash animaci, tedy bez JavaScriptu apod. V ActionScriptu 3.0 je řešení jednoduché, jak možné se dočíst v článku: Zarovnání objektů, detekce opuštění scény na flash.cz. V ActionScriptu 2.0 v3ak nejsou k dispozici odpovídající nástroje. Jak tedy "na to"?

Zdánlivě jednoduché, leč ne vždy funkční řešení:

Pro detekci opuštění Stage kursorem myši se nabízí zdánlivě jednoduché řešení: do Flash animace umísti neviditelný obdélník, který bude od krajů Stage vzdálen na všech stranách o mezeru, jejíž šířku zvolíme nějak rozumně. Pak stačí jen testovat kolizi polohy kursoru myši s tímto obdélníkem. Je-li kursor myši s obdélníkem v kolizi, je zřejmé, že uživatel se myší pohybuje nad Flash animací. Je-li kursor mimo obdélník, tak je uživatel na okraji animace, takže na nic uvnitř animace nemíří, nebo ji kursorem dokonce opustil. Proto je šířku mezery nutno zvolit rozumně, na 10-20% šířky Stage Flash Animace.

Funguje to skvěle. Tedy až do okamžiku, kdy uživatel odjede z Flash animace příliš rychle. Musíme si totiž uvědomit dvě skutečnosti. Za prvé, že testování kolize kursoru myši s obdélníkem uvnitř probíhá pouze jednou za časový interval, který je daný rychlostí přehrávání snímků Flash animace. Délka intervalu se v praxi pohybuje v řádu desetin až setin sekundy, což je na počítačové poměry dlooooouhá doba, během které může kursor myši poskočit o stovky pixelů. Za druhé, jakmile kursor myši opustí Stage Flash Animace, zůstane v proměnných _xmouse a _ymouse, vracejících aktuální polohu kursoru myši, "viset" poslední známá poloha myši před opuštěním Stage a tato poloha by mohla být stále "kolizní".

Co s tím? Zeptal jsem se v diskusi na flash.cz a od Petka jsem dostal tip, jak by to asi mohlo jít. Ten tip vedl k řešení, které se zatím ukazuje jako plně funkční. Díky Petko!

Tedy, jak na to:

Zapomeňme na testování kolize s obdélníkem. Nebudeme to potřebovat. Budeme si naopak do nějaké proměnné typu pole ukládat současnou [1] a předchozí [0] polohu kursoru myši. Budeme to dělat vždy v rámci události onEnterFrame, tedy každý okamžik, kdy Flash animace snímá polohu kursoru myši. Například takto:

_root.mouseX = new Array(0, 0);
_root.mouseY = new Array(0, 0);
_root.onEnterFrame = function()
{
_root.mouseX[0] = _root.mouseX[1];
_root.mouseX[1] = _root._xmouse;
_root.mouseY[0] = _root.mouseY[1];
_root.mouseY[1] = _root._ymouse;
};

Kdykoli pak nastane událost onEnterFrame vypočteme možnou budoucí [2] polohu kursoru a jakmile nám tato předpovězená poloha vyskočí ze Stage Flash animace, víme, že uživatel opustil Flash. Tento výpočet provedeme vždy před tím, než nastavíme nové hodnoty [0] a [1], takže:

_root.mouseX = new Array(0, 0);
_root.mouseY = new Array(0, 0);
_root.mouseOutOfStage = false;
_root.onEnterFrame = function()
{
_root.mouseX[2] = _root.mouseX[1] + (_root.mouseX[1] - _root.mouseX[0]);
_root.mouseY[2] = _root.mouseY[1] + (_root.mouseY[1] - _root.mouseY[0]);
if ((_root.mouseX[2] > Stage.width or _root.mouseX[2] < 0) or (_root.mouseY[2] > Stage.height or _root.mouseY[2] < 0))
{
_root.mouseOutOfStage = true;
}
_root.mouseX[0] = _root.mouseX[1];
_root.mouseX[1] = _root._xmouse;
_root.mouseY[0] = _root.mouseY[1];
_root.mouseY[1] = _root._ymouse;
};

Jednoduché že? Jenže co se situací, kdy uživatel vykoná uvnitř Flash animace pohyb s dost velkým krokem kursoru myši, aby to uvedený script vyhodnotil, jako úmysl opustit oblast Stage Flash animace, ale zastaví jej ještě uvnitř? A jak zahrnout funkčnost, která by nastavila zpětně _root.mouseOutOfStage = false;, když dojde k takové situaci, resp. i tehdy, když se uživatel nad oblast Stage kursorem opět vrátí?

Naštěstí je v ActionScriptu 2.0 k dispozici detekce události, jestli se kursor myši pohnul, či nikoli, a ta nám pomůže vše velmi jednoduchým způsobem vyřešit.

Výsledné řešení detekce opuštění Stage Flash Animace kursorem myši tedy vypadá takto:

_root.mouseX = new Array(0, 0, 0);
_root.mouseY = new Array(0, 0, 0);
_root.mouseOutOfStage = false;
_root.onEnterFrame = function()
{
_root.mouseX[2] = _root.mouseX[1] + (_root.mouseX[1] - _root.mouseX[0]);
_root.mouseY[2] = _root.mouseY[1] + (_root.mouseY[1] - _root.mouseY[0]);
if ((_root.mouseX[2] > Stage.width or _root.mouseX[2] < 0) or (_root.mouseY[2] > Stage.height or _root.mouseY[2] < 0))
{
_root.mouseOutOfStage = true;
}
_root.mouseX[0] = _root.mouseX[1];
_root.mouseX[1] = _root._xmouse;
_root.mouseY[0] = _root.mouseY[1];
_root.mouseY[1] = _root._ymouse;
};
_root.onMouseMove = function()
{
_root.mouseOutOfStage = false;
};

Toto řešení je založeno na předpokladu, který bude zřejmě oprávněný: když už uživatel z rychlého pohybu zabrzdí, takže Stage Flash animace přeci jen na poslední chvíli neopustí, ač by se to z predikce pohybu kursoru myši mohlo zdát, nestihne "zarazit" pohyb kursoru náhle. Než úplně zastaví, udělá kursorem myši ještě alespoň malý "krůček", který nás díky události onMouseMove opět "vrátí do hry".

Pomocí výpočtu předpokládané budoucí polohy bychom zpomalení pohybu mohli zjistit také a pomocí dalších podmínek if ošetřit, ovšem takto je výsledný script přeci jen jednodušší a navíc funguje i pro detekci opětovného najetí kursoru nad Stage.

Doplnění

Při testování se ukázalo, že uvedené řešení nevyhoví v případě, kdy uživatel opustí Stage Flash animace velmi pomalým pohybem myši. Tento problém však můžeme lehce odstranit tak, že se v ActionScriptu nebudeme testovat překročení samotných hranic Stage, ale budeme testovat překročení hranic obdélníkové oblasti odsazené od okraje Stage rovnoměrně po celém obvodu o určitou malou mezeru. Upravený script pak může vypadat takto:

_root.mouseX = new Array(0, 0, 0);
_root.mouseY = new Array(0, 0, 0);
_root.mouseOutOfStage = false;
_root.stagePadding = 2;
_root.mouseXmin = _root.stagePadding;
_root.mouseXmax = Stage.width - _root.stagePadding;
_root.mouseYmin = _root.stagePadding;
_root.mouseYmax = Stage.height - _root.stagePadding;
_root.onEnterFrame = function()
{
_root.mouseX[2] = _root.mouseX[1] + (_root.mouseX[1] - _root.mouseX[0]);
_root.mouseY[2] = _root.mouseY[1] + (_root.mouseY[1] - _root.mouseY[0]);
if ((_root.mouseX[2] < _root.mouseXmin or _root.mouseX[2] > _root.mouseXmax) or (_root.mouseY[2] < _root.mouseYmin or _root.mouseY[2] > _root.mouseYmax))
{
_root.mouseOutOfStage = true;
}
_root.mouseX[0] = _root.mouseX[1];
_root.mouseX[1] = _root._xmouse;
_root.mouseY[0] = _root.mouseY[1];
_root.mouseY[1] = _root._ymouse;
};
_root.onMouseMove = function()
{
_root.mouseOutOfStage = false;
};

1 komentář:

  1. Funguje naproto báječně... poradil jsem si s tím i já a to jsem začátečník co se flashu týče...
    děkuji za pomoc, moc mi to pomohlo

    OdpovědětSmazat

Chovejte se zde, prosím, jako v místnosti plné lidí, na kterých Vám záleží. Děkuji :o)