PDA

Volledige versie bekijken : KeySequence class & example


Mediamonkey
%Europe/Berlin %914 %2005, 22:56
Hey luitjes.
Ik had nog een beginnetje liggen van een class in ontwikkeling, die heb ik vanavond maar even in een uurtje afgemaakt. Voor wie ongeduldig op m'n tutorial over webservices zit te wachten: sorry.. prioriteiten he ;)

De KeySequence class is bedoeld om gemakkelijk een woord in te kunnen typen en daar een functie aan te koppelen die wordt getriggered zodra het woord af is. Ik gebruik 'm zelf om stiekeme eastereggs (verstopt grapje in een applicatie) in te bouwen. Wanneer je bijvoorbeeld "Mediamonkey" intypt komt er een animatie in beeld met mijn naam. Maar ik kan me ook wel wat nuttigers voorstellen. :D

import mx.events.EventDispatcher;
import mx.utils.Delegate;

class KeySequence {

private var keyListener:Object;
private var sequences:Array;
private var selectedSequence:Number;
private var pointer:Number;

public var addEventListener:Function;
public var removeEventListener:Function;
public var dispatchEvent:Function;
public var dispatchQueue:Function;

public function KeySequence() {
EventDispatcher.initialize(this);

sequences = new Array();
pointer = 0;

keyListener = new Object();
Key.addListener(keyListener);
keyListener.onKeyDown = Delegate.create(this, setSequenceKey);
}

public function addSequence(str:String, callback:Function):Void {
var seq:Array = new Array();
seq.callback = callback;
for (var i=0; i<str.length; i++) {
seq.push(str.charAt(i).toUpperCase());
}
sequences.push(seq);
}

private function setSequenceKey():Void {
var key:String = String.fromCharCode(Key.getCode());
if (!selectedSequence) selectedSequence = getCorrectSequence(key);
var seq = sequences[selectedSequence];
if (seq[pointer++] == key) {
if (seq.length == pointer) {
var returnObject:Object = {type:"complete", target:this , result:seq.join("").toLowerCase()};
dispatchEvent(returnObject);
seq.callback.apply(seq, returnObject);
resetSequence();
}
} else resetSequence();
}

private function getCorrectSequence(key:String):Number {
var i = sequences.length;
while (i--) if (sequences[i][pointer] == key) return i;
return null;
}

private function resetSequence():Void {
delete selectedSequence;
pointer = 0;
}
}

En nu een voorbeeld (plak dit op een frame in je fla-root) :

import mx.utils.Delegate;
import KeySequence;

var myKeySequencer:KeySequence;

init();

function init():Void {
myKeySequencer = new KeySequence();
myKeySequencer.addEventListener("complete", Delegate.create(this, onComplete));
myKeySequencer.addSequence("bart", Delegate.create(this, onBart));
myKeySequencer.addSequence("mediamonkey", Delegate.create(this, onMediamonkey));
}

function onComplete(evt:Object):Void {
trace("KeySequence complete: "+evt.result);
}

function onBart(evt:Object):Void {
trace("onBart method called");
}

function onMediamonkey(evt:Object):Void {
trace("onMediamonkey method called");
}

Als je het gebruik van de Delegate class niet snapt, bekijk dan ThaNarie's topic in dit subforum. Het is een handige manier om methodes door te geven/te koppelen terwijl je het bereik binnen een class behoudt. En als je niet vertrouwd bent met de EventDispatcher... GA HEM UITTESTEN! NU! Ik zweer erbij, hij is zo handig omdat je precies weet wanneer een functie klaar is met z'n proces, waarna je een andere functie kunt aanroepen met het verkregen resultaat. En de combinatie van een EventDispatcher met een Delegate.. OOOHHH *kwijl*! :p

- Mediamonkey -

Tha Narie
%Europe/Berlin %929 %2005, 23:18
LOL heb ik vroeger ook nog eens voor Flashgirl gemaakt:

-----

