Home Contents Forum Links Tip Jar
Animation Math in Lingo       

Object-Oriented Fundamentals

Intro
Overview
What An Object Is
Accessing Properties and Methods of an Object
Passing By Reference
How Director Uses Scripts
The Objects That Make Up Director Itself
Lingo Data Types
Cloning Objects
Comparing Objects For Equality
More On Calling Object Methods
Scripts Associated With Sprites
Garbage Collection
Speed Considerations
Inheritance
Object Oriented Design
Why OOP?

Intro
Understanding how objects can be used greatly simplifies the programming of complex animation. Investing a little time in getting the hang of it yields big returns.

Object-oriented programming was the latest big development in how computer programs are written. It has to do with how code is organized and executed. Object-oriented programming must be done in an object-oriented language, and any program written in an object-oriented language is an object-oriented program. The newer languages used today are object-oriented, such as C++ and Java.

Though it is not usually presented as one, Lingo is an object-oriented language.

Overview
All code written in an object-oriented language is organized into what are called classes. In Director, each script is a class.

A class is composed of two types of elements: properties and methods. Properties are variables belonging to the class, and methods are units of code belonging to the class. In Director, handlers are methods.

Before the code in a class can be executed, the class must be "instantiated" at run-time which creates an object from the class. An object is a working copy of the class, and multiple objects can be created from the same class. In Director, the new() method is used to create an object from a class.

This convention is relaxed in Director, so that code can be executed without creating objects using Lingo. Just designate a script as a "movie" script or attach it to a sprite, etc, and Director takes care of it. However, taking advantage of the strengths of object-oriented programming in Director requires creating and using objects in Lingo.

What An Object Is
Make a new script called "class1" with this code:

property var

on new(me, arg1)
  var = arg1
  return me
end

on
putVar()
  put var
end

After making changes to a script, remember to recompile it by clicking

This script has one property, var, and two methods, new() and putVar(). In object-oriented terminology handlers are called 'methods'. The word method will be used in place of handler in this section.

In the message window, type this command:

objRef = script("class1").new()

The new() method is a special one that creates a new object. This is what happens when the command above is executed:

  1. a chunk of memory is designated to hold a new object. The new object is set up with the properties and methods defined in the "class1" script. The contents of this chunk of memory is the object. It can be considered a working copy of the script it is created from.

  2. a pointer to this memory location is passed as the first parameter to the created object's new() method. In this script, the first parameter variable of the new() method is called 'me', so that the variable 'me' becomes a pointer to the new object. A pointer is simply the memory address of the object.

  3. the commands in the new() method are executed. In this script, the value of 'me' is returned, which is a pointer to the new object.

  4. since a pointer to the object is returned from the new() method, the variable objRef becomes a pointer to the new object, and can now be used to access the properties and methods of the new object.

Every time the new() method of a script is called, this process occurs and creates a new object at a different place in memory. New objects can continue to be created until memory is filled up.

In object-oriented terminology, a pointer to an object is called an "object reference", or just a "reference". A script is a "class". Objects are "instances" of classes, and "instantiating" a script creates an object.

The phrase "child object" in Director documentation means an object created from a script.

Accessing Properties and Methods of an Object
In most, if not all, object-oriented languages the properties and methods of an object are accessed using 'dot-syntax':

objRef.property
objRef.method(arg1,arg2,...)

In the message window, type:

objRef = script("class1").new(5)
put objRef.var
-- 5
objRef.putVar()
-- 5

objRef2 = script("class1").new(10)
put objRef2.var
-- 10
put objRef.var
-- 5

The object references objRef and objRef2 point to two different objects created from the same script. They are used to indicate which object's properties and methods are to be accessed.

Objects created from the same script have the same methods and properties, but may have different values for their properties.

Properties of an object may be set and retrieved, and methods of the object may be called with 0 or more arguments. When a method is called the statements in the method execute. If there is a return statement in a method, it returns a value to the statement in the program that called the method. Otherwise, the return value is void.

When a method contains statements that use a variable defined as a property for that object, it refers to that object's value for that property. In order to refer to a property of a different object, an object reference for that other object must be used (dot syntax).

Passing By Reference
When a variable var2 is set equal to another variable var1 which is an object reference, var2 becomes a reference to the same object as var1. This diagram represents what is happening in memory:


This means that if var2 is used to change properties of the object, those changes will show when accessing the object using var1 (and vice versa). There can be any number of pointers to the same object (until memory fills up).

Because of this, an object reference can be passed to a method as a parameter and the method will be able to operate on that object because its parameter variable will become a pointer to the same object.

How Director Uses Scripts
The reason it is possible to write and use Lingo without knowing about creating objects is that Director will create and use them for you. This is what happens when you "attach" a script to a sprite or designate a script a "movie" script.

Sprite Scripts
When a script is "attached" to a sprite, Director will create an object from that script when the playback head encounters the sprite in the score. The object reference will be placed in the sprite's scriptInstanceList, which causes Director to call certain of the object's methods in response to events related to that sprite, such as mouse events. When Director calls one of the object's methods, it passes the object reference as the first parameter. At run-time, you may create objects and add them to a sprite's scriptInstanceList to make Director call their sprite-related methods.

