Date: Wed, 22 May 2013 06:00:29 +0200
Quote:
- Casual Game Development
Hooks and Closures
http://blog.pettomato.com/?p=18
Text:
-
class standard.Hook{
-
-
private function Hook(){
-
// We never instantiate this class.
-
}
-
-
static public function MakeAddHookFunc(a:Array):Function{
-
-
var f = function(new_func:Function,bOverwrite:Boolean){
-
// The list of hooks can be cleared by sending new_func=null
-
// and bOverwrite=true.
-
-
// bOverwrite=true means to remove any previous hooks before
-
// adding new_func.
-
-
if(bOverwrite == true){
-
// If we set a to a new [], then it becomes a different
-
// array than the one we used with MakeRunHooksFunc. Therefore,
-
// we instead set the length to 0.
-
a.length = 0;
-
}
-
-
if(new_func != null){
-
a.push(new_func);
-
}
-
}
-
-
return f;
-
}
-
-
static public function MakeRunHooksFunc(a:Array):Function{
-
-
// Returns a function which, when called, will call each of the functions
-
// in 'a'. You can call the function with any number of arguments as long as
-
// the functions that you added to 'a' can take those args.
-
-
// *Note that the array that you pass to this function should be the same
-
// exact array that you used with MakeAddHookFunc.
-
-
var f = function(){
-
var N = a.length;
-
for(var i=0;i<N;i++){
-
a[i].apply(null, arguments); // apply is used so that we can pass any number
-
// of arguments to this function.
-
}
-
}
-
-
return f;
-
}
-
-
public function toString(Void):String{
-
-
return "Hook()";
-
}
-
}
-
class standard.widget.Simple_Button extends standard.widget.Widget{
-
-
private var _enabled:Boolean;
-
-
private var RunEnableHooks:Function;
-
private var RunDisableHooks:Function;
-
-
public var AddEnableHook:Function;
-
public var AddDisableHook:Function;
-
-
public var AddMouseDownEvent:Function;
-
public var AddMouseUpEvent:Function;
-
public var AddRollOverEvent:Function;
-
public var AddRollOutEvent:Function;
-
-
private var OnMouseDown:Function;
-
private var OnMouseUp:Function;
-
private var OnMouseOver:Function;
-
private var OnMouseOut:Function;
-
-
static private var COUNT:Number = 0;
-
-
function Simple_Button(parentMC, bCreateMC:Boolean){
-
-
super();
-
-
if(bCreateMC == null ||
-
bCreateMC == true)
-
Create_MC(parentMC);
-
-
var enable_hook_ar = [];
-
var disable_hook_ar = [];
-
-
AddEnableHook = standard.Hook.MakeAddHookFunc(enable_hook_ar);
-
RunEnableHooks = standard.Hook.MakeRunHooksFunc(enable_hook_ar);
-
-
AddDisableHook = standard.Hook.MakeAddHookFunc(disable_hook_ar);
-
RunDisableHooks = standard.Hook.MakeRunHooksFunc(disable_hook_ar);
-
-
var mouseDownEvent_ar = [];
-
var mouseUpEvent_ar = [];
-
var mouseOverEvent_ar = [];
-
var mouseOutEvent_ar = [];
-
-
AddMouseDownEvent = standard.Hook.MakeAddHookFunc(mouseDownEvent_ar);
-
AddMouseUpEvent = standard.Hook.MakeAddHookFunc(mouseUpEvent_ar);
-
AddRollOverEvent = standard.Hook.MakeAddHookFunc(mouseOverEvent_ar);
-
AddRollOutEvent = standard.Hook.MakeAddHookFunc(mouseOutEvent_ar);
-
-
OnMouseDown = MakeMouseEventFunc(mouseDownEvent_ar, _mc);
-
OnMouseUp = MakeMouseEventFunc(mouseUpEvent_ar, _mc);
-
OnMouseOver = MakeMouseEventFunc(mouseOverEvent_ar, _mc);
-
OnMouseOut = MakeMouseEventFunc(mouseOutEvent_ar, _mc);
-
-
_enabled = true; // will be set to false, immediately.
-
-
Enable(false);
-
}
-
-
public function Enable(b:Boolean):Void{
-
-
// Call Enable(true) to enable the button, and Enable(false) to
-
// disable it. If the button is already in the desired state, then
-
// the request will be ignored.
-
-
if(b){
-
-
if(!_enabled){
-
-
_enabled = true;
-
-
_mc.onPress = OnMouseDown;
-
_mc.onRelease = OnMouseUp;
-
_mc.onReleaseOutside = OnMouseUp;
-
_mc.onRollOver = OnMouseOver;
-
_mc.onRollOut = OnMouseOut;
-
-
RunEnableHooks();
-
}
-
}
-
else{
-
-
if(_enabled){
-
-
_enabled = false;
-
-
delete _mc.onRelease;
-
delete _mc.onPress;
-
delete _mc.onReleaseOutside;
-
delete _mc.onRollOver;
-
delete _mc.onRollOut;
-
-
RunDisableHooks();
-
}
-
}
-
}
-
-
public function IsEnabled(Void):Boolean{
-
-
return _enabled;
-
}
-
-
static private function MakeMouseEventFunc(a:Array,mc:MovieClip):Function{
-
-
// This is just like Hook.MakeRunHooksFunc, but we are assuming that all the
-
// mouse event hooks are going to take the current mouse x and y positions
-
// the only arguments. *I don't really like this, but I need it right now
-
// to support legacy code.
-
-
var f = function(){
-
-
var x = mc._xmouse;
-
var y = mc._ymouse;
-
-
var N = a.length;
-
for(var i=0;i<N;i++){
-
a[i](x,y);
-
}
-
}
-
-
return f;
-
}
-
-
public function toString(Void):String{
-
-
return "Simple_Button()";
-
}
-
}
-
private function CreateButtons(Void):Void{
-
-
var buttons = [];
-
-
for( var i=0;i<3;i++ ){
-
-
var b = new Simple_Button(_mc);
-
-
buttons.push(b);
-
-
var b_index = i + 1;
-
var b_mc = _mc["characterMC" + b_index];
-
-
b.AddMouseDownEvent(MakeMsgFunc(_fsm,MSG_ChangeState,300,{index: b_index}));
-
b.AddMouseDownEvent(MakeMCFrameFunc(b_mc,"down",true));
-
b.AddMouseDownEvent(MakePressButtonFunc(buttons));
-
b.AddRollOverEvent(MakeMCFrameFunc(b_mc,"over",false));
-
b.AddRollOutEvent(MakeMCFrameFunc(b_mc,"off",false));
-
-
b.Enable(true);
-
}
-
}
-
-
static private function MakeMsgFunc(sending_fsm:StateMachine_NEW,msg_type:Number,delay:Number,data:Object):Function{
-
-
if(delay == null)
-
delay = 0;
-
-
var f = function(){
-
sending_fsm.SendDelayedMsgToMe(delay,msg_type,SCOPE_TO_THIS_STATE,data);
-
}
-
-
return f;
-
}
-
-
static private function MakePressButtonFunc(button_ar:Array):Function{
-
-
var f = function(){
-
-
// disable all buttons
-
for(var i in button_ar)
-
button_ar[i].Enable(false);
-
}
-
-
return f;
-
}
-
-
private function MakeMCFrameFunc(mc:MovieClip,label:String,bPlay:Boolean):Function{
-
-
var f;
-
-
if(bPlay){
-
f = function(){
-
mc.gotoAndPlay(label);
-
}
-
}
-
else{
-
f = function(){
-
mc.gotoAndStop(label);
-
}
-
}
-
-
return f;
-
}
One design idiom that I have borrowed from Emacs Lisp is the concept of hooks. A hook is a function that's called when a particular event happens. In Emacs, for example, whenever I create a new file, I have a hook that checks if the file ends in ".as" and if it does, I automatically add some boilerplate code to the file (constructor, toString, etc). In a game, you could have a set of hooks that fire whenever a collision takes place, a timer runs out, a room is entered, etc. Any number of hooks can be added to an event and they are run in the order they were added. They can also be added/removed at runtime.
Below is a simple utility class, Hook.as, that provides two functions for working with hooks. The first function returns a new function that can be used to add hooks to an event. The second one returns a function that will run all the hooks that have been added.
Hook.as
The second class, Simple_Button.as, is something that I use in a lot of places to give button behaviors to MovieClips. It uses the functions in Hooks.as to create hooks for button events and for disabling/enabling the button. For a button press event, I might add one hook to change the button graphic and a second hook to do whatever action the button should perform.
Simple_Button.as
Here is some example code that I used to create three buttons for selecting a character in a game:
Example Usage
This might seem convoluted at first, but the best part is that everything only does one thing. One of the main goals of functional programming is that there are (nearly) no side-effects. Notice that there's no "owner" or "parent" crap in any of the buttons. Also, notice that "buttons" is a local variable, but all the functions that you create with MakePressButtonFunc will always keep a reference to it, even when it passes out of scope. That's the "closure" that Actionscript and Lisp can do that many languages can't. Lisp advocates think that it's one of Lisp's most important features.
I'm not sure about the implementation in Flash, but in Lisp there are not any performance issues with creating all those functions, because it isn't like creating a new object. The "new" functions are actually very similar to the methods of a class. That's why you don't have to say "var f = new function." Also, the closures have those passed-in variables bound to them, so they might even be faster than "owner.doSomething()" which has to look up "owner" and then call the function.
Anyway, for me, this stuff has solved a lot of problems that I've dealt with in the past and I've been able to reduce a lot of my code significantly.
Via FeedShow.com