Skip to content

Instantly share code, notes, and snippets.

@PierrotLL
Last active August 14, 2018 10:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PierrotLL/0fef590efccb16394cbe9ac9350ef90a to your computer and use it in GitHub Desktop.
Save PierrotLL/0fef590efccb16394cbe9ac9350ef90a to your computer and use it in GitHub Desktop.
package starling.extensions {
import flash.utils.Dictionary;
import starling.display.DisplayObject;
import starling.display.DisplayObjectContainer;
import starling.display.Mesh;
import starling.display.Sprite;
import starling.events.Event;
import starling.rendering.Painter;
import starling.text.TextField;
import starling.textures.Texture;
/**
* @author PierrotLL
* Designed for Starling 2 rendering system.
*
* This container allows you to assign a layer number to every child Mesh (or DisplayObjectContainer that you defined as final)
* It changes the rendering order of children without changing your display list structure.
* The rendering requires a bit more CPU time, but it can significantly reduce GPU draw calls
*
*
* Public methods:
*
* - getChildLayer
* @param child
* @throws ArgumentError if param is not a child
* @return the layer of the given child
*
* - getLayersList
* @return an ordered list of layers in use
*
* - setChildLayer
* @param child:DisplayObject
* @param layer:int
*
* - setChildrenLayer
* Define a behaviour that assignes a layer to each final child (even thoses added later)
* @param behaviour:Function(DisplayObject -> int)
*
* - setChildrenLayerByTexture
* An example of setChildrenLayer usage
* @param textures:Vector.<Texture>
*
* - setFinalContainer
* Add a rule that defines matching DisplayObjectContainers as final (their children will not be parsed)
* @param filter:(Class or Function(DisplayObjectContainer->Boolean) or DisplayObjectContainer)
* @throws ArgumentError if param is not a valid filter
*/
public class LayeredContainer extends Sprite {
protected var _children:Dictionary; /** map child:DisplayObject -> layer:int, list of final display objects (meshes or final containers) */
protected var _behavior:Function; /** function child:DisplayObject -> layer:int */
protected var _filters:Array; /** array of (Class, Function or DisplayObject), used to define containers treated as final display objects */
// cached objects to avoid allocation
protected var _visible:Dictionary; /** map child:DisplayObject -> visible:Boolean, used to restore previous visibility altered by render process */
protected var _layers:Vector.<int>; /** list of used layers */
public static const DEFAULT_FILTERS:Array = [TextField];
public function LayeredContainer() {
_children = new Dictionary(true);
_filters = DEFAULT_FILTERS.concat();
_visible = new Dictionary(true);
_layers = new Vector.<int>();
addEventListener(Event.ADDED, onChildAdded);
addEventListener(Event.REMOVED, onChildRemoved);
}
protected function onChildAdded(event:Event) : void {
if (event.target != this) {
var child:DisplayObject = event.target as DisplayObject;
var parent:DisplayObjectContainer = child.parent;
while (parent && parent != this && !(parent in _children)) parent = parent.parent;
if (!(parent in _children)) add(child);
}
}
protected function onChildRemoved(event:Event) : void {
if (event.target != this) {
remove(event.target as DisplayObject);
}
}
protected function add(child:DisplayObject) : void {
if (isFinal(child)) {
setChildLayer(child, getLayer(child));
} else {
var container:DisplayObjectContainer = child as DisplayObjectContainer;
for (var i:int = 0, l:int = container.numChildren ; i < l ; i++) {
add(container.getChildAt(i));
}
}
}
protected function remove(child:DisplayObject) : void {
var container:DisplayObjectContainer = child as DisplayObjectContainer;
if (container) {
for (var i:int = 0, l:int = container.numChildren ; i < l ; i++) {
remove(container.getChildAt(i));
}
}
delete _children[child];
delete _visible[child];
setRequireLayersUpdate();
}
protected function getLayer(child:DisplayObject) : int {
return _behavior ? _behavior(child) : 0;
}
protected function isFinal(child:DisplayObject) : Boolean {
if (child is DisplayObjectContainer) {
for each (var e:* in _filters) {
if (testFilter(child, e)) return true;
}
return false;
}
return true;
}
protected function testFilter(child:DisplayObject, filter:*) : Boolean {
return filter is Class ? child is filter
: filter is Function ? filter(child)
: child == filter;
}
protected function setRequireLayersUpdate() : void {
_layers.length = 0;
}
protected function updateLayersIfRequired() : void {
if (!_layers.length) {
var hash:Object = {};
for each (var layer:int in _children) hash[layer] ||= true;
for (layer in hash) _layers.push(layer);
_layers.sort(Array.NUMERIC);
}
}
/**
* Returns the layer of a child
* @param child A child display object
* @throws ArgumentError if param is not a child
* @return The layer of the given child
*/
public function getChildLayer(child:DisplayObject) : int {
if (!(child in _children)) {
throw new ArgumentError("child is not part of this LayeredContainer");
}
return _children[child];
}
/**
* @return ordered list of layers in use
*/
public function getLayersList() : Vector.<int> {
updateLayersIfRequired();
return _layers.concat();
}
/**
* Set the layer of a particular child
* @param child
* @param layer
*/
public function setChildLayer(child:DisplayObject, layer:int) : void {
if (child is DisplayObjectContainer && !(child in _children)) remove(child);
_children[child] = layer;
setRequireLayersUpdate();
}
/**
* Set layer of all children with a function.
* Also applied on children added later.
* @param behavior Function that returns a layer for a given display object. (function DisplayObject -> int)
*/
public function setChildrenLayer(behavior:Function) : void {
_behavior = behavior;
for (var child:DisplayObject in _children) {
setChildLayer(child, getLayer(child));
}
}
/**
* Group display objects by texture atlas to reduce draw calls.
* Set layer of each child depending on the concrete texture it uses (the texture of the first mesh for containers).
* Layer numbers start at 0. Children with no texture are put in layer -1.
* @param textures Predefined ordered textures list. Non-listed textures will replace first null slot in the list, or be added at the end if no slot is null.
*/
public function setChildrenLayerByTexture(textures:Vector.<Texture> = null) : void {
textures = textures ? textures.concat() : new Vector.<Texture>();
for (var i:int in textures) textures[i] &&= textures[i].root; // make sure we only have ConcreteTexture
setChildrenLayer(function (child:DisplayObject) : int {
if (child is Mesh && (child as Mesh).texture) {
var texture:Texture = (child as Mesh).texture.root;
var index:int = textures.indexOf(texture);
if (index < 0) {
index = textures.indexOf(null);
if (index < 0) index = textures.length;
textures[index] = texture;
}
return index;
} else if (child is DisplayObjectContainer) {
var container:DisplayObjectContainer = child as DisplayObjectContainer;
for (var i:int = 0, l:int = container.numChildren ; i < l ; i++) {
child = container.getChildAt(i);
var layer:int = arguments.callee(child);
if (layer >= 0) return layer;
}
}
return -1;
});
}
/**
* Add a rule to treat some containers as final display objects
* @param filter Class, Function(DisplayObjectContainer->Boolean) or DisplayObjectContainer
* @throws ArgumentError if param is not a valid filter
*/
public function setFinalContainer(filter:*) : void {
if (!(filter is Class || (filter is Function && filter.length == 1) || filter is DisplayObject)) {
throw new ArgumentError("filter must be a Class, Function or DisplayObject");
}
_filters.push(filter);
for (var child:DisplayObject in _children) {
var parent:DisplayObjectContainer = child.parent;
while (parent) {
if (testFilter(parent, filter)) {
remove(child);
add(parent);
parent = null;
} else parent = parent.parent;
}
}
}
override public function render(painter:Painter):void {
var firstLayer:Boolean = true;
updateLayersIfRequired();
for each (var layer:int in _layers) {
for (var child:DisplayObject in _children) {
if (firstLayer) {
if (_visible[child] && _layers.length > 1 && child.visible && !child.requiresRedraw) child.setRequiresRedraw(); // avoid child rendered from cache as invisible
_visible[child] = child.visible;
_visible[_children[child]] ||= _visible[child];
}
child.visible = _visible[child] && _children[child] == layer;
}
if (_visible[layer]) super.render(painter);
delete _visible[layer];
firstLayer = false;
}
for (child in _children) child.visible = _visible[child]; // restore visible value
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment