Citrus-Engine icon indicating copy to clipboard operation
Citrus-Engine copied to clipboard

Loading displaylist Ui menus from external swf

Open gsynuh opened this issue 12 years ago • 4 comments

This is something I'm using right now which is quite useful.

With feathers you don't get any wysiwyg editor, so using flash So I have done my research and applied that research, what we could be having for milestone 3.1.5 or later is people can build their pause menus, home screen menus etc, in flash (inside MovieClips)

then you can load them up inside your game and display them. you don't have to programatically do their layout.

I will not go into details if you don't think this is a good idea yet, but I can say that it works very well on my side.

though I can give hints of how my thing works :

In my document class (Main) I load a ui.swf file which contains all menus I'll be needing in its library. I have a BaseUI class which I extend, if my class name is HomeScreen, then it will load the HomeScreen class from ui.swf , then I plug it as a child of the HomeScreen class inside my project so HomeScreen contains the ui visually, and I can do my logic in there.

what I realized I can do as well, is upon loading an external swf, you can change the loader's application domain to set it as the "current apllication domain" which means instead of loading a class and instantiating it there, you would theoretically be able to simply extend it as it would be part of the current application domain once loaded !!

This is great news as well to load graphics from level.swf as well for prototyping !!!!

Anyway those tricks are needed for CE, for flash pro to be even more of an editor rather than just a simple thing we loop through to get properties and params etc...

I'd be glad to show you my classes, if you think its a good idea and everything. It can be optimized as well.

a visually complex ui would be more pleasent to edit with flash rather than feathers.

gsynuh avatar Mar 02 '13 15:03 gsynuh

Yup that sound quite interesting. I need more information ;)

alamboley avatar Mar 04 '13 08:03 alamboley

I didn't realise you answered here. sorry here's the base class I'm currently using, followed by a bit of explanations

package ui 
{
    import citrus.core.CitrusEngine;
    import flash.display.DisplayObject;
    import flash.display.DisplayObjectContainer;
    import flash.display.MovieClip;
    import flash.display.Sprite;
    import flash.display.Stage;
    import flash.utils.getDefinitionByName;
    import flash.utils.getQualifiedClassName;
    import mx.utils.StringUtil;
    import com.greensock.TweenLite;
    
    /**
     * Base Ui Class
     * @author gsynuh
     */
    public class BaseUI
    {
        public static var uiTarget:*;
        public static var container:Sprite;
        
        public var _thisClass:String;
        protected var _stage:Stage;
        protected var _ce:CitrusEngine;
        
        //Add tweened objects to this list
        private var _tweened:Vector. = new Vector.();
        
        protected var _visible:Boolean = true;
        
        public static var UiMasterList:Vector. = new Vector.();
        
        /**
         * the movieClip.
         */
        protected var _ui:MovieClip;
        
        public static function createUi(c:Class):*
        {
            if (!isInstanciated(c))
                return new c();
            else(isInstanciated(c))
                return getUi(c);
        }
        
        /**
         * Loaded UI will be in _ui.
         * BaseUI is NOT A MOVIECLIP.
         */
        public function BaseUI() 
        {
            if (!uiTarget)
                throw new Error("[BaseUI] target is undefined.");
            _thisClass = String(Object(this).constructor).slice(7).split("]")[0]; //"[class "
            
            if (_thisClass == "BaseUI")
                throw new Error("[BaseUI] Sorry, you can't instanciate " + _thisClass + "directly");
            
            _ui = createMC(_thisClass);
            
            if (!_ui)
            {
                throw new Error("[BaseUI] "+_thisClass+" Does not exist in" + uiTarget);
            }
            
            _ce = CitrusEngine.getInstance();
            _stage = _ce.stage;
            
            if (container)
            container.addChild(_ui);
            else
            _stage.addChild(_ui);
            init();
            UiMasterList.push(this);
            
        }
        
        /**
         * Override this to init the Ui.
         */
        protected function init():void
        {
            
        }
        
        /**
         * Overrid this to update the Ui.
         */
        public function update():void
        {
            
        }
        
        public function set alpha(value:Number):void
        {
            if (_ui)
            _ui.alpha = value;
        }
        
        public function get alpha():Number
        {
            return _ui.alpha;
        }
        
        public function set visible(value:Boolean):void
        {
            if(_ui)
            _visible = _ui.visible = value;
        }
        
        public function get visible():Boolean
        {
            if(_ui)
                return _visible;
            else
                return false;
        }
        
        public static function MC(name:String):MovieClip
        {
            return new (BaseUI.uiTarget.applicationDomain.getDefinition(name) as Class)();
        }
        
        protected function sendBehind(obj:DisplayObject):void
        {
            if(!_ui.getChildAt(0))
                _ui.swapChildrenAt(_ui.getChildIndex(obj), 0);
            else if(!(_ui.getChildAt(0) is DisplayObjectContainer))
                _ui.swapChildrenAt(_ui.getChildIndex(obj), 1);
            else
                trace("couldn't send", obj, "behind.");
        }
        
        protected function createMC(name:String):MovieClip
        {
            return MC(name);
        }
        
        public static function destroyAllUi():void
        {
            var bui:BaseUI;
            for each (bui in UiMasterList)
            {
                bui.destroy();
                removeFromMasterList(bui);
            }
        }
        
        /**
         * make only one Ui in master list visible.
         * switchVisible(null) turns all to invisible.
         * @param   c
         */
        public static function switchVisible(c:Class):void
        {
            var i:BaseUI;
            for each (i in UiMasterList)
                if(c)
                    if (i is c)
                        i.visible = true;
                    else
                        i.visible = false;
                else
                    i.visible = false;
        }
        
        /**
         * get ui from master list
         * @param c class
         */
        public static function getUi(c:Class):BaseUI
        {
            var i:BaseUI;
            for each (i in UiMasterList)
                if (i is c)
                    return i;
            return null;
        }
        
        public static function isInstanciated(c:Class):Boolean
        {
            var i:BaseUI;
            for each (i in UiMasterList)
                if (i is c)
                    return true;
            return false;
        }
        
        protected static function removeFromMasterList(bui:BaseUI):void
        {
            UiMasterList.splice(UiMasterList.indexOf(bui), 1);
        }
        
        public function destroy():void
        {
            if (_tweened.length > 0)
            {
                var t:Object;
                for each (t in _tweened)
                    TweenLite.killTweensOf(t);
            }
            
            while (_ui.numChildren > 0)
                _ui.removeChildAt(0);
            
            if (container.contains(_ui))
                container.removeChild(_ui);
            if (_stage.contains(_ui))
                _stage.removeChild(_ui);
                
                
            removeFromMasterList(this);
        }
        
        public function getInstance():BaseUI { return this; }
        
    }
}