Stap 1:
Plaats de onderstaande code op het eerste frame van de _root, met deze code krijg je de naam die je intypt, als een rijtje cijfers onder elkaar in het ouput venster.
Deze cijfers zijn de KeyCodes van de toetsen die je intypt, die je later vergelijkt met de toetsen die je op de site intikt.

myListener = new Object();
myListener.onKeyDown = function ()
{
trace (Key.getCode());
}
Key.addListener(myListener);


Stap 2:
Na de juiste keyCodes te hebben ga vervang je de code met de onderstaande code, en zet je de cijfers in de array 'easterEgg'.
Ik heb dit voorbeeld getest met "narie", waarvan de keyCodes ook in de array staan.

easterEgg = [ "78", "65", "82", "73", "69" ];
easterCheck = new Array();

myListener = new Object();
myListener.onKeyDown = function ()
{
if(easterEgg[easterCheck.length] != Key.getCode())
{
easterCheck = new Array();
trace("reset");
}
/**/
if(easterEgg[easterCheck.length] == Key.getCode())
{
easterCheck.push(Key.getCode());
if(easterEgg[easterEgg.length-1] == easterCheck[easterEgg.length-1])
{
trace("klaar");
/* laat hier de easterEgg spelen */
}
}
}
Key.addListener(myListener);


Uitleg :
Als eerste vullen we dus de array met de juiste keyCodes die nodig zijn voor de naam.
Daarna maken we lege array aan, waarin we de (juiste) toetsaanslagen opslaan.
Vervolgens maken we een listener aan die die functie doorloopt als er een toets in wordt gedrukt.

In die functie kijken we als eerst of de toets die je indrukt klopt met de keyCode die het moet zijn.
Dit doen we door te kijken op welke plek de toets in de array moet komen (1e toets op 0e plaats, length van de checkArray, die nog leeg is).
We kijken dan op de 0e plaats in de originele array, en kijken of het de juiste toets is.
Als dit niet het geval is dan wordt de array leeg gemaakt.

Als dit wel de juiste toets is, dan stoppen we deze achteraan de 'check-array'.
Bij de 2e toetsindruk is die arraylengte dus 1, en kijken we op index 1 (2e plek) van de originele array, of deze waarde overeenkomt met de toets die we indrukken..
Op deze manier gaan vullen we de check-array dus met goede waardes, en als er een foute waarde in wordt getoets dan wordt deze leeg gemaakt..

Dit zorgt ervoor dat je in 1 keer achter elkaar de goede toetsen in moet drukken.
Als je bijvoorbeeld halverwege de naam weer de 1e letter indrukt, wordt eerst de array leeg gemaakt (omdat deze daar niet hoort) maar daarna meteen weer op de 1e plek gezet (omdat dit wel de eerste letter is van het woordt) zodat je meteen weer door kan typen.

Er staan 2 traces in de code, bij reset, en bij klaar, deze kan je, als alles werkt, verwijderen.
Op de plek waar 'klaar' staat, moet je de EasterEgg laten spelen.

hopelijk heb je der wat aan ;)

-----

Groot verschil is dat die van MediaMonkey veel beter te gebruiken is, hij met letters ipv KeyCodes werkt, en dat hij AS2 is :p

Mooi stuk code!

Roenes
%Europe/Berlin %783 %2005, 19:48
Net eens rustig de boel doorgenomen en ik moet zeggen dat het een zeer mooi stukje code is! Zit lekker in elkaar en werkt goed! :)

Toch even een paar kleine vraagjes:

public var addEventListener:Function;
public var removeEventListener:Function;
public var dispatchEvent:Function;
public var dispatchQueue:Function;
Wat is de reden dat je deze declareerd? Zie ik niet echt het nut van in :)

- Waar haal jij de initialize functie van de EventDispatcher vandaan? Staat niet in AS-ref genoemd. Of is dit zo'n ongedocumenteerde functie? :)

