圣叹@游戏开发

我们面对现实,我们终于理想
如果想在Flex Actionscript工程里使用fl.*包下的组件,不妨使用这个SWC:
http://asform.googlecode.com/files/FlSWC.swc
我已经在其中包含了Yahoo Astra的系列Flash组件。
如果想仅仅使用部分其中的组件,可以在flash中先导入对应的组件,然后在Lib中右键,选择导出SWC即可。

当然,如果仅仅想使用Fl的UiComponent组件,可以到flash的安装目录下找到对应的源文件。但是在Flex Actionscript工程里直接使用其中的组件时,报错是必然的,因为皮肤文件没被导入:

TypeError: Error #2007: Parameter child must be non-null.
at flash.display::DisplayObjectContainer/addChildAt()
at fl.controls::BaseButton/fl.controls:BaseButton::drawBackground()[C:\Program Files\Adobe\Adobe Flash CS3\en\Configuration\Component Source\ActionScript 3.0\User Interface;fl\controls;BaseButton.as:538]
at fl.controls::LabelButton/fl.controls:LabelButton::draw()[C:\Program Files\Adobe\Adobe Flash CS3\en\Configuration\Component Source\ActionScript 3.0\User Interface;fl\controls;LabelButton.as:600]
at fl.controls::Button/fl.controls:Button::draw()[C:\Program Files\Adobe\Adobe Flash CS3\en\Configuration\Component Source\ActionScript 3.0\User Interface;fl\controls;Button.as:167]
at fl.core::UIComponent/fl.core:UIComponent::callLaterDispatcher()[C:\Program Files\Adobe\Adobe Flash CS3\en\Configuration\Component Source\ActionScript 3.0\User Interface;fl\core;UIComponent.as:1379]
at [renderEvent]

类别:Program | Tags: , , , 评论(3) 阅读(2807)
Apr
11
2009
如前一篇Blog所述,正式发布Form的beta版。这一版基本完成了框架的搭建,将部分常用的样式配置独立出来,并且支持通过在外部设置XML配置,动态的生成组件甚至是程序的个模块。这一版本包含的组件有:
1、控制类:多选/单选; 下拉框; 文本输入框;  列表; 标签; 按钮/状态按钮/链接按钮;
2、布局类:HBox/VBox;
3、其他:Alert; Popout;
示例:

使用了这套组件开发的程序示例(一款音乐播放器):
http://lads.myspace.cn/widget/form/light/lightplayer.html
项目地址:
http://code.google.com/p/asform/
目前没有直接提供下载,可以通过svn访问源代码。SWC位于http://asform.googlecode.com/svn/trunk/Form/src/swc/Form.swc;一个完整的Test示例位于http://asform.googlecode.com/svn/trunk/FormTest/src/FormTest.as。

此外,我会在一个月后从Myspace China离职,目前下家尚未确定,只是在和一些朋友、猎头沟通,因此这套组件库的更新也许会停下来一段时间。当然如果朋友们有不错的公司,是很欢迎给我引荐下的,再此谢过。

下面是简单的使用示例:
1、基础控件的使用,直接使用封装好的类即可。如果要改变样式,可以改变FormAsset.swf中的对应的样式即可。更高级的组件定制请见后文。:

var radio1:Radio = new Radio();
radio1.label = "radio 1";
radio1.formName = "Radio";
radio1.width = 65;
var radio2:Radio = new Radio();
radio2.label = "radio 2";
radio2.formName = "Radio";
radio2.width = 65;
var radio3:Radio = new Radio();
radio3.label = "radio 3";
radio3.formName = "Radio";
radio3.width = 65;

addChild(radio1);
addChild(radio2);
addChild(radio3);    

2、如果要使用布局类,如HBox/VBox等实现随场景自动伸缩,其root需要指向ApplicationContainer这个类。ApplicationContainer封装了所有布局类、Alert、Popout的接口。
如:

package {
  import flash.events.Event;
  import flash.events.MouseEvent;
  
  import form.ui.*
  import form.ui.component.Layout.HBox;

  [SWF(backgroundColor="0xFFFFFF", frameRate="30", width="450", height="400")]
  public class FormTest extends ApplicationContainer
  {
    private var listData:Array;
    public function FormTest()
    {
      super();
      this.layout.paddingLeft = 10;
      this.layout.paddingTop = 10;
      
      runDefault();      
    }
    
    private function delayTest(li:ListBase):void
    {
      for(var i:int = 300; i < 350; i++)
        li.addItem({"label": i, "b": "act"});
    }
    
    public function runDefault():void
    {      
      var checkBoxLabel:Label = new Label();
      checkBoxLabel.formValue = "CheckBox Group";
      checkBoxLabel.width = 200;
      addChild(checkBoxLabel);
      
      var checkBox1:CheckBox = new CheckBox();
      checkBox1.label = "checkBox 1";
      checkBox1.width = 70;
      checkBox1.checkedOnAddedToStage = true;
      var checkBox2:CheckBox = new CheckBox();
      checkBox2.label = "checkBox 2";
      checkBox2.width = 70;
            
      var checkBoxBox:HBox = new HBox();
      checkBoxBox.percentWidth = 100;
      
      checkBoxBox.addChild(checkBox1);
      checkBoxBox.addChild(checkBox2);      
      addChild(checkBoxBox);
      
      var select:Select = new Select();
      select.addHeader("Default Header");
      select.addOption("Select Opinion1", "Select Opinion1");
      select.addOption("Select Opinion2", "Select Opinion2");      
      select.prompt = "Select";
      
      var input:Input = new Input();
      input.prompt = "Input";
      
      var button:FormButton = new FormButton();
      button.label = "Button";
      button.width = 80;
      button.addEventListener(MouseEvent.CLICK, showAlert, false, 0, true);    
      
      var ioBox:HBox = new HBox();
      ioBox.percentWidth = 100;
      ioBox.horizontalGap = 10;
      
      ioBox.addChild(select);
      ioBox.addChild(input);
      ioBox.addChild(button);
      addChild(ioBox);    
    }
    
    private function showAlert(e:Event):void
    {
      
      Alert.show("Alert Title", "Alert Message");
    }
  }
}

3、最强大的部分:这套组件建立的核心是快速应对变化的需求,以不变(组件核心库)应万变(只改变配置文件)。下面先看一段XML:

<?xml version="1.0" encoding="utf-8" ?>
<layout>
  <layoutType>boxLayout</layoutType>
  <url>assets/blackAsset.swf</url>
  <global>
    <item name="cn.myspace.player.ui.DefaultTrackListRender" style="index-text-color: #FFFFFF; track-title-color:#FFFFFF; "/>
  </global>
  <data>    
    <app style="padding: 5px; vertical-gap:5px; background:bgClip_linkage; background-size:100%;">
      <hbox style="width:100%; height:35px; padding-top:2px; padding-bottom:5px;">
        <logo style="background:logo; width:100%;"/>
      </hbox>    
    </app>
  </data>
</layout>

传统的Flex中,样式(Skin)是可以编译成SWF从而实现在RunTime换肤。而这套组件就没必要重复造轮子了。
以播放器为例,播放器出去一些控制按钮、均衡器、波形表等基础组件外,再也没有其他的可变的部分了。这是“不变”的根本。变化的是这些部件的外观和各种组合方式。如果他们能写到配置文件里...是的,以后要做的就不再是开发,而是维护工作了。这边是上面这段XML做的事情。完整的皮肤配置文件位于http://lads.myspace.cn/widget/form/light/assets/userBlank.xml.
实际上 每个项目有一个组件工厂(必须是每个项目都各不相同,且很难定义一个统一的接口,所以不包含在Form库中。),工厂负责装配这些配置文件里的各个模块,它形如:

public class UIFactory
{
  public static function createUI(xml:XML):FMSprite
  {
    var name:String = xml.name().localName;
    var box:FMSprite;      
    name = StringUtils.trim(name).toLowerCase();
    switch (name)
    {
      case "app": box = ApplicationContainer.application; break;
      case "logo": box = new Logo; break;
      case "hbox": box = new HBox(); break;
      case "vbox": box = new VBox(); break;
      case "box": box = new Box(); break;
      case "art": box = new Art(); break;
      ...
    }
    
    var css:CSS = CssFactory.createCSS(box);
    css.fromXML(xml);
    return box;
  }
}

组件库有一个CSS分析器将所有XML组件元素标签的style属性转化为Flash的Class,然后由StyleManager类来管理这个组件的皮肤设置。
当然,这版还没有完成的一部分是List的ItemRender的动态配置。CSS的模块不一定每个项目都会用到,所以他和各个模块不存在依赖关系,实际项目中用到的时候才会import他们,尽管这个模块没多大。
更好的方式是,主程序只做容器,读取到配置文件都再决定加载它们,最后设定他们的外观——很多大型项目不都是基于这一思想实现的么?
播放器的代码由于是公司里的项目所以不便公开,这里只能给出一个大致的实现思想,很多人都可以顺着这个思想实现他。当然,这中想法在Flex里也是可以实现的。
Feb
26
2009
Flex太大,于是写了下面的轻量级Form控件,我会逐渐丰富、完善,直至...开源的那一天。
优点:轻量级(<50K);支持自定义皮肤;支持Runtime换肤...
在不久的将来会发布beta版,同时开放源代码。