Movie Scripts
When a script is designated a "movie" script, Director will call the script object's methods in response to events throughout the duration of the movie. This is a little different than creating a new object. Director seems to use the script cast member object itself rather than create a new instance.

Other Ways Director Uses Scripts
  • Frame scripts are instantiated by Director when the script is encountered in the score. Methods are called in response to events related to that frame.
  • If you place an object reference in 'the actorlist', Director will call that object's stepframe() method, if it has one, once per frame.
  • If you set 'the alertHook' to an object reference, Director will call that object's alertHook() method if an error occurs. Director will pass the object reference back to the method along with two strings describing the error.
  • I'll take a pass on cast member scripts since I never use them.

When Director creates an object, it immediately calls its new() method, if it has one.

Director documentation classifies scripts as movie, behavior, and parent although script cast members are all the same type of object. For example, you can attach a behavior script to a sprite, then change it to a movie script, and then instantiate it at run-time. The classification just tells Director how to use the script.

The Objects That Make Up Director Itself
Director itself is programmed in an object-oriented language. All the elements in the Director environment, such as sprites and cast members, are objects. The classes that define these objects are in the files that are put on your hard drive when you install Director. These objects are created when Director starts up.

Lingo phrases like sprite(1), member("foo"), (the stage), and model("bar") are all references to objects, and can be passed around like references to objects you create from your own scripts.

Lingo Data Types
The types of data that you work with in Lingo are either 'primitives', such as numbers, or objects. Primitive data types are stored without using pointers:

There are many object data types, such as linear list, property list, point, vector, rgb, rect, quad, image, transform, etc. Director will create a new object when you explicitly use an object data type in a statement. Try typing this in the message window:

put point(350, 700).loch
-- 350

pt1 = point(100, 200)
pt2 = pt1
pt2.locv = 500
put pt1
-- point(100, 500)

In the first statement, when Director encounters the phrase 'point(350, 700)' it creates a new point object and returns an object reference. Then it evaluates '.loch' and uses the reference to get the object's loch property value.

Many properties of Director objects are object data types, for example the loc property of a sprite object is a point data type. If you set a variable equal to the loc property of a sprite object, it will become a reference to a point object.

You might assume that since you have a reference to the sprite's loc point object, you can use that reference to change the loc of the sprite. However, this isn't the case. When you access a sprite's loc property Director creates a new point object and returns a reference to that, so that changing the loch and locv of that point object has no effect on the sprite:

put sprite(1).loc
-- point(100, 200)
pt = sprite(1).loc
pt.locv = 500
put sprite(1).loc
-- point(100, 200)

How Director is executing lingo behind the scenes is often not apparent. But by testing, as above with the loc property, you can tell if Director is giving you a reference to an object that it is using or is creating a new one for you.

A good example of this is the transform property of a 3D node. If you access the transform property, Director will return a reference to the actual transform object it is using when it renders the model. This means you can pass the transform object reference to a method that, say, rotates a transform, and it will rotate the 3D node. However, if you access the scale, position, or rotation of a transform of a 3D node, it will create a new vector object and give you a reference to that. Changing the x, y, or z of this vector object will have no effect on the 3D node.

Cloning Objects
Sometimes it is necessary to create a copy of an object, so that the copy can be used and changed without altering the original. This is a standard feature in object-oriented languages.

In Lingo there isn't a standard way to clone objects. List, cast member, image, transform, and vector objects have a duplicate() method that creates a copy and returns a reference to the copy.

I'm not aware of a standard way of cloning objects created from scripts. If you need to clone one of your own objects, you can write a cloning method that creates a new object from the same script, sets all its properties equal to the values in the original, and returns a reference.

Script "classX"
property var1, var2

--new() and other methods

on duplicate()
  dup = script("classX").new()
  dup.var1 = var1
  dup.var2 = var2
  return dup
end
A cloning method

Comparing Objects for Equality
In object-oriented languages in general, comparing two object references for equality will return true only if they both point to the same object. This is the case in Lingo when comparing references to objects created from scripts.

For other types of objects, Director uses different comparisons. Comparing references to point objects will return true if they have the same loch and locv. Similarly, transform, vector, rgb, etc objects are equal if they have equal numerical values.

Comparing for inequality, such as less/greater than or inequal, depends on the object. If you need to compare for inequality, test to see how Director is doing the comparison. For example, if you test whether one point is less than another, Director will return the result of comparing their loch coordinates.

If you want to be able to compare your own objects in some special way, you can write a method that accepts a reference to another object, performs the comparison, and returns the result:

property var1

-- new() and other methods

on equalTo(me, objOther)
  return (var1 = objOther.var1)
end
A comparison method

More On Calling Object Methods
When a method is called via an object reference, such as objRef.method(x,y,z), it is equivalent to calling method(objRef,x,y,z) (this is peculiar to Lingo, not object-oriented languages in general). This means an object reference is passed as the first parameter. The reference is to the script instance itself. Other passed arguments like x,y,z come after. When Director itself calls a method, like exitframe or mousedown, it always passes the reference.

