LingOOP: Object-Oriented Programming with Lingo

http://www.dreamlight.com/insights/07/objects.htm#06-20-02

 

DreamLight Director Talisman

LingOOP: Object-Oriented Programming with Lingo
& DreamLight Director Talisman Tips & Tricks

Now you can use DreamLight DreamObjects LingOOP Framework for rapid Director development.

What the bleep is LingOOP, some magical ointment? Sort of... when applied effectively to programing, it can bring your programs to life. LingOOP is a humorous abbreviation of Lingo Object-Oriented Programing that I made up to call attention to this fascinating area.

I had been programing in traditional languages for years including, HyperTalk, Basic, Pascal, C, and Assembly. I had even dipped into FORTRAN, Logo, Forth, PostScript and RIB. I learned Object-Oriented Programing the hard way. I taught myself OOP back in 1992 with Director 3’s Factories. The first project that I created with LingOOP was theaward-winning DreamLight Verttice. With Shockwave, you can play DreamLight Verttice On-line. The source code of the original version of DreamLight Verttice was included on Macromedia’s first Developer CD-ROM as an example of what could be done with Object-Oriented Lingo.

This early work had a profound impact on my programing style. All of a sudden, my programs sprang to life. Rather than thinking in terms of boring data and functions, I was thinking in terms of living, breathing, intelligent objects. I used LingOOP when creating the award-winning KeyQuest edutainment CD-ROM. I then pulled out all the stops to see just how far I could push LingOOP and developed our DreamLight Autiton, Intelligent Interactive Character technology. We have just filed for aprovisional patent for a method for simulating intelligence through the programming of a virtual mind as part of the DreamLight Autiton technology. You can see NineOh, the world’s first Autiton inQuipples: The Internet game show of satirical riddles. Check it out!

Enjoy,

Michael Scaramozzino
President and Creative Director

Director Tips & Tricks

Contents

  • Overview
  • Programming a Virtual Mind with LingOOP
  • Dynamic Linking to External Files with a filePath Class
  • Sharing Data & Handlers Across Multiple Movies
  • Multiple Inheritance in Lingo
  • Abstract Classes & Multiple Inheritance
  • Object Self-awareness and Me
  • Identity Crisis: When "me" Becomes Someone Else
  • 3D Array Objects
  • Managing Global Namespaces
  • Incremental Object Processing
  • Global Space Management with OOP
  • Object Disposal
  • Object Identification
  • OOVE: Object-Oriented Virtual Environment
  • Object Identification
  • Reusable Objects
  • Object and List Pointers
  • To OOP or Not to OOP?
  • Do Birthing Scripts Eat RAM?
  • Objects and Memory
  • Intelligent Characters with OOP
  • Truly Global Handlers
  • PhantomSprite & Stack Object
  • The Meaning of OOP
  • Before Parents and Children, there were Factories

Programming a Virtual Mind with LingOOP

Date: Jun. 6, 2002

Take a look under the hood at how we use LingOOP to program a virtual mind for our DreamLight Autiton Intelligent Interactive Characters in our recent provisional patent filing fora method for simulating intelligence through the programing of a virtual mind.

Dynamic Linking to External Files
with a filePath Class

Date: Oct. 2, 2001

i.e. member( "WmvDummy" ).filename = the moviepath & "video\demo.wmv"

I don't think that would work correctly cross platform, since you could end up with a string like this being built:

"Macintosh HD:projectFolder:video\demo.wmv"