- Ik heb de EventDispatcher doorgenomen in de AS-ref, maar kun je me toch vertellen wat het nut is van de dispatchEvent in dit stukje code? Want ik zie er het nut niet van in. (eerlijk gezegd zie ik ook niet echt wat ie doet.. :I)
if (seq.length == pointer) {
var returnObject:Object = {type:"complete", target:this , result:seq.join("").toLowerCase()};
dispatchEvent(returnObject);
seq.callback.apply(seq, returnObject);
resetSequence();
}

En voor de rest: nogmaals een mooi stukje code. Kan zeer bruikbaar zijn voor eastereggs, maar ook voor games (denk aan cheats)! :)

Mediamonkey
%Europe/Berlin %816 %2005, 20:36
Buh.. moet ik bij elke topic alle technieken uitleggen? :P
Ha ha ik doe 't graag hoor. Well, here goes.

De EventDispatcher is een class waarbij objecten zich kunnen registreren. Wanneer een class is initialized bevat hij 4 extra methoden waarmee je dit kunt doen: addEventListener, removeEventListener, dispatchEvent en dispatchQueue. Wanneer je bij deze class registreert via addEventListener en een methode vuurt een dispatchEvent af, dan ontvang je een softobject (een object met verzamelde data).

De initialize methode van de EventDispatcher class ziet er als volgt uit:

static function initialize(object:Object):Void {
if (_fEventDispatcher == undefined) {
_fEventDispatcher = new EventDispatcher;
}
object.addEventListener = _fEventDispatcher.addEventListener;
object.removeEventListener = _fEventDispatcher.removeEventListener;
object.dispatchEvent = _fEventDispatcher.dispatchEvent;
object.dispatchQueue = _fEventDispatcher.dispatchQueue;
}

Hij overschrijft daarmee methodes, of als die niet bestaan maakt hij ze aan. Om maar geen compile errors te krijgen declareer ik ze daarom vooraf als public variabelen.

Het grote voordeel is dat je erg makkelijk een soort interface creëert waar andere classes aan kunnen koppelen. Bovendien krijg je alleen de data binnen wanneer een methode dit toestaat, wat parallel kan lopen met andere calculaties (denk aan externe data zoals xml of plaatjes inladen). Meerdere objecten kunnen bij de dispatcher registreren en je hoeft ze niet zelf bij te houden of aan te roepen wat een stukje overhead en complexiteit scheelt.

Daarnaast is de combinatie met de Delegate class erg krachtig om kort en krachtig te interfacen met een class en het resultaat verder door te sturen.

Hier heb je een zeer eenvoudige versie van de implementatie voor een eigen class:
import mx.events.EventDispatcher;

class MyClass {

public var addEventListener:Function;
public var removeEventListener:Function;
public var dispatchEvent:Function;
public var dispatchQueue:Function;

public function MyClass() {
EventDispatcher.initialize(this);
}

public function doeIets():Void {
// hier een flinke calculatie of zo..
var resultaat:Number = 42;
dispatchEvent({type:"complete", target:this , result:resultaat});
}
}

Vervolgens kun je vanuit een andere class, of vanaf de root, hier bij registreren:
// methode 1:
var app = new MyClass();
var myListener = new Object();
app.addEventListener("complete", myListener);

myListener.complete = function(evt:Object) {
trace(this+" "+evt.result); // geeft "undefined 42" (onbekend en het resultaat)
}

// methode 2:
var app = new MyClass();
app.addEventListener("complete", mx.utils.Delegate.create(this, onComplete));

function onComplete (evt:Object) {
trace(this+" "+evt.result); // geeft "_level0 42" (de root en het resultaat)
}

Daarvoor gebruik je dus de Delegate, maar dat had Narie al uitgelegd ;)

Meer vragen? Kom maar op!

Roenes
%Europe/Berlin %829 %2005, 20:53
Dank voor de uitleg! Maakt een hoop duidelijk :)

Mediamonkey
%Europe/Berlin %334 %2005, 09:01
Oeps.. had niemand me er op kunnen wijzen dat als je 2 woorden neemt die hetzelfde beginnen, dat hij dan alleen de laatste pakt? Ik kwam er achter toen ik deze class in de praktijk wou gebruiken. :I Nou ja, fix ik nog wel :D