This means that the first parameter variable after the method name becomes a reference to the script instance itself when the method is called. In examples this variable is usually called 'me', although it can be anything. There is no significance attached to the word 'me', except that it gets colored blue in the script editor. It can be used for a variable like other variable names.

There are two situations where it is necessary to include a parameter variable for the reference: 1) you need to use the reference in the method, and 2) the method is called via reference and parameters (other than the reference) are passed. In case 2, the reference variable is needed as a placeholder since a reference will be passed as the first parameter.

If the method is written without any parameter variables, a passed reference (and any other passed parameters) get dropped. The method will still execute and still operate on that instance's properties.

The only way to call a method without using an object reference, for example method() as opposed to objRef.method(), is when calling it from another method in the same object. If no object reference is used, a reference does not get passed as the first parameter.

The methods of an object that Director calls can also be called in lingo, so that you can call methods like exitframe(), mouseup(), beginsprite(), etc in your code.

Scripts Associated With Sprites
When a script instance is associated with a sprite by attaching the script in authoring or adding the instance to the sprite's scriptInstanceList at run-time, Director allows its properties and methods to be accessed like sprite properties and methods. If the script instance has a property prop, it can be accessed with sprite(x).prop. This is equivalent to sprite(x).scriptInstanceList[1].prop (if the instance is the first one in the list).

When a method is called via a sprite object reference, the reference that gets passed to the method is still the script object reference.

Garbage Collection
When there are no more references to an object, the memory that the object occupies is marked as unused and eventually gets overwritten. This is called "garbage collection". It is good programming practice to remove references to an unneeded object so memory isn't used unnecessarily.

This is done by setting all references to the unneeded object to some other value, or void, and deleting references from any lists.

Speed Considerations
When trying to optimize an algorithm to run as fast as possible, one thing to do is minimize the number of objects created. Creating an object has computational overhead associated with it.

For example, say you have a general purpose method that rotates a vector:

on rot(vec, angleVec)
  trans = transform()
  trans.rotate(angleVec)
  return trans * vec
end

Every time this method executes, it is creating a new transform object. This can be avoided by creating one transform object and reusing it:

global trans

on
rot(vec, angleVec)
  trans.identity()
  trans.rotate(angleVec)
  return trans * vec
end

This optimization makes execution of the method two to three times as fast. The transform object would be created sometime before rot() is called, the reference being either global and used throughout the movie, or an object property.

Since the Lingo implementation is "black box" (Director isn't open-source software), the efficacy of most optimizations can only be determined by actual speed testing.

Inheritance
Inheritance allows a script (class) to build upon another script. When a script inherits another script, it gets all the script's properties and methods in addition to its own.

The advantage to using inheritance is that some core functionality you want several scripts to have can be put in one script, and then other scripts can inherit it. This way code doesn't need to be duplicated, and any changes needed to the core functionality only need to be made in one place.

In Lingo, inheritance is established by declaring a property called "ancestor" and then making it a reference to an object created from another script:

property ancestor
property var

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


on putVar()
  put var
end  

An object created from this script will have all the properties and methods of the script "ancestorScript" in addition to the property var and the method putVar().

For this object to reference the properties and methods it inherited from ancestorScript, Director requires that the 'me' reference be used (or whatever variable you use for the object reference):

me.inheritedVar
me.inheritedMethod()

If a script declares properties or methods that are also declared in the ancestor script, they override the ancestor's versions. In this case, the ancestor versions can be accessed using the ancestor object reference:

ancestor.var
ancestor.method()

If a script's ancestor itself has an ancestor (and so on), the script inherits from all of them.

An example of using inheritance can be seen in 3D Resource Objects, where tree node functionality is inherited to make two different types of trees.

Object Oriented Design
So how do you write a program using objects? Organizing properties and methods into a number of objects and deciding how the objects will work together is an exercise in object oriented design. This is a huge topic that is best discussed in the context of specific programming exercises.

With that in mind, there are several sections of this tutorial that touch on object oriented design. I recommend them in their order of complexity:

Case Study: Space Viewport
Case Study: Mr. Eyes
3D World Hierarchy
3D Resource Objects
3D Camera Movement

Why OOP?
Any program you can write in an OO language could be written in a non-OO language. (Or on a Turing Machine, for that matter). However, OOP has a number of advantages:

Organizing code into classes makes it easier to model a program as a set of well-defined components that work together, which is a natural way for people to think.

Classes (scripts) are highly portable and can be easily reused to assemble new programs.

Objects are stored and passed by reference. With one statement, all the properties and functionality (methods) of an object can be passed for another method to use and operate on. Objects can be assembled using references into data structures and functional configurations (a la Mr. Eyes).

This touches on some OOP concepts most relevant in Lingo programming and the types of programs Director is usually used for; for more on OOP in general see any of the many books and web pages written on the subject.

 
 


Copyright © 2003 JM Harward 
 jmckell~at~jmckell~dot~com
All lingo provided on this site may be used freely for educational purposes. Not for redistribution as uncompiled code. Instructional materials may not be reproduced without permission.