Last active
August 14, 2018 10:47
-
-
Save PierrotLL/0fef590efccb16394cbe9ac9350ef90a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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