下面是例子的主要代码:

package {
  import flash.events.Event;
  import flash.events.MouseEvent;
  import flash.system.Security;
  
  import form.ui.Alert;
  import form.ui.ApplicationContainer;
  import form.ui.CheckBox;
  import form.ui.FormButton;
  import form.ui.HSlider;
  import form.ui.Input;
  import form.ui.Label;
  import form.ui.Radio;
  import form.ui.Select;
  import form.ui.Spacer;
  import form.ui.VSlider;
  import form.ui.component.Layout.Box;
  import form.ui.component.Layout.HBox;
  
  [SWF(backgroundColor="0xFFFFFF", frameRate="30", width="450", height="300")]
  public class FormTest extends ApplicationContainer
  {
    public function FormTest()
    {
      Security.allowDomain("*");
      
      super(Box.VERTICAL)
      this.percentHeight = 100;
      
      runDefault();
      
      /*
      //StyleManager.getInstance().addEventListener(StyleEvent.STYLE_COMPLETE, onStyleLoad);
      StyleManager.getInstance().loadStyleDeclarations("assets/blackAsset.swf"); */
    }
    
    public function runDefault():void
    {
      var radioLabel:Label = new Label();
      radioLabel.formValue = "Radio Group";
      radioLabel.width = 200;
      addChild(radioLabel);
      
      var radio1:Radio = new Radio();
      radio1.label = "radio 1";
      radio1.formName = "Radio";
      radio1.width = 65;
      var radio2:Radio = new Radio();
      radio2.label = "radio 2";
      radio2.formName = "Radio";
      radio2.width = 65;
      var radio3:Radio = new Radio();
      radio3.label = "radio 3";
      radio3.formName = "Radio";
      radio3.width = 65;
      var radio4:Radio = new Radio();
      radio4.label = "radio 4";
      radio4.formName = "Radio";
      radio4.width = 65;
      
      var radioBox:HBox = new HBox();
      radioBox.percentWidth = 100;
      
      radioBox.addChild(radio1);
      radioBox.addChild(radio2);
      radioBox.addChild(radio3);
      radioBox.addChild(radio4);      
      
      addChild(radioBox);      
      
      var sp:Spacer = new Spacer();
      sp.height = 10;
      addChild(sp);
      
      var checkBoxLabel:Label = new Label();
      checkBoxLabel.formValue = "CheckBox Group";
      checkBoxLabel.width = 200;
      addChild(checkBoxLabel);
      
      var checkBox1:CheckBox = new CheckBox();
      checkBox1.label = "checkBox 1";
      checkBox1.width = 70;
      checkBox1.checkedOnAddedToStage = true;
      var checkBox2:CheckBox = new CheckBox();
      checkBox2.label = "checkBox 2";
      checkBox2.width = 70;
      var checkBox3:CheckBox = new CheckBox();
      checkBox3.label = "checkBox 3";
      checkBox3.width = 70;
      var checkBox4:CheckBox = new CheckBox();
      checkBox4.label = "checkBox 4";
      checkBox4.width = 70;
      
      var checkBoxBox:HBox = new HBox();
      checkBoxBox.percentWidth = 100;
      
      checkBoxBox.addChild(checkBox1);
      checkBoxBox.addChild(checkBox2);
      checkBoxBox.addChild(checkBox3);
      checkBoxBox.addChild(checkBox4);
      addChild(checkBoxBox);
      
      addChild(sp);
      
      var select:Select = new Select();
      select.addHeader("Default Header");
      select.addOption("Select Opinion1", "Select Opinion1");
      select.addOption("Select Opinion2", "Select Opinion2");
      select.addOption("Select Opinion3", "Select Opinion3");
      select.addOption("Select Opinion4", "Select Opinion4");
      select.addOption("Select Opinion5", "Select Opinion5");
      select.addHeader("Second Header");
      select.addOption("Select Opinion6", "Select Opinion6");
      select.addOption("Select Opinion7", "Select Opinion7");
      select.addOption("Select Opinion8", "Select Opinion8");
      select.addOption("Select Opinion9", "Select Opinion9");
      select.addSeparator();
      select.addOption("Select Opinion10", "Select Opinion10");
      select.addOption("Select Opinion11", "Select Opinion11");
      select.prompt = "Select";
      
      var input:Input = new Input();
      input.prompt = "Input";
      
      var button:FormButton = new FormButton();
      button.label = "Button";
      button.width = 80;
      button.addEventListener(MouseEvent.CLICK, showAlert, false, 0, true);    
      var ioBox:HBox = new HBox();
      ioBox.percentWidth = 100;
      ioBox.horizontalGap = 10;
      
      ioBox.addChild(select);
      ioBox.addChild(input);
      ioBox.addChild(button);
      addChild(ioBox);  
      
      var hslide:HSlider = new HSlider();
      hslide.value = 0.5;
      addChild(hslide);
      
      var box:Box = new Box(Box.ABSOLUTE);
      box.percentWidth = 100;
      box.height = 200;      
      addChild(box);
      
      var vslide:VSlider = new VSlider();
      vslide.y = 120;
      vslide.value = 0.2;
      vslide.setProgress(0.5);
      box.addChild(vslide);
      
      this.invalidateDisplayList(true);
    }
    
    public function onStyleLoad(e:Event):void
    {
      trace(e);
    }
    
    private function showAlert(e:Event):void
    {
      Alert.show("Alert Title", "Alert Message");
    }
  }
}