(Which wouldn't work the last time I specifically tested mixed delimiters in D7)

Try this instead, if you want full cross platform compatibility:

member( "WmvDummy" ).filename = "@\video\demo.wmv"

A string starting with the "@" symbol is interpreted as a specifically cross platform path, starting at the current movie path, for internal Director path references only (won't work with fileIO etc.).

On the Mac it would be interpreted as something like this:

"Macintosh HD:projectFolder:video:demo.wmv"

On Windows it would be interpreted as something like this:

"C:\projectFolder\video\demo.wmv"

On a Unix server, it would be interpreted as something like this:

/home/www/projectFolder/video/demo.wmv"

So, the problem was not that "@\video\demo.wmv" doesn't work, but that it won't work for a "sprite", you need to use it for the "member" instead.

I've often had problems dealing with paths, especially when using both internal Director Lingo with external Xtras like FileIO. "@" works internally, but not with Xtras, so I built our own filePath class that I use on projects that manipulate large amounts of dynamic media like: Quipples, the Internet game show of satirical riddles: http://Quipples.com

Here is my filePath class, for instructional purposes. Note that it uses a handler called "error" which is our custom error message handler, you could use a simple alert instead...

-- DreamLight FilePath Class
-- ==========================================================================
-- DreamLight(R) Incorporated
-- 14 Union Street, Suite 2R, Woburn, MA 01801, 781-932-6333, DreamLight.com
-- Interactive Multimedia * 2D/3D Illustration * Digital Design
-- ==========================================================================
-- (C) 1996-2001 DreamLight Incorporated, All Rights Reserved.
-- ==========================================================================
-- I am a FilePath object
-- I know my pathList
-- I know my delimiter

-- I know how to give my path string

-- Constructor:
-- new( Me,              objectReference
--      path,            path in the form af a list or a string
--      delimiter)       path delimiter to use

-- The following attributes are available for external public access
-- name:        The name of this object, often used for debugging

-- The following services are available for external public access
-- givePathStr(tempDelimiter)   Returns the file path as delimited string
-- addPath(newPath, tempDelimiter)   Add newPath to my path
-- ==========================================================================

-- Object Attributes

-- public
property name         -- object name used in debugging


-- protected
property pathList     -- liner path list like this [ "HD", "folder", "file" ] 
property delimiter    -- delimiter character such as ":", "/", or "\"


-- Public Object Construction Service

on new Me, nPath, nDelimiter
  -- create a new FilePath object
  -- Pass it a path as a FilePath object, a linear list or a string
  -- Pass it a single character delimiter or it will use ":"
  global gMac
  
  if voidP( gMac ) then -- set gMac if not set
    if the environment.platform contains "Macintosh" then gMac = TRUE
    else gMac = FALSE
  end if
  
  Me.name = "FilePath Object"        -- used for error Messages and debugging
  
  -- check and read input
  -- set delimiter first because it may be needed to parse path string
  if voidP( nDelimiter ) then 
    if gMac then Me.delimiter = ":"  -- default to local Mac file
    else Me.delimiter = "\"          -- default to local PC file
  else if stringP( nDelimiter ) AND length( nDelimiter ) = 1 then
    Me.delimiter = nDelimiter
  else
    error( "Delimiter must be a single character string.", Me, "new", TRUE )
  end if
  
  case ilk( nPath ) of
    #instance: 
      Me.pathList = duplicate( nPath.pathList ) -- copy list from other FilePath
      if voidP(nDelimiter) then Me.delimiter = nPath.delimiter
    #list: Me.pathList = duplicate( nPath )
    #string: Me.pathList = Me.parsePathStr( nPath, Me.delimiter )
    otherwise:
      error( "Path needs to be passed as a linear list or string", Me, "new", TRUE )
  end case
  
  return Me
end new


-- Public Implementation Services

on givePathStr Me, nDelimiter
  -- return the path in the form of a string delimited with nDelimiter or Me.delimiter
  
  -- set temporary output delimiter to Me.delimiter or nDelimiter if supplied
  if voidP( nDelimiter ) then tempDelimiter = Me.delimiter -- no temp delimiter
  else if stringP( nDelimiter ) AND length( nDelimiter ) = 1 then
    tempDelimiter = nDelimiter
  else
    error( "Delimiter must be a single character string.", Me, "givePathStr", TRUE )
  end if
  
  pathCount = count( Me.pathList )
  pathString = EMPTY
  repeat with n = 1 to pathCount
    put Me.pathList.getAt(n) after pathString
    if n < pathCount then put tempDelimiter after pathString -- no ending delimiter
  end repeat
  return pathString
end givePathStr

--

on giveDelimiter Me
  return Me.delimiter
end

--

on addPath Me, pathToAdd, nDelimiter
  
  -- set temporary delimiter to Me.delimiter or nDelimiter if supplied
  if voidP( nDelimiter ) then tempDelimiter = Me.delimiter -- no temp delimiter
  else if stringP( nDelimiter ) AND length( nDelimiter ) = 1 then
    tempDelimiter = nDelimiter
  else
    error( "Delimiter must be a single character string.", Me, "givePathStr", TRUE )
  end if
  
  -- create tempPathList from pathToAdd
  case ilk( pathToAdd ) of
    #instance: tempPathList = pathToAdd.pathList
    #list: tempPathList = pathToAdd
    #string: tempPathList = Me.parsePathStr( pathToAdd, tempDelimiter )
    otherwise:
      error( "Path needs to be passed as a linear list or string", Me, "addPath", TRUE )
  end case
  
  -- if last item in Me.pathList is empty, delete it.
  if Me.pathList.getLast() = EMPTY then Me.pathList.deleteAt( count( Me.pathList ) )
  
  -- add tempPathList to the end of Me.pathList
  repeat with folder in tempPathList
    Me.pathList.append( folder )
  end repeat
end addPath


-- Private Implementation Services

on parsePathStr Me, nPath, nDelimiter
  -- return the path in the form of a delimited string
  
  -- set temporary output delimiter to Me.delimiter or nDelimiter if supplied
  oldDelimiter = the itemDelimiter
  the itemDelimiter = Me.delimiter
  
  tempPathList = []
  repeat with n = 1 to the number of items in nPath
    tempPathList.append( nPath.item[n] )
  end repeat
  the itemDelimiter = oldDelimiter
  return tempPathList
end parsePathStr

Sharing Data & Handlers Across Multiple Movies

Date: Sep. 17, 2000

Basically how do I pass the percentage downloaded, and which files have
already been played, between movies?

A simple way would be to store them in global variables...

A more sophisticated way would be to use a "SoundManager" object. If you create a sound manager object you could feed it a list of SWA files to play in sequence. The manager would keep the list (in a property variable) and play them one after another, monitoring their progress by calling percentStreamed and/or percentPlayed. You'd create an instance (child) of the SoundManager in the main moive and then when you open a MIAW you'd tell the sound manager what SWA's to play. Once a child object is instanced it remains available to all subsequent movies as long as a reference is maintained. This can be done by simply using a global variable to point to the object.

Basically the strategy I try to use for EVERYTHING is to isolate a group of related functions and data into an object. Then let the object handle all the details. This is especially useful for areas of Director that are relatively low level and detail oriented such as handling sound or reading/writing to files. I build manager objects that I can interface with on a higher level of abstraction and let the manager handle all the details interfacing with Director. I then build the main functionality of my projects with objects as well and let the abstract project specific objects communicate with the generic manager objects which handle interfacing with Director itself.

I actually create a layered structure of objects for audio as follows:

  • AudioChannel object: Manages a single audio channel in Director.
  • AudioTrack object: Manages one or more AudioChannel objects in Director. This way an AudioTrack can do fancy cross fades, play multiple simultaneous sounds etc.

In most projects I then use two main audioTracks:

  • sfxTrack, an AudioTrack that I generally allocate four AudioChannels to use for simultaneous sound effects.
  • AmbaintTrack, a specialized AudioTrack with two AudioChannels, used for looping ambiant sounds. Using dual AudioChannels enables nice cross fades as the ambiant sounds are changed throughout a movie.

I hope this gives you some good ideas for managing your own sound, and many other things using LingOOP (Lingo Object Oriented Programming)

-MikeS

Multiple Inheritance in Lingo

Date: Sep. 8, 2000

I've been wrapping my brain around moderately complex class models
for a little while and it has become obvious that to perform certain
tasks correctly it would be nice to have multiple inheritance in
Lingo.

Hi Zav,

When I needed multiple inheritance for our Autitons, Intelligent Interactive Characters inQuipples, I used a self linking chain structure of ancestors. It's pretty simple, but it works very well and is very easy to implement.

-- 2. Multiple Inheritance: Simply means a subclass inherits from more than one class.

One way is to simply pass ancestors up stream and let them link up in a linear fashion such as this...

-- Some Sub Class
on new Me
  Ancestor = new( script "Some Class" )
  Ancestor.inherit( new( script "Some Other Class" ) ) 
  -- multiple inheritance
  return Me
end new
--
on inherit Me, inheritFromObj
  if voidP( Me.Ancestor ) then Me.Ancestor = inheritFromObj
  else callAncestor( #inherit, Me, inheritFromObj )
end inherit

In this case the ancestor class also has a service called inherit() which simply sets ITS ancestor to the object passed to it by its subclass... This effectively means that "Some Sub Class" is inheriting from "Some Class" and "Some Other Class."

If you define all your classes with an inherit() service that checked if it had an ancestor and if not set its ancestor to the passed object or if it already had an ancestor, passed the object upstream to its own ancestor, you have a simple way to create multiple inheritance. There are many more ways as well...

BTW The inherit handler above is off-the-cuff and not the actual code I'm using. It's for instructional purposes only... be careful of the SDF "When Me can become someone else" as described in the LingOOP section of the DreamLight Director Talisman which can cause problems if not understood.

Hope it helps,
-MikeS

Abstract Classes & Multiple Inheritance in Lingo

Date: Mar. 18, 2000

[In response to a question about abstract classes and multiple inheritance]

Here's some LingOOP that may help...

1. Abstract Class: "A class whose primary purpose is to define an interface"

Basically this just means a class intended to help you define the way subclasses will behave. An abstract class is not instanced in a language like C++ where there are constructs that prevent it. In Lingo there is no such enforcement but you can still use the concept of abstract classes. What's important is the theoretical use you make of such a concept when constructing complex systems in Lingo, not the actual implementation details that would be imposed in C++

Here's a very simple example of a Lingo abstract class I'm using in my current project:

-- Object Attributes

-- private
property Ancestor     -- An Activity is a Descendant of it's ActivityStack


-- Public Object Construction Service
on new Me, ActivityStack
  Ancestor = ActivityStack -- The ActivityStack that this Activity belongs to
  return Me
end new


-- Public Implementation Services
on init Me, argOrArgList
  -- responsible for initializing and invoking the Activity
  -- if the Activity has incremental processing then push it on the Stack
  -- override this service in the specialization class.
  -- only takes one additional argument. If more are needed, pass a list
  nothing
end
--
on resume Me
  -- responsible for resuming the Activity after another has finished
  -- override this service in the specialization class if needed
  nothing
end
--
on stepFrame Me
  -- responsible for an Activity's incremental processing
  -- override this service in the specialization class if needed
  nothing
end

As you can see, this class does nothing but define an interface. It is "abstract" and depends upon its subclasses to define what really happens on init, resume or stepFrame. What it does do though is define that all activity subclasses should know how to at least do the following...

init: initialize themselves,
stepframe: perform some sort of incremental processing, and
resume their incremental processing.

In Lingo you can indeed instance this object and actually it is really instanced anyway when used as an ancestor, though the instance really doesn't do anything, other than help the programmer design a complex system. If one of the required services is not defined in the subclass it's caught by the abstract class and does nothing (in C++ you'd probably get a compile error though to force the programmer to define a pure virtual function in the concrete subclass, this could be simulated by replacing "nothing" with an "alert()" in each of the default services)

Actually Lingo's method of inheritance is somewhat different than in C++. In C++ a class inherits from another class definition itself and therefore can be purely abstract, without instances. In Lingo, the typical way to inherit from an ancestor is to actually create an instance object of the class "parent" being inherited in this way:

Ancestor = new( script "Parent Class" )

Here's a simple example of an actual activity subclass of the abstract activity class... (What it's actually doing is not important for this discussion other than the fact that it does something on init, resume and stepFrame as it should)

-- Object Attributes

-- private
property Ancestor        -- This is a derivative of the Body via the activity
property mouseWatchTime  -- time when started watching mouse


-- Public Object Construction Service
on new Me, myBody
  Ancestor = new( script "Activity Class", myBody )
  return Me
end new

-- Public Implementation Services
on init Me
  -- jump on the ActivityStack
  -- begin watching mouse
  -- this message is repeatedly issued while mouse is moving.

  if NOT locked( Me.Self.ptrs.head ) then
    if Me.peek() <> Me then -- not already watching mouse
      Me.push( Me )         -- place this activity on the activityStack
    end if
    Me.mouseWatchTime = the ticks    -- save time when starting to watch mouse
    Me.Self.ptrs.Eyes.doActivity( #watchMouse )
  end if
end init
--
on resume Me
  init Me -- reinitialize my activity
end resume
--
on stepFrame Me
  -- see if the attention span has lapsed.
  -- losing attention ranges from 1 to 5 seconds of no mouse movement
  if the ticks - Me.mouseWatchTime >=Â
     Me.Self.ptrs.Mood.mouseFactor() * 240 + 60 then -- time to lose interest
    Me.pop()
  end if
end stepFrame

I implement many object services as these activity objects descended from activityStacks rather than as handlers built into plain objects themselves. This way, I can create run time dynamic objects whose handlers change at will since the handlers themselves are objects. This lets me build the thoughts and actions of our Autitons in a much more dynamic way than would be possible with traditional handlers.

Also note that I even use comments in the code indicating what should be public, protected or private attributes or services. Lingo does not enforce such, but I enforce them myself when designing and programming complex systems. This way my objects have clear interfaces that prevents sloppy programming errors.

-- 2. Multiple Inheritance: Simply means a subclass inherits from more than one class.

One example of this in Lingo is attaching multiple behaviors to a sprite. It inherits from all the behaviors which are really nothing more than parent scripts. This same effect can be achieved with other methods in normal parent script objects using lists of ancestors and lists of handlers such as the activity classes I use.

Another way is to simply pass ancestors up stream and let them link up in a linear fashion such as this...

-- Some Sub Class
on new Me
  Ancestor = new( script "Some Class" )
  Ancestor.inherit( new( script "Some Other Class" ) ) 
  -- multiple inheritance
  return Me
end new
--
on inherit Me, inheritFromObj
  if voidP( Me.Ancestor ) then Me.Ancestor = inheritFromObj
  else callAncestor( #inherit, Me, inheritFromObj )
end inherit

In this case the ancestor class also has a service called inherit() which simply sets ITS ancestor to the object passed to it by its subclass... This effectively means that "Some Sub Class" is inheriting from "Some Class" and "Some Other Class."

If you define all your classes with an inherit() service that checked if it had an ancestor and if not set it's ancestor to the passed object or if it already had an ancestor, passed the object upstream to its own ancestor, you have a simple way to create multiple inheritance. There are many more ways as well...

BTW The inherit handler above is off-the-cuff and not the actual code I'm using. It's for instructional purposes only... be careful of the SDF "When Me can become someone else" as described in the LingOOP section of the DreamLight Director Talisman which can cause problems if not understood.

Hope this helps... ;-) -MikeS

Object Self-awareness and Me

Date: Sep. 19, 1997

A rather simple concept that can be difficult to grasp when beginning to write object oriented code is the use of the "me" variable. The term "me" is not a reserved word or any magical variable. It could be named anything such as "self" or "this", but "me" has become the convention in Lingo. It is simply a reference to an object in memory. It is primarily used to identify objects and to give an object a sense of its own identity.

Define an empty object by creating a parent script with nothing in it except a comment. Then name this script cast member "empty object":

-- "empty object" parent script

Then create (or "instance") a child object from this script like so:

-- in the message window type the following...
set childObj to new( script "empty object" )

put childObj 
-- 

What's put into the message window is a description of an object reference. This starts with the word "offspring" followed by the name of the cast member in the cast slot where the script was located when the object was first created. This is usually the name of the script itself unless you have moved cast items or loaded different casts after the object was created, in which case the name may not be correct. It is finally followed by hexadecimal numbers that represent the actual memory address where the object instance resides.

The way an object is identified is by this object reference.

When you typed: set childObj to new( script "empty object" )

Lingo gets the call to new( script "script name" ) and it creates an instance of the object defined in the script "script name". This instance is nothing more than an area of memory that contains a pointer to the script's compiled handlers (in this case nothing but a comment) and a copy of any property variables defined in the script (in this case none). To keep track of this new object instance, Lingo creates a reference to it which it returns to the calling statement. Now obviously this type of empty object is not of much use to anyone... ;-)

Let's take a look at a slightly more interesting object...

-- "simple object" parent script

property name

on new me, whatName
  set the name of me to whatName
  return me
end

on getName me
  return the name of me
end

And let's instance a child object from this new script the same way we did previously but this time add a name after the script as so:

set childObj to new( script "simple object", "Fred" )

childObj 
-- 

Doesn't look much different, but after Lingo created the object reference this time, it then looked into the object script itself to see if it contained its own "new" handler and it found one. So it passed the object reference and the string "Fred" off to this handler which interpreted it in this way.

on new , "Fred"
  
  set the name of  to "Fred"
  
  return 
end

(NOTE: is a description of the object reference only, it can't actually be typed out this way in a real Lingo statement it is only shown in these examples for explanation. This is the description of the reference that would be passed to the "me" variable)

So within the scripts "new" handler, the handler can refer to the property variable that belongs to this particular object in memory since it has a reference to the object itself. This way the object has a sense of "self" through the "me" variable.

When the new handler reaches the return statement, it returns the value of me back to the original calling statement. Then this returned object reference is bound to the variable named "childObj".

To find the name of the child object, simply ask it this way:

put getName( childObj )

This passes the object reference stored in "childObj" to the getName handler in the object's parent script like so:

on getName 
  
  return the name of 
  
end

Or, you could simply access the name property variable directly, the same way the handler does by typing this:

put the name of childObj

--Which becomes:
put the name of 

So the me variable is used as a way to identify which particular object you are talking about and to let the handlers themselves know which object instance to work on. This is how one parent script can work on any number of instanced child objects, through these references.

Identity Crisis:
When “Me” Becomes Someone Else

Date: Sep. 18, 1997

DESCRIPTION:

There is a situation in the message call chain where your me variable can get derailed. This can result in the me variable from one object being used within another object leading to all sorts of problems as I'm sure you can imagine. Though technically this may not be a "Bug" (It's been this way since D4) it can cause loss of time debugging so I figured I'd mention it in case anyone happens to run across it during development.

STEPS TO REPRODUCE:

  1. Create a new movie
  2. Open the Script window and create a new script
    on startMovie
      
      set obj to new( script"Object 1" )
      
      put "----"
      whoAmI( obj )
      
    end
    
  3. Verify that this script is tagged as a movie script
  4. Create a new script
    -- Object 1 Script
    
    property secondObject
    
    on new me
      set the secondObject of me to new( script "Object 2" )
      return me
    end
    
    on whoAmI me
      put "My Name is: Object 1:" && "me =" && me
      whoAmI the secondObject of me
    end
    
  5. Set this script to be a parent script.
  6. Name this script "Object 1"
  7. Create a new script
    -- Object 2 Script
    
    on new me
      return me
    end
    
    on whoAmI me
      put "My Name is Object 2:" && "me =" && me
    end
    
  8. Set this script to be a parent script also.
  9. Name this script "Object 2"
  10. Open the message window and run the movie. You should see...
    -- "----"
    -- "My Name is: Object 1: me = "
    -- "My Name is Object 2: me = "
    

    So far so good... each me refers to the properly named object. Now for the fun part... ;-)

  11. Open the Object 2 script
  12. Comment out the entire whoAmI handler from the Object 2 script like so
    -- Object 2 Script
    
    on new me
      return me
    end
    
    --on whoAmI me
    --  put "My Name is Object 2:" && "me =" && me
    --end
    
  13. Now run the movie and you should see the following...
    -- "----"
    -- "My Name is: Object 1: me = "
    -- "My Name is: Object 1: me = "
    

    And you'll get an error about the "secondObject" property not existing. What's happening here is a little misdirection. Since Lingo doesn't find the whoAmI handler in the called object "Object 2" it doesn't generate a "handler not defined" error, it looks through the current object script and runs the "whoAmI" handler from within "Object 1" This of course causes all sorts of trouble because it passes the me variable which points to "Object 2" to the "whoAmI" handler in "Object 1"

    Now if you really want to have some fun try this out... ;-)

  14. Go back into the "Object 1" script and change the syntax of the call to "whoAmI" as follows...
    -- Object 1 Script
    
    property secondObject
    
    on new me
      set the secondObject of me to new( script "Object 2" )
      return me
    end
    
    on whoAmI me
      put "My Name is: Object 1:" && "me =" && me
      whoAmI secondObject
    end
    
  15. Run this and watch what happens... Be ready with command-period...
    -- "My Name is: Object 1: me = "
    -- "My Name is: Object 1: me = "
    -- "My Name is: Object 1: me = "
    -- "My Name is: Object 1: me = "
    -- "My Name is: Object 1: me = "
    -- "My Name is: Object 1: me = "
    -- "My Name is: Object 1: me = "
    -- "My Name is: Object 1: me = "
    -- "My Name is: Object 1: me = "
    -- ...
    

    Now not only does the second call of whoAmI use the handler from Object 1 with the me value of Object 2, but it goes into an infinite recursive loop because it doesn't use the me value to find the property variable which previously generated an error... ;-)

  16. Command-Period.
  17. Now look at the cast window. Somehow it changed the "Object 1" script from a script to a field cast member. Neato... ;-)

Is there a reason you'd want handler( objectRef ) to run a handler in the current object with the reference to the called object rather than generating an error? For error handling maybe? I'm currious, is anyone using this situation beneficially?

Perhaps this could be changed to generate a "handler not defined" error instead of looking through the current object when the handler is not found in the target object? I guess this can be avoided by using the new call command.

Any thoughts?

ENVIRONMENT:

Tested in D6.0 on a Macintosh 9500 MaxPower MP400+ 240MB RAM running MacOS 7.6.1

:-)

