One of the recurring questions in the Sandy forum is this.
”I want to create a MovieSkin with buttons, and when the user clicks on a button I want something to happen. Can I do that?”
The simple answer is no!
The reason is, that when you take your MovieClip with one or more Button(s) and use it in a MovieSkin, the whole clip, including the buttons is converted into a bitmap.
So what you see as a skin on a 3D object, is no longer a MovieClip with Button(s), but an image of it. The conversion is done, so we can get the perspective distortion, when we look at the object from different angles.
All Button functionality is lost.
This simple answer is a bit boring, so let's take a look the alternative answer. Yes you can! At least you can fake ( or recreate ) the Button.
All Sandy objects can react to mouse movements and mouse clicks, once we enable object events. What follows describes how to fake buttons on a MovieSkin using the Sandy 1.1 library. For later versions the code will differ more or less.
So let's craft that fake button movie clip. We want this clip as a skin on a square plane or one side of a cube. You may generalize this later. We start by creating a new symbol and give it the behavior of a MovieClip. We export it for ActionScript and give it a linkage ID “fakeButtonClip”. We use a Stage of 400 by 400 pixels.
To make the clip visible we draw a rectangle with some nice color and make it 400 by 400 pixels. It is important that the registration point is in the upper left corner.
Now we want to add the fake Button in the middle of the clip. We may draw our button with the rectangle tool, import an image, or create whatever rectangular shape we want. We have a great freedom here, because we will not make it into a real Button anyway.
Now is the time to decide on the mode and quality of the 3D object, so we can fit the button shape to the faces (polygons). The quality setting will determine how many faces we get. In this case, let's set q = 4 to get a 4-by-4 grid, for a total of 16 faces. We're going to create a Plane3D with the 'tri' mode. While the quad mode would be simpler, you might get really bad distortions as you look at the plane from different directions. This means that each of the quadratic divisions of the plane will be divided into two triangular faces, for a total of 32 faces (numbered 0 through 31) as visible in the sample SWF file below.
Back now to the drawing board, I'll switch on the grid in the Flash IDE and set it to 50 px division. Then I will paste the button onto the nicely colored square, so it occupies to rectangular grid divisions.
I first tried to use a Button from the Components library, but on the plane the label became illegible, and a clone of the label text appeared floating above the plane. So I decided to create my own fake button. Here is the fakeButtonClip with a rectangle with rounded corners and a text string forming the button shape.
When you use this MovieClip in a MovieSkin, the text added on top of the button square, will somehow break away, so you have to convert the text to graphics. You do that using Break Apart, first once for the whole text string and then again for each character.
For some primitives Sandy 1.1 flips the clip horizontally before pasting it to the faces. The text will show up mirrored.
You can fix this by mirroring the text horizontally within the MovieClip.
To enable and use our fake button, we have to enable object events on all faces making up the plane, and add a mouse listener to each face.
// Start by creating a Plane3D primitive. var plane:Object3D = new Plane3D(300,300, 4, 'tri');
// We need a holder clip to hold the clip we get from the library
var holder:MovieClip =
this.createEmptyMovieClip("holder", this.getNextHighestDepth());
holder.attachMovie("fakeButtonClip", "fake", 1);
holder._alpha = 0; // Hide the MovieClip on Stage
// Now create the skin and apply it to the plane
var skin:MovieSkin = new MovieSkin( holder );
plane.setSkin( skin );
// Show the back side the default skin
plane.enableBackFaceCulling = false;
// Loop through the faces, enable object events and add us as listener
faces = plane.getFaces();
for ( var i = 0; i < faces.length ; i++ ){
faces[i].enableEvents(true);
faces[i].addEventListener( ObjectEvent.onPressEVENT, this, onPress );
}
After creating the plane, we get the MovieClip with our fake button from the library and use it in a MovieSkin set as the front skin of the plane.
We may also disable backface culling, to be able to see the back side of the plane. Then comes the interesting part. We get the array of faces by calling getFaces() on the plane, and then loop through the array, enabling object events on each of them, and adding ourselves ( this ) as subscribers to the ObjectEvent.onPressEVENT. This event is broadcast every time the user clicks on the face. The event handler is the onPress() method, which we define next.
// The onPressEVENT event handler for the faces
function onPress(event:ObjectEvent ){
var obj:TriFace3D = event.getTarget();
var id = obj.getId();
trace("Face: " + id );
report.text = id;
if (id > 9 && id < 14)
hit.text = "You clicked the button";
else
hit.text ="";
}
When the event occurs, this method is called with an ObjectEvent object as the “event” argument. The event object carries a reference to the Face that was clicked, and we get it by calling its getTarget() method.
But the plane has a lot of faces and only a few of them represent the button, so how do we know if the user clicked the button or some face, that doesn't belong to the button? Fair question, don't you think?
I have superimposed an extra plane on the one with our fake button and dressed in a SimpleLineSkin, just to show the relationship between the triangular faces and the button graphics.
// Overlapping reference plane just to show the grid of faces var plane1:Object3D = new Plane3D(300,300, 4, 'tri'); var lineSkin = new SimpleLineSkin(0.5, 0x05113F, 60);
Every face in the whole Sandy world will have a unique index, which we can use to identify the face. You have to figure out which of the triangular faces are covered by the button square. I have used the trace() call above to do that. It shows that the triangular faces 10, 11, 12 and 13 are covered by the button. The if .. else clause here is just used to write a string to a text field on stage. I'm sure you can think of something a little bit more exciting to do with that piece of information.
The rest is normal setup of a Sandy world. The plane is rotated to give it an interesting angle, and I have added the facility to move the camera around, using the cursor ( arrow ) keys and the Home and End keys ( PC ).
You can give it a test run above or download the SWF file from here and download the ActionScript.
Use cursor keys, Home, and End to move the camera around.