Feb
16
2009
在试用了XML、JSON、AMF3后,最终不得不折服于AMF的高效率,AMF是个好东西,优点就不啰嗦了。在Google App Engine中使用AMF通信其实也很简单,只要加入PyAMF库即可。
1、Python端:
Echo的示例可以参考pyamf的官方文档。下面的例子展示了如何运用PyAMF访问数据库并序列号后返回给Flash端:

import datetime
import wsgiref.handlers

from pyamf.remoting.gateway.wsgi import WSGIGateway

from google.appengine.ext import db

def GetOwnerFlowers(platform, oid, max):
    flowers = FlowerModel.all().filter('pid =', platform).filter('oid =', oid).order('-date').fetch(max)
    return formatList(flowers)

def GetViewerFlowers(platform, oid, vid, max):
    flowers = FlowerModel.all().filter('pid =', platform).filter('vid =', vid).filter('oid =', oid).order('-date').fetch(max)
    return formatList(flowers)

def SaveFlower(data):
    flower = FlowerModel()
    flower.oid = data['oid']
    flower.oname = data['oname']
    flower.date = datetime.datetime.utcnow()
    flower.put()
    return True

def formatList(flowers, userCount, sysCount):
    list = []
    for flower in flowers:        
        result = {'oid':'', 'oname':'', 'date':''}
        result['oid'] = flower.oid
        result['oname'] = flower.oname
        result['date'] = flower.date
        list.append(result)
    return list

services = {
    'Garden.GetOwnerFlowers': GetOwnerFlowers,
    'Garden.GetViewerFlowers': GetViewerFlowers,
    'Garden.SaveFlower': SaveFlower
}

class FlowerModel(db.Model):
    oid = db.StringProperty() #owner id
    oname = db.StringProperty() #owner name
    date = db.DateTimeProperty() #created date
    
def main():
    application = WSGIGateway(services)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
    main()

2、Flash端不用做任何序列号的操作:

package com.moorwind.fans.utils
{
  import com.moorwind.fans.core.Constant;
  import com.moorwind.fans.core.Context;
  import com.moorwind.fans.events.ServiceEvent;
  import com.moorwind.fans.vo.FlowerVO;
  
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.events.IOErrorEvent;
  import flash.events.SecurityErrorEvent;
  import flash.net.NetConnection;
  import flash.net.Responder;
  
  public class ServiceUtils extends EventDispatcher
  {
    private var conn:NetConnection = new NetConnection();
    
    public function ServiceUtils()
    {
      conn.connect(Constant.END_POINT);
      conn.addEventListener(IOErrorEvent.IO_ERROR, onConnectError);
      conn.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onConnectError);
    }
    
    public function getOwner(oid:String, pid:int):void
    {
      var responder:Responder = new Responder(onGetOwner, onDataError);
      conn.call(Constant.GET_OWNERS, responder, pid, oid, Constant.LIMIT);
    }
    
    public function getViewer(oid:String, vid:String, pid:int):void
    {
      var responder:Responder = new Responder(onGetViewer, onDataError);
      conn.call(Constant.GET_VIEWERS, responder, pid, oid, vid, Constant.LIMIT);
    }
    
    public function saveFlower(flower:FlowerVO):void
    {
      var responder:Responder = new Responder(onSaveData, onDataError);
      conn.call(Constant.SAVE_FLOWERS, responder, flower);
    }
    
    private function onGetOwner(result:Object):void
    {
      Context.instance.ownerTotal = result.userCount;
      Context.instance.sysTotal = result.sysCount;
      var arr:Array = [];
      for each(var obj:Object in result.flowers)
      {
        var fl:FlowerVO = new FlowerVO();
        fl.oid = obj["oid"];
        fl.oname = obj["oname"];
        arr.push(fl);
      }
      var evt:ServiceEvent = new ServiceEvent(ServiceEvent.FLOWER_OWNER);
      evt.flowers = arr;
      dispatchEvent(evt);
    }
    
    private function onGetViewer(result:Array):void
    {
      var arr:Array = [];      
      for each(var obj:Object in result)
      {
        var fl:FlowerVO = new FlowerVO();
        fl.oid = obj["oid"];
        fl.oname = obj["oname"];
        arr.push(fl);
      }
      var evt:ServiceEvent = new ServiceEvent(ServiceEvent.FLOWER_VIEWER);
      evt.flowers = arr;
      dispatchEvent(evt);
    }
    
    private function onSaveData(result:Boolean):void
    {
      if(result)
      {
        dispatchEvent(new ServiceEvent(ServiceEvent.FLOWER_SAVED));
      }
    }
    
    private function onDataError(result:Object):void
    {
      trace(result);
    }
    
    public function onConnectError(e:Event):void
    {
      trace(e);
    }
    

  }
}