-MikeS

3D Array Objects

Date: Sep. 8, 1997

>set the cell2 of the row2 of the depth2 of gMyCube = 75
>
>This was almost EXACTLY what I was looking for. Big thanks Alexander! (Odd
>fact: no matter how much I know about Lingo I never quite seem to know
>enough...)
>
>If addressing the cells of the cube now uses scripting I would regard as
>intuitive, that's not a word I would use to describe the process of
>building the cube!

I'd suggest building your 3d cube as a full blown object with its own custom accessors. Then you could use syntax as simple as this...

setValue( 3dCube, x, y, z, value )

and

getValue( 3dCube, x, y, z )

--

We have previously posted a stack and 2D table object back in Director 3 usingfactories. The code would be different now but the concepts are similar and easily extended into 3D...

-MikeS

Managing Global Namespaces

Date: Sep. 2, 1997

> 9)  What: Limit the variable scope of globals to a single movie
>     Why:  helps to avoid conflicts with globals when developing in a team.

I wouldn't suggest that route. Globals should be globals, should be globals. But, you could add a new local file scope if you wish. Though we do that ourselves already...

We just use single global property lists or objects per movie if we wish to isolate our global name spaces...

For global variables and handlers for all movies we create a global object called gTheMovieManager. This is where we store property variables and common handlers for use by all subsequent movies.

If a movie joins the party that wants to keep its own variables and handlers for use within its movie only, it creates its own movie object. When the movie is over it deletes the object. A very simple way to manage the global name space. This way a single global variable points to an object that can be used to hold any number of variables and handlers... ;-)

There are some code fragments from our global gTheSoundManager object that we use to control sound processing on the Director TalismanManaging Sound page...

-MikeS

Incremental Object Processing

Date: Feb. 13, 1997

>My logic was that if I birthed another object, it
>would go on it's merry way and be independant of
>any other object. Instead, it seems that the method
>I'm using still makes for very linear logic flow.

In order for an object to "go on it's merry way" you must make sure you are giving it control incrementally to update itself and do its stuff...

One way to do this is to give the object an on stepFrame handler and then put a pointer of the object on the actorList. Then each time the stage is updated the object will be given a chance to update itself. Think in terms of small bite sized actions rather than continuous action.

To have animations work while other things are happening you need to program a little differently than you would otherwise. You must break all actions down to small increments that can keep track of where they are and where they are going. One way to acheive this is through LingOOP.

Here's the basic idea.

Create an object that remembers where it is in an animation and can incrementally update itself whenever told to do so. Then you can use the stepframe to provide the control to drive the animation.

You must then be cautious that no other action is allowed to stop the movie from flowing naturally. This means you can't use a repeat loop to wait for a mouse to be released etc. since that would stop the playback head and the stepframes as well.

Here's a simple "fake" example. This is not real tested code but just an example off the top of my head to point you in the right direction. Don't read it literally but rather to get the general idea.

-- object dog
property myMemberList
property myMember
property mySprite
property myPos

on birth me, whichSprite
   set myMemberList to [1, 2, 3]
   set myMember to 1
   set mySprite to whichSprite
   puppetSprite mySprite, TRUE
   set myPos to 1
end

on stepframe me
   set myPos to myPos + 1 -- NEED TO ADD CODE TO KEEP FROM RUNNING OFF SCREEN
   set the locH of sprite mySprite to myPos

   set myMember to myMember + 1 -- NEED TO ADD CODE TO WRAP AT END OF LIST
   set the memberNum of sprite mySprite to getAt( myMemberList, myMember )
end

-- In the main movie

set gDog to birth( script "dog", 48 )
add the actorList gDog

-- Now each time the playback head moves the dog will get a stepframe...

Hope that points you in the right direction.

-MikeS

Global Namespace Management with OOP

Date: Feb. 10, 1997

When we need to dynamically create global variables we add properties to a global object's property list held for this reason...

This has the added benefit of keeping our global space from getting all cluttered up and possibly interfering with other movies that we may run which have been developed separately. By keeping "global" variables that are really only needed in one movie within that movie's global property list we thereby isolate them.

We typically use objects to hold these property lists (as object propery variables) as well as "global" handlers that may be needed while the object is in scope.

We also keep a global property list for items that may need to be accessed by other movies. These we put under the control of our gMovieManager object.

You can access an object's property variables just like a property list. This gives you the ability to dynamically add properties to the object at run time. Quite a powerful possibility...

-MikeS

Object Disposal

Date: Nov. 20, 1996

>Does anybody knows whether the memory occupied
>by a big object is freed by simply using the command
>"set oVariable = 0" or there's any other similar way to do it?

Simply break all references to the object and it will be deleted. This means resetting ALL variables that are pointing to it (including any references in the actorlist etc.), to 0 or void. That will do the trick. Also refer to theMagaging Memory page.

-MikeS

Object Identification

Date: Dec. 2, 1996

>That's a reasonable solution, John.  The one thing which MM may wish to
>look at is the fact that if you get a script error in a MIAW caused by an
>object, clicking the "debug" button can't debug the script *AND* gives you
>an incorrect message about which script was offending (I think that
>clicking "script" gets it wrong, too).  I would almost prefer to be not
>told which script caused the error than to be pointed in the wrong
>direction.

Actually what is happening is this...

When you use put "object", Director puts the object identification information into the message window. It tries to identify the script used to create the object by looking at the script in the same cast slot as was there when it was created. This is also how it gives you information when displaying errors and tracing. This is usually fine for movies that use objects.

The problem occurs when using an object in a movie that is not the movie that created the object. This means movies you jump to as well as MIAWs. For instance, we use a start movie to birth a "gMovieManager" object. We then access that object from all subsequent movies. This produces incorrect identification using put gMovieManager and also if an error is encounterd or we use trace. In those cases Director uses information from the script (if any) that is currently in the cast slot where the original script was located in the original movie but now it's looking at the cast in the new movie or MIAW...

A simple work around that is not ideal but can be of great help is to do this.

During the development phase when testing an object in another movie, copy the script used to create the object and paste it into the same cast slot in the new movie. Now Director can properly identify and trace the script. This is not ideal because you must remember to update this script as well if you change the original. Another way would be to put all such scripts in the shared.dir during development and then move them out of the shared.dir into the original movie after development. In D5 you should be able to simply use shared casts.

-MikeS

OOVE: Object-Oriented Virtual Environment

Date: Dec. 2, 1996

>>     C  E  F
>>     |  | /
>>     A--B---G--I
>>     |  |   |
>>     D  H   J
>>
>>
>>     How would I code this? From A you can go north to C (and so
>>     automatically this implies that south from C takes you back to A);
>>     from B you can go west to A or east to G, etc etc.

Here is what we did on a current project...conceptually anyway...

We created a view object which controls displaying one of several picts on screen, based on the "state" of a view. ie. if the lights are out in a room the view will show the pict that is dark. So the view contains a list of all picts visible from that view.

Next we have a spot object which is used to represent a standing position. ie. you may be standing in the middle of a room and can turn around to see four different view objects... each spot object in a room is linked to each adjoining spot object and vice versa. Each spot object contains a list of its view objects.

    v ____BL____ v
    |/          \|
v - * - v    v - * - v
    |            |
    v            v

* = Spot
v = view
BL = Bidirectional Link

Next we have room objects. A room object contains one or more spot objects. The rooms are all linked together (bidirectionally) using pointers to adjoining rooms. When you leave one room we jump through the link to the next room. Each room object contains a list of it's spot objects.

So, basically we are just recreating exactly the structure of the entire environment in objects that are all linked together in a heirarchy. This way we don't need any external "intelligence" to direct our movement through the entire structure. We simply traverse the links in the object oriented environment directly...

From our master room, spot and view ancestors we can create decendents to control aspects that may be unique in each room, spot or view respectively.

What I would suggest is to abstract what you are trying to accomplish through creating well defined objects that control things immediately pertinent to that one object. Then simply enclose those low level objects in larger and larger container objects until you have your entire structure built...

For each object in the structure ask yourself two questions.

1) What does this particular object need to know?
2) What does this particular object need to do?

-MikeS

Object Identification

Date: Oct. 28, 1996

>on getParentScript anObject
>  return word 2 of string(anObject)
>end

Ah, you guys might want to test this if accessing the name of an object from a movie other than the movie where it was originally birthed... We typically birth an object in one movie and then jump further down the line into other movies while still using the original object through a global pointer.

I've noticed that (D4 anyway) reports the name of an object based on the script currently located in the cast slot where the original parent script was located NOT the original parent script itself. Once you jump to another movie the name of the object references the new script in that slot even though it's not really the parent. Also note that tracing and error messages (Again in D4 I haven't tested this extensively in D5) are pulling their info from the script at this cast slot rather than the REAL parent script which is no longer available...this can be real confusing when encountered if you don't know it's happening.

Just be aware when doing this...

-MikeS

Reusable Objects

Date: Oct. 28, 1996

>how do you keep track of your handlers and objects?
>
>i myself find it difficult. i feel like i am rewriting pretty much the
>same stuff over and over again.

One little trick I use is to create a MovieManager object. This object contains all code used throughout any movies to do normal maintenence such as file IO, prefs and user file management, memory checking, CD Speed tests etc. By birthing this object in the start movie all it's methods are subsequently available to any other movie that we jump to...quite powerful.

I also do this for our SoundManager object which handles fading ambiant soundtracks with voice and fx sounds. It manages custom soundChannel objects that directly control the true sound channels etc. We pass high level commands to the global SoundManager and it handles all the details...

-MikeS

Object and List Pointers

Date: Oct. 22, 1996

>If Lingo had true pointers we could create a reference to a any property
>then pass it to a function or method which does not care what the
>property name is.  In this way your functions or methods become truly
>virtual and can eliminate large ram and processor overhead from passing
>a lot of data (only ram addresses are passed).

Actually lists in Lingo are handled as pointers. This means you must be careful when you think you are passing a list to a method or assigning the list to a new variable. You are really only passing a pointer, so any changes you make to the contents through the new pointer actually change the original contents as well since they are one and the same. You really only have two views or pointers to the same contents.

Therefore if you want to use pointers for anything, simply use a list...

Property variables of objects are actually property lists as well. You should be able to access and use them as normal property lists which means they should be reachable via list pointers...

-MikeS

To OOP or Not to OOP?

Date: Oct. 22, 1996

>My only choice seems to be
>to go all OOP and use the stepFrame, which would be overkill

Actually using the stepframe and true objects is really quite easy. I wouldn't call it overkill at all. The biggest mindset change that you need to accomplish here is to think incrementally. The object will take small steps each frame rather than being programmed to go from beginning to end in one fell swoop.

Just relocate your animation code into a stepframe handler that incrementally updates the position of the object each frame. Create property variables to store the position, speed, direction, etc. for the object between stepframes. Once you get used to it you'll see it's really not that tricky.

>I would
>hesitate to send commands from within the objects to update the other
>objects, b/c that just seems like sloppy programming, and runs
>counterintuitive to what I've learned so far.  I would also hate to
>think that using OOP limits your Lingo flexibility, and it doesn't seem
>like it should.

Using OOP techniques can actually simplify your code one you get the knack. Think of the code from the point of view of the objects, not from a "master control program". Let's say you have some ducks that simply move across the screen. Well, all they really care about is where they are and where they are going. You could build in a handler that also takes care of what happens if one is hit. When you look at it from the objects view point you can narrow down the programming to just what the objects really need to know about and code that into the parent.

Think of the ducks as "intelligent" little objects that you just turn lose. What would they need to know to act appropriately? That is what you would code into the parent script...

Just be careful of pulling objects off the actorlist during a stepframe...it can cause many nasty crashes...

Later,
-MikeS

Do Birthing Scripts Eat RAM?

Date: Sep. 26, 1996

> 1. Does an instance of a 20K script take up 20K of RAM. Do 40 instances of
> that 20K script take up 800K of RAM.
>
> 400 instances - 8,000K ?        And so on.

No, there would only be 40 references to the script but not 40 copies of the script. Any property variables that the objects use and any cast members that they load would affect RAM, but not extra copies of the script.

> 2. If you can birth an object can you kill it also? I birth 800K worth of
> objects and then I want to flush them from RAM, how do I do this?

Just set all variables that point to the object to 0... This will free the objects memory (but not necessarily any castmembers that the object loaded).

You can watch the exact impact that objects have on your RAM with the DreamLight RAMLight...check it out... ;-)

-MikeS

Objects and Memory

Date: Sep. 13, 1996

>Can anyone fill me in on how director allocates memory to objects. Especially,
>how does director deal with the space requires for the script and properties.

Memory impact that objects have should be quite minimal. The only memory that objects use are for their property variables. So, unless your object is creating a memory leak (through loading unintended cast members into memory or creating massive lists, etc.) they shouldn't consume much RAM at all.

>So John Dowdell is absolutely right. Each object has only a copy of the
>properties, not the handlers. (But the documentation is not accurate,
>because it is written that the child object has also a copy of the
>*handlers*, which is wrong)

Yes, it is a common misconception that the scripts are duplicated with the objects. The script is notreally copied with each object but rather only referenced by each object. It acts as if it were copied but in reality it is only referenced. Thus the following statement is in error.

> Every object has its own separate script in memory. If ten objects are
>birthed from the same parent script there will be ten copies of the parent
>script around - one for each of the objects.

Upon careful examination, the example given below does not really support this statement above as intended, I'm sorry Peter Small, the logic is false. ;-). They are talking about two entirely different things. The example below is not really birthing ten objects from the same parent script as the statement suggests. It is birthing ten objects fromdifferent parent scripts that just happen to be located in the same cast member but at different times.

Since the script changes, these are all effectively different parent scripts and therefore the children will indeed each reference a different compiled script in memory. This is no different than birthing ten objects, one from each of ten different scripts located in ten different cast members.

>on mouseUp
>  global objects
>  set objects to []
>  put "" into field "girlie"
>  repeat with i =3D 1 to 10
>    put field "pScript" into workShop
>    put i into char (the number of chars in line 9 =A8
>    of workShop) of line 9 of workShop
>    set the scriptText of cast "pScript" to workShop
>    append objects,birth(script "pScript",i)
>    repeat with j in objects
>      mult j
>    end repeat
>  end repeat
>  put return & objects after field "girlie"
>end

If they were however all birthed from the same original unchanged parent script, then they would all be refering to the same compiled script in memory rather than to ten copies of the same script as the statement suggests.

This is my understanding of how Lingo works and I see this as a major benefit.

Otherwise birthing zillions of objects from a single parent wouldn't make much sense if each object actually had to copy the entire script each time. They all just REFER to the same compiled script in memory which is much more "practical".

;-)

-MikeS

PS. You can use the DreamLight RAMLight to examine the exact impact of birthing many objects from a single parent script.

Intelligent Characters with OOP

Date: Aug. 27, 1996

>This made me think of the thread about the friendly web-browser thread
>and other general AI considerations.  Does anyone have any thoughts on
>the ability of Lingo to produce such simulations?  Could your Director
>objects be programmed to think?

I didn't see the TV segment you mentioned, so I don't know off-hand what type of programming they were using. But in general, yes, you can program Lingo objects to do anything that you can describe in Lingo. You can build in rules and behaviors that will make the objects appear to think for themselves. That's not as hard as it seems. Can you write one that thinks in a general open manner as well as a human? I doubt it, not yet anyway. ;-)

But, by building in rules and variables into the object it can act intelligently within its own little world.

We are currently working on creating DreamLight(R) Microbots(TM) which are little intelligent characters for multimedia. A very crude sample of some initial tests is on our DreamLight WebSite asDreamLight Shock-O’-Lantern. Many of you have seen it. This was nothing more than an early test that started the ball rolling... In this test he doesn't do too much. From the safety of his pumpkin, he watches the user controlled bee. If the user clicks the mouse, 2K pops out of his pumpkin and scares the Bee to death. Simple test of the concept of object interaction used to control intelligent characters. (OK, so in this test 2K's not TOO intelligent, yet...)

First what you need to do is restrict the object's "world" to a controlable environment. Next define a set of rules within this "world" to act as your laws of nature. Now think of everything you'd like your object to be able to do in this world. Build in action handlers and decision handlers into the object to react to its world. You can also allow the user to interact with the world and your character would then play off what your user does.

You can have as many different objects in your world as you like. Both animate like our little 2K(TM) and inanimate like ourBubbleShock(TM). Both of these tests were programmed as objects in Lingo.

When we have a more advanced DreamLight(R) Microbot(TM) ready, we'll put a sample on our WebSite as well. Keep your eyes open.

[UPDATE: 06/20/02: We have dropped the Microbot name and renamed our interactive character technology "DreamLight Autiton Intelligent Interactive Characters." Take a look under the hood at how we use LingOOP to program a virtual mind for our DreamLight Autiton Intelligent Interactive Characters in our recent provisional patent filing for a method for simulating intelligence through the programming of a virtual mind. ]

-MikeS

Truly Global Handlers

Date: Aug. 5, 1996

>>     Also can global variables be shared by movies that call each other?
>
>Simple thing to remember: there is no way to make global variables
>anything other than totally always and everywhere shared and available.
>That's what 'global' means.
>
>However, 'global' handlers-- an imprecise way of saying 'movie scripts'--
>are not available for use from any arbitrary movie unless they are
>stored in a shared cast (or SHARED.DIR, for Director 4 users).

What we do is create objects with property variables and handlers we need access to in other movies. That way we can organize BOTH our variables and our handlers.

I make a "MovieMan" (Movie Manager) object that keeps all general movie handlers such as reading and writing files, updating prefs files, loading users, setting and resetting screen depth, checking memory size etc. Then from any other movies running I simply call the handlers or query the variables in the MovieManager.

We use a similar structure for our "SoundMan" (Sound Manager) object which handles cross fades between sound channels and all types of other audio gymnastics that our other movies may need.

