Duplicating Movie Clips Completely
The duplicateMovieClip() function, and its method cousin, MovieClip.duplicateMovieClip(), both do what their names imply: they duplicate movie clips. But there’s a catch. Let’s read a small excerpt from the ActionScript Language Reference for each feature.
Function: Variables in the original movie clip are not copied into the duplicate movie clip.
Method: Variables in the parent movie clip are not copied into the duplicate movie clip.
Aha. So the artwork gets copied; native class members, like MovieClip._x, _y, _width, _height, etc., get copied; but variables added by you, the developer, do not. This includes any functions assigned to the original clip’s events, such as …
originalClip.onEnterFrame = function() {
this._rotation++;
}
… which would simply not exist in the duplicated clip. Technically speaking, these variables are dynamic properties you add to each MovieClip instance, and maybe that’s the rationale for this fact of life. I’m just guessing, though. An important exception to the above is that Flash 5–style on() and onClipEvent() handlers are copied to the duplicate (p. 520, Moock’s ActionScript for Flash MX: The Definitive Guide, Second Edition; or just test for yourself).
Of course, this circumstance may not bother you. You might be content to duplicate movie clips and either re-assign your variables and event handlers to each copy or simply let them vanish. But if you want to clone your movie clips, you’ll have to use another approach.
Depending on the complexity of your SWF, a custom class might be a good route. Since Flash MX 2004 (aka Flash 7), ActionScript has supported class-based object-oriented programming, which allows you to write your own datatypes. You could, for example, write a MovieClip2 class that does everything a movie clip does, plus more. Instead of using a movie clip, then, you could use a MovieClip2 instance, which you would presumably have provided with a clone() method.
This isn’t to say custom classes are magic — they certainly can’t do anything outside the bounds of the ActionScript language — but they illustrate a liberating ideal: if ActionScript doesn’t provide a particular feature you need, try to “roll your own,” class or not. (The topic of custom classes is an exciting one, but outside the scope of this particular article. For a great introduction to the possibilities, check out Joey Lott’s three-part “ActionScript 2.0 Primer” and Colin Moock’s Essential ActionScript 2.0.)
To illustrate, I wrote a quick function that handles the kind of “cloning” we’re talking about, so for what it’s worth, let’s take a look at cloneMovieClip().
function cloneMovieClip(
target:MovieClip, newName:String,
depth:Number, initObject:Object
):MovieClip {
target.duplicateMovieClip(newName, depth, initObject);
for (var prop:String in target) {
target._parent[newName][prop] = target[prop];
}
return target._parent[newName];
}
This function accepts four parameters (the last one is optional) and returns a movie clip reference, so it works very much like MovieClip.duplicateMovieClip(), except it doesn’t belong to a class, so it’s a stand-alone function. To use it, copy/paste the above into a frame of your timeline, then invoke the function when needed.
// Existing movie clip with instance name originalClip
// Add an arbitrary new variable; here a string
originalClip.tunisianFolkRock = "Faouzi Ben Guamra";
// Heck, add another; this time, an array
originalClip.souvenirs = new Array("ibrik", "coffee mill", "hookah");
// Make this clip continuously rotate
originalClip.onEnterFrame = function() {
this._rotation++;
}
// Show that the new properties exist in the original
trace(originalClip.tunisianFolkRock);
trace(originalClip.souvenirs);
// Clone
cloneMovieClip(originalClip, "newClip", 0, {_x:300});
// Show that the new properties exist in the clone
// Note, also, that the clone rotates
trace(newClip.tunisianFolkRock);
trace(newClip.souvenirs);
Remember, that last parameter is optional. The curly braces, {}, are a shorthand way of indicating an object (short for new Object()). This parameter allows you to set values for native MovieClip properties in the clone. In the above example, I set the MovieClip._x property of the new clip to 300. In addition, I could have changed its vertical position and opacity like this …
{_x:300, _y:45, _alpha:80}
… which means, strictly speaking, that the new “clone” isn’t an exact duplicate at all! Ah, well. We’re bending these concepts a bit to accomplish what we really mean.
So, let’s step through how this works. The heart of the function is its for..in statement.
for (var prop:String in target) {
target._parent[newName][prop] = target[prop];
}
The for..in statement cycles through a given object, enumerating its properties. In the above, prop could have been named anything, but I chose “prop” because it describes what I’m collecting. Effectively, for..in says, “Hey, target object, show me what you’ve got in your pockets,” and the object hands over each item, in turn. Each property is named as a string, so to resolve the string back into a useful reference, I’m using the array access operator as described in How to Reference Objects Dynamically elsewhere in this blog — using it three times, in fact. First, _parent[newName] resolves the string value of newName into an actual reference to the new movie clip; second, [prop], suffixed to the first, resolves to the actual property named by the prop variable; finally, target[prop] resolves to the same property in the original clip.
April 14th, 2006 at 11:13 am
time and time again i have revisited this issue….how to really completely CLONE a movieclip. the method above is something that i often come up with…..however i have discovered a flaw in it….and it always confuses me until i figure it out again. here it is:
ok, so we “cloned” it right? i have discovered that the two are very much still linked together. for instance….AFTER cloning, add in the line:
originalClip.souvenirs[1] = “CHANGED”;
then print them BOTH out. you see that they BOTH changed.
now i have had enough programming to know what is going on here. any variable is passed by reference unless it is a primitive data type (int, string, etc); objects, arrays, movieclips, etc are all passed by reference. in other words…..there is still only ONE “souvenirs” array, it is stored on the originalClip and the newClip has a pointer to that.
so my question is this…..how do we REALLY clone something? still has yet to be determined…..i have played around with some pretty intense recursive functions that check to see if it is a primitive type or not, if it is not then it needs to create a new object, and go recursively copy all of it’s primitives as well. i don’t know if this makes sense, and i haven’t really done it in depthly yet.
i will post the solution if i ever have the time to figure it out, in the mean time….anyone else? what do you think David?
April 14th, 2006 at 12:58 pm
Daniel,
You bring up a good point. Sure enough, you’re right — and, of course, your passed-by-reference observation makes perfect sense. That said, the
MovieClipinstance is, I maintain, indeed cloned: the original points to an object by reference (here, an array), and the clone points to the same object by reference.Here again, what we mean by “clone” is kind of a cheat, isn’t it? We want a conceptual clone, not an actual one. We want cloned movie clips with cloned composite (non-primitive) properties.
Your comment has me thinking. If you figure something out, please post again.
I’ll do the same.
May 18th, 2006 at 9:06 pm
I think I figured something out…. My code will only clone the core classes of ActionScript… but adding in more isn’t that difficult if needed.
To fix your code, just change the line in the for loop:
target._parent[newName][prop] = copyMe(target[prop]);
The problem with copying is that when a property includes a reference to a clipC (say within clipA) and then you clone clipA to clipB. Should the reference to clipC be the one in clipA or the duplicated one in clipB?
Also, say clipA has a non primitive variable, A, and a pointer to it, B. When you clone, how will the code know that B should point to the duplicated A, and not make a clone of A and another of clone of A in B. My code will do the latter. Just like in real life, can cloning really be perfected???
Here’s my copyMe function… (hasn’t been tested completely though!)
function copyMe(obj) {
var n = {};
if(obj == undefined || obj == null){
return obj;
}else if (obj instanceof String || typeof (obj) == ’string’) {
n = (obj === obj.valueOf()) ? obj : new String(obj.valueOf());
} else if (obj instanceof Number || typeof (obj) == ‘number’) {
n = (obj === obj.valueOf()) ? obj : new Number(obj.valueOf());
} else if (obj instanceof Boolean || typeof (obj) == ‘boolean’) {
n = (obj === obj.valueOf()) ? obj : new Boolean(obj.valueOf());
} else if (obj instanceof Date) {
n = new Date(obj.valueOf());
} else if (obj instanceof Array) {
n = obj.slice();
} else if (obj instanceof Error) {
n = new Error();
}
if (obj instanceof MovieClip || obj instanceof Button || obj instanceof TextField) {
n = obj;
} else {
for (var x in obj) {
n[x] = copyMe(obj[x]);
}
}
return n;
}
June 21st, 2006 at 8:38 pm
Alright, I think I figured it out for the most part… Use:
resetRefs();
for (var prop:String in target) {
if(target._parent[newName][prop] == undefined){
target._parent[newName][prop] = copyMe(target[prop]);
}
}
My new code will keep “pointers” intact, e.g.:
resetRefs();
o = {};
o.a = new Date();
o.b = o.a;
o.c = new Date();
trace(’a: ‘+o.a); // these 3
trace(’b: ‘+o.b); // all show up
trace(’c: ‘+o.c); // the same
p = copyMe(o);
p.a.setTime(0);
trace(’a: ‘+p.a); // only this
trace(’b: ‘+p.b); // and this are the same
trace(’c: ‘+p.c); // this value didn’t change
Here is the necessary code to make this all work:
Array.prototype.indexOf = function(x){
for(var y in this){
if(this[y] === x){
return y;
}
}
return -1;
};
function resetRefs(){
_global.refs = [];
_global.dups = [];
}
function copyMe(obj) {
var n = {};
var i = _global.refs.indexOf(obj);
if(i!=-1){
return _global.dups[i];
}else{
if (obj == undefined || obj == null) {
return obj;
} else if (obj instanceof String || typeof (obj) == ’string’) {
n = (obj === obj.valueOf()) ? obj : new String(obj.valueOf());
} else if (obj instanceof Number || typeof (obj) == ‘number’) {
n = (obj === obj.valueOf()) ? obj : new Number(obj.valueOf());
} else if (obj instanceof Boolean || typeof (obj) == ‘boolean’) {
n = (obj === obj.valueOf()) ? obj : new Boolean(obj.valueOf());
} else if (obj instanceof Date) {
n = new Date(obj.valueOf());
} else if (obj instanceof Array) {
n = obj.slice();
} else if (obj instanceof Error) {
n = new Error();
}
if (obj instanceof MovieClip || obj instanceof Button || obj instanceof TextField) {
n = obj;
} else {
for (var x in obj) {
n[x] = copyMe(obj[x]);
}
}
_global.refs.push(obj);
_global.dups.push(n);
return n;
}
}
October 22nd, 2006 at 8:44 pm
Hey cool, thanks for the code.
February 19th, 2007 at 9:25 am
(Since I’m into the MovieClipLoader at present) What about duplicating a movieclip which has an image loaded into it, completely? When you duplicate a movieclip which contains an image loaded by loadClip() this content won’t duplicate with it.
February 19th, 2007 at 12:47 pm
Tiemen,
Wow, good catch! ActionScript 3.0 might have a way around this, but I believe you’re right: in AS2, loaded external content, such as a JPG, almost certainly would not “clone” in the sense discussed above. I can’t think of a way to check if external content has even been loaded, so this may be the one final obstacle that keeps AS2 object cloning almost, but not quite, an achievable goal.
February 22nd, 2007 at 5:42 am
Is there by any chance another way to re-use images that have been loaded? What you’d really want is a loader that puts the loaded images into the ‘library’, so that from the moment they’ve been loaded they can be accessed freely by any function inside the movieclip.
I’m currently creating a movie that has a real time reflection of the images loaded. Loading the images twice feels incredibly stupid and costs a load of bandwidth…
:-/
February 22nd, 2007 at 6:38 am
I take that back. Flash just uses the browser cache as library, so though flash takes time to load the images from the cache into flash, it does not load the images twice. Splendid.