Articles Archive
Articles Search
Director Wiki
 

Free range maze monsters

September 5, 1999
by Pat McClellan

Dear Multimedia Handyman,

In a game I am making I need 3 sprites to move around on their own while being bound by a maze similar to how the ghosts move around in Pac-man. Could you please show me how to do this. I heard a behavior could be easily made to allow this to happen. Thanks for your help!

Al

Dear Al,

There are a lot of variants on exactly how these ghosts might behave. I'm going to keep the behavior general and random, rather than dictate any particular biases that these free-roaming sprites might favor.

I spent a bit of time pondering the best approach for something like this. And I'm not sure I've got the best solution, so I'll share my thoughts and let you explore the options. I started out by considering whether the sprites should virtually "see" the walls of the maze -- and quickly decided no. You'll want to construct the maze as a single graphic in a single sprite channel, so there would be no way for the sprite to sense and react to a wall (unless you got really nutty and started doing some kind of a getPixel routine.)

So, if the sprites can't see the walls, how do they know what to do? I think they should be on "auto-pilot"... moving along in one direction until they are told to go a different direction. One possibility for implementing this would be an object -- kind of an omnipotent manager which keeps track of the maze, where the walls are and where the sprites are. This might work, but I'm averse to this omnipotent manager concept. I'd much rather have the intelligence dispersed and independent.

I hit upon this idea of "road sensors" -- imagine driving down the road in your car. When you come to an intersection, a sensor buried under the pavement signals your car which way to go and it responds. I like this idea, so the next question is where is the intelligence? Does the road sensor think about which direction you came from, and then decide which way you should go? I think not -- it seems more intuitive that the car would already know which way it is moving, so all it needs is to know the options and then it can decide (in my demo, the "decision" is completely random.)

Let's apply this concept to Director in this sequence of events.

  1. When the "maze mover" sprite first appears, it adds its own spriteNum to a global list called gMoverList.
  2. The maze mover sprite has been assigned an initial direction for motion, and sets out at a predetermined rate (10 pixels/frame) in the predetermined direction.
  3. Sensors are placed throughout the maze at every intersection and "doorway". Each sensor "knows" its particular options for egress: northExit, southExit, eastExit and westExit.
  4. On each enterFrame, these sensors check to see if any of the sprites in the gMoverList happen to be on top of it.
  5. If a mover sprite is over a sensor, the sensor sends a message to the mover sprite telling it to change direction -- and telling it which directions are possible options.
  6. The mover sprite gets the message to change direction and chooses a new direction from the options presented.

In this demo, I've chosen to make my "road sensors" visible, but they could easily be buried below a maze graphic. I just wanted you to see all of the decision points. I've also left the pink grid lines in to emphasize the point that you've really got to get everything lined up perfectly -- at least in my approach. The way I did it, the road sensors check to see if the maze mover sprite shares the same loc -- which means that the sensors must be a precise number of pixels apart, and the maze movers must move in evenly divisable increments.

Here's the road sensor behavior.


--Road Sensor maze behavior
--copyright © 1999, ZZP Online, LLC
-- free use for DOUG readers
property spriteNum, pNorthExit, pSouthExit, pEastExit
property pWestExit, pMyLoc, pOptions
global gMoverList
on getPropertyDescriptionList
  pList = [:]
  addProp pList, #pNorthExit, [#comment: "North Exit?", ¬
    #format: #boolean, #default:0]
  addProp pList, #pSouthExit, [#comment: "South Exit?", ¬
    #format: #boolean, #default:0]
  addProp pList, #pEastExit, [#comment: "East Exit?", ¬
    #format: #boolean, #default:0]
  addProp pList, #pWestExit, [#comment: "West Exit?", ¬
    #format: #boolean, #default:0]
  return pList  
  
end getPropertyDescriptionList
on beginSprite me
  pMyLoc = sprite(spriteNum).loc
  optionString = ""
  if pNorthExit then put "n" after optionString
  if pSouthExit then put "s" after optionString
  if pEastExit then put "e" after optionString
  if pWestExit then put "w" after optionString
  pOptions = symbol(optionString)
  