We birth these objects into global variables in our start movie and then branch to the other movies. As long as you have a global variable pointing to the object (and don't do a clearGlobals), you can access the property variables and handlers of the object from any movie.

This enables you to simply keep a couple of true global variables that refer to a much more structured set of handlers and variables. It takes a little longer to build from the beginning but then makes managing a large project that spans multiple movies much easier. I prefer this method rather than using the shared dir. I only use the shared dir if I really have "cast memebers" I need to share rather than only variables and handlers.

-MikeS

DreamLight PhantomSprite & Stack Objects

Date: Jul. 8, 1996

>>I'm currently working on an shockwave version of pacman. I have big problem:
>>How to overcome the 48 sprites limit? The level area is 15x15 pieces large.

Check out the code for the DreamLight Phantom Sprite in the DreamLight Verttice Insight. When we originally wrote Verttice in Director 3.11 there were only 24 sprites. So we created the phantom sprite which uses a stack of objects to update and then paints them onto the stage with trails on. One object per frame update.

The code uses factories so it would need to be rewritten for Director 5 but the concepts would still be valid.

The phantom sprite is most useful for objects that are not updated very often or covered by other sprites. We used it for all the readouts in DreamLight Verttice and the "energized laser gates"

You can also check out the newly released (this weekend) DreamLight RAMLight which is a RAM/memory management utility you can use during development. You can examine this code to see some examples of object oriented programming using Parent/Child scripts. This would help you write your own phantomsprite code for use in Director 5.

Thanks,
-Mikes

The Meaning of OOP

Date: Jun. 16, 1996

-->It is a sad fact of life though that the clever tacticians are reliant upon
-->the strategists and the creators to fund and initiate the work they do.

I have the unique position to be heavily involved in both the initial conceptual creative end as well as the down and dirty programming. This is due to a dual background in the creative arts and traditional programming. I find OOP to be extremely beneficial from a design and implementation standpoint.

-->Object oriented programming isn't about programming per se,
-->it's about conceptualization. It could well be that an object oriented code
-->ends up less efficient than an alternative programming algorithm, but,
-->without object oriented thinking you would never have got to that position
-->in the first place.

I agree, it may not be as efficient as straight programming (for the computer) but then neither is C as efficient as Assembly. I don't see major projects being developed entirely in Assembly today. Maybe a few small key elements being recoded as assembly to improve performance, but not the entire project. I think OOP stands a step above straight programming just as C stands in relation to assembly. It lets you see the forest for the trees. It took some time before most programmers adopted high level languages over assembly and it will take a similar span for them to shift their thinking to OOP. It takes another paradigm shift.

OOP adds another level of abstraction to the programming process. A loose analogy would be writing. A great writer creates a story from abstract thoughts and concepts that are communicated through paragraphs, sentences, words and finally letters. It would be very hard to create a great novel if the author only thought in terms of words and letters. I'm not saying the author shouldn't worry about or deal with the words and letters but only that without the higher levels of conceptual abstraction the novel wouldn't be very good. The opposite is also true. A writer with great ideas that can't translate them into words and letters would be just as hard pressed to write a great novel. Also, if one person has the ideas but another does the actual writting, the novel may not be as tight as if a single mind brought the ideas all the way down to paper. I feel the best writers master both the conceptual abstraction as well as the use of the words. I feel the best software designers also master both the higher levels of abstraction that OOPS enables as well as the lower levels of programming such as C and assembly.

OOP enables more complex systems to be designed and maintained more efficiently by humans at the expense of some efficiency for the computer. The computers are getting faster and more capable much more swiftly than the humans that program them -- so this trade off seems entirely valid to me. When speed is an issue for a specific object, that object can be rewritten in C or Assembly.

A dictionary is a very efficient compilation of words and letters -- but it doesn't make very entertaining reading...

-MikeS

Before Parents and Children, there were Factories

Date: 1992

Here is a sample of our early OOP work on DreamLight Verttice. This dates from 1992 and was built in Director 3's factories. The code itself is obsolete but the concepts are entirely valid and easily applied to today's parent/child scripts. This was one of many objects from DreamLight Verttice that hopped on and off my early version of the actorList calledTheActiveObjects. Each object in TheActiveObject stack received an mNextFrame message so that it could animate itself. I also created astack object for DreamLight Verttice. I hope you enjoy this look back at OOP in Director 3. Believe me, OOP in Director 6 is much easier.

--===========================================================
-- makePhoton Factory - controls the movement of the Photons 
--===============================================================
-- (C) 1992 DreamLight(R) Incorporated, All Rights Reserved
-----------------------------------------------------------------
-- Creates and controls the Photons themselves
-- Accessed through the lattice object
-----------------------------------------------------------------
factory makePhoton
  
method mNew whichPhoton
  -- doesn't check for existing because it is stored only in a
  -- stack which would have already purged any existing nodes
  global firstPhotonSprite
  
  instance myPhoton       -- 1 - 8, for setting sprite
  instance mySprite
  
  instance myHdir, myVdir -- Easy to flip directions
  instance myRow, myCol   -- to track location on the lattice
  instance myAnimStep     -- 0 - 4, step counter for animation
  instance myEnergy
  instance myColor
  instance myAction     -- me(mRollAnim), mZapAnim, mActAnim, mEvacAnim
  instance myLastAction
  instance myPauseTime    -- delay to give user a pause
  instance mySide
  instance myNode
  
  set myPhoton = whichPhoton
  
  --**DEBUG
  --put "makePhoton: mNew: " & myPhoton
  
  set mySprite = firstPhotonSprite + myPhoton
  set myPauseTime = 2 * 60  -- seconds
end mNew

--

method mReset
  global boardCenterH, boardCenterV
  global vertticeOn
  
  --**DEBUG
  --put "makePhoton: mReset: " & myPhoton
  
  if vertticeOn then
    activateSprite(mySprite, me)
    set the visibility of sprite mySprite = 0
    set the ink of sprite mySprite = 0
    me(mSetCast, "Activated", 0)
    set the locH of sprite mySprite = boardCenterH
    set the locV of sprite mySprite = boardCenterV
    set myRow = 0
    set myCol = 0
    set myAction = "nothing"
    set myLastAction = "nothing"
    set myAnimStep = 0
    set mySide = 0
    set myNode = 0
  else
    set the ink of sprite mySprite = 0
    set the visibility of sprite mySprite = 1
    set the foreColor of sprite mySprite = 15
    deActivateSprite(mySprite)
  end if
  
  deActivateObject(me)
end mReset

--

method mRelease
  
  --**DEBUG
  --put "makePhoton: mRelease: " & myPhoton
  
  deActivateObject(me)
  set the visibility of sprite mySprite = 1
  
end mRelease

------------------------------------------------------------------------------
-- User Interactive methods
------------------------------------------------------------------------------
method mMouseDown
  global vertticePaused
  if NOT vertticePaused then -- pass a keydown to the correct sprite
    me(testNodes, "mMouseDown")
  end if
end mMouseDown

--

method mMouseUp
  global vertticePaused
  if NOT vertticePaused then -- pass a keydown to the correct sprite
    me(testNodes, "mMouseUp")
  end if
end mMouseUp

--

method mKeyDown
end mKeyDown

------------------------------------------------------------------------------
-- Communication methods
------------------------------------------------------------------------------
method mBlastNode
  global theLattice
  
  --**DEBUG
  --put "Photon: mBlastNode"
  
  if myAction = "me(mRollAnim)" OR¬
     myAction = "me(mEvacAnim)" then -- only blast if rolling
    set myNextRow = myRow
    set myNextCol = myCol
    
    if myVdir then set myNextRow = myRow + myVdir
    else set myNextCol = myCol + myHdir
    if myNextRow = 0 AND myNextCol = 0 then -- blast node accross from reactor
      if myVdir then set myNextRow = myNextRow + myVdir
      else set myNextCol = myNextCol + myHdir
    end if
    
    set theNextNode = theLattice(mGetIt, myNextRow, myNextCol)
    if objectP(theNextNode) then theNextNode(mKeyDown)
  end if
end mBlastNode

--

method mActivate
  
  --**DEBUG
  --put "makePhoton: mActivate: " & myPhoton
  
  set tempDir = random(4)
  if tempDir = 1 then
    set myHdir = 0
    set myVdir = 1
  else if tempDir = 2 then
    set myHdir = 1
    set myVdir = 0
  else if tempDir = 3 then
    set myHdir = 0
    set myVdir = -1
  else
    set myHdir = -1
    set myVdir = 0
  end if
  
  me(mResetEnergy)
  me(mColor)
  
  set myAction = "me(mActAnim)"
  activateObject(me)
  startTimer 
end mActivate

--

method mAgitate
  global theLattice
  me(mSetCast, "Photon:Agitate", mySide)
  set theNode = theLattice(mGetIt, myRow, myCol)
  theNode(mAgitate)
end mAgitate

--

method mStop whichSide, whichNode
  
  puppetSound "EnergizePowerNode"
  set mySide = whichSide
  set myNode = whichNode
  set myAction = "me(mStopAnim)"
end mStop

--

method mZap whichNode
  --**DEBUG
  --put "makePhoton: mZap: " & myPhoton
  set myNode = whichNode
  set myAction = "me(mZapAnim)"
end mZap

--

method mEvacuate
  global theLattice
  --**DEBUG
  --put "makePhoton: mEvacuate: " & myPhoton
  
  set theNode = theLattice(mGetIt, myRow, myCol)
  theNode(mEvacuate)
  activateObject(me)
  set myAction = "me(mEvacAnim)"
end mEvacuate

--

method mWarp
  set myAction = "me(mWarpAnim)"
  puppetSound "WarpThrough"
end mWarp

--

method mBounce
  set myHdir = myHdir * -1
  set myVdir = myVdir * -1
  me(mEnergize, 3)
  puppetSound "WarpBounce"
end mBounce

--

method mTurn whichWay
  if whichWay <> 0 then
    if myHdir <> 0 then 
      set myVdir = myHdir * (whichWay * -1) 
      set myHdir = 0
    else 
      set myHdir = myVdir * (whichWay * -1)
      set myVdir = 0
    end if
  end if
  me(mEnergize, 1)
end mTurn

--

method mName
  --** DEBUGGING METHOD
  Return( "Photon:" && myPhoton)
end mName

------------------------------------------------------------------------------
-- Animation methods
------------------------------------------------------------------------------
method mNextFrame
  global vertticePaused
  if NOT vertticePaused then do myAction
end mNextFrame

--

method mWarpAnim
  global boardCenterV, boardCenterH
  --**DEBUG
  --put "makePhoton: mWarp: " & myPhoton
  
  if myRow = 0 then
    set myCol = myCol * -1
  else set myRow = myRow * -1
  
  me(mEnergize, 2)
  set the LocV of sprite mySprite = boardCenterV + (myRow * 75) 
  set the LocH of sprite mySprite = boardCenterH + (myCol * 75)
  
  set myAction = "me(mRollAnim)"
end mWarpAnim

--

method mEvacAnim
  if myAnimStep = 0 then puppetSound "ActivatePhoton"
  
  if myAnimStep mod 2 = 0 then me(mSetCast, "Photon:Hilite", mySide)
  else  me(mSetCast, "Photon:Agitate", mySide)
  
  set myAnimStep = myAnimStep + 1
  if myAnimStep = 10 then
    set myAnimStep = 0
    set the ink of sprite mySprite = 0
    me(mColor)
    set myAction = "me(mRollAnim)"
  end if
end mEvacAnim

--

method mActAnim
  
  --**DEBUG
  --put "makePhoton: mActAnim: " & myPhoton
  
  if the timer >= myPauseTime then
    me(mSetCast, "Activated", myAnimStep)
    if myAnimStep = 1 then
      puppetSound "ActivatePhoton"
      set the visibility of sprite mySprite = 1
    end if
    set myAnimStep = myAnimStep + 1
    
    if myAnimStep = 5 then
      set myAnimStep = 0
      me(mColor)
      set myAction = "me(mRollAnim)"
    end if
  end if
end mActAnim

--

method mRollAnim
  
  --**DEBUG
  --put "makePhoton: mRollAnim: " & myPhoton
  
  if myAnimStep = 5 then -- On a node so we need to touch it
    me(mTouchNode)
    set myAnimStep = 0
    if myAction <> "me(mRollAnim)" then exit
  end if
  if myHdir then
    set the LocH of sprite mySprite = the LocH of sprite mySprite + 15 * myHdir
  else
    set the LocV of sprite mySprite = the LocV of sprite mySprite + 15 * myVdir
  end if
  set myAnimStep = myAnimStep + 1
end mRollAnim

--

method mStopAnim
  global theReactorNode
  
  deActivateObject(me)
  set myAction = "nothing"
  
  set the ink of sprite mySprite = 8 -- matte
  me(mSetCast, "Photon:Hilite", mySide)
  --set the backColor of sprite mySprite = 12 -- back blue
  --set the foreColor of sprite mySprite = 15 -- black
  
  -- reverse direction
  set myHdir = myHdir * -1
  set myVdir = myVdir * -1
  
  me(mResetEnergy)
  
  myNode(mCheckLock, me)
  theReactorNode(mNextPhoton)
end mStopAnim

--

method mZapAnim
  global theReactorNode
  
  --**DEBUG
  --put "makePhoton: mZapAnim: " & myPhoton
  if myAnimStep = 0 then 
    set the backColor of sprite mySprite = 255 -- black
    set the ink of sprite mySprite = 36
    if objectP(myNode) then myNode(mCheckLock, 0)
    puppetSound "Explosion"
  end if
  
  me(mSetCast, "Zapped", myAnimStep)
  
  set myAnimStep = myAnimStep + 1
  if myAnimStep = 5 then -- done zapping
    deActivateObject(me)
    theReactorNode(mKillPhoton, me)
    theReactorNode(mNextPhoton)
    me(mReset)
  end if
end mZapAnim

------------------------------------------------------------------------------
-- Internal methods
------------------------------------------------------------------------------
method testNodes whatMessage
  global firstNodeSprite, theSprites
  repeat with i = firstNodeSprite + 1 to firstNodeSprite + 8
    if sprite mySprite intersects i then
      set theSprite = theSprites(mGet, i)
      theSprite(mPerform, whatMessage)
      exit
    end if
  end repeat
  puppetSound "No" -- not found
end testNodes

--

method mTouchNode
  global theLattice
  
  set myAnimStep = 0
  if myVdir then set myRow = myRow + myVdir
  else set myCol = myCol + myHdir
  set theNode = theLattice(mGetIt, myRow, myCol)
  theNode(mTouch, me)  -- the node will then tell me what to do
end mTouchNode

--

method mResetEnergy
  global energyLevel
  if EnergyLevel = 1 then set myEnergy = 0
  else if EnergyLevel = 2 then set myEnergy = 50
  else if EnergyLevel = 3 then set myEnergy = 75
  else if EnergyLevel = 4 then set myEnergy = 90
end mResetEnergy

--

method mEnergize howMuch
  global theReactorNode
  set myEnergy = myEnergy + howMuch
  me(mColor)
  if myEnergy >= 100 then
    me(mZap) -- exploded
  end if
end mEnergize

--

method mColor
  set the ink of sprite mySprite = 0
  set the backColor of sprite mySprite = 0 -- back white
  set the foreColor of sprite mySprite = 15 -- black
  
  if myEnergy < 50 then set myColor = 0
  else if myEnergy < 75 then set myColor = 1
  else if myEnergy < 85 then set myColor = 2
  else set myColor = 3
  
  me(mSetCast, "Photon", myColor)
end mColor

--

method mSetCast castName, castModifier
  set the castNum of sprite mySprite to¬
     ((the number of cast castName) + castModifier)
end mSetCast

--=======================================================
-- END makePhoton
--=======================================================

-MikeS

Please Support Our Continuing Independent Film Making Efforts!
Order your Award-winning BlastOff! Special Edition DVD today!

BlastOff! T-ShirtsBlastOff! HatsBlastOff! MugsBlastOff! Calendar, Prints & PostersBlastOff! Mousepads

To view this site properly, pleaseupgrade to a browser that supports current Web standards.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Director,lingo,object,variables,inheritance,reference,class,character)