FSE是一个用于Flash AS3的轻量GPU混合渲染框架。旨在使用传统Flash开发方法快速构建能够与Unity体验相近的2D高帧率应用。
FSE.mp4
- 快速开始,与传统AS3项目对接
- 不用写Starling代码即可轻松创建Starling项目
- 适用于个人级别轻量化富位图的高帧率GPU项目快速开发
- 支持传统Flash的窗口自适应策略
- 支持传统发光,模糊,投影滤镜
-
今夕是何年?没错,现在是2026年,距离Flash技术正式退出历史舞台已有五年之久。正是在这样的时间点上,FSE(Flash Starling Enhance)混合渲染框架如一次跨越时空的技术“回响”,悄然诞生。
-
我是一名来自中国的热爱独立游戏创作的大三学生,正在逐步向Unity技术栈转型。今年恰是我接触ActionScript 3.0开发的第十年。这些年间,我始终怀有一种愧疚——虽对这一技术有了相当地了解,却未曾用它创作出什么令人瞩目的作品。
-
前段时间,我沉浸于Starling Wiki和GitHub中大量关于Starling框架的资料,同时也陷入了某种瓶颈。我逐渐意识到,在如今的环境中用Flash做出优秀作品实属不易。但作为一个Aser,我仍想为年轻的自己、也为这段技术旅程画上一个更完整的句点。
-
有时候,我们需要寻找一种方式与自己和解,不是吗?这个框架便是我的答案。
-
好吧,用白话说:这个框架只是我本人以学习为目的开发,欢迎学习讨论。
如果你是一位资深的Flash开发工程师,那么你大概率了解过Adobe AIR SDK的GPU模式
我先简单介绍一个这个模式,在AIR for Android/AIR for IOS配置中,GPU设置是可用的
选择了这个模式以后确实可以让整个画面看起来更流畅,但帧率依然只能限制在60FPS,以及滤镜等一些功能存在兼容性BUG
对于AIR for Desktop,GPU模式竟然直接被隐藏了,据小道消息貌似是Adobe推行了一半但因为很多兼容性BUG所以放弃推行了。
并且在传统的Flash IDE的导出设置里是没有这个模式选择的
-------------------可爱的分界线----------------------
那么作为一个Aser,要快速构建高性能高帧率的Flash应用,我们常常会遇到这些问题。
Flash IDEA开发的传统应用体验不佳{帧率限制60,帧率不稳定,大分辨率场景位图移动直接跳帧}
而转向Starling 框架 后,性能虽然上来了,但制作动画却变得异常棘手——缺乏成熟、可视化的动画解决方案,这几乎是致命的短板。
那有没有一种可能,在制作个人级别的手机、桌面应用或是游戏时能够兼顾GPU的高帧渲染效率,又能够享受到Flash IDE现有的动画方案呢?
答案:有的兄弟,有的~ FSE正式进入舞台(一语双关)
1. 首先,准备好FSE框架
- 一共包含3个文件
fse starling fse.as (注意,这个starling包是经过我调整过的,与官方版本starling不兼容) (fse.as这个文件是FSE.as的快捷入口文件)
2. 将FSE框架复制到你的项目中
-
Flash IDE工程中
/your_project_path =========================== fse starling fse.as =========================== xxx.fla xxx.swf -
IDEA/FB/FD工程中
/your_project_path/src =========================== fse starling fse.as =========================== YourMainClass.as
3. 在你的项目中注入Starling框架。
-
Flash IDE工程中
//在舞台根目录第一帧上 import fse.core.FSE; FSE.init(stage,this); //这两行等效于 fse.init(stage,this); //请确保你给出的第二个参数为舞台容器剪辑根
-
IDEA/FB/FD工程中
//在你的类文件中 package { import flash.display.Sprite; import flash.events.Event; import fse.core.FSE; public class Main extends Sprite { public function Main() { if (stage) { onAddedToStage(); } else { addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); } } private function onAddedToStage(event:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); FSE.init(stage,this); //你的代码 } } }
-
特性
- FSE接管后所有MovieClip都会默认强制暂停播放
- 播放由FSE内置的动画管理器驱动
- 默认情况下,FSE会自动管理所有MovieClip的播放,但你可以通过以下方式来手动控制播放
-
常用API
//=====初始化================================================================ fse.init(stage,Object(root)); //常规初始化 fse.init(stage,Object(root),false); //特殊初始化,不进行GPU渲染,只提帧并接管动画系统 //=====动画控制================================================================ mc.play(); //必须改写成 fse.play(mc); //播放动画 mc.stop(); //必须改写成 fse.stop(mc); //停止播放动画 mc.gotoAndStop(index); //必须改写成 fse.gotoAndStop(mc,index); //跳转到指定帧 mc.gotoAndPlay(index); //必须改写成 fse.gotoAndPlay(mc,index); //跳转到指定帧并播放 var v:Boolean = mc.visible; //必须改写成 var v:Boolean = fse.getVisible(mc); //获取MovieClip对象可见性 mc.visible=false; //必须改写成 fse.visible(mc,false); //改变MovieClip对象可见性 //=====循环控制================================================================ //高频循环 (在240帧的情况下每秒刷新240次) //开启 mc.addEventListener(Event.ENTER_FRAME,Update); //关闭 mc.removeEventListener(Event.ENTER_FRAME,Update); //低频循环 (无论在什么情况下都以60次每秒运行[频率在Config.as中可调]) //开启 fse.addEventListener(mc,FSE_Event.FIX_ENTER_FRAME,Update); fse.addLoop(mc,Update); fse.loop(Update); //3句等效 //关闭 fse.removeEventListener(mc,FSE_Event.FIX_ENTER_FRAME,Update); fse.removeLoop(mc,Update); fse.stopLoop(Update); //3句等效
-
层级 “三明治” 问题
在 Flash Player / AIR 的运行时架构中: 顶层 (Top): 原生 Display List (CPU)。 底层 (Bottom): Stage3D (GPU/Starling)。 视频层: StageVideo (如果有的话,通常在最最底层)。 所以FSE框架的实现在此基础上将Stage3D层分成了3层 每一层级都是一个starling.display.Sprite容器 在舞台的第1层,也就是最底层,会有一层底层用户操作层,使用fse.starlingRootBack进行访问 在舞台的第2层,就是最核心的映射层,fse框架会将你的舞台结构映射到此层 在舞台的第3层,会有一层底层用户操作层,使用fse.starlingRoot进行访问 在CPU渲染层,也就是最顶层,因为CPU层覆盖在GPU渲染层之上,一些存在兼容性问题的元件可以使用cpu层来进行渲染 -
高级API
fse.cpu(mc:MovieClip); fse.ban(mc:MovieClip); //与上一行等效 fse.isIgnore(mc:MovieClip):Boolean //获取mc对象的特例状态 //使用cpu渲染 /* 为了解决某些兼容性问题, 被用户标记的剪辑将不会在FSE的Starling舞台上被渲染,而是直接在传统舞台上渲染 注意,这么做会导致mc始终显示在GPU层的上方 */ fse.setKeyRole(mc:MovieClip); //将当前的影片剪辑设置为关键角色 fse.getKeyRole():String //获取设置过的关键角色 /* fse框架允许用户注册一个关键角色剪辑,一旦这个剪辑产生变化Starling马上渲染下一帧 这个方法一般应用与哪些与玩家输入操作相关的剪辑,比如鼠标跟随,键盘控制移动等 用于实现用户一输入就马上相应的效果 */ fse.setNodeCached(mc:MovieClip,v:Boolean); //设置缓存特例 /* 设置特例后,这个对象的以及当前容器节点的所有肉子节点(Bitmap、Shape)都不参与缓存系统 */ fse.gpuClear(); //强制清楚缓存面板上的所有缓存 //=====与Starling混用============================================================ fse.starlingRoot:starling.display.Sprite //为用户提供的Starling舞台根容器顶层(推荐用于添加粒子效果以及特效) fse.starlingRootBack:starling.display.Sprite //为用户提供的Starling舞台根容器底层(推荐用于添加背景等底层内容元件)
package fse.conf
{
import flash.system.Capabilities;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.display.DisplayObject;
import flash.display.SimpleButton;
import starling.textures.TextureSmoothing;
/**
* FSE 全局配置类
* 用于统一管理逻辑帧率、渲染精度等静态参数
*/
public class Config
{
// -------------------------------------------------
// 舞台显示相关(Stage Display)
// -------------------------------------------------
public static const DEVICE_W:uint = Capabilities.screenResolutionX; //设备窗口大小
public static const DEVICE_H:uint = Capabilities.screenResolutionY;
public static const FULL_SCREEN:Boolean = false;
public static var AUTO_ADAPT:String = "AUTO"; //舞台自适应方案
// ***可选项
//"FULL" 填满视窗适配,无论如何填满视窗(不保证舞台比例)
//"SYN_HEIGHT" 舞台画面紧贴屏幕上下两边,并保证舞台比例
//"SYN_WIDTH"舞台画面紧贴屏幕左右两边,并保证舞台比例
// "AUTO" 缩放边界自动决定,始终保持舞台比例
// "NONE" 框架不干预适配行为,但依然会控制渲染窗口和舞台高宽同步(我也不知道这个选项有什么用)
//舞台对齐,除非有特殊开发需要不然一般不修改此项设置
//特殊说明,如果舞台自适应方案与此项冲突,则此项设置无效(比如你的舞台始终紧贴左右两边,那你又设置了左对齐,那就失去意义了)
public static var ALIGN_X:String = "CENTER";
// ***可选项
//"CENTER" 锚定屏幕中央位置,这是最推荐的设置
//"LEFT" 紧贴屏幕左侧
//"RIGHT" 紧贴屏幕右侧
public static var ALIGN_Y:String = "CENTER";
// ***可选项
//"CENTER" 锚定屏幕中央位置,这是最推荐的设置
//"TOP" 紧贴屏幕上侧
//"BOTTOM" 紧贴屏幕下侧
public static const BG_COLOR:uint=0x211F20; //背景颜色
public static const EXT_FPS:uint=400; //Starling 最高帧限(通常设置为超过大多数屏幕刷新率)
// -------------------------------------------------
// 画面配置相关(Quality)
// -------------------------------------------------
public static var TEXTURE_SMOOTHING:String = TextureSmoothing.BILINEAR; //纹理平滑设置
// ***可选项
//TextureSmoothing.NONE (不平滑/最近邻插值) ###如果你的游戏的像素风格游戏推荐使用这个选项
//TextureSmoothing.BILINEAR (双线性过滤 - 默认值)
//TextureSmoothing.TRILINEAR (三线性过滤)
// -------------------------------------------------
// 缓存策略相关(Cache)
// -------------------------------------------------
public static const CACHE_THRESHOLD:uint = 3; //持久化阈值:如果场景同时出现超过这个数的同样纹理,那么这个纹理将被持久化存入缓存
public static const WATCHER_COLD_TIME:uint = 20;
// -------------------------------------------------
// 调试相关(Debug)
// -------------------------------------------------
public static const TRACE_CORE:Boolean = false; //无关紧要的一些启动信息
public static const TRACE_DEBUG:Boolean = true; //Starling GPU性能信息
public static const TRACE_WATCHER:Boolean = false; //节点数监控调试信息
public static const TRACE_NODE:Boolean = false; //单个节点行为调试信息
public static const TRACE_CACHE:Boolean = false; //缓存器信息
// ------------------------------------------------
// 游戏配置 (Game)
// ------------------------------------------------
public static const STOP_ALL:Boolean = true; //在接管后默认暂停所有影片剪辑
private static var _logicFrameRate:int = 60; //逻辑帧率
private static var _logicTimestep:Number = 1000.0 / _logicFrameRate;
private static var _case_render:Array = [isInputText,isSimpleButton]; //经过这些断言判断为真的话不用starling渲染
//输入文本断言
private static function isInputText(obj:DisplayObject):Boolean {
if (obj is TextField) {
var textField:TextField = obj as TextField;
// TextFieldType.INPUT 是静态常量,值为 "input"
return textField.type == TextFieldType.INPUT;
}
return false;
}
//按钮断言
private static function isSimpleButton(obj:*):Boolean{
// 检查是否为flash.display.SimpleButton实例
return obj is SimpleButton;
}
// ------------------------------------------------
// 公共参数
// ------------------------------------------------
/**
* 最大的追赶时间 (毫秒)
* 如果设备极度卡顿,每一帧最多只处理这么长时间的逻辑,防止死循环
* 默认 200ms (即最差情况每帧追赶约 12 个逻辑帧)
*/
public static var maxAccumulator:Number = 200;
/**
* 纹理缩放系数 (未来用于支持 Retina/高清屏)
* 1 = 原倍, 2 = 2倍高清
*/
public static var contentScaleFactor:Number = 1.0;
// ------------------------------------------------
// Getter / Setter
// ------------------------------------------------
public static function get case_render():Array{
return _case_render;
}
/**
* 目标逻辑帧率 (默认为 60)
* 修改此值会自动更新 timestep
*/
public static function get logicFrameRate():int
{
return _logicFrameRate;
}
public static function set logicFrameRate(value:int):void
{
if (value < 1) value = 1; // 安全限制
if (_logicFrameRate == value) return;
_logicFrameRate = value;
_logicTimestep = 1000.0 / _logicFrameRate;
trace("[FSE_Config] Logic FPS set to: " + _logicFrameRate + " (Timestep: " + _logicTimestep.toFixed(2) + "ms)");
}
/**
* [只读] 每一逻辑帧的时间步长 (毫秒)
* 例如 60fps = 16.666ms
*/
public static function get logicTimestep():Number
{
return _logicTimestep;
}
}
}
- 如果你的项目中出现兼容性问题,请按需求添加渲染特例
-
我们都知道,使用starling的flash项目,传统的cpu渲染会在starling的上层,并且一旦开始GPU渲染,flash项目帧限会从60fps到与刷新率同步例如,在400hz的电脑上可以跑到400fps
-
那么我们做一个游戏混合渲染方案
- 像简单静态的内容,比如高清背景图片等,或者简单移动的场景图片,我打算使用starling进行渲染。
- 如果复杂的动态内容,比如动画中右嵌套的动画,比如枪械开火动画绑定在人物手部动画上,那我们将使用传统的Flash cpu的方式渲染,但我们会将cpu渲染层所有的传统剪辑强制隐藏(visible=false),只有当flash.display.movieclip更新(其中某个影片剪辑或者子剪辑的帧发生变化)后则更新draw出位图数据作为显示纹理,再由starling进行渲染。
-
那么到了这里肯定会有小朋友要问了
- “Starling框架不使用图集并高频率上传纹理不会导致性能爆炸吗?”
- “怎么说好呢,我们这个框架本来就不是用来开发专业应用的,对于个人级别的项目,如果您的游戏中能保证动态更新部分较少,并且能控制纹理大小(控制在几百像素以内),那么我认为不会影响性能 况且,我制作的框架具有一些性能优化算法,能解决一部分你的疑虑”
-
这么说来的话,我实际做的也就是这几件事情
-
逻辑驱动控制
- 比如一些预期的动画只能由60fps播放
- 一些逻辑代码只能以60fps执行
- 使用框架后的一切逻辑和动画都要由新的60帧逻辑驱动
- 不能因为主观设备(屏幕刷新率)的不同导致程序运行效果/动画效果有出入
-
舞台监控
- 对Flash传统Stage生成实时管理的场景结构树
- 场景树上的每一个节点对应着一个flash.display.DisplayObject对象,并存储他们的所有状态(例如currentFrame,visible,alpha,x,y,scaleX,scaleY,rotation等),最关键的,锚点信息
- 对场景树进行遍历检查,并比对每个子剪辑是否发生变化,变化分为两种
- 1. 形变(影片剪辑的帧数,子剪辑集合改变,内容改变等)
- 2. 属性值改变(transfer属性改变,同步数值即可)
-
Starling渲染
- 对于flash传统stage生成实时管理的场景树,Starling舞台要实时同步场景树上影片剪辑发生的变化,同步到Starling舞台上
- 在同一父剪辑内的所有starling.display.DisplayObject对象的图层顺序可以按照传统剪辑同步过来的childrenIndex值按照大小进行显示图层排序
-
纹理缓存管理
- 为了减少DrawCalls,本框架使用MaxRects算法进行二维空间uv装箱
- 对场景里的每帧/每个DisplayObject对象,都生成纹理相应的唯一哈希字符串
-
输入事件转发
- 由于CPU层的所有内容被隐藏,这意味着注册在他们身上的鼠标点击事件都将失效
- 我设计的输入转发器可以转发鼠标输入事件到对应的flash.display.DisplayObject身上
-
- 包类结构为
- fse
- core包
- FSE (核心静态类,接口封装)
- FSE_Kernel (核心静态类的真身)
- FSE_Manager (脏活累活都他干,此类需要实例化,使用单例模式)
- FSE_Input (输入事件转发器)
- display包
- Watcher (场景树监控)
- Node (存储单个影片剪辑详细信息,并包含Node子集)
- Scanner (用于扫描传统舞台上的场景树)
- Controller (动画剪辑逻辑控制器)
- StatusSaver (状态保存器)
- events包
- FSE_Event (这个其实作用不大,就是写一些事件常量,比如FIX_ENTER_FRAME)
- conf包
- Config (配置类)
- starling包
- StarlingMain (初始化Starling舞台)
- StarlingVO (Starling影片剪辑根)
- StarlingManager (负责同步并管理)
- cache包
- AtlasPage (图集分页管理器)
- CacheManager (纹理缓存管理器)
- Cache (纹理缓存实体类)
- utils包
- Hash (BitmapData快速哈希)
- MD5 (散列加密)
- core包
- 作者: undefined (一位有理想的独立游戏制作人)
- 微信: hbx098hbx123 (欢迎交朋友)
- 邮箱: 2199182141@qq.com
- 你可以添加作者微信,反馈BUG或者建议
祝大家2026年马到成功,代码会老,但创造的心永远年轻。
如果这个框架,能让你在未来的某天,更轻盈、更自由地做出心中所想,那便是它全部的意义。
感谢 AS3,感谢仍在这里的你。
2026.1.29
于一个即将春暖花开的日子前