end beginSprite
on enterFrame me
  repeat with testSprite in gMoverList
    testLoc = sprite(testSprite).loc
    if testLoc = pMyloc then
      sendSprite(testSprite, #changeDir, pOptions)
      put "changeDir" && pOptions
    end if
  end repeat
  
end exitFrame

To apply this behavior, you simply indicate whether a maze mover (when on top of this sensor) could exit to the north, south, east, and/or west. Based on your input, it creates a value for the pOptions property: example, #nsw signifies that the maze mover could proceed from this sensor to the north, south, or west. Again, this sensor behavior simply sends a message to the maze mover telling it what the options are.

The behavior for the maze mover sprites is considerably longer, but don't get freaked out. It's not all that complicated. Most of the code is focused on the logic of picking a new direction to go. It's tedious and repetitive, and I don't know of a quicker way. (Please let me know if anyone can think of a cleaner approach.)


-- Maze Mover behavior
-- copyright © 1999, ZZP Online, LLC
-- free use for DOUG readers 
property spriteNum, pSpeed, pMyDirection, pVdir, pHdir
global gMoverList
on getPropertyDescriptionList
  pList = [:]
  dirList = [#north, #south, #east, #west]
  addProp pList, #pSpeed, [#comment: "Speed (a factor of 40):", ¬
    #format: #integer, #default: 10]
  addProp pList, #pMyDirection, [#comment: "Initial Direction:", ¬
    #format:#symbol, #default:#north, #range: dirList]
  return pList
  
end getPropertyDescriptionList
on beginSprite me
  add gMoverList, spriteNum
  
  if voidP(gMoverList) then
    gMoverList = []
  end if
  
  pHDir = 0
  pVDir = 0
  
  case pMyDirection of
    #north: pVDir = -1
    #south: pVDir = 1
    #east: pHDir = 1
    #west: pHDir = -1
  end case
  
end beginSprite
on exitFrame me
  sprite(spriteNum).flipH = not sprite(spriteNum).flipH
  sprite(spriteNum).locH = sprite(spriteNum).locH + (pSpeed * pHDir)
  sprite(spriteNum).locV = sprite(spriteNum).locV + (pSpeed * pVDir)
  
end exitFrame
on changeDir me, options 
  put pMyDirection && "before"
  
  case pMyDirection of
  
    #south:
    
      case options of
      
        #ne: pMyDirection = #east
        #nw: pMyDirection = #west
        #new:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #east
            2: pMyDirection = #west
          end case
        #nse:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #east
            2: pMyDirection = #south
          end case
        #nsw:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #west
            2: pMyDirection = #south
          end case
      end case
      
    #north:
    
      case options of
      
        #se: pMyDirection = #east
        #sw: pMyDirection = #west
        #sew:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #east
            2: pMyDirection = #west
          end case
        #nse:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #east
            2: pMyDirection = #north
          end case
        #nsw:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #west
            2: pMyDirection = #north
          end case
      end case
      
    #west:
    
      case options of
        #ne: pMyDirection = #north
        #se: pMyDirection = #south
        #new:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #north
            2: pMyDirection = #west
          end case
        #sew:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #west
            2: pMyDirection = #south
          end case
        #nse:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #north
            2: pMyDirection = #south
          end case
      end case
      
    #east:
    
      case options of
      
        #sw: pMyDirection = #south
        #nw: pMyDirection = #north
        #sew:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #east
            2: pMyDirection = #south
          end case
        #new:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #east
            2: pMyDirection = #north
          end case
        #nsw:
          pickOne = random(2)
          case pickOne of
            1: pMyDirection = #north
            2: pMyDirection = #south
          end case
          
      end case
      
  end case
  
  put pMyDirection && "now"
  
  pHDir = 0
  pVDir = 0
  
  case pMyDirection of
    #north: pVDir = -1
    #south: pVDir = 1
    #east: pHDir = 1
    #west: pHDir = -1
  end case
  
end changeDir

The downside of this approach is that there are a lot of sensors in even this small maze. And every enterFrame, each of these sensors is checking to see if every sprite in gMoverList happens to be on top of it. In most instances, this will not be the case. One alternative approach would be to have each of the maze movers check through a list of sensors, but this is the same number of calculations, so it doesn't really save us anything.

Download the sample movie (in Mac or PC format) and spend some time playing with it and dissecting the code -- which is intended as a jumpstart, not a final solution. There's lots of room for variation and improvement... so get creative. Good luck with your game.

Patrick McClellan is Director Online's co-founder. Pat is Vice President, Managing Director for Jack Morton Worldwide, a global experiential marketing company. He is responsible for the San Francisco office, which helps major technology clients to develop marketing communications programs to reach enterprise and consumer audiences.

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