The effect is basically a cluster effect where when you click on an object any similar objects cluster around it, and other objects get out of the way. I approached this problem by utilizing a node system. All nodes have sibling nodes that are “similar” to it and should cluster around. With a little math we get this result:
To attack this problem I started with a construct of two basic classes:
The first, Node.as, is very simple. It extends the Sprite class so that it can contain DisplayObjects and Graphics, and have a position. On top of that it contains an array called “_siblings”. This is a list of all other nodes that are considered similar to it. I protected this array so that way you can only add or remove objects of type “Node” to/from it. We don’t want any other kind of object appearing as a sibling or the engine would break! There is also a method for retrieving a clone of the array of all siblings. We don’t want to hand out a direct reference to this array or it would allow other objects to put stuff in the array that are not of type Node. This is one of the downsides to working with untyped arrays in AS3. In the latest version of Actionscript Adobe has added typed arrays, which will allow much more freedom with storing objects in these kinds of lists.
Next we have the Engine, ClusterEvent.as, in which we perform all the actual work to move nodes around. The engine is far more of a complex class then Node.as. So let’s dig into this class to get a look at what it does exactly.
Note I protect ALL properties of my Classes. This is a good practice for any developer to work on doing. This gives you the coder the freedom to control any and all accessibility to your properties, and seperate the internal variable name from the external property name. Note how I use the underscore for all internal properties, but the getter/setter names for said properties are more descriptive and lack the underscore.
Static constant properties on the other hand I keep public, they can’t be changed anyways because they are constants!
In the engine I store several properties:
- _stage – a reference to the stage that should be passed in the constructor method. Because the engine isn’t a DisplayObject but needs access to the stage we need this
- _nodes – an array of all the nodes in the node tree for this engine
- _activeNode – an internal property that refers to the node being dragged at the moment, if any
- _siblingPositions – a Dictionary of points that describe where all the siblings of the activeNode should be with respect to the activeNode
The first few methods in my Class are just getter and setter methods to access any protected variables I would like to be made public. Note that some of these properties perform extra tasks in setting the related protected value. For instance if I change the reference to the stage to something new I should remove all event listeners from the old stage reference first. Or when I hand out a copy of the node tree, I don’t want to give direct access to it, but instead a clone of the node tree.
I follow this up with two methods to start and stop the engine.
The algorithm is fairly basic, but is made up of several methods to facilitate all the things that occur. First their are the event Listeners. At the end of the Class I have 4 methods for adding and removing events. There is a MOUSE_DOWN event set to each node in the node tree, and two events added to the stage: ENTER_FRAME for updating and lerping positions, and MOUSE_UP to stop dragging any active nodes.
There is also a small group of repetetive functions I abstracted from the main algorithm just to clean the code up. This includes: generating the Dictionary of points around the activeNode for the sibling cluster; lerping the position of a node to a specific point; and filtering the node tree of any siblings and returning a cloned array.
Finally there is the Algorithm for the effect. This is made of three parts:
- grabNewNode – this adds a reference to the recently clicked active node, and generates a cluster of points around itself for all sibings.
- dropCurrentNode – this removes any reference to an active node when the mouse is released. It also clears the dictionary of points.
- updateNodePositions – Here we update the positions of all nodes in the node tree with respect to the activeNode
When updating the positions we do 3 basic steps:
- Update the activeNode – find the mouse position and lerp the currently active node toward it
- Update sibling nodes – grab a copy of the array of siblings for the currently active node, compar it to the dictionary, and update their positions
- Update non-sibling nodes – filter the node tree of all siblings and then update their positions if and only if they are to close to the active node.
And that is it! In the package I’ve included 2 more classes though. The Main.as file contains an instance of all the nodes and the engine, and the BoxBug.as Class extends the Node.as Class so as to add some visual information to the Nodes.