Mapping, as it's called, is the process of applying a texture map to a 3D model. Maps are applied to the model based on a set of coordinates known as UVW coordinates. They're really the same as XYZ, but named differently so you can tell what's being referred to. These coordinates tell the 3D engine how to "wrap" the texture onto the object. By creating the cards in an external 3D program, the mapping coordinates have already been applied. So the first step is to import the images that will be used to create the textures.
Import the eight textures t1.jpg through t8.jpg from the Lesson15\media folder on the CD into the internal cast.
The images have been named t1 through t8 to make them easy to reference with Lingo. With the images imported you can create a method that will create textures within the 3D world, from the images.
Select the next empty cast member and press Ctrl/Command+0 to open a script window. Name the script Main. Make sure the script is a movie script, and not a behavior. You can use the Script tab in the Property inspector to change to a movie script if necessary:
Create the following createTextures() method within the script:
on createTextures wrld = member("memory") repeat with cnt = 1 to 8 texName = "t" & string(cnt) wrld.newTexture(texName, #fromCastMember, member(texName)) end repeat end
First, a reference to the 3D member is stored in the local variable wrld. Within the repeat loop the texName is created by concatenating the letter "t" with the string value of the current loop iteration. This makes texName be "t1" through "t8," which of course corresponds to the names of the imported bitmaps. The new Texture command then creates textures t1 through t8 from the same named cast members. As you can see, naming the cast members this way makes it easy to automatically create the textures from them.
Once the textures have been created they can be applied, in random fashion, to the backs of the cards.
Within the Main script, create the assignTextures() method, as shown here:
on assignTextures wrld = member("memory") tList = [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8] randList =  repeat while tList <>  ind = random(tList.count) randList.add(tList[ind]) tList.deleteAt(ind) end repeat repeat with cnt = 1 to 16 modName = "c" & string(cnt) tex = "t" & string(randList[cnt]) wrld.model(modName).shaderList.texture = wrld.texture(tex) end repeat _global.solveList = randList end
First, tList is created and contains the numbers 1 through 8, each duplicated twice, because the same texture needs to be applied to two different cards. To randomize the list, an empty list is created and stored in randList. Next, as long as tList it not empty, the repeat loop will continue to execute. Within the loop a random index is created by using the random function on the count of tList. Next, the value within tList at position ind is added to the randListand then deleted from tList. By continuing this until tList is empty, randList will contain all of the items initially in tList, but in a random order. Let's walk through an iteration of this to see how randList becomes a random version of tList.
Initially, tList and randList appear like so:
tList = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8] randList = 
Next, a random index into tList is created:
ind = random(tList.count)
Because tList initially has 16 items, this is equivalent to:
ind = random(16)
Let's assume ind comes back as 5. The value of tList at position 5 is then added to randList:
Because the number 3 is the fifth item in tList, this can be restated as:
Finally, the value is deleted from tList:
This removes the first 3 from tList, leaving tList with 15 items in it. Because the list is not empty, the repeat loop continues.
ind = random(tList.count)
This is equivalent to:
ind = random(15)
Let's say ind comes back as 10 this time:
Because the number 6 is the tenth element in the list, randList now looks like so:
randList = [3, 6]
The tenth item is then deleted from tList, leaving 14 items remaining. This continues until tList has been emptied and randList contains the original 16 items, but in random order:
randList = [1, 8, 2, 7, 4, 5, 2, 3, 6, 5, 6, 4, 1, 3, 7, 8]
Developers often use this technique of creating one list with known values and then pulling values out at random locations to create a second, random list. If you were creating a card game, for instance, you might fill the original list with the numbers 1 to 52 in sequential order, representing the deck of cards, before randomizing it. With the list in random order and stored in randList, the next repeat loop within the method executes:
repeat with cnt = 1 to 16 modName = "c" & string(cnt) tex = "t" & string(randList[cnt]) wrld.model(modName).shaderList.texture = wrld.texture(tex) end repeat
This loop counts from 1 to 16 and sets modName to "c1" through "c16" as it does. At the same time, tex is set to "t" and concatenated with the value in the random listrandList. Do you see how this works?
Let's assume randList = [2, 4, 7, 4, 8, 6, 1, 3, 2, 5, 8, 7, 3, 1, 5, 6]
Then as the loop progresses modName and tex would look like so:
And so on, through 16. As you can see, cards 2 and 4 would have a match. Now, the final line within the loop is what does the actual work of placing each texture into the appropriate place in the card:
wrld.model(modName).shaderList.texture = wrld.texture(tex)
I mentioned that a shader could contain up to eight textures, and that the material given each card within 3ds max had three sub-materials in it.
That second sub-material is the material placed on the back of the card, and is accessed, in Director, by referencing the second shader, or shaderList. By setting the texture of each card's second shader to the texture from the random list, all eight textures are placed onto the backs of the cards in random fashion.
The final line in the assignTextures() method creates a global variable solveList and sets it equal to the randList. This will be used later to detect matches.
You're nearly ready to see these two methods in action. However, all the cards are showing their tops, so you won't be able to see what happens unless you flip them over. Let's create a quick test method that will allow you to flip all of the cards at once.
Create the following flipAll() method within the movie script:
on flipAll wrld = member("memory") repeat with cnt = 1 to 16 wrld.model("c" & string(cnt)).rotate(0,180,0) end repeat end
As you can see, all that this handler does is rotate model "c1" through "c16" by 180 degrees in the Y axis, effectively flipping each card over.
Rewind and play the movie, then open the Message window. Enter the following:
All the cards flip over and display the same image on their back side. Now, create the textures within the 3D world:
Visually nothing happens, but each of the eight bitmaps in the cast has been used to create textures within the 3D world. Now apply the textures to the cards:
When you hit Enter the cards all appear with different images on them, although the same image is always shared by two cards:
You can call assignTextures() repeatedly, and the textures on the cards will be randomized each time you do.
Stop the movie and right-click the 3D sprite either in the Score or on the Stage. Select Script from the context menu. Replace the default mouseUp with the following beginSprite handler:
property wrld on beginSprite me wrld = sprite(me.spriteNum).member wrld.resetWorld() createTextures() assignTextures() end
First the wrld property is defined and used to reference the 3D member. Next the resetWorld() command is used, on the 3D cast member. The resetWorld command resets the world to its original state, as if it had just been imported. This clears out the textures that you created using the createTextures() method. This is necessary because if the textures exist and you try to create them again, you will receive an error telling you the same named texture already exists.
Once the world has been reset, createTextures() and assignTextures() are called, readying the game board for play.
Stop the movie.
Let's look at one final thing regarding the shaders attached to the cards.
Import top_dm.jpg from the Lesson15\media folder on the CD. Rewind and play the movie. Open the Message window and enter the following:
t = member("memory").newTexture("test", #fromCastMember, member("top_dm"))
As you know, this creates a new texture named "test" within the 3D world, using the newly imported image. Enter the following:
member("memory").model("c1").shader.texture = t
Nifty, eh? Instantly, every card's top image changes. This is because all of the tops of the cards share a single shader, because the sub-material was not uniquely named in 3ds max. Changing the texture of the shader therefore changes the texture of every model using that shader.
Stop the movie, and delete the top_dm cast member from the internal cast. Save the movie before continuing.
There's no reason to keep the image, as this was just a test to show you how you can instantly change the look of the game board. When the movie is rewound and played, the board will revert to its original look because of the resetWorld() command.
That's it for this lesson. In the next lesson you will build the functionality that allows a card to be clicked on to be flipped overand animated as it does so.