唯一不好的是。。。使用PYAMF占用CPU太高,哎
Jan
20
2009
Simplejson是不能直接把Appengine中的db.Model序列化成JSON的,我是这么做的:
Model模块做如下扩展:

def getter(func):
    if not func.__name__.startswith("get_"):
        raise InvalidMethodName("method name must start with 'get_'")
    func.getter = True
    return func

class Resource(UserDict.DictMixin):
    def exposed_attrs(self):
        """attribute names to be exposed as keys"""
        return []
    
    def hidden_keys(self):
        """Keys to hide from iteration. A Key will still be accessible if it is
        a getter or if it is listed in exposed_attrs()"""
        return []
    
    def child_object(self, name):
        """Called to get a child object if it isn't found as a getter or an attribute"""
        raise AttributeError
        
    # Dictionary Methods
    def __getitem__(self, key):
        """Called to implement evaluation of self[key]"""
        getter = getattr(self, 'get_'+key, None)
        if hasattr(getter, 'getter'):
            return getter()
        if key in self.exposed_attrs():
            return getattr(self, key)
        return self.child_object(key)
        
    def __setitem__(self, key, value):
        """Called to implement assignment to self[key]"""
        setter = getattr(self, 'set_'+key, None)
        if hasattr(setter, 'setter'):
            return setter(value)
        
    def __delitem__(self, key):
        """Called to implement deletion of self[key]"""
        deleter = getattr(self, 'del_'+key, None)
        if hasattr(deleter, 'deleter'):
            return deleter(value)
        
    def keys(self):
        """List of keys for iteration"""
        getter_keys = [name.replace('get_', '', 1) for name in dir(self)
                   if hasattr(getattr(self, name, None), 'getter')]
        exposed_keys = getter_keys + self.exposed_attrs()
        return [key for key in exposed_keys if key not in self.hidden_keys()]

当继承db.Model时,同时继承Resource:

class PersonModel(Resource, db.Model):
    user_id = db.StringProperty()
    user_content = db.TextProperty()
  
    @getter
    def get_uid(self):
        return self.user_id
    @getter
    def get_content(self):
        return self.user_content

在处理请求的时候,再做如下处理:

class RestHandler(webapp.RequestHandler):  
    def post(self):
        self.dispatch_request()
        
    def dispatch_request(self):
        self.response.headers["Content-Type"] = "text/plain"
        params = {}
        for key in self.request.params:
            params[str(key)] = self.request.get(key)
        
        obj = //fetch data from PersonModel.
        obj = self.preserialize(obj)
        out = simplejson.dumps(obj)
        self.response.out.write(out)
    
    def translate(self, obj, depth):
        return obj
    
    def preserialize(self, obj, depth=0):
        obj = self.translate(obj, depth)
        
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        elif isinstance(obj, db.users.User):
            return obj.nickname()
        elif isinstance(obj, (db.GeoPt, db.IM, db.Key)):
            return str(obj)
        elif isinstance(obj, types.ListType):
            return [self.preserialize(item, depth+1) for item in obj]
        elif isinstance(obj, (types.DictType, Resource)):
            copy = {}
            for key in obj:
                copy[key] = self.preserialize(obj[key], depth+1)
            return copy
        return obj

这个方法不是太好,还有很大的优化空间...
Jan
17
2009
分页: 8/24 第一页 上页 3 4 5 6 7 8 9 10 11 12 下页 最后页 [ 显示模式: 摘要 | 列表 ]