Articles Archive
Articles Search
Director Wiki
 

Using Your Ancestors

August 7, 2002
by Dave Mennenoh

Recently, I was hired to produce a small demo of a potential 3D CADD simulation aimed at children. The premise is that the kids would be able to build, color, and manipulate simple 3D primitives and finally, build a toy in another section of the program. The demo only needed to simulate the first section where the kids could create and manipulate a few primitives - a cube, a sphere, and a cylinder. The interface I created for it looks similar to the following:

First of all, I started out coding the 3D aspect of the project as many developers might - as a single behavior attached directly to the 3D sprite. Although it worked fine, it was messy. The various buttons all had to use the sendSprite command to call handlers within the sprite's behavior. The more I thought about it, the more using Lingo's parent scripts seemed a better method. I could have three code-based objects: the cube, the sphere and the cylinder and each one would contain the methods appropriate only to that particular 3D object. To change the texture on the cube, for instance, would simply require a:

theCube.changeTexture ("wood")

So, I took a few hours and created my three parent scripts and modified the interace elements to work with the objects. It worked great. I was proud I had OOP'ed my project. But, it was still fairly messy since all three objects contained many of the same methods. If I wanted to update how the changeTexture method worked, I had to change it in all three parent scripts. This held true for many of the methods. It dawned on me then that I could put Lingo's ancestor property to work and create a separate "common" parent script that would contain the methods common to all three of the main parent scripts.

About Ancestors

Lingo's ancestor property is the means by which OOP in Lingo provides inheritance, as it's sometimes called in other OOP languages. Basically, what it means is that one object can "inherit" the properties and methods of another object. If you call a method within an object and that method doesn't exist, Director will search the methods within the object's ancestor. This can give you a ton of flexibility, as you will see. And, though we won't go into it here, you can change an object's ancestor property on the fly, allowing you to instantly change the behavior of your object.

Now, there are really two ways you can go about using ancestors to extend your objects. You could have one base object that had different ancestors that you swapped out, or you can have different parent objects with one shared ancestor. If you've been paying attention you'll know that I chose to use the later method, because it simply seemed most logical for my project.

The Base Objects

Let's go ahead and look at the parent script for the cube, and then its ancestor next. The first thing that any parent script needs is the new method - known as the constructor in other languages. This method lets you initialize your object and also returns a reference to the instance of the object, in the form of a pointer to a memory address. That's really not important though, just know that the new method returns a unique reference to the object's instance, and you can store this reference in a global variable.

property wrld
property resRef, modRef
property shdRef
property myType

property ancestor

on new me, worldRef

  me.ancestor = script ("common").new()

  myType = #cube
  wrld = member (worldRef)

  if voidP(wrld.modelResource ("aBox")) then
    resRef = wrld.newModelResource ("aBox", #box)
    resRef.width = 40
    resRef.length = 40
    resRef.height = .2
  else
    resRef = wrld.modelResource ("aBox")
  end if

  if voidP(wrld.model ("aBox")) then
    modRef = wrld.newModel ("aBox", resRef)
    removeModel (me)
  else
    modRef = wrld.model ("aBox")
  end if

  buildShader (me)
  setStyle (me, #wire)
  return me
end

As you can see, the new method simply creates the cube's resource and model and does some simple error checks. Notice however, that we're defining the ancestor property and then using that property on the first line of the new method:

me.ancestor = script ("common").new ()

What this does is call the new method within the parent script named "common". And this is a very simple new method, since all we need in the cube script is the reference to the "common" script. The ancestor's new method is then simply:

on new me
  return me
end

As stated previously, the new method returns a unique reference to the script being instantiated. In this case the unique reference is stored in the ancestor property of our cube parent script. Our cube script has now "inherited" the methods, and properties, of the "common" parent script and is free to use them as if they were contained within the cube's own script. Simple, right?

Me, Me, Me

Now, let's have a look at some of the ancestor script. Though there are quite a few more methods within the script, these will be enough for a good example:

on new me
  return me
end

on eraseTexture me
  me.modRef.shaderList.texture = void
end

on setStyle me, theStyle
  me.shdRef.renderStyle = theStyle
end

on removeModel me
  me.modRef.removeFromWorld ()
end

Now, look back at the last line, before the return me, in the cube's new method:

setStyle (me, #wire)

Although the setStyle method is located in the ancestor script, the cube object can still call it because of the ancestor property that was defined. First, you send the cube's reference (me) to the setStyle method, along with the #wire parameter that sets the cube's renderStyle property to wireframe mode. Notice that this method is referencing the shdRef property, a property defined not within this parent script but within the original cube script. Actually, there are no properties defined here, they are all defined within the base obejct itself. And this is why the property is preceded by the me reference. Recall that you sent the cube's reference into the method when you called setStyle in the first place. So, me.shdRef.renderStyle is really like saying theCubeObject.shdRef.renderStyle.

You are probably starting to notice that these scripts are quite specialized to their particular purpose, and are not generic at all. If the ancestor script is to be common to the three parents ("cube", "sphere", and "cylinder") then all three of those base objects need to use the same property names (shdRef, modRef, etc.). When you're doing the designing, though, this shouldn't be a problem. And for this project using the ancestor script still greatly simplified the whole thing.

In Closing

Although I've illustrated a specialized use of ancestors hopefully you will see how you could make use of such constructs in your own project. Remember, each object can have only one ancestor, though that ancestor can be swapped out at will. Before wrapping up completely, let's have a quick look at just how that works by creating a very simple test. Open Director, and open a new script window. Enter the following:

property ancestor

on new me
  me.ancestor = script ("test1").new ()
  return me
end

Name this script member "obj" and be sure to use the Property Inspector to make the script a Parent script. Next, make two more parent scripts in the same manner. Name the first one "test1" and the second one "test2". The script for "test1" is as follows:

on new me
  return me
end

on test me
  put "hello"
end

And "test2" is exactly the same aside from the put statement:

on new me
  return me
end

on test me
  put "world"
end

Open the Message window and instantiate the "obj" parent script, and then execute the test method:

p = script("obj").new()

p.test()
-- "hello"

You can now swap the ancestor and use the other "test2" script as the ancestor instead:

p.ancestor = script("test2").new()

p.test()
-- "world"

Nifty eh? You could take this all the way to allow enemy ships in a game to have various behaviors simply by swapping ancestors. And there are, of course, many more uses once you start thinking in terms of using objects and ancestors in your projects. Once you start using them more you'll be surprised at how much they can simplify things, and make them more powerful all at once.

Unfortunately, I can't release the completed demo project but if you'd like the complete scripts for the cube and it's ancestor you can click here to download the Director 8.5 .cst file as a ZIP or SIT archive..

All colorized Lingo code samples have been processed by Dave Mennenoh's brilliant HTMLingo Xtra, available from his site at http://www.crackconspiracy.com/~davem/

Dave Mennenoh is a freelance multimedia developer for-hire, living in Milwaukee Wisconsin. Recently, he authored the book "Director 8.5 Shockwave Studio: The Complete Reference" for Osborne / McGraw-Hill and has been busy jotting down notes and ideas for the next one. When Dave's not at the computer he can usually be found bombing through the woods on his mountain bike, cooking a healthy vegan meal, or watching "The Simpsons."

Copyright 1997-2024, Director Online. Article content copyright by respective authors.