There's TweenLite in it but it can be removed of course.

So the idea is in your .swf you've got all your menus that you set up with FlashPro. as an example, you have set up MenuMC in your .fla library, that is "exported for as3". lets call the .swf, uielements.swf .

from now on, we go back to our ide with citrus engine in it etc...

in your Main.as you load uielements.swf along with your assets.

you then do the following :

BaseUI.container = uiLayer; //the sprite layer where you want your ui to be added by default. BaseUI.uiTarget = e.currentTarget; // (e.currentTarget) is the loader - the swf file.

in your projects file you create a MenuMC.as class which extends BaseUI.

in any state, you can do : BaseUI.createUi(MenuMC); which will instanciate MenuMC from your project and also put in the MenuMC._ui property, the actual movieclip that's in your swf's library !

(the class in your project must have the same name as the class name of the movie clip in your .fla's library - I just decided that so its not confusing when you work on them)

so you don't have to compile or do anything with flash pro from now on (unless you need to change the graphics) , here's how MenuMC.as could look like :

    public class HomeScreenMC extends BaseUI
    {
        public function HomeScreenMC() 
        {
            super();
        }
        
        override protected function init():void
        {
            _ui.x = _stage.stageWidth / 2;
            _ui.y = _stage.stageHeight / 2;
        }
        
        override public function destroy():void
        {
            super.destroy();
        }
        
    }

so its a kind of movie clip wrapper and _ui is like a "view" in a way.

anything from the swf is in _ui, notice that in init I center everything on stage (which I have access to with _stage) it is automatically added to whatever sprite I set up as uiLayer.

so I can do whatever I want such as _ui.button1.addEventListener etc... without having to go through flash pro ever again. I can handle my logic inside my Ide and access the visuals I did in flash pro like so.

as it is now, it's not really fit for citrus engine (it's a bit standalone) but still we can discuss it.

I have a couple of utility functions such as BaseUI.switchVisible() , if I call BaseUI.switchVisible(MenuMC) then my MenuMC will become visible. BaseUI.switchVisible(null) will hide everything. I know I could have another way of doing this.

I'm using class names to refer to things, MenuMC is a class name here, and since most of my menus are persistent throughout the game, i didn't go for static variables or anything, BaseUI just keeps any ui things in a list, which I can destroy individually if I want using (BaseUi.getUi(MenuMC) as MenuMC).destroy;

Also BaseUI.createUi creates the movie clip and the instance of MenuMC only if it doesn't exist, if it does, it returns the existing one, that's helpful if you are going to set up your ui's in a state and if that state can reload... it also guarantees you to have your menus etc throughout the game.

you could also manually instantiate anything that was in your .fla library as a movieclip (a popup, or a temporary movieclip... this can also be modified to return Bitmaps instead) using createMC() which actually returns : public static function MC(name:String):MovieClip { return new (BaseUI.uiTarget.applicationDomain.getDefinition(name) as Class)(); }

This is the key to everything here.

Again this can seem sloppy, and its better seen in action, but I hope this gives a little taste of the cool things it can do with that simple trick.

And this magical MC function, could lead to better prototyping levels as well. if in a level.swf you exported your graphics for as3, like a movie clip or a bitmap, then you can load them using the same principle, and set them as a view to whatever object, for example if we have a platform in level.swf and you wanted it to be drawn using bitmap1.jpg that's in your library, you can export bitmap1 as Bitmap1, and could well set the params of the platforms like so : {name:"platform1", view:"external.Bitmap1"} I added "external." of course, if the ObjectMaker recognized "external." it would know it would have to get Bitmap1 not from the actual classes in your project but from level.swf and so level designing - and quick prototyping of a game idea could become a bit less painful for people just starting or people who don't want to bother setting up their views manually.

gsynuh avatar Mar 18 '13 16:03 gsynuh

Since 3.2 is soon to be released, I might need a bit more time to adapt the code for better integration - can we have another milestone so I can put this issue in it?

gsynuh avatar Mar 29 '13 00:03 gsynuh

Done ;)

alamboley avatar Mar 29 '13 08:03 alamboley